diff --git a/CHANGELOG.md b/CHANGELOG.md
index 405263e..f9202c5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to this project are documented here. The format follows [Kee
## [Unreleased]
+### Added
+
+- `validatePostalCode` / `isValidPostalCode` for six-digit Romanian postal code format validation
+
## [0.1.0] - 2026-10-27
### Added
diff --git a/README.md b/README.md
index 086d9e7..570411b 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
Try it in your browser
-Validate and decode Romanian identifiers (**CNP**, **CUI/CIF** and **IBAN**) with error messages you can show directly to users. Zero dependencies, fully typed, runs in Node and in the browser.
+Validate and decode Romanian identifiers (**CNP**, **CUI/CIF**, **IBAN** and postal codes) with error messages you can show directly to users. Zero dependencies, fully typed, runs in Node and in the browser.
```bash
npm install validro
@@ -60,7 +60,7 @@ type ValidationResult =
| { valid: false; error: string; data: null };
```
-Only need true or false? Use `isValidCnp`, `isValidCui` and `isValidIbanRo`.
+Only need true or false? Use `isValidCnp`, `isValidCui`, `isValidIbanRo` and `isValidPostalCode`.
## API
@@ -69,7 +69,8 @@ Only need true or false? Use `isValidCnp`, `isValidCui` and `isValidIbanRo`.
| `validateCnp(cnp, now?)` | `ValidationResult`; `now` sets the date used to reject future birth dates |
| `validateCui(cui)` | `ValidationResult` |
| `validateIbanRo(iban)` | `ValidationResult` |
-| `isValidCnp`, `isValidCui`, `isValidIbanRo` | `boolean` |
+| `validatePostalCode(postalCode)` | `ValidationResult`; exactly 6 digits |
+| `isValidCnp`, `isValidCui`, `isValidIbanRo`, `isValidPostalCode` | `boolean` |
| `cnpControlDigit(first12)` | the CNP control digit |
| `cuiControlDigit(body)` | the CUI control digit |
| `ibanChecksumOk(iban)` | mod-97 check, works for any country |
@@ -82,6 +83,7 @@ Only need true or false? Use `isValidCnp`, `isValidCui` and `isValidIbanRo`.
| CNP | 13 digits, sex and century digit, real calendar date including leap years, not in the future, county code (01–52, 70), sequence not 000, control digit (key `279146358279`) |
| CUI / CIF | optional `RO` prefix, 2–10 digits, no leading zero, control digit (key `753217532`) |
| IBAN (RO) | 24 characters: `RO`, 2 check digits, 4-letter bank code, 16 alphanumerics, ISO 13616 mod-97 |
+| Postal code | exactly 6 digits |
Spaces, dots and dashes in the input are ignored.
@@ -101,7 +103,7 @@ All examples in this repository are fictitious: sample CNPs use birth dates in t
- [ ] Romanian licence plates
- [ ] Phone numbers, normalized to `+40`
-- [ ] Postal codes
+- [x] Postal codes
- [ ] Python port
Ideas and bugs are welcome in [issues](https://github.com/Robert-GeorgeDev/ValidRO/issues). See [CONTRIBUTING.md](CONTRIBUTING.md) to get started.
diff --git a/src/index.ts b/src/index.ts
index 64c5499..6758db9 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -4,5 +4,7 @@ export { validateCui, isValidCui, cuiControlDigit } from "./cui.js";
export type { CuiInfo } from "./cui.js";
export { validateIbanRo, isValidIbanRo, ibanChecksumOk } from "./iban.js";
export type { IbanInfo } from "./iban.js";
+export { validatePostalCode, isValidPostalCode } from "./postal-code.js";
+export type { PostalCodeInfo } from "./postal-code.js";
export { CNP_COUNTIES } from "./counties.js";
export type { ValidationResult } from "./result.js";
diff --git a/src/postal-code.ts b/src/postal-code.ts
new file mode 100644
index 0000000..b46ec52
--- /dev/null
+++ b/src/postal-code.ts
@@ -0,0 +1,15 @@
+import { clean, fail, ok, type ValidationResult } from "./result.js";
+
+export interface PostalCodeInfo {
+ postalCode: string;
+}
+
+/** Validates the six-digit format of a Romanian postal code. Spaces, dots and dashes are ignored. */
+export function validatePostalCode(input: string): ValidationResult {
+ if (typeof input !== "string") return fail("Codul poștal trebuie să fie un șir de caractere");
+ const postalCode = clean(input);
+ if (!/^\d{6}$/.test(postalCode)) return fail("Codul poștal trebuie să conțină exact 6 cifre");
+ return ok({ postalCode });
+}
+
+export const isValidPostalCode = (input: string): boolean => validatePostalCode(input).valid;
diff --git a/test/postal-code.test.ts b/test/postal-code.test.ts
new file mode 100644
index 0000000..5a5d9bd
--- /dev/null
+++ b/test/postal-code.test.ts
@@ -0,0 +1,19 @@
+import { describe, expect, it } from "vitest";
+import { isValidPostalCode, validatePostalCode } from "../src/postal-code.js";
+
+describe("Romanian postal code", () => {
+ it("accepts exactly six digits and rejects other formats", () => {
+ expect(validatePostalCode("010101")).toEqual({ valid: true, error: null, data: { postalCode: "010101" } });
+ expect(isValidPostalCode("12345")).toBe(false);
+ expect(isValidPostalCode("1234567")).toBe(false);
+ expect(isValidPostalCode("12345a")).toBe(false);
+ });
+
+ it("ignores spaces, like the other validators", () => {
+ expect(validatePostalCode(" 010 101 ").data).toEqual({ postalCode: "010101" });
+ });
+
+ it("explains what is wrong", () => {
+ expect(validatePostalCode("1234").error).toContain("exact 6 cifre");
+ });
+});