# Add CLItrail to WordPress

Where the CLItrail tag goes in WordPress, in its own idiom, with the consent, CSP and client-side routing details and how to check it works.

Written for WordPress 7 (the same code works from 5.7)

## Where the tag goes

Edit `wp-content/mu-plugins/clitrail.php (a must-use plugin, so it survives theme changes)`. Put the tag on every page that shows your install command; site-wide is best, after your existing analytics tags.

```html
<?php
/**
 * Plugin Name: CLItrail
 * Description: Adds the CLItrail website tag to every page.
 */
add_action('wp_head', function () {
    wp_print_script_tag([
        'src' => 'https://YOUR_SERVICE/browser.js',
        'defer' => true,
        'data-website' => 'YOUR_WEBSITE_ID',
    ]);
});
```

## Only after consent

Use the loader in place of the tag: nothing from CLItrail loads until `startClitrail()` runs. Load the loader before your consent manager's script and call `startClitrail()` from the manager's accept callback (most managers also run it on later visits once a choice is stored; check yours). Use the marketing category when installs go to Google Ads, Meta, TikTok or X Ads, and the analytics category when they only reach GA4 or your webhooks. Priority 1 prints the loader early in `<head>`, before enqueued scripts (WordPress prints those at priority 9).

Consent loader

```js
// Loads CLItrail only after the visitor agrees. Call startClitrail() from your
// consent manager's "accepted" callback; nothing is fetched or stored before that.
window.startClitrail = () => {
  if (document.getElementById('clitrail')) return;
  const tag = document.createElement('script');
  tag.id = 'clitrail';
  tag.src = 'https://YOUR_SERVICE/browser.js';
  tag.dataset.website = 'YOUR_WEBSITE_ID';
  document.head.append(tag);
};
```

```html
<?php
/**
 * Plugin Name: CLItrail
 * Description: Loads CLItrail after consent (clitrail-consent.js defines startClitrail()).
 */
add_action('wp_head', function () {
    wp_print_script_tag([
        'src' => plugins_url('clitrail-consent.js', __FILE__),
        'defer' => true,
    ]);
}, 1);
```

## Client-side routing

WordPress pages load in full, so the tag is enough. If your theme changes pages without a reload, follow its navigation event with the route helper.

## Content Security Policy

A security plugin or your server's headers: add `https://YOUR_SERVICE` to `script-src` and `connect-src`, and `blob:` to `worker-src` (only Safari before 26 needs that one).

```
script-src 'self' https://YOUR_SERVICE;
connect-src 'self' https://YOUR_SERVICE;
worker-src 'self' blob:;
```

## Verify it works

1. Upload the file and open the site.
2. Open a page with the tag in Chrome or Firefox (accept analytics first if you gate CLItrail on consent), open the developer console and run `await InstallAttribution.ready`. It resolves to `{ ok: true, urlId, hasAnalyticsContext, expiresAt }`.
3. In the CLItrail dashboard, open Visits & identities: the visit is listed.
4. Anything else names the cause: `disabled` (no consent yet, or `data-enabled="false"`), `opfs_unavailable` (the page is not served over https or from localhost), `Error` (the service refused the visit: add the page's origin, including a development origin such as `http://localhost:3000`, under Settings → Additional domains). If `InstallAttribution` is undefined, the tag did not load: check the Network tab and your Content-Security-Policy.

Then run your installer on the same computer and check **Install events**, or run the hook with [`--doctor`](https://clitrail.com/docs/doctor).

## Notes

- `wp_print_script_tag` escapes each attribute and prints `defer` as a bare attribute.
- On WordPress.com, custom code needs a plan that allows plugins; a site-wide header setting from your theme or a header-code plugin works the same way with the plain tag.

WordPress documentation: [developer.wordpress.org/reference/functions/wp_print_script_tag](https://developer.wordpress.org/reference/functions/wp_print_script_tag/)

Using other install paths or destinations? The [setup generator](https://clitrail.com/docs/setup-generator) puts this snippet together with your hooks and destination checklist.
