Address Autocomplete
Address suggestions and structured address lookup for checkout forms
Autocomplete addresses during checkout — user selects country, starts typing, and gets structured suggestions (street, city, ZIP).
This is a paid feature. Usage is tracked per 15-minute interval and billed monthly based on request count.
How It Works
- Customer selects country (required, ISO 3166-1 alpha-2)
- Starts typing an address → debounced API call returns suggestions
- Customer picks a suggestion → full structured address is resolved
- Fields
street,city,zip,countryare auto-populated in your form
Framework Examples
Pick your stack — every example below is copy-paste ready:
SDK Client (any JS)
Framework-agnostic promise API — use it from a Node script, Worker, or any bundler.
React
useAddressAutocomplete — works in Vite, CRA, Remix, Astro, anywhere React runs.
React checkout form
Full checkout address example with country selector.
Vue 3 / Nuxt 3
Drop-in composable built on the SDK client.
Next.js Server Action
Keep the SDK key server-side, call from a thin client component with useTransition.
SDK Client
Framework-agnostic — works in any JavaScript / TypeScript project.
// Search suggestions (country is required)
const { suggestions } = await storefront.addresses.autocomplete('Vodickova 12', 'CZ');
// Get full structured address from a suggestion
const address = await storefront.addresses.getDetail(suggestions[0].placeId);
// {
// street: "Vodičkova 699/30",
// city: "Praha",
// zip: "11000",
// country: "Czechia",
// countryCode: "CZ",
// lat: 50.0815,
// lng: 14.4243,
// }React Hook
The useAddressAutocomplete hook handles debouncing, caching, and selection out of the box. Works in any React app — Vite, CRA, Remix, Next.js, Astro React islands, whatever.
import { useAddressAutocomplete } from '@behio/storefront-sdk/react';
function AddressInput() {
const {
query,
setQuery,
suggestions,
isLoading,
select,
selected,
isSelecting,
clear,
} = useAddressAutocomplete({
country: 'CZ', // required — ISO 3166-1 alpha-2
debounce: 300, // ms (default: 300)
minChars: 3, // min characters before searching (default: 3)
});
return (
<div>
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Start typing your address..."
/>
{isLoading && <div>Searching…</div>}
{suggestions.length > 0 && (
<ul>
{suggestions.map(s => (
<li key={s.placeId} onClick={() => select(s.placeId)}>
{s.description}
</li>
))}
</ul>
)}
{selected && (
<div>
<p>Street: {selected.street}</p>
<p>City: {selected.city}</p>
<p>ZIP: {selected.zip}</p>
<p>Country: {selected.country}</p>
</div>
)}
</div>
);
}Checkout Form Example (React)
Combine with a country selector for a complete checkout address form:
import { useState } from 'react';
import { useAddressAutocomplete } from '@behio/storefront-sdk/react';
function CheckoutAddressForm({ onComplete }) {
const [country, setCountry] = useState('CZ');
const { query, setQuery, suggestions, select } = useAddressAutocomplete({
country,
debounce: 300,
});
// When user picks a suggestion, fill all fields.
const handleSelect = async (placeId: string) => {
const address = await select(placeId);
onComplete({
street: address.street,
city: address.city,
zip: address.zip,
country: address.countryCode,
});
};
return (
<div>
<select value={country} onChange={e => setCountry(e.target.value)}>
<option value="CZ">Czech Republic</option>
<option value="SK">Slovakia</option>
<option value="DE">Germany</option>
<option value="AT">Austria</option>
<option value="PL">Poland</option>
</select>
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Street address"
/>
{suggestions.map(s => (
<div key={s.placeId} onClick={() => handleSelect(s.placeId)}>
{s.description}
</div>
))}
</div>
);
}Vue 3 / Nuxt 3 Composable
The SDK ships a React hook; Vue users can drop the same logic into a composable using the client directly.
// composables/useAddressAutocomplete.ts
import { ref, watch } from 'vue';
import { useStorefront } from './useStorefront';
import type { AddressSuggestion, AddressDetail } from '@behio/storefront-sdk';
export function useAddressAutocomplete(opts: {
country: string;
debounce?: number;
minChars?: number;
}) {
const storefront = useStorefront();
const query = ref('');
const suggestions = ref<AddressSuggestion[]>([]);
const selected = ref<AddressDetail | null>(null);
const isLoading = ref(false);
const isSelecting = ref(false);
const debounce = opts.debounce ?? 300;
const minChars = opts.minChars ?? 3;
let timer: ReturnType<typeof setTimeout> | null = null;
watch(query, (q) => {
if (timer) clearTimeout(timer);
if (q.length < minChars) {
suggestions.value = [];
return;
}
timer = setTimeout(async () => {
isLoading.value = true;
try {
const res = await storefront.addresses.autocomplete(q, opts.country);
suggestions.value = res.suggestions;
} finally {
isLoading.value = false;
}
}, debounce);
});
async function select(placeId: string) {
isSelecting.value = true;
try {
selected.value = await storefront.addresses.getDetail(placeId);
return selected.value;
} finally {
isSelecting.value = false;
}
}
function clear() {
query.value = '';
suggestions.value = [];
selected.value = null;
}
return { query, suggestions, selected, isLoading, isSelecting, select, clear };
}Vue template
<script setup lang="ts">
import { useAddressAutocomplete } from '~/composables/useAddressAutocomplete';
const { query, suggestions, isLoading, selected, select } = useAddressAutocomplete({
country: 'CZ',
});
</script>
<template>
<input v-model="query" placeholder="Start typing your address…" />
<div v-if="isLoading">Searching…</div>
<ul v-if="suggestions.length">
<li v-for="s in suggestions" :key="s.placeId" @click="select(s.placeId)">
{{ s.description }}
</li>
</ul>
<p v-if="selected">{{ selected.formattedAddress }}</p>
</template>Next.js — Server Action + Client Input
For Next.js App Router users who want to keep the API key private and call the SDK from a Server Action:
// app/actions/address.ts
'use server';
import { BehioStorefront } from '@behio/storefront-sdk';
const storefront = new BehioStorefront({ apiKey: process.env.BEHIO_SDK_KEY! });
export async function searchAddress(query: string, country: string) {
if (query.length < 3) return [];
const { suggestions } = await storefront.addresses.autocomplete(query, country);
return suggestions;
}
export async function resolveAddress(placeId: string) {
return storefront.addresses.getDetail(placeId);
}// app/checkout/AddressInput.tsx
'use client';
import { useState, useTransition } from 'react';
import { searchAddress, resolveAddress } from '../actions/address';
export function AddressInput({ country }: { country: string }) {
const [query, setQuery] = useState('');
const [suggestions, setSuggestions] = useState<any[]>([]);
const [isPending, startTransition] = useTransition();
function onChange(value: string) {
setQuery(value);
startTransition(async () => {
setSuggestions(await searchAddress(value, country));
});
}
async function onPick(placeId: string) {
const detail = await resolveAddress(placeId);
console.log('Resolved:', detail);
}
return (
<div>
<input value={query} onChange={(e) => onChange(e.target.value)} />
{isPending && <span>Searching…</span>}
{suggestions.map((s) => (
<div key={s.placeId} onClick={() => onPick(s.placeId)}>
{s.description}
</div>
))}
</div>
);
}Hook Options
| Option | Type | Default | Description |
|---|---|---|---|
country | string | required | ISO 3166-1 alpha-2 country code |
debounce | number | 300 | Debounce delay in ms |
minChars | number | 3 | Min characters before searching |
enabled | boolean | true | Disable the hook |
Hook Return
| Property | Type | Description |
|---|---|---|
query | string | Current input value |
setQuery | (value: string) => void | Input onChange handler |
suggestions | AddressSuggestion[] | Current list of suggestions |
isLoading | boolean | Fetching suggestions |
select | (placeId: string) => Promise<AddressDetail> | Resolve a suggestion to a full address |
selected | AddressDetail | null | Resolved structured address |
isSelecting | boolean | Resolving place detail |
clear | () => void | Reset everything |
Address Detail Fields
After calling select():
| Field | Type | Example |
|---|---|---|
street | string | "Vodičkova 699/30" |
streetNumber | string | "699/30" |
city | string | "Praha" |
zip | string | "11000" |
country | string | "Czechia" |
countryCode | string | "CZ" |
formattedAddress | string | "Vodičkova 699/30, 110 00 Praha 1, Czechia" |
lat | number | 50.0815 |
lng | number | 14.4243 |