# How to add a cookie consent banner to a Laravel app

> In Laravel, paste the snippet in the head of the Blade layout every page extends (resources/views/components/layout.blade.php or resources/views/layouts/app.blade.php), above @vite and any other script. Read the site key from config/services.php rather than calling env() in the view, and mark tracking tags in the same layout. Livewire's wire:navigate needs no extra code: the banner stays in place across visits.

Source: https://cookiequiet.com/platforms/laravel
Published: 2026-10-21
Publisher: Cookiequiet

## Steps

1. **Copy the snippet** In the Cookiequiet dashboard, add the website and copy the snippet with your site key, data-consent-mode="1" and data-auto-block="1".
2. **Store the key in config** Add COOKIEQUIET_SITE to .env and a cookiequiet entry to config/services.php that reads it with env(), so the value survives php artisan config:cache.
3. **Paste the snippet in the layout** Open the layout every page uses and paste the snippet in the head, above @vite and any other script, with data-site set from config('services.cookiequiet.site').
4. **Mark your tags** Paste analytics and advertising tags below the snippet with type="text/plain" and data-cq-category, inside @production if they should only run in production.
5. **Handle Livewire or Inertia** With wire:navigate, send per-page code such as page views from a livewire:navigated listener. With Inertia, gate components as the React or Vue guide shows.
6. **Add the Cookie preferences link** In the footer partial, add a plain anchor with href="#cq-preferences" and the data-cq-show-preferences attribute.
7. **Test with Reject all** Open the site in a private window, click Reject all and check that only cq_consent and your session cookies are set. Accept all, reload and check the consent log.

## Where does the snippet go in Laravel?

Laravel pages share a Blade layout: a component such as `resources/views/components/layout.blade.php` rendered with `<x-layout>`, or a parent view such as `resources/views/layouts/app.blade.php` that child views name with `@extends`. Paste the snippet in the head of that layout, above `@vite` and any other script:

```blade
{{-- resources/views/layouts/app.blade.php --}}
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://cookiequiet.com/cq.js" data-site="{{ config('services.cookiequiet.site') }}" data-consent-mode="1" data-auto-block="1" defer></script>
    <title>{{ config('app.name') }}</title>
    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
    @yield('content')
</body>
</html>
```

**Keep the key in config.** Add `COOKIEQUIET_SITE=your-key` to `.env` and read it in `config/services.php`:

```php
'cookiequiet' => [
    'site' => env('COOKIEQUIET_SITE'),
],
```

Laravel's documentation warns that once `php artisan config:cache` has run, the `.env` file is no longer loaded and `env()` only returns system-level environment variables, so it should only be called in configuration files. Calling `config()` in the view avoids an empty `data-site` in production.

If the app has several layouts (a guest layout for login pages, an app layout for signed-in users), put the snippet in each, or in a partial they all include with `@include`.

## How do I block my tags until consent?

Paste tracking tags under the snippet and change their opening tag. Blade's `@production` directive keeps them out of local and staging environments:

```blade
@production
<script type="text/plain" data-cq-category="analytics" src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script>
<script type="text/plain" data-cq-category="analytics">
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXX');
</script>
@endproduction
```

Cookiequiet runs marked tags when the category is granted, and at once on later visits where the stored choice includes it. The [Google Analytics 4 guide](/platforms/google-analytics-4) covers the Consent Mode alternative and the [Meta Pixel guide](/platforms/meta-pixel) the pixel. Tags pushed from child views with `@push` into a stack in the head work the same way, as long as the stack is printed below the snippet. Scripts added at runtime by JavaScript are held by the auto-blocker when they come from one of the about 80 trackers it knows.

## What about Livewire and Inertia?

**Livewire with wire:navigate.** Livewire's documentation says scripts in the head run only on the first visit, so the snippet runs once and the stored choice stays applied. It also says the whole body is replaced on each visit. The banner's container lives in the body, and Cookiequiet puts it back into each new body, so a banner waiting for an answer, the preferences button and `showPreferences()` keep working without extra code.

Keep marked tags in the layout head, and listen for `livewire:navigated`, which fires after every navigation and on the first load, for code that sends page views.

**Inertia.** The root template (`resources/views/app.blade.php` with `@inertiaHead`) takes the snippet above `@vite`. The pages are React or Vue components, so read the choice in them as the [React](/platforms/react) and [Vue](/platforms/vue) guides show, with the `cq:consent` event Cookiequiet dispatches on `window` each time it applies a choice.

## How do I add the Cookie preferences link?

In the footer partial:

```html
<a href="#cq-preferences" data-cq-show-preferences>Cookie preferences</a>
```

## How do I test it?

1. Run `php artisan config:cache` once to check that the key still renders, then view the page source: `cq.js` is the first script in the head.
2. Open the site in a private window and click **Reject all**. The cookies are `cq_consent`, your session cookie and `XSRF-TOKEN`: these two are Laravel's own, set for every visitor, and are strictly necessary.
3. Clear the cookies, click **Accept all**, reload: the marked tags run.
4. With wire:navigate, open a second page before choosing: the banner stays visible.
5. Check the consent log in the dashboard.

A Content Security Policy must allow `https://cookiequiet.com` in `script-src` and `connect-src`. Cookiequiet is free for one website with geo-targeting; Starter is €3 per website per month with unlimited pageviews.

## Questions

### Is there a Composer package for Cookiequiet?

No. The banner is one script tag in your Blade layout, with the site key read from config. There is nothing to install with Composer or npm.

### Why use config() instead of env() in the layout?

Laravel's documentation says that once the configuration is cached, the .env file is not loaded and env() only returns system-level environment variables, so env() belongs in configuration files only. Reading the key through config/services.php keeps it working after php artisan config:cache.

### Do Laravel's session and XSRF-TOKEN cookies need consent?

They keep the session and protect forms against cross-site request forgery, which makes them strictly necessary in most readings of the ePrivacy rules. List them in the cookie table as necessary; the banner does not block them.

### Does the banner work with Livewire's wire:navigate?

Yes. The script in the head runs once and the choice stays applied. Livewire replaces the body on each visit, and Cookiequiet moves its banner's container into the new body by itself, so a visitor who navigates before choosing still sees the banner.

## Sources

- [Laravel: Blade templates (layouts, @production, stacks)](https://laravel.com/docs/blade)
- [Laravel: Configuration (configuration caching and env())](https://laravel.com/docs/configuration)
- [Livewire: Navigate (wire:navigate, livewire:navigated)](https://livewire.laravel.com/docs/navigate)
- [Laravel: Vite and asset bundling (@vite)](https://laravel.com/docs/vite)
