Silverstripe Klaro! implements KIProtect/klaro for GDPR-compliant cookie consent management with Google Consent Mode v2 support.
composer require lerni/klaro-cookie-consent
dev/build
dev/tasks/klaro-defaults
(default values for SiteConfig)- Go to /admin/settings#Root_CookieConsent and enable "Cookie Is Active" & configureyour needs
- SilverStripe CMS ^5 or ^6
- PHP ^8.1
lerni/silverstripe-tracking
- for Google Analytics, GTM, and Clarity integration
# For SilverStripe 5.x (current)
composer require lerni/klaro-cookie-consent
# For SilverStripe 6.x
composer require lerni/klaro-cookie-consent:6.x-dev
# Legacy versions
composer require lerni/klaro-cookie-consent:v2-dev # SS 4.x/5.x
composer require lerni/klaro-cookie-consent:3.x-dev # SS 3.x
CookieEntries & CookieCategories are automatically populated. To add values to SiteConfig use the task below, it populates SiteConfig with default translations from Klaro and applies custom translations from your language file.
php ./vendor/silverstripe/framework/cli-script.php dev/tasks/klaro-defaults
Defaults can always be overridden, for example to start with only essential cookies (PHPSESSID, klaro), create app/_config/klaro_defaults.yml
before DB is populated with dev/build
.
---
Name: empty_klaro_defaults
After: klaro_defaults
---
Kraftausdruck\Models\CookieEntry:
default_records: null
---
Name: my_klaro_defaults
After: empty_klaro_defaults
---
Kraftausdruck\Models\CookieEntry:
default_records:
Klaro:
Title: 'klaro! consent manager'
Purpose: 'Stores consent and rejection of cookies.'
CookieName: 'klaro'
CookieKey: 'klaro'
Default: 'false'
OptOut: 'false'
CookieCategoryID: 1
PHPSESSID:
Title: 'PHP Session'
Purpose: 'Stores PHP session ID for unique user identification.'
CookieName: 'PHPSESSID'
CookieKey: 'PHPSESSID'
Default: 'true'
OptOut: 'false'
CookieCategoryID: 1
- Settings > Cookie Consent
- Enable "Cookie Is Active"
- Configure services (Google Analytics, GTM, etc.)
- Customize consent modal text and appearance
<!-- Manual link -->
<a href="#klaro" onClick="klaro.show();return false;">Cookie Settings</a>
<!-- Or use ShortCode in CMS -->
[ConsentLink beforeText="Manage your " afterText=" preferences"]
Replace src
with data-src
and add consent attributes:
<!-- Before: Regular script -->
<script src="https://example.com/tracking.js"></script>
<!-- After: Consent-managed script -->
<script type="text/plain"
data-type="text/javascript"
data-name="analytics"
data-src="https://example.com/tracking.js">
</script>
Support for Google's privacy-compliant tracking with consent updates.
- Google Tag Manager
- Google Analytics
- Google Ads
- Microsoft Clarity
Configure custom JavaScript for each service in Settings > Cookie Consent:
// Google Analytics example
OnAccept: if(typeof gtag === "function") { gtag("consent", "update", { analytics_storage: "granted" }); }
OnDecline: if(typeof gtag === "function") { gtag("consent", "update", { analytics_storage: "denied" }); }
// Microsoft Clarity example
OnAccept: if(typeof clarity === "function") { clarity("consentv2", { ad_Storage: "granted", analytics_Storage: "granted" }); }
OnDecline: if(typeof clarity === "function") { clarity("consentv2", { ad_Storage: "denied", analytics_Storage: "denied" }); }
When using with lerni/silverstripe-tracking
, GTM events are automatically fired based on the callbacks set in CMS:
Default Events:
klaro-google-analytics-accepted/declined
klaro-google-ads-accepted/declined
klaro-google-tag-manager-accepted/declined
Setting up GTM Triggers:
- Create Custom Event trigger in GTM
- Use event name (e.g.,
klaro-google-analytics-accepted
) - Fire your tracking tags based on consent
Override defaults in your app/_config/klaro.yml
:
Kraftausdruck\Models\CookieEntry:
default_records:
Analytics:
Title: 'Custom Analytics Title'
# Override any default settings
Styling Customization
// Example SCSS customization
html .klaro {
--notice-max-width: 440px;
.cookie-modal,
.cookie-notice {
z-index: 9100;
a {
color: lighten($link-color, 70%);
}
.cm-btn {
cursor: pointer;
font-size: 14px;
border-radius: 0.1em;
margin-right: 1.2em;
padding-top: .6em;
}
}
.cookie-notice {
.cn-body {
border: 1px solid $gray;
border-radius: 0.1em;
// klaro sets font-size on block elements - we're calculating back to maintain horizontal spacing :-/
@media (max-width: 1023px) {
padding-right: #{$lh * math.div($font-size, 14px)}em !important;
padding-left: #{$lh * math.div($font-size, 14px)}em !important;
@include breakpoint($Mneg) {
padding-right: #{0.5 * $lh * math.div($font-size, 14px)}em !important;
padding-left: #{0.5 * $lh * math.div($font-size, 14px)}em !important;
}
}
}
h2 {
font-size: 1.1em;
margin-top: 0.6em;
}
p {
margin: 0.3em 0 !important;
}
.cn-ok {
display: flex;
flex-wrap: wrap;
justify-content: flex-start !important;
.cn-buttons {
display: flex !important;
order: 1;
// decline
.cm-btn.cn-decline {
background-color: $gray;
order: 1;
}
// accept all
.cm-btn.cm-btn-success {
background-color: $link-color;
order: 0;
}
}
// modal link
.cn-learn-more {
display: block;
margin-right: 0;
order: 2;
flex: 0 0 auto;
padding: 0.5em 0;
}
}
}
.cookie-modal {
.cm-modal {
border: 1px solid $gray;
}
.cm-header a {
@include bold;
}
.cm-app-title {
font-size: 14px;
}
// switch disabled
.cm-list-label .slider {
background-color: $gray;
}
// slider-switches
.cm-list-input:checked + .cm-list-label .slider {
background-color: $link-color;
}
// required switch enabled
.cm-list-input.required:checked + .cm-list-label .slider {
background-color: darken($link-color, 10%);
&::before {
background-color: darken($white, 16%);
}
}
// halve is used on parent if children are on & off
.cm-list-input.only-required + .cm-list-label .slider,
.cm-list-input.half-checked:checked + .cm-list-label .slider {
background-color: mix($link-color, $white, 71%);
}
.cm-list-description {
color: $gray--light;
}
// accept all
.cm-btn.cm-btn-accept-all {
background-color: $link-color;
}
// save selection, decline
.cm-btn.cm-btn-accept,
.cm-btn.cm-btn-decline {
background-color: $gray;
}
.cm-btn.cm-btn-accept {
order: 99;
margin-right: 0;
}
// klaro link
.cm-modal .cm-footer .cm-powered-by {
display: none;
}
}
}
// klaro! contextual consent
[data-type="placeholder"] {
position: absolute;
background-color: $gray--light;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100%;
top: 0;
right: 0;
bottom: 0;
left: 0;
padding: #{$lh}em;
.cm-as-context-notice {
height: auto;
}
.context-notice {
&:last-child {
margin-bottom: 0;
}
.cm-buttons {
display: flex;
gap: 1em;
}
button.cm-btn {
display: inline-block;
padding: #{math.div($lh, 4)}em #{math.div($lh, 2)}em;
border: none;
text-transform: uppercase;
color: $white;
font-size: 1em;
@include bold;
border-radius: 0;
margin: 0 !important;
cursor: pointer;
&:first-of-type {
background-color: $link-color;
}
&:last-of-type {
background-color: mix($link-color, $gray--light, 70%);
}
&:not(:last-of-type) {
margin-right: #{$lh}em;
}
}
}
}