Japanese postal code → address lookup for Vue 3. TypeScript-first · works out of the box · fully self-hostable (offline, zero third-party calls) · self-updating data · handles 〒 and full-width input
〒150-0002 → { prefecture: "東京都", city: "渋谷区", town: "渋谷" }
Every Japanese web form needs 郵便番号 → 住所 autofill. Your options today:
| API-based (zipcloud etc.) | yubinbango-core2 | nihonpost | |
|---|---|---|---|
| Works offline / no third-party dependency | ❌ | ✅ | ✅ when self-hosted |
| Zero-config first run (CDN fallback) | — | ❌ | ✅ |
| TypeScript types | ❌ | ❌ | ✅ |
| Vue 3 composable | ❌ | ❌ | ✅ |
| Promise-based | ❌ | ❌ (callbacks) | ✅ |
| Full-width & 〒 input normalization | ❌ | partial | ✅ |
| Full-width kana output (シブヤ not シブヤ) | ❌ | ❌ | ✅ |
| Data auto-updated monthly via CI | — | ❌ | ✅ |
npm i nihonpost<script setup lang="ts">
import { usePostalCode } from 'nihonpost/vue'
const { code, address, loading, notFound } = usePostalCode()
</script>
<template>
<label>郵便番号</label>
<input v-model="code" placeholder="150-0002" />
<span v-if="loading">検索中…</span>
<span v-if="notFound">該当する住所が見つかりません</span>
<template v-if="address">
<input :value="address.prefecture" readonly />
<input :value="address.city" readonly />
<input :value="address.town" readonly />
</template>
</template>That's it. The composable watches code, normalizes whatever the user types
(〒150ー0002 works), and fills address the moment 7 digits exist.
const {
code, // Ref<string> — bind to your input
normalized, // ComputedRef<string|null> — "1500002" when valid
address, // ComputedRef<JpAddress|null> — first match
addresses, // ShallowRef<JpAddress[]> — all matches (some codes span 2 cities!)
loading, // Ref<boolean>
notFound, // Ref<boolean> — valid code, no match
error, // Ref<Error|null>
search, // (value?) => Promise<JpAddress|null> — manual trigger
reset, // () => void
} = usePostalCode({
auto: true, // lookup automatically at 7 digits (default)
debounce: 0, // ms; rarely needed since length is fixed
})No Vue? Use the core directly:
import { lookup, lookupAll, formatPostalCode } from 'nihonpost'
const addr = await lookup('150-0002')
// { prefecture: '東京都', city: '渋谷区', town: '渋谷',
// prefectureKana: 'トウキョウト', cityKana: 'シブヤク', townKana: 'シブヤ',
// prefectureCode: 13 }
await lookupAll('4980000')
// → 2 results: 愛知県弥富市 AND 三重県桑名郡木曽岬町 (shared codes are real!)
formatPostalCode('1500002') // "150-0002"The dataset (~124k codes) is chunked by the first 3 digits into ~900 small JSON files — your app loads only the chunks it touches, a few KB each.
Zero config: it works out of the box. With no setup, lookups fetch
chunks from the jsDelivr CDN, pinned to your installed nihonpost version
(a one-time console.info reminds you this is happening). Upgrading the
package automatically moves the pin — nothing goes stale.
// Equivalent explicit form, if you prefer no console notice:
import { configureLoader, cdnLoader } from 'nihonpost'
configureLoader(cdnLoader())Self-hosting (offline, intranet, CSP, privacy policies): copy
node_modules/nihonpost/data into your static assets and point the loader
at it once, at app startup:
import { configureLoader, fetchLoader } from 'nihonpost'
configureLoader(fetchLoader('/nihonpost-data'))
// lookups now GET /nihonpost-data/150.json etc., cached after first hitVite example — add to vite.config.ts:
import { viteStaticCopy } from 'vite-plugin-static-copy'
plugins: [
viteStaticCopy({
targets: [{ src: 'node_modules/nihonpost/data/*', dest: 'nihonpost-data' }],
}),
]Any static host works too (GitHub Pages, Cloudflare Pages, S3). It's static files — there is no API server anywhere.
Bundle size note: none of this data ever enters your JS bundle. The browser fetches only the chunks a lookup touches — a few KB each, cached after the first hit. The full ~11 MB exists only in
node_modules(and travels as a 1.9 MB tarball).
Custom sources implement one function:
configureLoader(async (prefix) => {
const res = await fetch(`https://cdn.example.com/jp-postal/${prefix}.json`)
return res.ok ? res.json() : null
})npm run build:data # downloads latest utf_ken_all from Japan Post
npm run build:data -- ./my.csv # or use a local copyThe pipeline handles KEN_ALL's sharp edges: multi-row 町域 continuation merging, parenthetical annotation stripping, 「以下に掲載がない場合」 placeholders, half-width → full-width kana, and deduplication.
A GitHub Action rebuilds the data on the 1st of every month and publishes a patch release automatically — installs stay current without a server.
- Postal codes resolve to town (町域) level — that's how Japan's postal system works. Users still type the block/building portion themselves.
- Some codes map to multiple municipalities;
addressgives the first,addressesgives all. Offer a picker if you need precision. - Data source: Japan Post 郵便番号データ (public data).
MIT © Thiyagu Arunachalam