Parsing Turkish addresses: mahalle, sokak, kapı no, daire
Turkish addresses are written free-form and read right-to-left in scope. Here is why western parsers fail on them and how to get province, district, street and door number reliably.
If you ship to Türkiye, your checkout collects one free-text field and your warehouse needs five structured ones. Western address parsers get this wrong in a specific way, and the failure is quiet: you do not get an error, you get a package in the wrong district.
Why a western parser fails here
A typical Turkish address:
Atatürk Mah. 1234 Sok. No:5 Daire:3 Kadıköy/İstanbul 34710
Four things break a generic parser:
- Scope runs small-to-large, but not strictly. Neighbourhood first, then street, then door, then district and province — while the postcode may sit at either end.
- Everything is abbreviated, inconsistently.
Mah.,Mh.,Mahallesiare the same thing; so areSok.,Sk.,Sokak,Cad.,Cd.,Bulvar,Blv.. - Streets are often numbers. "1234 Sok." is a street named 1234. A parser looking for "the number after the street" happily grabs it as the door number.
- Turkish case folding.
"İSTANBUL".toLowerCase()in JavaScript producesi̇stanbulwith a combining dot, which never matches youristanbulkey. This one silently destroys dictionary lookups.
'İSTANBUL'.toLowerCase() === 'istanbul' is false in JavaScript. Use a Turkish-aware
fold (İ→i, I→ı) before comparing anything.The parts you actually need
| Turkish | English | Typical markers |
|---|---|---|
| İl | Province (81 of them, plate-coded 1–81) | usually last, after a slash |
| İlçe | District | immediately before the province |
| Mahalle | Neighbourhood | Mah. Mh. Mahallesi |
| Cadde / Sokak / Bulvar | Avenue / street / boulevard | Cad. Sok. Blv. |
| Kapı no | Building number | No: No. Nu. |
| Daire | Apartment | D: Daire Kat (floor) |
| Posta kodu | Postal code | 5 digits; first two encode the province |
One call, structured out
curl -X POST https://api.temsor.com/v1/tr/address/parse \
-H 'content-type: application/json' \
-d '{"address":"Atatürk Mah. 1234 Sok. No:5 Daire:3 Kadıköy/İstanbul 34710"}'
{
"normalized": "Atatürk Mah. 1234 Sok. No: 5 D: 3 34710 Kadıköy / İstanbul",
"province": "İstanbul",
"provinceCode": 34,
"district": "Kadıköy",
"districtSource": "dictionary",
"neighborhood": "Atatürk",
"street": { "name": "1234", "type": "Sokak" },
"buildingNumber": "5",
"apartment": "3",
"postalCode": "34710",
"confidence": 1,
"unparsed": [],
"warnings": []
}
Read these three fields before you trust the rest
confidence— how much of the input was accounted for. Route anything below your threshold to a human instead of shipping it.unparsed— the leftovers. If a company name or a building name is sitting here, your form is collecting two things in one field.districtSource—dictionarymeans the district was matched against a known list;heuristicmeans it was inferred from position. Heuristic is usually right and occasionally confidently wrong, which is exactly when you want to know.
Validating what you parsed
Parsing tells you the shape; it does not tell you the values agree. The postcode carries the province
in its first two digits, so a mismatch between postalCode and provinceCode is a
cheap, high-signal check — it catches the very common case of a customer changing the city on a saved
address but leaving the old postcode.
import Client from 'temsor-api';
const api = new Client();
const a = await api.trAddressParse({ address: input });
const postalProvince = Number(a.postalCode?.slice(0, 2));
if (a.confidence < 0.6) return needsReview(a);
if (a.postalCode && postalProvince !== a.provinceCode) return needsReview(a, 'postcode/province mismatch');
return a;
Practical advice for the form itself
The cheapest fix is upstream: split the province and district into selects and leave one free-text line for the rest. Parsing then only has to handle the street, door and apartment — the part that genuinely cannot be enumerated. Parse what you cannot control; control what you can.
Frequently asked
What is a "mahalle"?
The neighbourhood, the smallest administrative unit in a Turkish address. It is written before the street and is required for correct delivery in most cities.
How many provinces does Türkiye have?
81. Each has a plate code from 1 to 81, and the first two digits of a five-digit postal code identify the province.
Why does my JavaScript lowercase comparison fail on Turkish city names?
Because "İ".toLowerCase() produces an i followed by a combining dot. Fold İ→i and I→ı explicitly before comparing.