# Install FlowConsent on a custom-built site (HTML)

Install FlowConsent on a site built with HTML, PHP, Django, Rails, Laravel, Hugo, Jekyll or Eleventy: paste the snippet once in the shared template's <head>.

> Canonical: https://www.flowconsent.com/en/doc/install-html
> Last updated: 2026-10-01
On a custom-built site, the FlowConsent snippet is pasted **once**, at the very top of the `<head>` in the template shared by all your pages (layout, included header, base template), before any other script. Setup takes two steps: copy the snippet, then place it in that shared template. The two usual pitfalls: a Content-Security-Policy that blocks the banner, and client-side navigation that requires SPA Mode.

## Before you start

- An account on [app.flowconsent.com](https://app.flowconsent.com) (the Free plan costs nothing, needs no credit card and covers up to 25,000 sessions per month).
- A banner created and deployed. On the Free plan, deployment goes through your own Cloudflare account ([Cloudflare guide](/en/doc/cloudflare-deployment)); on Pro and Enterprise, it is one click.
- Access to your site's source code and to its deployment process.

## Step 1: Copy the snippet

Open **Builder → Deployment → Integration** and copy the snippet from the **HTML / No-code** tab. It has two parts, in this order: the Google Consent Mode v2 stub (`denied` defaults), then the banner loader.

```html title="At the very top of the <head>"
<head>
  <script>
  // FlowConsent Loader - place BEFORE any other script in the <head>
  window.dataLayer=window.dataLayer||[];
  function gtag(){dataLayer.push(arguments);}
  gtag('consent','default',{
    'ad_storage':'denied',
    'analytics_storage':'denied',
    'ad_user_data':'denied',
    'ad_personalization':'denied',
    'wait_for_update':500
  });
  gtag('set','url_passthrough',true);
  gtag('set','ads_data_redaction',true);
  </script>
  <!-- FlowConsent CMP -->
  <script src="https://YOUR-LICENSE-CODE.consent.flowconsent.com/cookie-manager.js?code=YOUR_LICENSE_CODE"></script>

  <meta charset="utf-8">
  <!-- your other tags and scripts come after -->
</head>
```

Any script placed above this snippet escapes prior blocking, so it must come before Google Analytics, Meta Pixel, Google Tag Manager and any other tracker.

## Step 2: Paste it once in the shared template

Find the file that renders the `<head>` of every page. Depending on your stack:

- **Static HTML site**: with no template, every `.html` file has its own `<head>`. Paste the snippet into each one (a find and replace on `<head>` does the job), or use a server-side include if your host supports it.
- **PHP**: the shared header, often `header.php`, pulled into each page with `include` or `require`.
- **Laravel**: the Blade layout, for example `resources/views/layouts/app.blade.php`.
- **Django**: the base template (often `templates/base.html`) that your pages extend with `{% extends "base.html" %}`.
- **Ruby on Rails**: `app/views/layouts/application.html.erb`.
- **Hugo**: the base template `layouts/baseof.html` (`layouts/_default/baseof.html` before Hugo 0.146), or the `head.html` partial it calls. With a theme, copy that file into your project's `layouts/` folder instead of editing the theme.
- **Jekyll**: the `_layouts/default.html` layout, or `_includes/head.html` with a theme such as Minima. To override a theme file, copy it to the same path in your site.
- **Eleventy**: the base layout in `_includes/` (for example `_includes/base.njk`), selected with the `layout` key in front matter.

The snippet is plain HTML: no template engine needs it escaped. If your site has several root layouts, repeat the step in each. Then rebuild or redeploy the site.

Do not hard-code your trackers (Google Analytics, Meta Pixel…) in the template: declare them in **Builder → Services**, and the banner loads them only after consent. For Google Tag Manager, see the [dedicated guide](/en/doc/install-google-tag-manager).

## Content-Security-Policy and client-side navigation

> [!WARNING]
> If your site sends a `Content-Security-Policy` header, the banner is blocked until its domain is listed. Allow your snippet's domain in `script-src` (loading the script) and in `connect-src` (the banner's requests to that same domain): `https://*.consent.flowconsent.com` on Pro and Enterprise, your Cloudflare Worker URL on the Free plan. Because the stub is an inline script, it also needs a `nonce`, a `sha256-…` hash (recompute it if you change the snippet) or `'unsafe-inline'`.

```text title="Example header (Pro and Enterprise)"
Content-Security-Policy: script-src 'self' 'nonce-RANDOM' https://*.consent.flowconsent.com; connect-src 'self' https://*.consent.flowconsent.com
```

Then add `nonce="RANDOM"` to both `<script>` tags in the snippet, with a value generated for every response. Services the banner loads after consent must be allowed too, just as they would be without FlowConsent. When something is blocked, the browser console names the directive and the refused domain.

> [!NOTE]
> If your site navigates without full page reloads (Turbo in Rails, Livewire's `wire:navigate`, htmx `hx-boost`, a JavaScript framework), turn on **SPA Mode** in the Builder's **Settings** tab, then redeploy the banner: consent is re-checked on every client-side navigation. Details in the [Next.js and SPA guide](/en/doc/nextjs-integration).

## Verify the installation

1. Open the live site in a private window: the banner appears.
2. In the **Network** tab, no third-party request goes out before you click **Accept**. The **Console** reports no CSP violation.
3. Run the [setup wizard](/en/doc/setup-wizard) (Builder → Integration): it checks the live page (loader present with the right license code, Consent Mode stub present and placed before every Google tag).
4. Finish with a refusal scan from the **Compliance** tab ([compliance verification](/en/doc/compliance-verification)).

## Do it from your AI agent (MCP)

FlowConsent has an official MCP server, `https://mcp.flowconsent.com`, usable from Claude, ChatGPT or any MCP client. You connect through OAuth 2.0 with your FlowConsent account, no API key required. Your agent fetches the snippet and placement instructions (`get_banner_snippet`, `get_setup_instructions`), verifies the installation with a real scan (`verify_consent_compliance`) and can set up recurring monitoring (`monitor_compliance`). You, or your coding agent, edit the template: the MCP server provides the snippet, the instructions and the verification.

Example request:

> Give me my banner snippet for my custom HTML site, tell me where to put it in my template, then check that it is properly installed on https://mysite.com

Learn more: [MCP server](/en/doc/mcp-server) and [FlowConsent for AI agents](/en/mcp).

## Frequently asked questions

### Do I need to paste the snippet on every page?

It must be present on every page, but you paste it only once if your pages share a template. Only a plain HTML site with no template needs it added file by file.

### Can I put it at the bottom of the `<body>`?

No. The Consent Mode stub must run before any tracker: at the bottom of the page, scripts higher up would already have fired. Put it at the very top of the `<head>`.

### The banner does not show up: where do I start?

Open the browser console. A CSP error points to a domain to allow. Otherwise, check that the snippet is in the HTML actually served (view the page source), with your license code.

### My site is statically generated: does the banner work without a server?

Yes. The snippet runs in the browser, so it works on any static file hosting.
