@apideposu/tr-validation is a local-only validation and normalization toolkit for Turkiye-specific form data.
Turkish documentation: README.tr.md
- The package runs completely inside the user's own project.
- It does not call API Deposu backend.
- It does not send data anywhere.
- It does not perform registry lookup.
- It does not include telemetry, analytics, or network calls.
- It does not perform official person, company, tax, or bank-account verification.
- It provides structural validation, known control algorithms, and normalization only.
validateIbanis TR-only.normalizePhonedoes not verify the current operator and does not check portability records.possibleOriginalOperatoris a prefix-based hint only.normalizeProvinceandnormalizeDistrictrely on bundled static data, not live government or address registries.- Ambiguous district names such as
Merkezmay require province context. - A successful result does not mean official verification. See Out of Scope for the full list of registries this package never calls.
npm install @apideposu/tr-validationimport {
normalizePhone,
validateIban,
validateTckn,
} from "@apideposu/tr-validation";
const tckn = validateTckn("100 000 001-46");
const iban = validateIban("tr62 0001 0012 3456 7890 1234 56");
const phone = normalizePhone("0532 123 45 67");Exports:
validateTcknvalidateVknvalidateIbanformatIbanvalidatePlatevalidateCreditCardvalidateMersisvalidatePostalCodevalidateBarcodevalidateBatchgetReasonMessageparseTurkishNumberparseTurkishCurrencyresolveIbanBanklistTrBankstitleCaseTurkishnormalizeTurkishTextslugifyTurkishnormalizePhonegetProvincesgetDistrictsByProvincenormalizeProvincenormalizeDistrict
Base validation result shape:
{
ok: boolean;
input: string;
normalized: string;
reasons: string[];
mode: "structural_validation" | "number_plan_parse" | "static_dataset";
localOnly: true;
officialVerification: false;
registryLookup: false;
}Function summary:
| Function | Purpose | Notes |
|---|---|---|
validateTckn(input) |
Structural validation for TCKN | Format + known control algorithm only |
validateVkn(input) |
Structural validation for VKN | Format + known control algorithm only |
validateIban(input) |
Structural validation for TR IBAN | TR-only, MOD-97 checksum |
formatIban(input) |
Formats an IBAN in 4-character groups | Normalizes separators and casing |
validatePlate(input) |
Structural validation for TR license plate | KGM block rules + province code cross-check |
validateCreditCard(input) |
Luhn checksum + BIN-based scheme detection | Visa, Mastercard, Amex, Troy, Discover, JCB, Diners, UnionPay |
validateMersis(input) |
Structural validation for 16-digit MERSIS number | Extracts the embedded VKN and reuses the VKN checksum |
validatePostalCode(input) |
Structural validation for 5-digit TR postal code | First two digits cross-checked against the province dataset |
validateBarcode(input) |
EAN-13 / EAN-8 checksum validation | Detects type and flags Turkish GS1 prefixes (868, 869) |
validateBatch(items) |
Runs mixed validators/parsers in order | Preserves input order and delegates to existing core functions |
getReasonMessage(code, locale?) |
Maps reason codes to UI-friendly text | Localized tr / en messages for form and import workflows |
parseTurkishNumber(input) |
Locale-aware numeric parser | Detects TR (1.234,56) vs EN (1,234.56) grouping; flags ambiguous 1.234 |
parseTurkishCurrency(input) |
Currency-aware numeric parser | Recognizes ₺, $, €, £, TL, ISO 4217 codes (TRY, USD, EUR, GBP, ...) |
resolveIbanBank(input) |
Resolves a bank from a valid TR IBAN | Uses bundled BDDK code table, returns null for unknown codes |
listTrBanks() |
Returns the bundled TR bank list | Defensive copy, suitable for dropdowns |
titleCaseTurkish(input) |
Title-cases text with Turkish I/İ rules | Treats whitespace, -, / as word separators; keeps apostrophe-suffixed words single |
normalizeTurkishText(input) |
Turkish-aware text normalization | Returns trimmed, normalized, ascii, slug, searchKey |
slugifyTurkish(input) |
Slug helper for Turkish text | Consistent with normalizeTurkishText(input).slug |
normalizePhone(input, options?) |
Local Turkish phone parsing and normalization | Adds e164, national, country, type, and prefix-based operator hint |
getProvinces() |
Returns bundled province records | Static dataset only |
getDistrictsByProvince(provinceCodeOrSlug) |
Returns districts for a province | Static dataset only |
normalizeProvince(input) |
Matches a province by code, name, or slug | Returns province on success |
normalizeDistrict(input, options?) |
Matches a district, optionally within a province | Returns district and province on success |
import {
formatIban,
getDistrictsByProvince,
getProvinces,
normalizeDistrict,
normalizePhone,
normalizeProvince,
normalizeTurkishText,
slugifyTurkish,
getReasonMessage,
validateBatch,
validateIban,
validateTckn,
validateVkn,
} from "@apideposu/tr-validation";
const tckn = validateTckn("100 000 001-46");
const vkn = validateVkn("734.033.4753");
const iban = validateIban("tr62 0001 0012 3456 7890 1234 56");
const formattedIban = formatIban("tr620001001234567890123456");
const text = normalizeTurkishText(" ISTANBUL / Kadikoy ");
const slug = slugifyTurkish("Cekmekoy Belediyesi");
const phone = normalizePhone("0532 123 45 67");
const provinces = getProvinces();
const districts = getDistrictsByProvince("34");
const province = normalizeProvince("Istanbul");
const district = normalizeDistrict("Kadikoy", { province: "34" });
const batch = validateBatch([
{ type: "tckn", value: "10000000146" },
{ type: "iban", value: "TR62 0001 0012 3456 7890 1234 56" },
{ type: "phone", value: "0532 123 45 67" },
]);
const message = getReasonMessage("INVALID_CHECKSUM", "tr");CommonJS:
const {
normalizePhone,
validateIban,
validateTckn,
} = require("@apideposu/tr-validation");
const result = normalizePhone("0532 123 45 67");normalizePhone("0532 123 45 67");
// {
// ok: true,
// input: "0532 123 45 67",
// normalized: "+905321234567",
// reasons: [],
// mode: "number_plan_parse",
// localOnly: true,
// officialVerification: false,
// registryLookup: false,
// e164: "+905321234567",
// national: "0532 123 45 67",
// extension: null,
// country: "TR",
// type: "mobile",
// possibleOriginalOperator: "Turkcell",
// operatorConfidence: "prefix_based"
// }
validatePlate("34 ABC 12");
// {
// ok: true,
// input: "34 ABC 12",
// normalized: "34ABC12",
// reasons: [],
// mode: "structural_validation",
// localOnly: true,
// officialVerification: false,
// registryLookup: false,
// province: { code: "34", name: "İstanbul" },
// letters: "ABC",
// digits: "12",
// formatted: "34 ABC 12"
// }
validateCreditCard("4111 1111 1111 1111");
// {
// ok: true,
// input: "4111 1111 1111 1111",
// normalized: "4111111111111111",
// reasons: [],
// mode: "structural_validation",
// localOnly: true,
// officialVerification: false,
// registryLookup: false,
// scheme: "visa",
// bin: "411111",
// last4: "1111"
// }
normalizeProvince("34");
// {
// ok: true,
// input: "34",
// normalized: "istanbul",
// reasons: [],
// mode: "static_dataset",
// localOnly: true,
// officialVerification: false,
// registryLookup: false,
// province: {
// code: "34",
// name: "Istanbul",
// normalized: "istanbul",
// phoneAreaCodes: ["212", "216"],
// districtCount: 39
// }
// }
normalizeDistrict("Merkez");
// {
// ok: false,
// input: "Merkez",
// normalized: "merkez",
// reasons: ["AMBIGUOUS_DISTRICT"],
// mode: "static_dataset",
// localOnly: true,
// officialVerification: false,
// registryLookup: false,
// district: null,
// province: null
// }Common reason codes:
| Code | Meaning |
|---|---|
EMPTY_INPUT |
Input is empty after normalization |
UNSUPPORTED_CHARACTERS |
Input contains unsupported characters |
INVALID_LENGTH |
Input length does not match the expected structural length |
INVALID_CHECKSUM |
Input fails a known control algorithm |
TCKN-specific:
| Code | Meaning |
|---|---|
LEADING_ZERO |
First digit is 0 |
REPEATED_DIGITS |
All digits are the same |
VKN-specific:
| Code | Meaning |
|---|---|
REPEATED_DIGITS |
All digits are the same |
IBAN-specific:
| Code | Meaning |
|---|---|
NON_TR_IBAN |
IBAN does not start with TR |
Phone-specific:
| Code | Meaning |
|---|---|
INVALID_PHONE |
Input cannot be parsed as a valid phone number |
NON_TR_PHONE |
Input is valid as a phone number but not a Turkish one |
Location-specific:
| Code | Meaning |
|---|---|
PROVINCE_NOT_FOUND |
Province match could not be resolved |
DISTRICT_NOT_FOUND |
District match could not be resolved |
AMBIGUOUS_DISTRICT |
District name matches multiple provinces |
Plate-specific:
| Code | Meaning |
|---|---|
INVALID_FORMAT |
Plate does not match the <2-digit code><1-3 letters><1-4 digits> structure |
INVALID_PROVINCE_CODE |
First two digits are not a recognized province code (01-81) |
INVALID_LETTER_BLOCK |
Letter block contains forbidden letters (Q/W/X) or wrong count |
INVALID_DIGIT_BLOCK |
Digit count does not match the letter block (e.g. 1 letter requires 4 digits) |
Card-specific:
| Code | Meaning |
|---|---|
UNKNOWN_SCHEME |
Card prefix does not match any known scheme (Visa, Mastercard, Amex, Troy, Discover, JCB, Diners, UnionPay) |
INVALID_LENGTH_FOR_SCHEME |
Card length does not match the detected scheme's allowed lengths |
MERSIS-specific:
| Code | Meaning |
|---|---|
INVALID_EMBEDDED_VKN |
The first 10 digits do not form a valid VKN (fail VKN checksum) |
Postal-code-specific:
| Code | Meaning |
|---|---|
INVALID_PROVINCE_CODE |
First two digits are not a recognized province code (01-81) |
Barcode-specific:
| Code | Meaning |
|---|---|
UNSUPPORTED_BARCODE_TYPE |
Length does not match any supported barcode type (currently EAN-13 and EAN-8) |
Number-specific:
| Code | Meaning |
|---|---|
INVALID_NUMBER_FORMAT |
Input does not parse as a valid number under either locale |
AMBIGUOUS_GROUPING |
Input like 1.234 could be either thousands grouping or a decimal — caller must disambiguate |
Currency-specific:
| Code | Meaning |
|---|---|
INVALID_CURRENCY_FORMAT |
Multiple currency tokens or otherwise malformed currency input |
UNKNOWN_CURRENCY |
Currency token is not in the bundled ISO 4217 / symbol set |
normalizePhoneuseslibphonenumber-jslocally.possibleOriginalOperatoris derived from number prefixes only and may be outdated because of number portability.getProvincesandgetDistrictsByProvinceuse bundled static JSON data.normalizeProvinceandnormalizeDistrictuse the bundled static dataset plus Turkish text normalization.validateIbanperforms structural validation plus MOD-97 checksum for TR IBAN values only.
This package never performs:
- NVI / e-Devlet / KPSPublic / KPSPublicV2 lookup
- Person identity verification by name, surname, mother or father name, or birth year
- ID document (TCKK / old wallet) serial number verification
- BDDK or TCMB account-status lookup
- EGM or KGM plate registry lookup
- MERSIS or Trade Registry corporate-status lookup
- GIB or e-Fatura active-taxpayer lookup
- PTT or UAVT address registry lookup
- Live currency exchange rate lookup
A successful result from this package means the input is structurally valid and passes the published checksum or control algorithm. It does not mean the corresponding person, company, account, plate, address, or product exists in any official registry.
Some synthetic inputs that satisfy the published algorithm are still accepted as structurally valid (for example, a TCKN whose digits follow a crafted pattern can pass the 10th and 11th check-digit math). Confirming whether such an input belongs to a real person, company, or account requires an authoritative registry lookup, which this package never performs.
Published ecosystem packages:
@apideposu/tr-validation-zod- npm: https://www.npmjs.com/package/@apideposu/tr-validation-zod
- GitHub: https://github.com/apideposu/tr-validation-zod
- purpose: optional Zod schemas and helper wrappers for the stable core validators
- Contribution guide: CONTRIBUTING.md
- Dataset maintenance: DATASETS.md
- Ecosystem roadmap: ECOSYSTEM_ROADMAP.md
- Examples: examples/README.md
- Release notes: RELEASE_NOTES.md
- Roadmap: ROADMAP.md
npm run datasets:check
npm test
npm run build
npm run smoke:runtime
npm run size:check
npm run benchmark