Turkey's tax authority (GİB) requires all invoices above a certain threshold to be issued electronically in UBL-TR format — a localized version of Universal Business Language. Here's how to validate these XML documents client-side.
UBL-TR Structure
A Turkish e-Invoice XML follows this hierarchy:
<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
<cbc:UBLVersionID>2.1</cbc:UBLVersionID>
<cbc:CustomizationID>TR1.2</cbc:CustomizationID>
<cbc:ProfileID>TICARIFATURA</cbc:ProfileID>
<cbc:ID>ABC2026000000001</cbc:ID>
<cbc:IssueDate>2026-09-22</cbc:IssueDate>
<cbc:InvoiceTypeCode>SATIS</cbc:InvoiceTypeCode>
<cbc:DocumentCurrencyCode>TRY</cbc:DocumentCurrencyCode>
<!-- Digital Signature (XAdES) -->
<cac:Signature>
<cbc:ID>...</cbc:ID>
<cac:SignatoryParty>...</cac:SignatoryParty>
<cac:DigitalSignatureAttachment>
<cac:ExternalReference>
<cbc:URI>#Signature_ABC2026000000001</cbc:URI>
</cac:ExternalReference>
</cac:DigitalSignatureAttachment>
</cac:Signature>
<!-- Supplier (Satıcı) -->
<cac:AccountingSupplierParty>
<cac:Party>
<cac:PartyIdentification>
<cbc:ID schemeID="VKN">1234567890</cbc:ID>
</cac:PartyIdentification>
</cac:Party>
</cac:AccountingSupplierParty>
<!-- Line Items -->
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity unitCode="C62">10</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="TRY">1000.00</cbc:LineExtensionAmount>
<cac:TaxTotal>
<cbc:TaxAmount currencyID="TRY">200.00</cbc:TaxAmount>
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="TRY">1000.00</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="TRY">200.00</cbc:TaxAmount>
<cac:TaxCategory>
<cac:TaxScheme>
<cbc:TaxTypeCode>0015</cbc:TaxTypeCode>
<cbc:Name>KDV</cbc:Name>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
</cac:TaxTotal>
</cac:InvoiceLine>
</Invoice>
Client-Side Validation Engine
class EFaturaValidator {
constructor(xmlString) {
const parser = new DOMParser();
this.doc = parser.parseFromString(xmlString, "text/xml");
this.ns = {
inv: "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2",
cbc: "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2",
cac: "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
};
this.errors = [];
}
xpath(expr, context = this.doc) {
return this.doc.evaluate(expr, context, (prefix) => this.ns[prefix],
XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}
validate() {
this.checkRequiredFields();
this.checkVKN();
this.checkTaxCalculations();
this.checkInvoiceId();
return { valid: this.errors.length === 0, errors: this.errors };
}
checkRequiredFields() {
const required = [
["cbc:ID", "Fatura Numarası"],
["cbc:IssueDate", "Düzenleme Tarihi"],
["cbc:InvoiceTypeCode", "Fatura Tipi"],
["cbc:DocumentCurrencyCode", "Para Birimi"]
];
for (const [path, label] of required) {
if (!this.doc.querySelector(path)) {
this.errors.push(`Zorunlu alan eksik: ${label} (${path})`);
}
}
}
checkVKN() {
// Turkish tax number (VKN) must be exactly 10 digits
const vknNode = this.doc.querySelector(
'AccountingSupplierParty PartyIdentification ID[schemeID="VKN"]'
);
if (vknNode) {
const vkn = vknNode.textContent.trim();
if (!/^\d{10}$/.test(vkn)) {
this.errors.push(`Geçersiz VKN: "${vkn}" (10 haneli olmalı)`);
}
}
}
checkTaxCalculations() {
const lines = this.doc.querySelectorAll("InvoiceLine");
for (const line of lines) {
const lineId = line.querySelector("ID")?.textContent;
const amount = parseFloat(
line.querySelector("LineExtensionAmount")?.textContent || "0"
);
const taxAmount = parseFloat(
line.querySelector("TaxTotal TaxAmount")?.textContent || "0"
);
const taxable = parseFloat(
line.querySelector("TaxSubtotal TaxableAmount")?.textContent || "0"
);
if (Math.abs(amount - taxable) > 0.01) {
this.errors.push(
`Satır ${lineId}: Matrah uyumsuzluğu (${amount} ≠ ${taxable})`
);
}
}
}
checkInvoiceId() {
// GİB format: 3 letters + year + 9 digits
const id = this.doc.querySelector("ID")?.textContent?.trim();
if (id && !/^[A-Z]{3}\d{13}$/.test(id)) {
this.errors.push(
`Fatura numarası GİB formatına uymuyor: "${id}" (ABC2026000000001 gibi olmalı)`
);
}
}
}
// Usage
const validator = new EFaturaValidator(xmlContent);
const result = validator.validate();
console.log(result);
// { valid: false, errors: ["Geçersiz VKN: ..."] }
Tax Code Reference
| Code | Tax Type | Rate |
|---|---|---|
| 0015 | KDV (VAT) | 1%, 10%, 20% |
| 0003 | ÖTV (Special Consumption) | Varies |
| 0021 | Banka Muamele Vergisi | 5% |
| 0071 | Damga Vergisi (Stamp) | 0.948% |
| 9015 | KDV Tevkifatı (Withholding) | Varies |
Try It Yourself
Our open-source e-Fatura XML Viewer parses and displays UBL-TR invoices entirely in the browser.
All tools: github.com/eimza-kep
Top comments (0)