PHPackages                             core45/laravel-cookieconsent - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. core45/laravel-cookieconsent

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

core45/laravel-cookieconsent
============================

Laravel wrapper for orestbida/cookieconsent: PHP-first config, lang-file i18n, server-side consent reads, script gating, iframemanager, and a database consent audit trail.

v1.1.0(2w ago)156MITPHPPHP ^8.2CI passing

Since Jul 9Pushed 1w agoCompare

[ Source](https://github.com/core45/laravel-cookieconsent)[ Packagist](https://packagist.org/packages/core45/laravel-cookieconsent)[ RSS](/packages/core45-laravel-cookieconsent/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (9)Dependencies (18)Versions (5)Used By (0)

core45/laravel-cookieconsent
============================

[](#core45laravel-cookieconsent)

A Laravel wrapper for [orestbida/cookieconsent v3](https://cookieconsent.orestbida.com/) and its companion [iframemanager](https://github.com/orestbida/iframemanager). It gives you a PHP-first configuration file instead of a hand-written JS blob, i18n through normal Laravel lang files, server-side reads of what the visitor consented to, Blade-driven script gating, and a database audit trail of every consent decision whose model write path is append-only.

The package bundles orestbida/cookieconsent **3.1.0** and iframemanager **1.3.0**, served self-hosted from `resources/dist` / the published `vendor/cookieconsent` assets — no CDN request is made. Upstream updates are re-vendored and checksum-verified by the maintainers, then shipped as a new package release; there is no auto-update mechanism, so pin the package version if you need a specific upstream JS version.

CapabilityWhat you getConfigurationA single `config/cookieconsent.php` array, passed through to `CookieConsent.run()` almost verbatimTranslationsStandard Laravel lang files (`lang/vendor/cookieconsent/{locale}/cookieconsent.php`), `active` or `all` localesScript gating`@cookiescript` Blade directives that emit blocked `` tagsServer-side reads`@consent`/`Consent`/`CookieConsent` facade + a `consent:*` route middlewareAudit trailA `consent_logs` table with an append-only model write path, export/prune Artisan commands, optional Filament resource (v3, v4 or v5)Media consentAn `@iframemanager` / `@iframe()` integration for YouTube-style click-to-load embedsSecurityOptional CSP nonce injection for every script tag the package rendersInstallation
------------

[](#installation)

Requires **PHP 8.4+** and **Laravel 12 or 13**. [Filament](#filament-plugin) is optional; if you have it, **v3, v4 and v5 are all supported**.

Laravel 11 support was dropped in 3.0: every `laravel/framework` 11.x release is now covered by unfixed security advisories, so Composer refuses to install it by default. On PHP 8.2/8.3 use `^1.1`; on Laravel 11 use `^2.0`, understanding that the framework underneath it is out of security support.

```
composer require core45/laravel-cookieconsent

php artisan vendor:publish --tag=cookieconsent-config
php artisan vendor:publish --tag=cookieconsent-assets
php artisan migrate
```

Other publishable tags, used only if you need to customize them:

```
php artisan vendor:publish --tag=cookieconsent-lang        # lang/vendor/cookieconsent/{locale}/*.php
php artisan vendor:publish --tag=cookieconsent-views        # resources/views/vendor/cookieconsent/*.blade.php
php artisan vendor:publish --tag=cookieconsent-migrations   # copy the migration into database/migrations
```

Add `@cookieconsent` to your layout's ``. This is the recommended placement — the banner script waits for `DOMContentLoaded` internally, so it's safe to load it before the rest of the page:

```

    ...
    @cookieconsent

```

The package auto-registers its consent cookie (`cookieconsent.cookie.name`, default `cc_cookie`) as an exception to Laravel's `EncryptCookies` middleware on boot. You do not need to edit `bootstrap/app.php` yourself.

Configuration
-------------

[](#configuration)

`config/cookieconsent.php` is published as a plain PHP array. Every key **except** `translations_mode`, `csp_nonce`, `logging`, and `iframemanager` is passed straight through to `CookieConsent.run()` — see the [configuration reference](https://cookieconsent.orestbida.com/reference/configuration-reference.html) for every option orestbida/cookieconsent understands.

```
return [

    // Passed through to CookieConsent.run() — see the configuration reference linked above.
    'cookie' => [
        'name' => 'cc_cookie',
        'expiresAfterDays' => 182,
        // WARNING: enabling `useLocalStorage` disables server-side consent
        // reads (@consent, Consent::has(), consent:* middleware). See below.
    ],

    'mode' => 'opt-in',
    'revision' => 0,

    'guiOptions' => [
        'consentModal' => ['layout' => 'box', 'position' => 'bottom left'],
        'preferencesModal' => ['layout' => 'box'],
    ],

    'categories' => [
        'necessary' => ['enabled' => true, 'readOnly' => true],
        'analytics' => [
            'autoClear' => [
                'cookies' => [
                    // Use CookieConsent::regex('^_ga') for RegExp matching.
                    ['name' => '_gid'],
                ],
            ],
        ],
    ],

    // Package-only keys, stripped before the array reaches the JS config:
    'translations_mode' => 'active', // 'active' = current locale only, 'all' = every published locale
    'csp_nonce' => null,             // see "Content Security Policy" below
    'logging' => [ /* ... */ ],      // see "Consent audit trail" below
    'iframemanager' => [ /* ... */ ], // see "iframemanager" below
];
```

### RegExp cookie matching

[](#regexp-cookie-matching)

`autoClear.cookies[].name` normally does an exact string match. To emit a real JS `RegExp` instead, use the `CookieConsent::regex()` helper — it returns a sentinel array that the package's Blade view revives into `new RegExp(...)` client-side:

```
use Core45\CookieConsent\Facades\CookieConsent;

'autoClear' => [
    'cookies' => [
        ['name' => CookieConsent::regex('^_ga')],       // matches _ga, _ga_ABC123, ...
        ['name' => CookieConsent::regex('^_gid$', 'i')], // second arg is JS RegExp flags
    ],
],
```

A plain string (`['name' => '_gid']`) is always an exact match.

Translations
------------

[](#translations)

Publish the language files and edit them like any other Laravel translation:

```
php artisan vendor:publish --tag=cookieconsent-lang
```

```
lang/vendor/cookieconsent/{locale}/cookieconsent.php
lang/vendor/cookieconsent/{locale}/iframemanager.php

```

The package ships `bg`, `cs`, `de`, `el`, `en`, `es`, `et`, `fi`, `fr`, `hr`, `hu`, `it`, `lt`, `lv`, `nl`, `pl`, `pt`, `sk`, `sl`, `sq`, `sv` and `uk` out of the box. `cookieconsent.php`maps directly onto orestbida's `language.translations.{locale}` shape (`consentModal`, `preferencesModal`, etc); `iframemanager.php` supplies the per-service `loadBtn`/`notice`/... strings.

`config('cookieconsent.translations_mode')` controls how many locales are sent to the browser:

- `'active'` (default) — only the current `app()->getLocale()` locale is embedded.
- `'all'` — every published locale directory is embedded, so client-side locale switches (`CookieConsent.setLanguage()`) work without a page reload.

Script gating
-------------

[](#script-gating)

`@cookiescript` blocks a `` tag until the visitor has consented to its category, using orestbida's `type="text/plain" data-category="..."` convention.

External script:

```
@cookiescript('analytics', 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX')
```

Inline script:

```
@cookiescript('analytics')
    window.dataLayer = window.dataLayer || [];
    function gtag(){ dataLayer.push(arguments); }
    gtag('js', new Date());
    gtag('config', 'G-XXXXXXX');
@endcookiescript
```

You can also negate a category to run cleanup code when consent is *not* given (or is withdrawn), and pass extra HTML attributes as a third argument:

```
@cookiescript('!analytics')
    // runs when analytics consent is absent
@endcookiescript

@cookiescript('analytics', 'https://example.com/ga.js', ['data-service' => 'Google Analytics'])
```

> **Blade quirk:** don't glue `@endcookiescript` directly onto a word character with no whitespace or punctuation between them — Blade's compiler won't recognize `someCode@endcookiescript` as a directive boundary. This is rarely an issue in practice because inline scripts almost always end in `;`, `}`, or a newline before the closing directive, as in the examples above.

Letting visitors change their mind
----------------------------------

[](#letting-visitors-change-their-mind)

Once the banner has been answered it never shows again on its own, so the site must provide a way back into the preferences modal — GDPR requires that consent be as easy to withdraw as it is to give. `@cookiepreferences`renders that trigger:

```
@cookiepreferences
```

It emits a button carrying orestbida's `data-cc="show-preferencesModal"` hook:

```
Manage preferences
```

The default label is the `consentModal.showPreferencesBtn` translation line, so it follows the current locale with no extra strings to publish (falling back to `Manage preferences` if you've removed that line — sites offering only accept-all/reject-all in the banner do). Pass your own label and any HTML attributes to style it:

```
@cookiepreferences('Cookie settings', ['class' => 'footer__link', 'aria-label' => 'Open cookie settings'])
```

Attribute values are escaped; `true` renders a bare attribute and `false`/`null` omits it. `data-cc` and `type` are owned by the directive and cannot be overridden, so the trigger can't be silently detached from the modal. Inline `on*` event handlers are dropped — the nonce-based CSP this package supports would block them anyway.

Put it wherever your privacy links live — footer, cookie policy page, account settings. Reopening the modal and saving new choices fires `cc:onChange`, which is logged as a `change` action when the [audit trail](#consent-audit-trail) is enabled.

Any element works, not just the directive's button — the attribute is what matters:

```
Cookie settings
```

> **Dynamically inserted triggers:** the library binds these listeners once, when `CookieConsent.run()` executes. A trigger injected later (Livewire re-render, Turbo/htmx swap, modal opened from JS) won't be wired up. Call `window.CookieConsent.showPreferences()` from your own handler in that case.

Server-side consent
-------------------

[](#server-side-consent)

Read what the visitor has consented to from PHP, without waiting for JavaScript:

```
@consent('analytics')
    Analytics-only content.
@endconsent
```

```
use Core45\CookieConsent\Support\Consent;
use Core45\CookieConsent\Facades\CookieConsent;

app(Consent::class)->has('analytics');   // bool
CookieConsent::has('analytics');         // same, via the facade
CookieConsent::categories();             // list, e.g. ['necessary', 'analytics']
CookieConsent::acceptType();             // 'all' | 'necessary' | 'custom' | null (no cookie yet)
```

Gate a whole route behind a category with the `consent:*` middleware — it aborts with `403` when consent is missing:

```
Route::get('/dashboard/analytics', AnalyticsController::class)
    ->middleware('consent:analytics');
```

> **Not an authorization boundary.** `@consent`, `Consent::has()`, and the `consent:*` middleware all read the unencrypted `cc_cookie` (it's excluded from `EncryptCookies` because orestbida/cookieconsent needs to read/write it as plaintext client-side). A visitor can freely edit that cookie's contents. Treat these checks as a UX/ compliance gate that reflects the visitor's own stated preference — never as access control for sensitive resources; use real authorization (policies, gates, middleware backed by server-side state) for that.

> **`useLocalStorage` warning.** If you set `cookie.useLocalStorage` to `true` in the config, orestbida/cookieconsent stores consent in the browser's `localStorage` instead of a cookie. That value never reaches the server, so `@consent`/`@endconsent`, `Consent::has()`, `CookieConsent::has()`, and the `consent:*` middleware will **always**report "no consent" — they degrade silently to `false`/`[]`/`null`, except for a **one-time** `Log::warning()`the first time a read is attempted (guarded by a static flag, so it fires once per PHP process — once per request under classic PHP-FPM, once total under Octane). Leave `useLocalStorage` off (the default) if you rely on any server-side consent check.

Consent audit trail
-------------------

[](#consent-audit-trail)

Every accept/change interaction the banner fires (`cc:onFirstConsent`, `cc:onChange`) is POSTed to an internal `cookieconsent.log` route and written as a new row in `consent_logs`. The `ConsentLog` model's write path is append-only: it throws if you try to update an existing row through the model. This guard is model-instance-level only — it does **not** stop raw `DB::table('consent_logs')->update(...)`, a mass `ConsentLog::where(...)->update(...)`, or `delete()`, all of which bypass Eloquent's `saving` hook. For a hard guarantee, restrict UPDATE/DELETE grants on the `consent_logs` table at the database user level, or add a database trigger that rejects them outright.

#### Hardening the audit trail at the database level

[](#hardening-the-audit-trail-at-the-database-level)

The model guard only covers writes made through Eloquent instances. For a guarantee that holds even against raw SQL, `DB::table(...)`, or a compromised app process, revoke `UPDATE`/`DELETE` on `consent_logs` from the application's database user and grant it only `SELECT`/`INSERT`:

```
-- MySQL
REVOKE UPDATE, DELETE ON consent_logs FROM 'app_user'@'%';

-- PostgreSQL
REVOKE UPDATE, DELETE ON consent_logs FROM app_user;
```

The `cookieconsent:prune` command deletes expired rows, so if you apply this hardening, run pruning under a separate, more privileged database role/connection instead of the app's normal user.

ColumnContents`consent_id`, `revision`orestbida's own consent identifier and config revision`action``first_consent` or `change``accept_type``all`, `custom`, or `necessary``accepted_categories`, `rejected_categories`, `accepted_services`JSON arrays`language_code`locale active on the banner at the time of consent`policy_version`, `policy_hash`your configured version string, and a canonicalized sha256 of the effective config — see `CookieConsent::policyHash()``ip_address`, `user_agent`evidentiary metadata, subject to the PII config below`user_type`, `user_id`polymorphic link to the authenticated user, if any`payload`the full raw cookie payload, for future-proofingEvidentiary fields (`ip_address`, `user_agent`, `user_type`/`user_id`, `policy_hash`) are always derived server-side from the request — they are never trusted from client input.

> **Delivery.** The consent-log POST does not block or affect the banner UI. It retries transient failures (network errors, `429`, `5xx`) up to twice with backoff, and each event carries a server-checked idempotency key so a retried or duplicated delivery never creates a second row. If the tab closes or navigates away before the request completes, the pending payload is sent as a last-chance `navigator.sendBeacon()` call on `pagehide`/tab- hidden. Non-retryable client errors (other `4xx`) and exhausted retries are logged to the browser console, not retried further.

### PII configuration

[](#pii-configuration)

```
'logging' => [
    'enabled' => true,
    'csrf' => true,
    'capture_ip' => 'raw',            // 'raw' | 'hashed' | false
    'ip_hash_salt' => env('COOKIECONSENT_IP_SALT'),
    'capture_user_agent' => true,
    'link_user' => true,
    'morph_id_type' => 'int',         // 'int' | 'uuid' | 'ulid' | 'string' — must match your User key type
    'policy_version' => null,
    'retention_days' => null,         // null disables pruning
    'rate_limit' => '30,1',           // Laravel throttle expression applied to the log endpoint
],
```

> **Legal note.** Storing an IP address and/or user agent alongside a consent decision is itself an act of processing personal data, even though the purpose is to *prove* consent. Document this processing activity in your own privacy policy, and tune `capture_ip` (`'hashed'` or `false`) and `retention_days` to match your data minimization obligations under GDPR/LOPD-GDD or the regime that applies to you. This package stores evidence; it does not decide your retention policy for you.

> **Security note.** `capture_ip => 'raw'`/`'hashed'` relies on `$request->ip()`, whose accuracy depends on your application's `trustProxies` configuration (`bootstrap/app.php` → `->withMiddleware()->trustProxies(...)`, or `App\Http\Middleware\TrustProxies` on older skeletons). Behind a load balancer, reverse proxy, or CDN, an unconfigured trusted-proxy list means every request is logged with the proxy's IP, not the visitor's.

### Retrieval

[](#retrieval)

```
php artisan cookieconsent:export --format=csv > consent-evidence.csv
php artisan cookieconsent:export --format=json --user=42
php artisan cookieconsent:export --format=json --user=42 --user-type="App\\Models\\Admin"
php artisan cookieconsent:export --consent-id=abc123 --from=2026-01-01 --to=2026-06-30
```

`--to` accepts a date-only value (as above) and treats it as inclusive of the whole day — a row created at `2026-06-30 23:00` is included, not excluded by an implicit midnight bound. `--user` alone matches `user_id` only; it is **not** scoped by `user_type`, so ids can collide across different morph types (e.g. an `Admin` and a `Customer` both with id `42`). Pass `--user-type` alongside `--user` to scope the match to a specific morph class. `--format` only accepts `json` or `csv`; anything else fails with an error and a non-zero exit code.

The underlying `ConsentLog` model exposes matching Eloquent scopes:

```
ConsentLog::forConsentId('abc123')->get();
ConsentLog::forUser($user)->get();
ConsentLog::between('2026-01-01', '2026-06-30 23:59:59')->get();
// or, with Carbon:
ConsentLog::between('2026-01-01', now()->parse('2026-06-30')->endOfDay())->get();
ConsentLog::latestPerConsentId()->get(); // one row per consent_id, the most recent
```

> **`between()` bounds are used verbatim.** Unlike the export command's `--to`, the `between()` scope passes its bounds straight to `whereBetween('created_at', [$from, $to])` — it does **not** auto-expand a date-only end bound to end-of-day. `ConsentLog::between('2026-01-01', '2026-06-30')` excludes anything after `2026-06-30 00:00:00`. Pass an explicit time (`'2026-06-30 23:59:59'`) or a Carbon `endOfDay()` instance to include the whole day.

### Retention

[](#retention)

```
php artisan cookieconsent:prune
```

Deletes rows older than `logging.retention_days`. It's a no-op (logs an info message, deletes nothing) when `retention_days` is `null`. Schedule it if you want automatic pruning:

```
// routes/console.php
Schedule::command('cookieconsent:prune')->daily();
```

### Filament plugin

[](#filament-plugin)

An optional, read-only `ConsentLogResource` is available if `filament/filament` is installed. **Filament v3, v4 and v5 are all supported**, from the same one-line registration — the plugin detects the installed major and registers the matching resource, so there is no version to pass.

**There is nothing to publish.** The resource is plain PHP registered at runtime; no `vendor:publish` tag covers it, and no assets or views are involved. You only add the plugin to your panel provider — typically `app/Providers/Filament/AdminPanelProvider.php`:

```
use Core45\CookieConsent\Filament\CookieConsentFilamentPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...your existing configuration
        ->plugin(CookieConsentFilamentPlugin::make());
}
```

Reload the panel and a **Consent Logs** item appears in the sidebar, at `/{your-panel-path}/consent-logs`.

Two reasons it might not show up:

- **The panel does not discover it on its own.** `discoverResources(in:, for:)` scans only the directory and namespace you hand it — your app's, not a package's — so the plugin line above is what registers the resource. It is not optional.
- **Authorization.** The item is hidden if a `ConsentLog` policy denies `viewAny()`, or if you run `strictAuthorization()` without one. Check with `ConsentLogResource::canViewAny()`.

The resource sets no navigation icon and no navigation group, so it appears at the top level of the sidebar with an empty icon slot.

To see which Filament build was detected:

```
php artisan cookieconsent:filament
```

```
  Filament version  v4.12.6
  Detected major..  v4
  Infolist API....  schemas (v4+)
  Resource in use.  Core45\CookieConsent\Filament\Resources\ConsentLogResource

```

The two builds are feature-identical; only the Filament API differs. Filament v4 moved infolists onto `Filament\Schemas\Schema` and renamed the table row-action method, and that changed the signature of `Resource::infolist()` — so a resource written for one side of that break is a *fatal error at class-load time*on the other, not a recoverable failure. That is why the two live in separate namespaces (`Filament\Resources\…` for v4/v5, `Filament\V3\Resources\…` for v3) and why the branch is a class-name probe rather than a runtime check. Only the matching one is ever autoloaded. If you reference the resource class directly rather than through the plugin, use `Core45\CookieConsent\Filament\FilamentVersion::resourceClass()`instead of naming it yourself.

The resource has no create/edit pages — consent logs are evidence, not editable records. Each row opens a read-only modal showing every stored field, including `rejected_categories`, `accepted_services`, `policy_hash`and the raw `payload`.

That modal is a `ViewAction`, which is authorized by a `view()` policy method. If you run Filament's `strictAuthorization()` and have a `ConsentLog` policy, it now needs `view()` alongside `viewAny()`, otherwise Filament throws a `LogicException`. Apps with no policy, or without strict authorization, are unaffected.

iframemanager
-------------

[](#iframemanager)

Gate third-party embeds (YouTube, Vimeo, maps, ...) behind a consent category with a click-to-load placeholder, using [iframemanager](https://github.com/orestbida/iframemanager).

```
'iframemanager' => [
    'enabled' => true,
    'services' => [
        'youtube' => [
            'category' => 'analytics', // which cookieconsent category unlocks this service
            'embedUrl' => 'https://www.youtube-nocookie.com/embed/{data-id}',
            'thumbnailUrl' => 'https://i3.ytimg.com/vi/{data-id}/hqdefault.jpg',
            'iframe' => [
                'allow' => 'accelerometer; autoplay; encrypted-media',
            ],
        ],
    ],
],
```

Place `@iframemanager` near `@cookieconsent` (typically right after it):

```
@cookieconsent
@iframemanager
```

Then drop a placeholder wherever you'd normally embed the iframe:

```
@iframe('youtube', $videoId, ['params' => 'rel=0', 'thumbnail' => $customThumbnailUrl])
```

Per-service copy (`loadBtn`, `notice`, ...) comes from `lang/vendor/cookieconsent/{locale}/iframemanager.php`, keyed by service name (`youtube` in the example above), just like the main `cookieconsent.php` lang file.

Advanced JS / custom callbacks
------------------------------

[](#advanced-js--custom-callbacks)

Two ways to hook into the banner's behavior beyond what the config array exposes:

**1. `cc:*` window events** — orestbida/cookieconsent dispatches `cc:onFirstConsent`, `cc:onConsent`, `cc:onChange`, `cc:onModalReady`, `cc:onModalShow`, and `cc:onModalHide` on `window`. Listen to them from any script on the page, inline or bundled:

```

window.addEventListener('cc:onConsent', () => {
    if (window.CookieConsent.acceptedCategory('analytics')) {
        // ...
    }
});

```

**2. The `cookieconsent:config` push stack** — runs after the revived `config` JS variable is declared but *before* `CookieConsent.run(config)` is called, so you can mutate `config` in place (e.g. make a static option dynamic):

```
@push('cookieconsent:config')

    config.cookie.expiresAfterDays = () => 365;

@endpush
```

### Google Consent Mode recipe

[](#google-consent-mode-recipe)

Combining both mechanisms to wire up [Google Consent Mode v2](https://developers.google.com/tag-platform/security/guides/consent):

```
@push('cookieconsent:config')

    window.dataLayer = window.dataLayer || [];
    function gtag() { dataLayer.push(arguments); }
    gtag('consent', 'default', { analytics_storage: 'denied', ad_storage: 'denied' });
    window.addEventListener('cc:onConsent', () => {
        if (window.CookieConsent.acceptedCategory('analytics')) {
            gtag('consent', 'update', { analytics_storage: 'granted' });
        }
    });

@endpush
```

Content Security Policy
-----------------------

[](#content-security-policy)

If your app runs under a strict CSP, set `csp_nonce` to a callable that returns the per-request nonce:

```
'csp_nonce' => fn () => \Illuminate\Support\Facades\Vite::cspNonce(),
```

Every inline and external `` tag **the package itself renders** (`@cookieconsent`, `@iframemanager`) gets a `nonce="..."` attribute automatically.

> **CSP caveat.** Scripts *you* push via `@push('cookieconsent:config')` are plain Blade/HTML — they do **not**automatically inherit the configured nonce. Under a strict CSP you must add `nonce="..."` to your own pushed `` tags yourself, e.g. `...`.

Versioning &amp; License
------------------------

[](#versioning--license)

This package follows [Semantic Versioning](https://semver.org/). It is open-sourced under the [MIT license](LICENSE.md), Copyright (c) 2026 core45.

###  Health Score

44

—

FairBetter than 90% of packages

Maintenance98

Actively maintained with recent releases

Popularity13

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~11 days

Total

4

Last Release

15d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/17904235?v=4)[Kornel](/maintainers/core45)[@core45](https://github.com/core45)

---

Top Contributors

[![core45](https://avatars.githubusercontent.com/u/17904235?v=4)](https://github.com/core45 "core45 (37 commits)")

---

Tags

laravelcookiesgdprconsentcookieconsent

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/core45-laravel-cookieconsent/health.svg)

```
[![Health](https://phpackages.com/badges/core45-laravel-cookieconsent/health.svg)](https://phpackages.com/packages/core45-laravel-cookieconsent)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.4M354](/packages/psalm-plugin-laravel)[api-platform/laravel

API Platform support for Laravel

58190.1k21](/packages/api-platform-laravel)[forjedio/inertia-table

Backend-driven dynamic tables for Laravel + Inertia.js

272.0k](/packages/forjedio-inertia-table)[simplestats-io/laravel-client

Server-side analytics for Laravel that follows the full funnel from visit to registration to payment, attributed to the channel that drove it. Revenue, MRR, churn and ad-spend profit (ROAS/CAC) per channel. GDPR compliant, ad-blocker proof.

5226.7k](/packages/simplestats-io-laravel-client)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

265.2k](/packages/aedart-athenaeum)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
