# Address autocomplete and validation in Contact Form 7

> Give the Contact Form 7 fields ids, attach the address widget, and reject addresses that cannot be found when the form is sent.
>
> https://danadresse.dk/en/guides/contact-form-7 · verified 2026-11-02
Contact Form 7 builds plain input fields from form tags, and you can give each field an id right in the tag. That is all the widget needs. Because CF7 sends the form to your own server, you can also check the address there before the email goes out.

## The quick route: the Lynbro Danadresse plugin

The free plugin [Lynbro Danadresse](https://wordpress.org/plugins/lynbro-danadresse/) from wordpress.org adds address autocomplete to WooCommerce, Contact Form 7, Gravity Forms, WPForms, Fluent Forms, Elementor, GiveWP, Forminator and more. It calls the API through your own server, so the key never reaches the browser.

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. Contact Form 7 has no address field type of its own, so the plugin finds the address field by auto-detect, or you enter the field's CSS selector under **Integrations** (e.g. `#dk-adresse` from step 2 below).

The plugin fills postcode and city in separate fields only for form plugins with their own address field (WooCommerce, Gravity Forms, WPForms and others), and it checks addresses on the WooCommerce checkout only. If your CF7 form should fill postcode and city, or refuse addresses that do not exist, use your own code as below.

## 1. Create keys

You need two keys from [Keys](/dashboard/keys):

- a *live* browser key locked to the site's domains under **Allowed domains** (see [Browser keys and allowed domains](/en/guides/browser-noegler-domaener))
- a server key without a domain lock for the validation in step 4. Put it in the `DANADRESSE_API_KEY` environment variable on the server

## 2. The fields in the form

Edit the form under **Contact → Contact Forms**:

```text id=cf7-tags run=no
<label>Address [text* adresse id:dk-adresse]</label>
<label>Postcode [text* postnr id:dk-postnr]</label>
<label>City [text* by id:dk-by]</label>
```

## 3. The widget as an mu-plugin

Save the file as `wp-content/mu-plugins/danadresse.php`:

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

add_action('wp_enqueue_scripts', function () {
    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 streetLine(a) {
  let line = a.vejnavn + " " + a.husnr;
  if (a.etage) line += ", " + a.etage + ".";
  if (a["dør"]) line += " " + a["dør"];
  return line;
}

const input = document.getElementById("dk-adresse");
if (input) {
  DanadresseAutocomplete.attach(input, {
    apiKey: API_KEY,
    onSelect(a) {
      input.value = streetLine(a);
      document.getElementById("dk-postnr").value = a.postnr || "";
      document.getElementById("dk-by").value = a.postnrnavn || "";
    },
  });
}
JS
    );
});
```

CF7 reads the fields directly when the form is sent, so setting `value` is enough here.

## 4. Reject addresses that do not exist

Add this at the bottom of the same file (without a new `<?php` line). It checks street, number and postcode against `/datavask/adgangsadresser` and shows an error at the field if the result is category C.

```php id=cf7-validate run=syntax
<?php
add_filter('wpcf7_validate_text*', function ($result, $tag) {
    if ($tag->name !== 'adresse') {
        return $result;
    }
    $adresse = sanitize_text_field(wp_unslash($_POST['adresse'] ?? ''));
    $postnr = sanitize_text_field(wp_unslash($_POST['postnr'] ?? ''));
    $street = trim((string) strtok($adresse, ','));

    $res = wp_remote_get(
        'https://api.danadresse.dk/datavask/adgangsadresser?'
            . http_build_query(['betegnelse' => $street . ', ' . $postnr]),
        ['headers' => ['X-Api-Key' => getenv('DANADRESSE_API_KEY')], 'timeout' => 5]
    );
    if (is_wp_error($res) || wp_remote_retrieve_response_code($res) !== 200) {
        return $result; // the API did not answer: let the form through
    }
    $vask = json_decode(wp_remote_retrieve_body($res), true);
    if (($vask['kategori'] ?? '') === 'C') {
        $result->invalidate($tag, 'We could not find the address. Pick it from the list.');
    }
    return $result;
}, 20, 2);
```

Floor and door are not checked, only the building. What A, B and C mean is explained in [Address cleansing in checkout](/en/guides/datavask-i-checkout).

## 5. Test

Send the form twice: once with an address from the list (should go through) and once with a made-up address such as "Findesikkevej 999, 9999" (should show the error). `403 origin_not_allowed` in the console means the domain is missing from the browser key.

## Next steps

- The [CF7 recipe](/en/integrationer/contact-form-7). It uses the older `danadresse-widget.js`, which drills down from street to house number to unit and can fill separate fields with its `fields` option.
- [Gravity Forms](/en/guides/gravity-forms) and [WPForms](/en/guides/wpforms)
- The plugin on wordpress.org: [Lynbro Danadresse](https://wordpress.org/plugins/lynbro-danadresse/)
