Paste the whole address and be done
tw-fuzzy-zipcode
Look up Taiwan ZIP codes from addresses that were never split into fields, and render them in English following Chunghwa Post's own rules. It understands 台/臺, full-width characters, and Chinese numerals, and works even without the city or district — every lookup runs client-side, with no server, WASM, or SQLite.
IDEA
Addresses as users actually paste them
Addresses in a form are rarely split cleanly: raw strings pasted by users, OCR output, and single text columns inherited from older systems all arrive as one run of text. tw-fuzzy-zipcode takes them as-is, with no requirement to separate city, district, and road first. It is a JavaScript port of the matching algorithm in moskytw/zipcodetw, with no runtime dependencies.
FUZZY MATCHING
However the address is written
It first normalizes 台/臺, full-width characters, and common Chinese numerals, then splits the address into city, district, road section, lane, alley, and house-number fragments and matches them layer by layer. With enough information it applies the house-number rules and returns six digits; with less, it falls back to a usable three-digit code — and when the address is ambiguous it returns an empty string rather than guessing.
臺北市信義區市府路1號
A complete address matches the house-number rules and returns six digits.
台北市秀山街
台 is normalized to 臺; a road name alone is enough.
松山區
A district on its own returns a usable three-digit code.
松江路100號
The road name is unique nationwide, so the city and district are filled back in.
UNIQUE ROAD
The missing parts, filled back in
When an address omits the city or district, and what remains matches exactly one road in Taiwan, the missing fragments are restored and matching continues to six digits. If several roads match — Zhongzheng Rd. exists nearly everywhere — the address is left alone rather than guessed.
Input
松江路100號
↓
Filled in
臺北市中山區松江路100號
104091
SPEED
Under 2 microseconds per lookup
The browser index flattens sorted data into one string per column with Int32Array offsets and binary search: about a quarter of the memory, still around 500,000 lookups per second.
1.7–2.0 µs
Per lookup
34 ms
Loading the index in a browser
0.80 MB
Brotli transfer size (gzip approx. 1.21 MB)
5.3 MB
Memory after loading (browser)
Measured against Chunghwa Post's June 2026 3+3 ZIP code data — 79,845 house-number rules and 162,470 gradual address entries — on an Apple M3 Pro. A 90,950-query differential test against the Python reference implementation reports zero differences.
基隆愛三路郵局第5號信箱
P.O. Box 5, Keelung Ai 3rd Road, Keelung City 200900
P.O. BOX
P.O. boxes too
A box address is not a street address: its ZIP code comes straight from the post office's name, never from the house-number rules. 899 post offices that actually operate boxes are covered; the source data lists another 314 that have six-digit codes assigned but no box service yet, and those always return an empty string — the number looks deliverable but is not.
ENGLISH ADDRESS
English addresses, while we are at it
translate() reverses the word order and inserts the ZIP code following Chunghwa Post's own writing rules, producing an English address you can print straight onto an envelope. Every name comes from the official Chinese-English tables — nothing is romanized by guesswork — and against the bundled directory, 99.99% of addresses translate completely.
臺北市中正區忠孝東路一段1巷1弄1號1樓
1F., No. 1, Aly. 1, Ln. 1, Sec. 1, Zhongxiao E. Rd., Zhongzheng Dist., Taipei City 100009, Taiwan (R.O.C.)
Floor, number, alley, lane, and section each map to the official abbreviation, with the ZIP code looked up and inserted.
政大郵局第12號信箱
P.O. Box 12, National Chengchi University, Taipei City 116979, Taiwan (R.O.C.)
English post-office names for P.O. boxes also come from official data; the fields become the box number and the office name.
臺北市信義區四維三路2號
english: ''
Siwei 3rd Rd. is in Lingya Dist., Kaohsiung — Xinyi Dist. has no such road. A name being translatable does not make the address real, so cross-validation returns an empty string rather than an English address that cannot be delivered.
OPEN SOURCE
MIT licensed, open source, free to use
What ships is the source: no build step, loadable directly in a browser. TypeScript declarations are included for both the Node and browser entry points, so no @types package is needed. The data comes as three independent dictionaries — the address index, the P.O. box table, and the Chinese-English name tables — so you load only the ones you need.
The ZIP code directory, Chinese-English name tables, and P.O. box data all come from Chunghwa Post, used under its 3+3 ZIP code open licensing statement and the Open Government Data License. The matching algorithm is ported from moskytw/zipcodetw.
USAGE
How to use it
No runtime dependencies, and what ships is the source. On Node you import and query directly; browsers have no fs, so loadZipcode() fetches the data files published with the package. TypeScript declarations are bundled, so no @types package is needed.
TERMINAL
$ npm install tw-fuzzy-zipcodeNODE.JS
import { find, lookup, translate } from 'tw-fuzzy-zipcode'find('臺北市信義區市府路1號')// '110204'find('松江路100號')// '104091',路名全臺唯一,縣市與行政區可以省略lookup('臺北市')// { zipcode: '1', source: 'gradual', resolution: 'prefix' }translate('臺北市信義區市府路1號').english// 'No. 1, Shifu Rd., Xinyi Dist., Taipei City 110204, Taiwan (R.O.C.)'
BROWSER
import { loadZipcode } from 'tw-fuzzy-zipcode/browser'// 瀏覽器沒有 fs,改用 loadZipcode() 載入隨套件發布的資料檔const zip = await loadZipcode({gradualUrl: '/data/gradual.tsv',preciseUrl: '/data/precise.tsv',mailboxUrl: '/data/mailbox.tsv',})zip.find('臺北市信義區市府路1號')// '110204'
Common API
find(address)Returns a directly usable three- or six-digit ZIP code, or an empty string when nothing is certain — never an invalid four- or five-digit intermediate prefix.
lookup(address)Use it when the UI needs to show how the address matched: returns { zipcode, source, resolution }, or null when nothing is found.
findAddress() / findMailbox()Sub-functions that search only street addresses or only P.O. boxes, for when the data is guaranteed to be one form.
translate(address)Returns { english, parts, untranslated, complete }. The ZIP code is looked up and inserted automatically, or supplied via { zipcode }.
loadZipcode({ gradualUrl, preciseUrl, mailboxUrl })Loads the data files in a browser and builds a Zipcode whose methods share the names above. The three dictionaries are independent — load only what you need.
loadTranslator({ roadUrl, districtUrl })The Chinese-English tables for browsers; wire up verify and mailbox for road cross-validation and P.O. box translation.
What lookup() returns
sourceprecise · gradual · mailbox
Where the ZIP code came from: the house-number rules, the gradual address index, or the P.O. box table.
resolutionsix-digit · three-digit · prefix
How far the address resolved: six digits, three digits, or a city-level prefix that is not a valid ZIP code on its own.