Address autocomplete widget in plain JavaScript
Add an address field with suggestions to any page with one script tag, and fill street, postcode and city into separate fields.
We ship two ready-made scripts for an address field. They need no build tools, no framework and no other libraries. This guide shows which one to pick, how to set it up and how to get the chosen address into separate form fields.
Both scripts call /autocomplete straight from the visitor's browser. The key is therefore in your page source and must be a domain-locked browser key. Read Browser keys and allowed domains before you go live.
On a webshop checkout where you cannot write JavaScript, use the one-tag script https://api.danadresse.dk/js/danadresse-auto.min.js with data-key instead. It finds the address fields itself and fills street, postcode and city. See the platform pages under /en/integrationer.
1. Pick a script
danadresse-autocomplete-1.0.0.js | danadresse-widget.js | |
|---|---|---|
| URL | https://danadresse.dk/static/js/danadresse-autocomplete-1.0.0.js | https://danadresse.dk/static/js/danadresse-widget.js |
| Versioned | Yes, the file never changes | No, updated in place |
| Accessibility | ARIA 1.2 combobox, live region, Home/End | ARIA 1.2 combobox, live region |
| Drill-down (street → house number → unit) | Yes | Yes |
onSelect receives | A flat object with street, house number, postcode etc. | The raw suggestion from /autocomplete |
| Separate fields | You write them in onSelect (step 5) | Built in: fields: { street, postnr, city } |
| Error display | Shows a message and leaves a plain text field | A message per cause (key, domain, too many searches, network) |
| Languages | da, en, uk | Danish text; override with messages |
Use danadresse-autocomplete-1.0.0.js for new pages. danadresse-widget.js is still served and kept up to date because existing pages use it. Use one of them per page. danadresse-widget.js always registers window.DanadresseWidget and takes window.DanadresseAutocomplete only when 1.0.0 is not on the page, so the two no longer overwrite each other.
2. Add the field and the script
A field with a label, the script and one call to attach:
<label for="adr">Address</label> <input id="adr" name="address" autocomplete="off"> <script src="https://danadresse.dk/static/js/danadresse-autocomplete-1.0.0.js"></script> <script> const API_KEY = "dawa_live_your_browser_key"; // domain-locked key DanadresseAutocomplete.attach("#adr", { apiKey: API_KEY, lang: "en", onSelect: function (a) { console.log(a.tekst, a.id); }, }); </script>
Attributes alone also work. Once the page has loaded, the script finds every input with data-danadresse-key:
<input id="adr" data-danadresse-key="dawa_live_your_browser_key" data-danadresse-lang="en"> <script src="https://danadresse.dk/static/js/danadresse-autocomplete-1.0.0.js"></script>
The attributes the script reads are data-danadresse-key, data-danadresse-lang, data-danadresse-type and data-danadresse-base. For an onSelect you need attach, or you listen for the danadresse:select event (see step 4).
3. Options
| Option | Default | Meaning |
|---|---|---|
apiKey | none | Your browser key. Without one the API answers 401 and the field shows the error message |
lang | <html lang>, else da | da, en or uk. Sets placeholder, empty text, error text and screen-reader text |
type | adresse | adresse (with floor/door), adgangsadresse (stops at the house number) or vejnavn |
limit | 8 | Number of suggestions (per_side). The API allows at most 100 |
minLength | 2 | Characters typed before a search starts |
debounce | 150 | Wait in milliseconds after the last keystroke |
baseUrl | https://api.danadresse.dk | Change only if you route the calls through your own proxy |
credit | true | false removes the "Addresses from danadresse.dk" line |
fuzzy | true | false sends fuzzy=false and turns typo tolerance off. Default: typos are corrected |
onSelect(a) | none | Called once the user has picked a final address |
onError(err) | none | Called on a network or HTTP error. err.status is the status code |
attach returns an object with close(), search(q) and destroy(). Calling attach twice on the same field returns the same object, so you never get duplicate lists.
4. Read the chosen address
With type: "adresse" the choice takes up to three steps. When the user picks a street name, the script inserts it, puts the caret after it and searches again. The same happens for an entrance with several flats. onSelect runs only once an address of the type you asked for has been chosen.
onSelect receives a flat object:
{
"type": "adresse",
"tekst": "Rådhuspladsen 1, 3300 Frederiksværk",
"id": "762a9a1f-a5fb-4e47-b1b2-9fa94026bc80",
"adgangsadresseid": "0a3f507f-0959-32b8-e044-0003ba298018",
"vejnavn": "Rådhuspladsen",
"adresseringsvejnavn": "Rådhuspladsen",
"vejkode": "1214",
"kommunekode": "0260",
"husnr": "1",
"husnummer": "1",
"husbogstav": null,
"etage": null,
"dør": null,
"supplerendebynavn": null,
"postnr": "3300",
"postnrnavn": "Frederiksværk",
"lat": 55.97657023,
"lon": 12.01612365,
"raw": { "type": "adresse", "tekst": "…", "data": { "…": "…" } }
}husnr is the house number as one string (12A). husnummer and husbogstav are the same value split (12 and A), which many ERP and shipping systems require. raw is the original suggestion from /autocomplete, for any field the flat object does not carry.
The field dør (door) has an ø in its name. Read it with a["dør"] in JavaScript.
5. Fill separate fields
Many forms keep street, postcode and city in separate fields. Let the user search in one field and write the result into the others. Always store id: it lets you look the address up again later without parsing text.
<form id="delivery"> <label for="search">Find address</label> <input id="search"> <label for="street">Street and number</label> <input id="street" name="street"> <label for="unit">Floor and door</label> <input id="unit" name="unit"> <label for="postcode">Postcode</label> <input id="postcode" name="postcode" inputmode="numeric"> <label for="city">City</label> <input id="city" name="city"> <input type="hidden" id="address_id" name="address_id"> </form> <script src="https://danadresse.dk/static/js/danadresse-autocomplete-1.0.0.js"></script> <script> const API_KEY = "dawa_live_your_browser_key"; const f = document.getElementById("delivery"); DanadresseAutocomplete.attach("#search", { apiKey: API_KEY, lang: "en", onSelect: function (a) { f.street.value = a.vejnavn + " " + a.husnr; f.unit.value = [a.etage ? a.etage + "." : "", a["dør"] || ""].join(" ").trim(); f.postcode.value = a.postnr; f.city.value = a.postnrnavn; f.address_id.value = a.id; }, }); // The user edits the fields afterwards: the id no longer matches. ["street", "unit", "postcode", "city"].forEach(function (name) { f[name].addEventListener("input", function () { f.address_id.value = ""; }); }); </script>
If you would rather not write an onSelect, listen for the event. It bubbles up through the DOM with the same object in detail:
document.addEventListener("danadresse:select", (e) => { const a = e.detail; console.log("Chosen:", a.tekst, a.postnr, a.postnrnavn); });
6. When the API does not answer
On a network error, 401, 403, 429 or 5xx the script shows "Address lookup is unavailable — type the address manually" (in the chosen language), and the field keeps working as a plain text input. Your form must therefore accept an address without an id. Cleanse it on the server with /datavask/adresser if you need the codes.
Use onError to log the cause. err.status is undefined on a network error:
const API_KEY = "dawa_live_your_browser_key"; DanadresseAutocomplete.attach("#adr", { apiKey: API_KEY, onError(err) { if (err.status === 403) console.warn("Check the key's allowed domains"); else if (err.status === 429) console.warn("Too many calls right now"); else console.warn("Address lookup failed", err); }, });
Error responses carry CORS headers, so the browser sees the real status code and not just a network error. A browser key has a per-visitor limit of 120 searches a minute. Above it the API answers 429 rate_limit_per_visitor with Retry-After, and the field shows the error message until the user types again.
danadresse-widget.js shows a Danish message per cause: missing or invalid key (401), domain not allowed (403), too many searches (429) and network error. Its onError receives { kind } with unauthorized, forbidden, rate, network or error.
7. See the call the script makes
The script sends only the typed text, the caret position, the type, the limit and fuzzy. The key travels in the X-Api-Key header, and no cookies are sent (credentials: "omit"). You can repeat the call from a terminal:
curl -s "https://api.danadresse.dk/autocomplete?q=Strandvejen%20100%2C%202900&type=adresse&per_side=8&fuzzy=" \ -H "X-Api-Key: $DANADRESSE_API_KEY"
If this works but the browser fails, the cause is almost always the key's domain list. See Browser keys and allowed domains.
8. Styling, SRI and self-hosting
The list follows prefers-color-scheme. The colours are CSS variables you can override:
.dda2 {
--dda2-bg: #ffffff;
--dda2-fg: #1a1a1a;
--dda2-active: #fff4d6;
--dda2-link: #8a4b00;
}danadresse-autocomplete-1.0.0.js never changes, so you can use Subresource Integrity. Compute the hash of exactly the file you embed and put it in integrity together with crossorigin="anonymous":
curl -s https://danadresse.dk/static/js/danadresse-autocomplete-1.0.0.js \ | openssl dgst -sha384 -binary | openssl base64 -A
You may also copy the file into your own static folder. It is MIT-licensed. Keep the version in the file name so you can tell which version you run. Do not use SRI on danadresse-widget.js or on the alias danadresse-autocomplete.js: they are not versioned, and the hash stops matching when the file is updated.
Next steps
- Browser keys and allowed domains — lock the key to your domain.
- Accessible address combobox — how keyboard and screen reader support work in the script.
- Address field in React — if you would rather build the field yourself.
- Live demo and more examples: /en/widget.
Related guides
Markdown source Spotted an error? Tell us API-reference (Swagger)