WritedocsWritedocs

Integrations

integrations turns on an analytics service or a chat widget by name. Give it the ID from your account, and the service’s script is added to every page:

{
  "integrations": {
    "ga4": { "measurementId": "G-XXXXXXXXXX" }
  }
}

Turn on as many as you need - two analytics services at once while you move from one to the other, for example.

Google Analytics 4

{ "integrations": { "ga4": { "measurementId": "G-XXXXXXXXXX" } } }
measurementId string required

The G- ID of your property’s web data stream.

Google Tag Manager

{ "integrations": { "googleTagManager": { "containerId": "GTM-XXXXXXX" } } }
containerId string required

The GTM- ID of your container.

If Google Analytics already runs through your Tag Manager container, don’t turn on ga4 too - every page view would count twice.

Plausible

{ "integrations": { "plausible": { "domain": "docs.example.com" } } }
domain string required

The site’s domain, as you added it in Plausible.

src string default: https://plausible.io/js/script.js

The script’s address - for a self-hosted Plausible, your server’s.

Fathom

{ "integrations": { "fathom": { "siteId": "ABCDEFGH" } } }
siteId string required

The site ID from your Fathom dashboard.

PostHog

{ "integrations": { "posthog": { "apiKey": "phc_xxxxxxxxxxxxxxxxxxxx" } } }
apiKey string required

Your project’s API key (phc_...).

apiHost string default: https://us.i.posthog.com

PostHog’s address. For the EU cloud, https://eu.i.posthog.com; for a self-hosted PostHog, your server’s.

Umami

{ "integrations": { "umami": { "websiteId": "11111111-2222-3333-4444-555555555555" } } }
websiteId string required

The website ID Umami gave your site.

src string default: https://cloud.umami.is/script.js

The script’s address - for a self-hosted Umami, your server’s.

Ask AI

A chat bubble on every page, where readers ask questions and get answers from your docs. It’s powered by DocsBot: create a bot there, point it at your published site, and give its ID here.

{ "integrations": { "askAi": { "id": "yourTeamId/yourBotId" } } }
id string required

The teamId/botId value from your bot’s embed code, in DocsBot’s dashboard - one value, with the slash.

The widget loads last, after everything else on the page.

Setting the ID when the site is built

The ID can also come from the WRITEDOCS_ASK_AI_ID environment variable, where the site is built:

WRITEDOCS_ASK_AI_ID="yourTeamId/yourBotId"

It takes precedence over integrations.askAi.id, and turns the widget on without an askAi entry in writedocs.json - useful for a different bot per deployment.

The ID isn’t a secret: it’s in the HTML of every page, where any visitor can read it. The variable changes where it’s set, not who can see it.

By default, analytics load with every page. consent asks the reader first: a banner along the bottom of the window, with Accept and Decline. Nothing from the analytics services above loads until the reader accepts.

{
  "integrations": {
    "ga4": { "measurementId": "G-XXXXXXXXXX" },
    "consent": true
  }
}

true uses the default texts. To write your own, or link your privacy policy, give an object:

{
  "integrations": {
    "ga4": { "measurementId": "G-XXXXXXXXXX" },
    "consent": {
      "message": "We use cookies to see which pages help and which don't.",
      "acceptLabel": "Accept",
      "declineLabel": "No thanks",
      "policyUrl": "/docs/privacy/",
      "policyLabel": "Privacy policy"
    }
  }
}
message string default: We use cookies to understand how this site is used and to improve it.

The banner’s text.

acceptLabel string default: Accept

The accept button’s text.

declineLabel string default: Decline

The decline button’s text.

policyUrl string

A link after the message: a page on the site, or a full URL. Without it, there’s no link.

policyLabel string default: Privacy policy

The link’s text.

How it works:

  • Accept loads the analytics right away, and on every later page and visit without asking again.
  • Decline loads nothing, and the banner doesn’t come back.
  • The answer is kept in the reader’s browser, so it’s asked once per browser. A reader who clears their browser’s data is asked again.
  • The Ask AI widget isn’t held back - it’s a tool the reader chooses to open, not analytics.

Changing the answer

A link to #cookie-settings, anywhere on the site, opens the banner again. The footer is the usual place:

{
  "footer": {
    "columns": [
      {
        "title": "Legal",
        "links": [{ "label": "Cookie settings", "href": "#cookie-settings" }]
      }
    ]
  }
}

In a page, it’s an ordinary link: [Cookie settings](#cookie-settings). A reader who accepted and then declines gets the page reloaded, without the analytics.

Your own scripts

A script in scripts waits for the same answer with "consent": true:

{
  "scripts": {
    "body": [{ "src": "https://tracker.example.com/t.js", "consent": true }]
  }
}

For code of your own that depends on the answer, window.wdConsent.get() returns "granted", "denied", or null before the reader has answered, and the page fires a wd:consent event when they do:

document.addEventListener("wd:consent", (event) => {
  if (event.detail === "granted") startSessionRecording();
});

Limits

  • The banner has one set of texts - on a site in several languages, it’s the same in all of them.
  • It’s accept or decline for everything: there are no categories to choose from.
  • It doesn’t send Google’s consent signals. Google Analytics and Tag Manager simply aren’t loaded until the reader accepts.
  • Whether your site needs a banner, and what it must say, depends on the laws that apply to you - Writedocs can’t decide that.

What isn’t covered

For a service that isn’t listed here, add its own snippet with scripts - with "consent": true if it should wait for the reader’s answer.