# Address autocomplete in the WooCommerce checkout

> Install the Lynbro Danadresse plugin or a small mu-plugin for address autocomplete in WooCommerce, and validate the address when the order is placed.
>
> https://danadresse.dk/en/guides/woocommerce · verified 2026-11-02
WooCommerce has two checkouts: the classic one (the `[woocommerce_checkout]` shortcode) and the block checkout, which is the default in new shops. Both are made of plain input fields, so the address widget can attach to them. Here are the two routes: our WordPress plugin and a small mu-plugin that you control.

## 1. The plugin: Lynbro Danadresse

Install [Lynbro Danadresse](https://wordpress.org/plugins/lynbro-danadresse/) from wordpress.org:

1. In WordPress go to **Plugins → Add New**, search for "Lynbro Danadresse", click **Install** and **Activate**.
2. Create a key under [Keys](/dashboard/keys). Use a normal *live* key **without** allowed domains: the plugin calls the API from your own server, and a domain-locked key is refused there because a server call has no `Origin`.
3. Paste the key under **Lynbro → Danadresse → Settings**. The **Status** tab runs a test lookup with the key.
4. Under **Integrations**, WooCommerce is switched on by default.

What the plugin does in WooCommerce:

- Autocomplete in the classic checkout, the block checkout and the address forms under My Account. Postcode and city are filled for you.
- When an order with a Danish delivery address is placed, the address is checked against the register (datavask). The result (exact, close or not recognised) is saved on the order and shown to the shop admin. You can choose to stop orders with clearly invalid addresses. If the API cannot be reached, the checkout is never blocked.
- **Statistics** shows the site's lookups per day, and **Log** explains recent errors in plain words.

If you prefer your own code, or want the browser to call the API directly with a domain-locked key, use the mu-plugin below.

## 2. Create a browser key for the mu-plugin

The mu-plugin calls the API straight from the browser. Create a *live* key under [Keys](/dashboard/keys) and lock it to `myshop.dk` and `www.myshop.dk` under **Allowed domains**. See [Browser keys and allowed domains](/en/guides/browser-noegler-domaener).

## 3. Add the mu-plugin

Save the file as `wp-content/mu-plugins/danadresse.php`. WordPress loads files in `mu-plugins` automatically.

```php id=mu-plugin run=syntax
<?php
/**
 * Plugin Name: Danadresse autocomplete (WooCommerce)
 */
if (!defined('ABSPATH')) {
    exit;
}

add_action('wp_enqueue_scripts', function () {
    if (!function_exists('is_checkout') || !(is_checkout() || is_account_page())) {
        return;
    }
    wp_enqueue_script(
        'danadresse-autocomplete',
        'https://danadresse.dk/static/js/danadresse-autocomplete-1.0.0.js',
        [],
        null,
        true
    );
    wp_add_inline_script('danadresse-autocomplete', <<<'JS'
const API_KEY = "YOUR_BROWSER_KEY";

function setField(el, value, events) {
  if (!el || value == null) return;
  const setter = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(el), "value").set;
  setter.call(el, value);
  for (const type of events || ["input", "change"]) {
    el.dispatchEvent(new Event(type, { bubbles: true }));
  }
}

function streetLine(a) {
  let line = a.vejnavn + " " + a.husnr;
  if (a.etage) line += ", " + a.etage + ".";
  if (a["dør"]) line += " " + a["dør"];
  return line;
}

// Classic: billing_address_1 → billing_postcode. Blocks: billing-address_1 → billing-postcode.
function bind(input) {
  DanadresseAutocomplete.attach(input, {
    apiKey: API_KEY,
    onSelect(a) {
      setField(input, streetLine(a), ["change"]);
      setField(document.getElementById(input.id.replace("address_1", "postcode")), a.postnr);
      setField(document.getElementById(input.id.replace("address_1", "city")), a.postnrnavn);
    },
  });
}

function bindAll() {
  document
    .querySelectorAll("#billing_address_1, #shipping_address_1, #billing-address_1, #shipping-address_1")
    .forEach(bind);
}
bindAll();
// The block checkout renders its fields later, and again when the customer switches address.
new MutationObserver(bindAll).observe(document.body, { childList: true, subtree: true });
JS
    );
});
```

The widget attaches to a field only once, so it is harmless that `bindAll` runs on every change to the page.

## 4. Test both checkouts

Type an address, pick a suggestion, and place a test order. Check in the order that street, postcode and city were saved. The block checkout is built with React. The code fires `input` and `change` events so React notices the value, but check it on your WooCommerce version before you go live. You can see the field ids with **Inspect**.

## 5. Optional: validate on order

With a server key without a domain lock you can check the address before the order is created. The code sends street, number and postcode to `/datavask/adgangsadresser`, which checks the building. Floor and door are not checked. The hook runs in the classic checkout only. The key is read from the `DANADRESSE_API_KEY` environment variable.

```php id=checkout-datavask run=syntax
<?php
add_action('woocommerce_after_checkout_validation', function (array $data, WP_Error $errors) {
    $prefix = !empty($data['ship_to_different_address']) ? 'shipping_' : 'billing_';
    if (($data[$prefix . 'country'] ?? '') !== 'DK') {
        return;
    }
    // Street and number only (before the first comma) + postcode.
    $street = trim(strtok((string) ($data[$prefix . 'address_1'] ?? ''), ','));
    $betegnelse = $street . ', ' . ($data[$prefix . 'postcode'] ?? '');
    $res = wp_remote_get(
        'https://api.danadresse.dk/datavask/adgangsadresser?' . http_build_query(['betegnelse' => $betegnelse]),
        ['headers' => ['X-Api-Key' => getenv('DANADRESSE_API_KEY')], 'timeout' => 5]
    );
    if (is_wp_error($res) || wp_remote_retrieve_response_code($res) !== 200) {
        return; // the API did not answer: let the order through
    }
    $vask = json_decode(wp_remote_retrieve_body($res), true);
    if (($vask['kategori'] ?? '') === 'C') {
        $errors->add('validation', 'We could not find the address. Check street, house number and postcode.');
    }
}, 10, 2);
```

Categories A and B are accepted, C stops the order. More in [Address cleansing in checkout](/en/guides/datavask-i-checkout).

## Next steps

- The [WooCommerce page](/en/woocommerce-adresse) and the [checkout overview](/en/checkout-adresse)
- [Elementor forms](/en/guides/elementor) on the same WordPress site
- The plugin on wordpress.org: [Lynbro Danadresse](https://wordpress.org/plugins/lynbro-danadresse/)
