# Vask adresser i Google Sheets med =DATAVASK()

> Tilføj en egen funktion =DATAVASK(A2) til Google Sheets med Apps Script, der vasker adresser, cacher svarene og kan skrive faste værdier tilbage.
>
> https://danadresse.dk/guides/google-sheets · verified 2026-11-02
Med et lille Apps Script får dit Google-regneark en ny funktion, `=DATAVASK(A2)`, der slår adressen i A2 op og returnerer den rensede adresse. Du kan også bede om et enkelt felt, fx postnummer eller kommune. Scriptet cacher svarene, så samme adresse ikke koster et nyt kald, hver gang arket regner.

## 1. Gem nøglen i scriptets egenskaber

1. Opret en nøgle under [Nøgler](/dashboard/keys).
2. Åbn regnearket og vælg **Udvidelser → Apps Script**.
3. Klik på tandhjulet (**Projektindstillinger**), rul ned til **Scriptegenskaber**, og tilføj egenskaben `DANADRESSE_API_KEY` med nøglen som værdi.

Nøglen ligger så uden for koden. Deler du regnearket, kan andre redaktører stadig åbne scriptet og læse egenskaberne, så brug en nøgle, du kan tilbagekalde. En domænelåst browsernøgle virker ikke her, for Googles servere sender ingen `Origin`.

## 2. Indsæt scriptet

Slet indholdet i `Code.gs`, indsæt koden, og gem.

```js id=apps-script run=syntax
/**
 * Vasker en dansk adresse med Danadresse.
 * @param {string} adresse Hele adressen, fx "Strandvejen 100, 2900 Hellerup".
 * @param {string} felt Valgfrit: kategori, id, vejnavn, husnr, postnr, by, kommune, kommunekode, x eller y.
 * @return Den vaskede adresse, det valgte felt eller en fejltekst.
 * @customfunction
 */
function DATAVASK(adresse, felt) {
  if (Array.isArray(adresse)) {
    return adresse.map((raekke) => [DATAVASK(raekke[0], felt)]);
  }
  if (!adresse) return "";
  const r = vask_(String(adresse).trim());
  if (r.fejl) return r.fejl;
  if (!felt) return `${r.vejnavn} ${r.husnr}, ${r.postnr} ${r.by}`;
  return r[felt] ?? "";
}

function vask_(betegnelse) {
  const cache = CacheService.getScriptCache();
  const digest = Utilities.computeDigest(
    Utilities.DigestAlgorithm.MD5, betegnelse, Utilities.Charset.UTF_8);
  const cacheNoegle = "dv:" + Utilities.base64EncodeWebSafe(digest);
  const gemt = cache.get(cacheNoegle);
  if (gemt) return JSON.parse(gemt);

  const apiKey = PropertiesService.getScriptProperties().getProperty("DANADRESSE_API_KEY");
  if (!apiKey) return { fejl: "#NØGLE MANGLER" };

  const svar = hent_("https://api.danadresse.dk/datavask/adgangsadresser?betegnelse="
    + encodeURIComponent(betegnelse), apiKey);
  if (svar.fejl) return svar; // Fejl gemmes ikke i cachen: prøv igen senere.
  const data = svar.data;
  const top = data.resultater.length ? data.resultater[0].adresse : null;
  let resultat = { fejl: "#IKKE FUNDET" };
  if (top) {
    // Vaskesvaret har ikke kommune og koordinater: ét kald mere til dem.
    const adg = hent_("https://api.danadresse.dk/adgangsadresser/" + top.id, apiKey).data || {};
    const punkt = (adg.adgangspunkt && adg.adgangspunkt.koordinater) || [];
    resultat = {
      kategori: data.kategori,
      id: top.id,
      vejnavn: top.vejnavn,
      husnr: top.husnr,
      postnr: top.postnr,
      by: top.postnrnavn,
      kommune: adg.kommune && adg.kommune.navn,
      kommunekode: adg.kommune && adg.kommune.kode,
      x: punkt[0], // længdegrad
      y: punkt[1], // breddegrad
    };
  }
  cache.put(cacheNoegle, JSON.stringify(resultat), 21600); // 6 timer, det højeste Google tillader
  return resultat;
}

function hent_(url, apiKey) {
  const valg = { headers: { "X-Api-Key": apiKey }, muteHttpExceptions: true };
  let res = UrlFetchApp.fetch(url, valg);
  // 429 rate_limit_per_minute har Retry-After (1-60 s). Vent og prøv én gang til,
  // men kun kort: en egen funktion må højst køre 30 sekunder.
  // 429 quota_exceeded har ingen Retry-After: månedskvoten er brugt, det nytter ikke at vente.
  const vent = Number(header_(res, "retry-after"));
  if (res.getResponseCode() === 429 && vent > 0 && vent <= 20) {
    Utilities.sleep(vent * 1000);
    res = UrlFetchApp.fetch(url, valg);
  }
  const status = res.getResponseCode();
  if (status === 429) return { fejl: "#GRÆNSE NÅET" };
  if (status !== 200) return { fejl: "#FEJL " + status };
  return { data: JSON.parse(res.getContentText()) };
}

/** Header-navne kan komme med små eller store bogstaver. */
function header_(res, navn) {
  const h = res.getHeaders();
  const k = Object.keys(h).find((x) => x.toLowerCase() === navn);
  return k ? h[k] : null;
}

/** Menu til at vaske markerede celler og skrive faste værdier ved siden af. */
function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu("Danadresse")
    .addItem("Vask markerede adresser", "vaskMarkerede")
    .addToUi();
}

function vaskMarkerede() {
  const omraade = SpreadsheetApp.getActiveRange();
  const ud = omraade.getValues().map(([adresse]) => {
    if (!adresse) return ["", "", "", "", ""];
    const r = vask_(String(adresse).trim());
    return [r.fejl || r.kategori, r.id || "", r.postnr || "", r.by || "", r.kommune || ""];
  });
  omraade.offset(0, omraade.getNumColumns(), ud.length, 5).setValues(ud);
}
```

## 3. Brug funktionen

Skriv i en celle:

| Formel | Resultat |
|---|---|
| `=DATAVASK(A2)` | `Strandvejen 100, 2900 Hellerup` |
| `=DATAVASK(A2; "kategori")` | `A` |
| `=DATAVASK(A2; "kommune")` | `Gentofte` |
| `=DATAVASK(A2; "id")` | Adgangsadressens UUID |
| `=DATAVASK(A2:A50; "postnr")` | Ét postnummer pr. række |

Med dansk landestandard adskilles argumenter med semikolon. Med engelsk bruger du komma.

Kategori A er et præcist match. B er ét sikkert match med små stavefejl: brug det, og behold den officielle tekst, funktionen returnerer. C er usikkert (flere kandidater eller større afvigelser): tjek C-rækker manuelt, og godkend dem aldrig automatisk. `#IKKE FUNDET` betyder, at API'et ikke fandt nogen kandidat. `#FEJL 401` eller `#FEJL 403` betyder, at nøglen er forkert eller spærret.

Funktionen vasker på opgangsniveau med `/datavask/adgangsadresser`, så alle lejligheder i en opgang giver samme resultat. Skal du have den præcise lejlighed, så skift stien til `/datavask/adresser`: parseren læser etage og dør fra teksten (`Strandvejen 100, 2. tv, 2900 Hellerup`), og resultatet har også `etage`, `dør` og `adgangsadresseid`. Det andet kald virker stadig med det id.

En adresse, der ligger i cachen, koster ingenting. En, der ikke gør, koster to kald: vasken og opslaget af kommune og koordinater.

## 4. Formel eller menu?

En formel bliver regnet igen, når argumentet ændrer sig, og cachen holder kun 6 timer. Til en liste, der skal vaskes én gang, er menuen bedre: Genindlæs arket, markér adressekolonnen, og vælg **Danadresse → Vask markerede adresser**. Scriptet skriver kategori, id, postnummer, by og kommune i de fem kolonner til højre som faste værdier. Første gang beder Google dig om at godkende scriptet.

Google tillader, at en egen funktion kører i højst 30 sekunder. Brug derfor små områder som `A2:A50`, eller brug menuen til lange lister.

## 5. Forbrug

Hver adresse, der ikke ligger i cachen, er to kald. På gratisplanen har kontoen 2 000 kald om måneden plus en buffer på 20 %, før kald bliver afvist. Når den også er brugt, viser cellerne `#GRÆNSE NÅET` indtil den 1. i næste måned. Afviste kald tæller ikke. Se forbruget under [Forbrug](/dashboard/usage) og reglerne i [Grænser, kvoter og 429](/guides/graenser-og-429). Har du tusindvis af rækker, så upload en CSV under [Bulk-vask](/dashboard/bulk).

## Test kaldet uden regneark

```bash id=test-call run=live expect="kategori"
curl -s "https://api.danadresse.dk/datavask/adgangsadresser?betegnelse=Strandvejen%20100%2C%202900%20Hellerup" \
  -H "X-Api-Key: $DANADRESSE_API_KEY"
```

## Næste skridt

- [Vask adresser i Excel med Power Query](/guides/excel-power-query)
- [Rens en CRM-eksport som CSV](/guides/rens-crm-csv)
