Guide · Node.js · SDK · REST API

PESEL Validation in Node.js

The Polish national identification number — 11 digits encoding birth date, gender, and a check digit. Here's how to validate it correctly and extract the data it carries.

1. What is a PESEL number?

PESEL (Powszechny Elektroniczny System Ewidencji Ludnosci) is the Polish national identification number. Every Polish citizen — and every foreign resident registered in Poland — receives a unique 11-digit PESEL at birth or upon registration. It is the primary identifier used across virtually every public and private system in the country:

  • Tax filings — used as the taxpayer ID for individuals without a separate NIP
  • Banking and financial services — required for opening accounts, applying for loans, and KYC verification
  • Healthcare — identifies patients in the NFZ (National Health Fund) system
  • E-government — login to ePUAP, mObywatel, and other digital services
  • Social security (ZUS) — tracks contributions and pension entitlements
  • Education — student records, university enrollment, and exam registration

Unlike many national ID numbers, PESEL is not just an opaque identifier. It encodes two pieces of real-world data: the holder's date of birth and their biological gender. This makes it both a validation target and a data source — but also means that getting the parsing wrong can produce silently incorrect results.

ℹ️PESEL was introduced in 1979 and is maintained by the Ministry of Digital Affairs. As of 2024, every person registered in Poland's PESEL registry has a unique 11-digit number that remains unchanged for life.

2. PESEL anatomy

A PESEL number is always exactly 11 digits. Each group of digits carries specific meaning:

PositionsDigitsMeaning
1–2YYLast two digits of the birth year
3–4MMBirth month (with century offset — see below)
5–6DDBirth day
7–10SSSSSerial number (4 digits) — the 10th digit encodes gender
11CCheck digit (weighted MOD-10)

For example, the PESEL 44051401358 breaks down as: year 44, month 05, day 14, serial 0135, check digit 8.

Century encoding

Since the year portion is only two digits, PESEL uses an offset on the month value to distinguish centuries. This is the most commonly misunderstood part of the format:

CenturyYear rangeMonth offsetMonth values
19th1800–1899+8081–92
20th1900–1999+001–12
21st2000–2099+2021–32
22nd2100–2199+4041–52
23rd2200–2299+6061–72

This means a PESEL with month value 25 does not represent the 25th month — it represents May (month 5) in the 2000s (offset +20). For example, 05 is May 1900s, while 25 is May 2000s.

Gender encoding

The 10th digit of the PESEL (the last digit of the serial number) determines gender:

  • Odd (1, 3, 5, 7, 9) → male
  • Even (0, 2, 4, 6, 8) → female

In our example 44051401358, the 10th digit is 5 (odd), indicating a male holder.


3. The checksum algorithm

PESEL uses a weighted MOD-10 checksum. The algorithm is straightforward but the weight pattern is specific and must be applied exactly:

  1. Take the first 10 digits of the PESEL
  2. Multiply each digit by its corresponding weight: 1, 3, 7, 9, 1, 3, 7, 9, 1, 3
  3. Sum all the products
  4. Take the last digit of the sum (i.e. sum mod 10)
  5. Subtract that value from 10 — if the result is 10, the check digit is 0; otherwise the result is the check digit
  6. Compare with the 11th digit of the PESEL

Step-by-step example

Let's validate 44051401358:

PositionDigitWeightProduct
1414
24312
3070
45945
5111
64312
7070
8199
9313
105315

Sum = 4 + 12 + 0 + 45 + 1 + 12 + 0 + 9 + 3 + 15 = 101

Last digit of sum = 101 mod 10 = 1

Check digit = (10 − 1) mod 10 = 9

Wait — the 11th digit is 8, not 9. That means this example PESEL does not pass the checksum. This is exactly the kind of subtle error that an API handles for you automatically.

Here is a JavaScript implementation of the algorithm:

function validatePeselChecksum(pesel) {
  const d = pesel.split('').map(Number);
  const weights = [1, 3, 7, 9, 1, 3, 7, 9, 1, 3];
  const sum = weights.reduce((acc, w, i) => acc + d[i] * w, 0);
  const check = (10 - (sum % 10)) % 10;
  return check === d[10];
}
⚠️This checksum function only verifies the check digit. It does not validate the birth date, handle century encoding, or extract gender. A number that passes the checksum can still be semantically invalid.

4. Why manual validation isn't enough

Century encoding is easy to get wrong

A PESEL with month 25 is not invalid — it represents May for someone born in the 2000s. Naive validation that rejects months above 12 will incorrectly reject every child born after the year 2000. You need to decode the century offset before checking whether the date is valid.

Gender extraction requires the right digit

Gender is encoded in the 10th digit (not the 9th, not the 11th). An off-by-one error gives you the wrong answer silently — the code compiles, the tests might even pass with certain inputs, but the gender is wrong for half the population. The rule is: odd = male, even = female, applied to the 10th digit (0-indexed position 9).

Date validity is more than range checking

After decoding the century and extracting the birth date, you still need to verify that the date actually exists. February 30th passes a simple range check but is not a real date. Leap year rules apply: a PESEL claiming birth on February 29th is only valid if the decoded year is actually a leap year.

All-zeros and degenerate inputs

The string 00000000000 passes the MOD-10 checksum (the sum is 0, and (10 − 0) mod 10 = 0, which matches the 11th digit). But it encodes a birth date of January 0, 1900 — a date that does not exist. Checksum validation alone cannot catch this.


5. The right solution: one API call

The IsValid PESEL API handles the complete validation and data extraction pipeline in a single GET request:

1

Checksum validation

Weighted MOD-10 with the correct weight pattern (1,3,7,9,1,3,7,9,1,3)

2

Birth date extraction

Full century decoding, leap year handling, and date existence checks

3

Gender detection

Correctly reads the 10th digit to determine male or female

4

Age verification

Computes isOver15, isOver18, and isOver21 relative to the current date

Get your free API key at isvalid.dev. The free tier includes 100 calls per day with no credit card required.

Full parameter reference and response schema: PESEL Validation API docs →


6. Node.js code example

Using the @isvalid-dev/sdk package or the built-in fetch API (Node.js 18+):

// pesel-validator.mjs
import { createClient } from '@isvalid-dev/sdk';

const iv = createClient({ apiKey: process.env.ISVALID_API_KEY });

// ── Validate a PESEL number ────────────────────────────────────────────────
const result = await iv.pl.pesel('44051401358');

if (!result.valid) {
  console.log('Invalid PESEL');
} else {
  console.log('Birth date:', result.birthDate);  // "1944-05-14"
  console.log('Gender    :', result.gender);      // "male"
  console.log('Over 18?  :', result.isOver18);    // true
}
The SDK uses the country-specific namespace iv.pl.pesel(). All Polish identifiers (PESEL, REGON, KRS, NIP) are grouped under iv.pl.

7. cURL example

Validate a PESEL number directly from the terminal:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/pl/pesel?value=44051401358"

A successful response includes the extracted birth date, gender, and age flags:

{
  "valid": true,
  "birthDate": "1990-09-05",
  "gender": "male",
  "isOver15": true,
  "isOver18": true,
  "isOver21": true
}

8. Understanding the response

Valid PESEL

{
  "valid": true,
  "birthDate": "1990-09-05",
  "gender": "male",
  "isOver15": true,
  "isOver18": true,
  "isOver21": true
}

Invalid PESEL

{ "valid": false }
FieldTypeDescription
validbooleanWhether the PESEL passed checksum and date validation
birthDatestringISO 8601 date (YYYY-MM-DD) extracted from the PESEL, with full century decoding applied. Only present when valid: true
genderstring"male" or "female", derived from the 10th digit. Only present when valid: true
isOver15booleanWhether the PESEL holder is at least 15 years old as of today. Useful for age-gated services and youth account eligibility
isOver18booleanWhether the holder is at least 18 — the legal age of majority in Poland. Essential for banking KYC, contract signing, and alcohol/tobacco sales
isOver21booleanWhether the holder is at least 21. Used in certain licensing and regulatory contexts

9. Edge cases

(a) 2000s birth dates

Children and young adults born after the year 2000 have month values in the 21–32 range. A PESEL like 10271012345 encodes July 2010 (month offset +20, so 27 − 20 = July). If your validation rejects month values above 12, you are rejecting an entire generation of valid numbers. The IsValid API handles century decoding automatically.

// A child born on July 10, 2010
const result = await iv.pl.pesel('10271012345');

if (result.valid) {
  console.log('Birth date:', result.birthDate); // "2010-07-10"
  console.log('Over 15?  :', result.isOver15);  // true (as of 2026)
  console.log('Over 18?  :', result.isOver18);  // false
}

(b) Gender verification

In KYC workflows, you may need to cross-check the gender extracted from the PESEL against a separately provided value (e.g. from a form or ID scan). The API returns the gender as a simple string, making the comparison straightforward:

const result = await iv.pl.pesel(userPesel);

if (result.valid) {
  if (result.gender !== userDeclaredGender) {
    console.warn('Gender mismatch: PESEL says', result.gender,
                 'but user declared', userDeclaredGender);
    // Flag for manual review
  }
}

(c) Age threshold checks for KYC and onboarding

Polish regulations require age verification in many contexts: 18 for bank accounts and contracts, 15 for youth savings accounts, 21 for certain professional licenses. Instead of computing age from the birth date yourself (and dealing with timezone and date math edge cases), use the pre-computed age flags:

const result = await iv.pl.pesel(applicantPesel);

if (!result.valid) {
  return { error: 'Invalid PESEL number' };
}

// Bank account opening — must be 18+
if (!result.isOver18) {
  return { error: 'Applicant must be at least 18 years old' };
}

// Youth savings account — must be 15+ but under 18
if (result.isOver15 && !result.isOver18) {
  return { accountType: 'youth-savings', birthDate: result.birthDate };
}

// Standard adult account
return { accountType: 'standard', birthDate: result.birthDate };

Summary

PESEL is a deceptively simple format. Eleven digits, one checksum — but the century encoding, gender extraction, date validation, and age computation add layers of complexity that are easy to get wrong. The IsValid API handles all of it in a single call and returns structured, ready-to-use data.

Do not reject month values above 12 — 2000s births use months 21–32
Do not compute age manually — timezone and leap year bugs are common
Do not assume a valid checksum means a valid PESEL — the encoded date must also exist
Use the API to get birthDate, gender, and age flags in one call
Check isOver18 for KYC — pre-computed against the current date
Verify gender from the 10th digit — odd is male, even is female

See also

Validate PESEL numbers instantly

Free tier includes 100 API calls per day. No credit card required. Checksum validation, birth date extraction, gender detection, and age verification included.