thai_provinces 0.3.1 copy "thai_provinces: ^0.3.1" to clipboard
thai_provinces: ^0.3.1 copied to clipboard

Thailand province/district/subdistrict data with postal codes for Dart & Flutter: offline lookup, search, validation, and address resolution. Pure Dart.

thai_provinces #

pub package pub points likes license: MIT

ข้อมูลเขตการปกครองของประเทศไทย (จังหวัด/อำเภอ/ตำบล พร้อมรหัสไปรษณีย์) สำหรับภาษา Dart ฝังข้อมูลมาในตัว ค้นหา/เติมคำอัตโนมัติ/ตรวจสอบที่อยู่ได้ทันที ไม่ต้องต่อเน็ต ไม่พึ่ง Flutter

A pure-Dart, MIT-licensed library of Thailand's administrative areas — 77 provinces (จังหวัด), 928 districts (อำเภอ/เขต), and 7,452 subdistricts (ตำบล/แขวง) with postal codes — fully embedded, with lookup, hierarchy navigation, autocomplete, validation, and address resolution. No network or files at runtime, and no Flutter dependency (works in CLI, server and Flutter alike).

Install #

dependencies:
  thai_provinces: ^0.2.0
dart pub add thai_provinces

Requires Dart SDK 3.0+.

Features #

  • Embedded dataset — zero network/filesystem access at runtime; parsed once, lazily, on first use.
  • Complete coverage — 77 provinces, 928 districts, 7,452 subdistricts, postcodes.
  • Official DOPA geocodes — 2-digit provinces (10–96), 4-digit districts, 6-digit subdistricts; the hierarchy is derivable (districtCode ~/ 100 == provinceCode, subCode ~/ 100 == districtCode).
  • Lookups by code and hierarchy navigation (parent/child both ways).
  • Postal-code lookups — subdistricts by postcode, or postcodes of a district.
  • Name search & autocomplete — exact (normalized) find plus prefix search.
  • Validation of a province/district/subdistrict triple.
  • Address resolution — match free-form fragments (plus an optional postcode) to concrete Province/District/Subdistrict results.
  • Cascading address forms — drive province → district → subdistrict dropdowns and auto-fill the postcode (see the recipe below).
  • JSON serialization — every model has toJson()/fromJson with a round-trip guarantee, ready for REST APIs and local storage.
  • Thai + English names for every area, plus six regions.

Quick start #

Build a cascading address form #

The most common use: three linked dropdowns — province → district → subdistrict — that auto-fill the postcode. The data layer is identical whether you wire it to Flutter DropdownButtonFormFields or a CLI prompt.

import 'package:thai_provinces/thai_provinces.dart';

// 1. Province dropdown — all 77, ordered by code:
final provinceItems = provinces();

// 2. User picks a province -> show its districts:
final selectedProvince = provinceByCode(50)!;        // เชียงใหม่
final districtItems = selectedProvince.districts;     // ordered by code

// 3. User picks a district -> show its subdistricts:
final selectedDistrict = districtItems.first;
final subdistrictItems = selectedDistrict.subdistricts;

// 4. User picks a subdistrict -> the postcode auto-fills:
final selectedSubdistrict = subdistrictItems.first;
final postcode = selectedSubdistrict.postcode;        // the official postcode

print('${selectedProvince.nameTh} > ${selectedDistrict.nameTh} > '
    '${selectedSubdistrict.nameTh} — $postcode');

In Flutter, feed each list to a DropdownButtonFormField, and clear the child selections whenever a parent changes. Everything is in memory, so rebuilding the options on every change is instant.

Look up by code and read names #

import 'package:thai_provinces/thai_provinces.dart';

final p = provinceByCode(10);          // nullable; null if unknown
print('${p!.nameTh} / ${p.nameEn}');   // กรุงเทพมหานคร / Bangkok
print(p.region.nameEn);                // Central
final cm = provinceByCode(50);         // เชียงใหม่ / Chiang Mai

for (final d in cm!.districts) {
  print('${d.code} ${d.nameTh}');
  for (final s in d.subdistricts) {
    print('  ${s.code} ${s.nameTh} (${s.postcode})');
  }
}

// Walk back up from a subdistrict (nullable parents):
final s = subdistrictByCode(100101);
print('${s!.nameTh} -> ${s.district?.nameTh} -> ${s.province?.nameTh}');

Find by postal code #

for (final s in byPostcode(50200)) {
  print('${s.nameTh}, ${s.district?.nameTh}, ${s.province?.nameTh}');
}

// All postcodes within a district:
print(postcodesOf(5001)); // e.g. [50000, 50100, 50200, 50300]
for (final s in searchSubdistricts('สุเทพ')) {
  print('${s.nameTh} — ${s.district?.nameTh}, ${s.province?.nameTh}');
}

Resolve a full address #

try {
  final matches = resolve(const AddressQuery(
    subdistrict: 'สุเทพ',
    district: 'เมืองเชียงใหม่',
    province: 'เชียงใหม่',
    postcode: 50200,
  ));
  for (final m in matches) {
    print('${m.province.nameTh} > ${m.district.nameTh} > '
        '${m.subdistrict.nameTh} (${m.subdistrict.postcode})');
  }
} on ThaiAddressException catch (e) {
  // no match / ambiguous / no usable field
  print(e.message);
}

Serialize to / from JSON #

Every model (Province, District, Subdistrict, AddressMatch) is self-describing: toJson() emits all fields, and fromJson rebuilds the value with no dataset lookup, so X.fromJson(x.toJson()) == x. A Province's region is stored as its integer region code (1..6).

import 'dart:convert';
import 'package:thai_provinces/thai_provinces.dart';

final p = provinceByCode(10)!;            // Bangkok
final wire = jsonEncode(p.toJson());
// {"code":10,"nameTh":"กรุงเทพมหานคร","nameEn":"Bangkok","region":2}

final back = Province.fromJson(jsonDecode(wire) as Map<String, dynamic>);
print(back == p);                         // true

// AddressMatch nests each level's JSON:
final m = resolve(const AddressQuery(subdistrict: 'สุเทพ', postcode: 50200)).first;
final m2 = AddressMatch.fromJson(
    jsonDecode(jsonEncode(m.toJson())) as Map<String, dynamic>);
print(m2 == m);                           // true

An unknown region code passed to Province.fromJson throws a FormatException; a missing/wrongly-typed key throws.

Validate a code triple #

try {
  validate(50, 5001, 500101);
} on ThaiAddressException catch (e) {
  print('invalid address codes: ${e.message} (${e.kind})');
}

Caveat: duplicate names #

Many subdistrict names repeat across different provinces (e.g. "ในเมือง" names 22 subdistricts across 19 provinces), so name-based lookups (findSubdistricts, searchSubdistricts) return a List, not a single result. Disambiguate with district/province context or a postcode — resolve(AddressQuery(...)) is built for exactly this.

Note also that Bangkok uses เขต/แขวง terminology (its district names carry the "เขต" sense), and that postal codes attach at the subdistrict level — a single district can span several postcodes.

Notes on normalization (NFC) #

normalizeName trims and collapses whitespace, lowercases (ASCII/Latin only; Thai has no case), and strips one leading admin prefix (จังหวัด/อำเภอ/อ./ตำบล/ต./เขต/แขวง/กิ่งอำเภอ and English changwat/amphoe/khet/ tambon/khwaeng). Unlike the Go original it does not apply Unicode NFC: the Dart core library ships no NFC implementation, and the embedded dataset is already NFC, so composed/decomposed folding is unnecessary in practice.

Data source & license #

This library is MIT-licensed (see LICENSE).

The embedded dataset is a reshaped snapshot of github.com/kongvut/thai-province-data by Kongvut Sangkla, used under the MIT License, with district codes validated against the Department of Provincial Administration (กรมการปกครอง, DOPA) dataset published on Thailand's open-government-data portal (data.go.th). Official two-digit province codes were derived and the field layout slimmed, but it is the same underlying data. The factual codes/names are government administrative data; credit DOPA when redistributing.

This package is a faithful pure-Dart port of the Go library go-thaiaddress.

0
likes
160
points
333
downloads

Documentation

API reference

Publisher

verified publisher10v3n4m.cc

Weekly Downloads

Thailand province/district/subdistrict data with postal codes for Dart & Flutter: offline lookup, search, validation, and address resolution. Pure Dart.

Repository (GitHub)
View/report issues

Topics

#thailand #address #geography #postal-code #i18n

License

MIT (license)

More

Packages that depend on thai_provinces