keepsuit/laravel-cookie-solution
Make your site comply with EU cookie law
This package provides a configurable cookie banner for your Laravel application. It also includes templates for cookie policy and privacy policy pages.
Note that this package is not a legal advice. You should always consult a lawyer and change texts and policies for your needs.
Installation
You can install the package via composer:
composer require keepsuit/laravel-cookie-solution
You can publish the config file with:
php artisan vendor:publish --tag="cookie-solution-config"
This is the contents of the published config file:
<?php
return [
/*
|--------------------------------------------------------------------------
| Enabled
|--------------------------------------------------------------------------
|
| Enable or disable the cookie solution (banner, script and API).
|
*/
'enabled' => env('COOKIE_SOLUTION_ENABLED', true),
/*
|--------------------------------------------------------------------------
| Consent Cookie
|--------------------------------------------------------------------------
|
| Name of the cookie where the user's consent is stored, its lifetime
| in days and its domain. Changing the name asks every user for consent
| again. Set the domain (ex. '.example.com') to share the consent across
| subdomains (they must register the same services, or each one asks
| again), or env('SESSION_DOMAIN') to follow the session cookie. The
| domain must cover the current host. If null, the consent applies to
| the current host only.
|
*/
'cookie_name' => 'laravel_cookie_solution',
'cookie_lifetime' => 365,
'cookie_domain' => env('COOKIE_SOLUTION_COOKIE_DOMAIN'),
/*
|--------------------------------------------------------------------------
| Highlight Color
|--------------------------------------------------------------------------
|
| Banner highlight color (ex. #3522dd), any CSS color format. If null,
| the default color is used, or the `--cs--color-highlight` CSS
| variable when the page sets it.
|
| Colors that keep white button text and are used unchanged (at least
| 4.5:1 on white): #c2410c (orange, the default), #dc2626 (red),
| #db2777 (pink), #7c3aed (violet), #4f46e5 (indigo), #2563eb (blue),
| #0e7490 (cyan), #0f766e (teal), #15803d (green), #334155 (slate).
| Lighter colors get black button text and are darkened where used as
| text, focus ring or icon.
|
*/
'highlight_color' => null,
/*
|--------------------------------------------------------------------------
| Layout
|--------------------------------------------------------------------------
|
| Banner layout: 'modal' (a dialog centered on the page, which blocks
| the page until the user chooses) or 'floating' (a compact card in the
| bottom corner on the toggle_position side, a bottom sheet on mobile,
| which leaves the page usable).
|
*/
'layout' => 'modal',
/*
|--------------------------------------------------------------------------
| Color Scheme
|--------------------------------------------------------------------------
|
| Colors of the banner and the policy views: 'light', 'dark' or 'auto'
| (follows the visitor's system setting). With 'dark' and 'auto' the
| policy views expect a dark page background.
|
*/
'color_scheme' => 'light',
/*
|--------------------------------------------------------------------------
| Floating Toggle
|--------------------------------------------------------------------------
|
| Show the floating button that reopens the banner (true, false or
| 'auto'), and its position (left or right). Elements with the
| "data-cookie-solution-toggle" attribute open the banner too: with
| 'auto' the floating button is hidden while the page contains one.
|
*/
'toggle_enabled' => true,
'toggle_position' => 'right',
/*
|--------------------------------------------------------------------------
| Data Owner
|--------------------------------------------------------------------------
|
| The entity responsible for the processing of the data, shown in the
| privacy policy. The name and address are parsed as Markdown (you can
| use __text__ for bold and _text_ for italic).
|
*/
'data_owner' => [
'contact_email' => null,
'name_and_address' => null,
],
/*
|--------------------------------------------------------------------------
| API
|--------------------------------------------------------------------------
|
| JSON endpoints: banner configuration (GET {path}), banner content
| (GET {path}/content, loaded by every banner when it opens, Blade
| included) and policies (GET {path}/policies). The locale is read from
| the `locale` query parameter. Added middleware must not require
| authentication, start a session or rate limit by IP (server-side
| rendering sends every visitor from one IP).
|
*/
'api' => [
'domain' => env('COOKIE_SOLUTION_API_DOMAIN'),
'path' => env('COOKIE_SOLUTION_API_PATH', 'cookie-solution'),
'middleware' => [],
],
];
Optionally, you can publish the views and translations using:
php artisan vendor:publish --tag="cookie-solution-views"
php artisan vendor:publish --tag="cookie-solution-translations"
By default the banner script is served by a package route (/cookie-solution/laravel-cookie-solution.js).
If you publish the assets, the script is served from public/vendor/cookie-solution through asset(), so it uses your ASSET_URL (e.g. a CDN):
php artisan vendor:publish --tag="cookie-solution-assets"
The package detects the published assets automatically. Remember to publish them again on every update, for example with a composer post-update-cmd script:
{
"scripts": {
"post-update-cmd": [
"@php artisan vendor:publish --tag=cookie-solution-assets --force"
]
}
}
Usage
The package can be used in two ways:
- Blade: the banner script and its configuration are included in your layout (see below).
- Headless (React, Vue, any SPA/SSR frontend): Laravel exposes the configuration as JSON and the frontend mounts the banner with the
@keepsuit/cookie-solutionnpm package.
In both cases services, texts, translations and policies are configured in Laravel.
Include the cookie solution script in your layout (it's recommended to include it in the <head> tag):
@include('cookie-solution::script')
It also defines window.cookieSolutionSavedStatus(), which returns the consent saved for the current configuration (or undefined) synchronously, before the deferred script loads. To use it earlier in <head>, include cookie-solution::status there. See saved consent in <head>.
Register used services in your 'AppServiceProvider' (You can register your own services with Service class):
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Keepsuit\CookieSolution\CookieSolution;
use Keepsuit\CookieSolution\ServiceFactories\Google\GoogleAnalytics4ServiceFactory;
use Keepsuit\CookieSolution\ServiceFactories\Google\GoogleDataProcessingLocation;
use Keepsuit\CookieSolution\ServiceFactories\Google\GoogleTagManagerServiceFactory;
use Keepsuit\CookieSolution\ServiceFactories\Meta\FacebookPixelServiceFactory;
use Keepsuit\CookieSolution\ServiceFactories\Meta\MetaDataProcessingLocation;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
$this->app->afterResolving(CookieSolution::class, function (CookieSolution $cookieSolution) {
$cookieSolution->register(GoogleAnalytics4ServiceFactory::new()->location(GoogleDataProcessingLocation::IRELAND)->build())
->register(GoogleTagManagerServiceFactory::new()->location(GoogleDataProcessingLocation::IRELAND)->build())
->register(FacebookPixelServiceFactory::new()->location(MetaDataProcessingLocation::IRELAND)->build());
});
}
}
CookieSolution is a scoped instance: Octane and queue workers build a new one for every request and job, running the afterResolving callbacks again.
Register services and resolvers there, not on the instance during boot(): they would be lost after the first request.
The callbacks can read the current site or tenant, e.g. to register the services configured in a CMS.
When a single request or job works on several sites, call app()->forgetInstance(CookieSolution::class) and \Keepsuit\CookieSolution\Facades\CookieSolution::clearResolvedInstance() before switching site.
Update the config config/cookie-solution.php file with your data owner's contact email and name and address.
return [
// ...
/**
* The entity responsible for the processing of the data.
*/
'data_owner' => [
/**
* Email address of the data owner.
*/
'contact_email' => 'your_email@example.com',
/**
* Name/Company name and address of the data owner.
* This is parsed as Markdown (you can use __text__ for bold and _text_ for italic).
* An Htmlable value (e.g. HtmlString) is rendered as is: set it at runtime, objects can't be cached by `config:cache`.
*/
'name_and_address' => <<<MARKDOWN
__Your Company Name__
Your Street 1
City, Country
MARKDOWN,
],
];
Create a route for the cookie policy page and include the cookie policy partial:
<body>
@include('cookie-solution::cookie-policy')
</body>
Create a route for the privacy policy page and include the privacy policy partial:
<body>
@include('cookie-solution::privacy-policy')
</body>
See customization for the available parts and how to customize the views.
Policy texts
The policy texts are the Markdown files in resources/views/vendor/cookie-solution/policy (published with the views), then the package ones, then the en ones.
To provide them from your app (e.g. a CMS), register a resolver where you register the services:
use Keepsuit\CookieSolution\CookieSolution;
use Keepsuit\CookieSolution\Policy;
$this->app->afterResolving(CookieSolution::class, function (CookieSolution $cookieSolution) {
$cookieSolution->resolvePolicyTextUsing(fn (Policy $policy, string $locale) => PolicyText::for($policy->value, $locale)?->markdown);
});
Return a Markdown string, rendered like the files, or an Htmlable, printed as is (an empty HtmlString hides the text).
null or a blank string falls back to the files: the resolver is called again for en when the locale has no file.
The data owner, services and cookies are still appended after the text, and the banner and the policies endpoint use the same texts.
defaultPolicyText($policy, $locale) returns the file for a locale (null when it has none), ignoring the resolver: e.g. to start an edited text from it.
The resolved text is not sanitized, HTML in the Markdown included: sanitize untrusted texts yourself. The package doesn't cache the resolved texts: cache them in your app if needed.
Documentation
- Customization: highlight color, layout, color scheme, floating toggle, policy views and
::part()styling. - Headless frontends: JSON API and the
@keepsuit/cookie-solutionnpm package (React, SSR, SPA). - Connecting tracking services: scripts that wait for consent (
type="text/plain"), Google Tag Manager consent mode, Meta Pixel, PostHog, Microsoft Clarity.
Checking status
You can check the user's consent status from laravel using the CookieSolution::status() method,
which returns a CookieSolutionStatus object with helpers to check if a purpose has been accepted.
Upgrading
See UPGRADING.
AI agents
The package ships an agent skill (cookie-solution) with setup instructions for Blade and headless frontends.
- Laravel projects with Laravel Boost: it is installed by
php artisan boost:install. - Any other project:
npx skills add keepsuit/laravel-cookie-solution.
Testing
composer test
pnpm test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.
Related Packages
Version History
| Version | Released | PHP | Laravel | License |
|---|---|---|---|---|
| 2.4.0 | ^8.2 | ^12.0 || | MIT | |
| 2.3.0 | ^8.2 | ^12.0 || | MIT | |
| 2.2.1 | ^8.2 | ^12.0 || | MIT | |
| 2.2.0 | ^8.2 | ^12.0 || | MIT | |
| 2.1.0 | ^8.2 | ^12.0 || | MIT | |
| 2.0.0 | ^8.2 | ^12.0 || | MIT | |
| 1.6.2 | ^8.2 | ^11.0 || | MIT | |
| 1.6.1 | ^8.2 | ^11.0 || | MIT | |
| 1.6.0 | ^8.2 | ^11.0 || | MIT | |
| 1.5.1 | ^8.1 | ^10.0 || | MIT | |
| 1.5.0 | ^8.1 | ^10.0 || | MIT | |
| 1.4.0 | ^8.1 | ^10.0 || | MIT | |
| 1.3.0 | ^8.1 | ^10.0 || | MIT | |
| 1.2.1 | ^8.1 | ^10.0 || | MIT | |
| 1.2.0 | ^8.1 | ^10.0 || | MIT | |
| 1.1.1 | ^8.1 | ^10.0 || | MIT | |
| 1.1.0 | ^8.1 | ^10.0 || | MIT | |
| 1.0.1 | ^8.1 | ^10.0 || | MIT | |
| 1.0.0 | ^8.1 | ^10.0 || | MIT | |
| 0.5.1 | ^8.1 | ^9.0 || | MIT | |
| 0.5.0 | ^8.1 | ^9.0 || | MIT | |
| 0.4.1 | ^8.1 | ^9.0 || | MIT | |
| 0.4.0 | ^8.1 | ^9.0 || | MIT | |
| 0.3.7 | ^8.1 | ^9.0 || | MIT | |
| 0.3.6 | ^8.1 | ^9.0 || | MIT | |
| 0.3.5 | ^8.1 | ^9.0 || | MIT | |
| 0.3.4 | ^8.1 | ^9.0 || | MIT | |
| 0.3.3 | ^8.1 | ^9.0 || | MIT | |
| 0.3.2 | ^8.1 | ^9.0 || | MIT | |
| 0.3.1 | ^8.1 | ^9.0 || | MIT |
Showing the latest 30 of 42. See every release on Packagist
Pre-releases (2)
| Version | Released | PHP | Laravel | License |
|---|---|---|---|---|
| 2.0.0-beta.2 | ^8.2 | ^12.0 || | MIT | |
| 2.0.0-beta.1 | ^8.2 | ^12.0 || | MIT |