# 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.
>
> https://danadresse.dk/en/guides/vanilla-autocomplete-widget · verified 2026-11-02
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](/en/guides/browser-noegler-domaener) 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](/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`:

```html id=basic-embed run=no
<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`:

```html id=declarative-embed run=no
<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:

```json id=onselect-payload run=no
{
  "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.

```html id=separate-fields run=no
<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`:

```js id=select-event run=syntax
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:

```js id=on-error run=syntax
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:

```bash id=widget-request run=live expect="forslagstekst"
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](/en/guides/browser-noegler-domaener).

## 8. Styling, SRI and self-hosting

The list follows `prefers-color-scheme`. The colours are CSS variables you can override:

```css id=theme-vars run=no
.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"`:

```bash id=sri-hash run=no
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](/en/guides/browser-noegler-domaener) — lock the key to your domain.
- [Accessible address combobox](/en/guides/tilgaengelig-combobox) — how keyboard and screen reader support work in the script.
- [Address field in React](/en/guides/react-autocomplete) — if you would rather build the field yourself.
- Live demo and more examples: [/en/widget](/en/widget).
