ConsentLayer
Guides

Lovable Integration

You set up ConsentLayer on a Lovable app with one prompt. The ConsentLayer dashboard creates the site and gives you a prompt that has your real project key in it. You paste the prompt into the Lovable chat, Lovable adds the banner tag, and you publish.

This works on both kinds of Lovable project:

  • TanStack Start (server-rendered). New Lovable projects use this since May 2026. The tag goes in the root route's document, src/routes/__root.tsx.
  • Vite (single-page app). Older projects use this. The tag goes in index.html.

The banner runs in the browser in both cases. You don't need to know which one your project uses — the prompt covers both.

1. Get your prompt

  1. Sign in to the ConsentLayer dashboard.
  2. Go to Sites → Set up through an AI agent → Lovable / Bolt / Replit.
  3. Enter your app's published domain — yourapp.lovable.app or your custom domain. Do not use the editor preview URL.
  4. Click Get my prompt. ConsentLayer creates the site with the banner, cookie categories and script blocking pre-configured.

2. Paste the prompt into Lovable

Copy the prompt from the dashboard and send it in your Lovable project's chat. Sending a prompt uses one Lovable credit. The prompt looks like this, with your real project key in place of YOUR_PROJECT_KEY:

Add ConsentLayer cookie consent to this project.

1. Put this exact script tag in the shared document <head> so it loads on every page:

<script src="https://b.consentlayer.com/YOUR_PROJECT_KEY/banner.js"></script>

Use the root layout or document for this project's framework: in a TanStack Start app, the <head> of the root route's document in src/routes/__root.tsx, before <HeadContent />; in a Vite app, the <head> of index.html. Keep server rendering as it is. It must be the first script in <head>, before any analytics or tracking scripts.

2. Keep it a plain, synchronous <script> tag. Never add async or defer, never load it from useEffect or other client-side code, and never swap it for a framework script helper or a head() scripts entry. It must run before anything else to block trackers.

3. From now on, never let analytics or ad pixels run before consent. Add any new tracking script as <script type="text/plain" data-cl-category="statistics"> (analytics) or data-cl-category="marketing" (ads and pixels), or load it only after hasConsent("statistics") / hasConsent("marketing") from @consentlayer/sdk returns true (re-check with onConsentChange).

Change nothing else. When you're done, remind me to publish the project. I'll verify the installation from the ConsentLayer dashboard on the published domain (*.lovable.app or my custom domain), not the editor preview.

Use the prompt from the dashboard

Don't paste the example above as-is. YOUR_PROJECT_KEY is a placeholder, and a tag with a placeholder key loads no banner.

In a TanStack Start project, the result in src/routes/__root.tsx looks like this:

// src/routes/__root.tsx
import { HeadContent, Scripts, createRootRoute } from "@tanstack/react-router";

export const Route = createRootRoute({
  shellComponent: RootDocument,
});

function RootDocument({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <script src="https://b.consentlayer.com/YOUR_PROJECT_KEY/banner.js" />
        <HeadContent />
      </head>
      <body>
        {children}
        <Scripts />
      </body>
    </html>
  );
}

In a Vite project, it's one line in index.html:

<!-- index.html -->
<head>
  <script src="https://b.consentlayer.com/YOUR_PROJECT_KEY/banner.js"></script>
</head>

Why the tag must stay synchronous

The banner blocks trackers by running before them. A plain <script> in <head> runs while the browser parses the page, before any script after it. With async or defer, inside useEffect, or as a framework script helper, the tag runs later — after Google Analytics or a pixel may already have fired. If Lovable changes the tag on a later edit, ask it to put it back exactly as above. The dashboard's install check flags async and defer.

3. Add a project Knowledge entry

The prompt's rules apply to one chat. Lovable can add a tracker weeks later when you ask for "analytics" or "a Meta pixel". To make the rules hold for every future edit, add them to your project's Knowledge: open the project settings, go to Knowledge, and add:

Cookie consent (ConsentLayer):
- The ConsentLayer <script> tag in the document <head> (src/routes/__root.tsx or index.html) must stay the first script in <head>. Never add async or defer, never move it into useEffect or a framework script helper, never remove it.
- Never load analytics, ad pixels or other tracking scripts before consent.
- Add analytics scripts as <script type="text/plain" data-cl-category="statistics">.
- Add ad and marketing pixels as <script type="text/plain" data-cl-category="marketing">.
- Or load them from code only after hasConsent("statistics") / hasConsent("marketing") from @consentlayer/sdk returns true, and re-check with onConsentChange.

4. Publish and verify

  1. In Lovable, click Publish (or Update if the app is already published).
  2. Go back to the ConsentLayer dashboard. The panel watches the published domain and changes to Banner detected on your site when the tag is live and the banner loads.
  3. Open your published app in a private window. The banner shows on the first visit.

The check runs on the published domain only. The Lovable editor preview does not count, and a change that you have not published does not show up.

Next, run a scan from the dashboard so ConsentLayer finds the trackers your app uses and puts them in the right categories.

Gating trackers

ConsentLayer blocks many known trackers automatically. A tracker that Lovable adds as inline code has no URL to match, so tag it or gate it yourself. There are two ways.

Option A: tag the script

Set type="text/plain" and a data-cl-category. The browser does not run a text/plain script. The banner runs it when the visitor consents to that category — at once for a returning visitor who already consented.

Google Analytics (statistics):

// src/routes/__root.tsx — inside <head>, after the banner tag
<script
  type="text/plain"
  data-cl-category="statistics"
  src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXX"
/>
<script
  type="text/plain"
  data-cl-category="statistics"
  dangerouslySetInnerHTML={{
    __html: `
      window.dataLayer = window.dataLayer || [];
      function gtag(){dataLayer.push(arguments);}
      gtag('js', new Date());
      gtag('config', 'G-XXXXXXXXX');
    `,
  }}
/>

Meta Pixel (marketing):

<script
  type="text/plain"
  data-cl-category="marketing"
  dangerouslySetInnerHTML={{
    __html: `
      !function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
      n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
      n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
      t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
      document,'script','https://connect.facebook.net/en_US/fbevents.js');
      fbq('init', 'YOUR_PIXEL_ID');
      fbq('track', 'PageView');
    `,
  }}
/>

In a Vite project, put the same tags in index.html with the code between <script type="text/plain" data-cl-category="..."> and </script>.

Option B: gate it in code

Install the SDK (ask Lovable to add @consentlayer/sdk) and load the tracker only after consent:

// src/components/ConsentTrackers.tsx — render once, in the root route
import { useEffect } from "react";
import { hasConsent, onConsentChange } from "@consentlayer/sdk";

let gaLoaded = false;

function loadGA() {
  if (gaLoaded || !hasConsent("statistics")) return;
  gaLoaded = true;
  const s = document.createElement("script");
  s.src = "https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXX";
  document.head.appendChild(s);
  const w = window as any;
  w.dataLayer = w.dataLayer || [];
  w.gtag = function () { w.dataLayer.push(arguments); };
  w.gtag("js", new Date());
  w.gtag("config", "G-XXXXXXXXX");
}

export function ConsentTrackers() {
  useEffect(() => {
    loadGA();
    return onConsentChange(() => loadGA());
  }, []);
  return null;
}

Use hasConsent("marketing") the same way for the Meta Pixel. The Google Analytics and Meta Pixel guides have more examples, including ConsentGate for React components.

Google Consent Mode

If you turn on Google Consent Mode for the site, Google's own tags can load before consent in a restricted mode, and ConsentLayer sends them the consent signals. See Google Consent Mode.

Troubleshooting

The dashboard never shows "Banner detected". Check that you published, and that the domain in ConsentLayer is the published one. Open the published app, view the page source, and look for banner.js in <head>.

The tag has async or defer, or sits in a useEffect. Ask Lovable: "Put the ConsentLayer script tag back as a plain synchronous <script> as the first script in <head> of the root document. Don't add async or defer." Then publish again.

A tracker fires before consent. Open DevTools → Network in a private window and reload without consenting. If you see requests to google-analytics.com, googletagmanager.com or facebook.com/tr, tag or gate that script as shown above.