Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions concepts/commerce/content/cookie-consent-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
participant CookieProvider

User->>Storefront: Visit shop
Storefront->>StoreAPI: GET /store-api/cookie/groups
Storefront->>StoreAPI: GET /store-api/cookie-groups
StoreAPI->>CookieProvider: getCookieGroups()
CookieProvider-->>StoreAPI: Cookie groups + hash
StoreAPI-->>Storefront: Cookie configuration
Expand Down Expand Up @@ -154,10 +154,11 @@

The cookie consent system itself uses special cookies:

| Cookie | Purpose | Lifetime |
| -------------------- | ----------------------------------------- | -------- |
| `cookie-preference` | Stores user's consent choices | 30 days |
| `cookie-config-hash` | Tracks configuration changes per language | 30 days |
| Cookie | Purpose | Lifetime |
| -------------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
| `cookie-preference` | Stores user's consent choices | 30 days |
| `cookie-config-hash` | Tracks configuration changes per language | 30 days |
| `cookie-consent-id` | Links the consent decisions of this browser in the server-side consent log. Only set while consent logging is on | Retention period of the log, 120 days by default |

### Protected Cookies

Expand All @@ -183,12 +184,26 @@

The cookie consent system exposes its configuration through the Store API endpoint:

**Endpoint:** `GET /store-api/cookie/groups`
**Endpoint:** `GET /store-api/cookie-groups`

This endpoint enables headless implementations, custom frontends, and third-party integrations to retrieve cookie configuration, the configuration hash, and the language ID. The hash is provided as a string, and the language ID is also returned by the endpoint. When stored in the browser's `cookie-config-hash` cookie, both values should be stored as an object where the language ID is the key and the hash is the value, for example: `{"019ada128cfb711aa7a0d00f476d5961":"998cdcc090e92b3ecdd057241d0fd01f"}`

Every cookie group has a `technicalName`, such as `cookie.groupStatistical`. Unlike `name`, it is the same in every language.

For full API documentation, see the [Store API - Fetch all cookie groups](https://shopware.stoplight.io/docs/store-api/f9c70be044a15-fetch-all-cookie-groups) reference.

## Server-Side Consent Logging

::: info
Server-side consent logging is available starting with Shopware 6.7.17.0.
:::

The cookie banner can record every consent decision on the server. Logging is off by default. When it is on, the storefront sends each decision to the server in the background. The server stores the decision together with a snapshot of the banner that the visitor saw.

A record contains a random consent ID, but no IP address, session or customer. Shops can store the log in the database, on the filesystem or in a custom storage.

<PageRef page="../../../guides/hosting/configurations/shopware/cookie-consent-logging" />

Check warning on line 205 in concepts/commerce/content/cookie-consent-management.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] concepts/commerce/content/cookie-consent-management.md#L205

Unpaired symbol: ‘"’ seems to be missing (EN_UNPAIRED_QUOTES) URL: https://languagetool.org/insights/post/punctuation-guide/#what-are-parentheses Rule: https://community.languagetool.org/rule/show/EN_UNPAIRED_QUOTES?lang=en-US Category: PUNCTUATION
Raw output
concepts/commerce/content/cookie-consent-management.md:205:85: Unpaired symbol: ‘"’ seems to be missing (EN_UNPAIRED_QUOTES)
 URL: https://languagetool.org/insights/post/punctuation-guide/#what-are-parentheses 
 Rule: https://community.languagetool.org/rule/show/EN_UNPAIRED_QUOTES?lang=en-US
 Category: PUNCTUATION

## Extension Points

The cookie consent system can be extended in multiple ways:
Expand Down Expand Up @@ -269,6 +284,7 @@
- ✅ **Easy withdrawal** - Users can change preferences at any time
- ✅ **Configuration tracking** - Hash mechanism ensures change detection
- ✅ **Documented lifecycle** - Full audit trail of cookie changes
- ✅ **Consent log** - Optional server-side record of every consent decision

::: info
These features provide technical support for GDPR compliance, but shop owners must also ensure they have proper legal documentation (privacy policy, terms of service), data processing agreements, and regular compliance audits. Consult with legal counsel to ensure full compliance with GDPR and other applicable regulations.
Expand Down
199 changes: 199 additions & 0 deletions guides/hosting/configurations/shopware/cookie-consent-logging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
---
nav:
title: Cookie consent logging
position: 60

---

# Cookie Consent Logging

::: info
This feature is available since Shopware 6.7.17.0.
:::

## Overview

The built-in Shopware cookie banner can record every consent decision on the server, together with the banner that the visitor saw. Logging is off by default.

::: warning
Shop owners are responsible for using the consent log in line with the GDPR and other applicable data protection regulations.
:::

For the cookie banner itself, see [Cookie Consent Management](../../../../concepts/commerce/content/cookie-consent-management.md).

## What is recorded

The storefront sends every decision to the server in the background. This includes the cookie banner (accept all, only required, own selection) and the consent dialog of single features, such as the wishlist. The banner does not wait for this request.

Check warning on line 26 in guides/hosting/configurations/shopware/cookie-consent-logging.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/hosting/configurations/shopware/cookie-consent-logging.md#L26

Did you mean “except”? (ACCEPT_EXCEPT[2]) Suggestions: `except` URL: https://languagetool.org/insights/post/spelling-except-accept/ Rule: https://community.languagetool.org/rule/show/ACCEPT_EXCEPT?lang=en-US&subId=2 Category: CONFUSED_WORDS
Raw output
guides/hosting/configurations/shopware/cookie-consent-logging.md:26:102: Did you mean “except”? (ACCEPT_EXCEPT[2])
 Suggestions: `except`
 URL: https://languagetool.org/insights/post/spelling-except-accept/ 
 Rule: https://community.languagetool.org/rule/show/ACCEPT_EXCEPT?lang=en-US&subId=2
 Category: CONFUSED_WORDS

The browser only reports raw facts: a random consent ID, the action, and the names of the cookies that the visitor accepted. The server derives everything else from its own cookie configuration.

| Field | Content |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `consentId` | Random token that the browser creates on the first decision and keeps in the `cookie-consent-id` cookie |
| `consentAction` | `accept_all`, `accept_required` or `accept_selected` |
| `groupDecisions` | Decision per cookie group, keyed by the technical name of the group: `accepted`, `partial` or `rejected` |
| `acceptedCookies` | Names of the accepted cookies that need consent. Names that the cookie configuration does not know are ignored |
| `configHash` | Hash of the banner configuration that the visitor saw |
| `salesChannelId` | Sales channel of the decision |
| `languageId` | Language of the banner that the visitor saw |
| `createdAt` | Time of the decision |

Technically required groups are always `accepted`. Any other group is `accepted` only when the visitor accepted every cookie that they could select in it. A group without selectable cookies is `rejected`.

### Banner snapshots

Shopware stores the banner configuration once per configuration hash, as a snapshot. The snapshot contains the cookie groups with their texts, as the visitor saw them. The texts are part of the hash, so every language has its own snapshot. Every decision refers to its snapshot through `configHash`.

Snapshots contain only the banner configuration, no visitor data. The cleanup task does not delete them.

### Withdrawals

A visitor can open the banner again and accept less. This writes a second record with the same consent ID and the narrower decision. All records of one consent ID show the history of the decisions of one browser.

## Enable logging

Select a storage in your `shopware.yaml`, for example in `config/packages/z-shopware.yaml`:

```yaml
shopware:
cookie_consent:
# none (default), database, filesystem or the name of a custom storage
log_storage: database
# Days a recorded decision is kept, 120 by default
retention_days: 365
```

Clear the cache after the change.

While logging is on:

- The storefront sets the cookie `cookie-consent-id`. The banner lists it as a technically required cookie. Its lifetime is `retention_days`, and every decision renews it.
- The banner configuration changes when you turn logging on or off. Visitors see the banner once more, so their decision gets logged.

While logging is off, the storefront sends no request and sets no `cookie-consent-id` cookie. `/store-api/cookie-groups` does not list this cookie then, so headless frontends can skip the request too.

## Storages

Select the storage that fits the traffic of your shop. The setting applies to the whole installation, not per sales channel.

### Database

The `database` storage writes to the tables `cookie_consent_log` and `cookie_consent_config_snapshot`. These tables exist in every shop and stay empty until you select this storage.

Sales channel and language are stored as IDs, without foreign keys. The records stay when you delete a sales channel or a language.

### Filesystem

The `filesystem` storage writes one JSON file per decision to the private filesystem (`shopware.filesystem.private`), below `cookie-consent/`. The directories group the decisions by year, month, day and hour (UTC). The consent ID is part of the file name, so a file search finds all decisions of one visitor. The banner snapshots are in `cookie-consent/snapshots/`.

Point the private filesystem to object storage, such as Amazon S3, to keep a high-volume log out of the shop database.

### Custom storage

A plugin can add its own storage. Implement `Shopware\Core\Content\Cookie\ConsentLog\CookieConsentLogStorageInterface`:

```php

Check warning on line 95 in guides/hosting/configurations/shopware/cookie-consent-logging.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/hosting/configurations/shopware/cookie-consent-logging.md#L95

File types are normally capitalized. (FILE_EXTENSIONS_CASE[1]) Suggestions: `PHP` URL: https://languagetool.org/insights/post/spelling-capital-letters/ Rule: https://community.languagetool.org/rule/show/FILE_EXTENSIONS_CASE?lang=en-US&subId=1 Category: CASING
Raw output
guides/hosting/configurations/shopware/cookie-consent-logging.md:95:3: File types are normally capitalized. (FILE_EXTENSIONS_CASE[1])
 Suggestions: `PHP`
 URL: https://languagetool.org/insights/post/spelling-capital-letters/ 
 Rule: https://community.languagetool.org/rule/show/FILE_EXTENSIONS_CASE?lang=en-US&subId=1
 Category: CASING
<?php declare(strict_types=1);

namespace Swag\BasicExample\ConsentLog;

use Shopware\Core\Content\Cookie\ConsentLog\CookieConsentConfigSnapshot;
use Shopware\Core\Content\Cookie\ConsentLog\CookieConsentLogStorageInterface;
use Shopware\Core\Content\Cookie\ConsentLog\CookieConsentRecord;

class ExampleConsentLogStorage implements CookieConsentLogStorageInterface
{
public function log(CookieConsentRecord $record): void
{
// Store one decision
}

public function snapshot(CookieConsentConfigSnapshot $snapshot): void
{
// Store the banner configuration. A known hash keeps its first snapshot.
}

public function cleanup(\DateTimeInterface $before): void
{
// Delete the decisions recorded before $before
}

public function iterate(\DateTimeInterface $from, \DateTimeInterface $to, ?string $salesChannelId = null): iterable
{
// Return the decisions from $from (inclusive) to $to (exclusive), oldest first

Check warning on line 123 in guides/hosting/configurations/shopware/cookie-consent-logging.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/hosting/configurations/shopware/cookie-consent-logging.md#L123

A determiner may be missing. (THE_SUPERLATIVE[2]) Suggestions: `the oldest` URL: https://languagetool.org/insights/post/grammar-comparatives-superlatives/#superlative-forms-of-adjectives Rule: https://community.languagetool.org/rule/show/THE_SUPERLATIVE?lang=en-US&subId=2 Category: GRAMMAR
Raw output
guides/hosting/configurations/shopware/cookie-consent-logging.md:123:75: A determiner may be missing. (THE_SUPERLATIVE[2])
 Suggestions: `the oldest`
 URL: https://languagetool.org/insights/post/grammar-comparatives-superlatives/#superlative-forms-of-adjectives 
 Rule: https://community.languagetool.org/rule/show/THE_SUPERLATIVE?lang=en-US&subId=2
 Category: GRAMMAR
return [];
}
}
```

Tag the service with `shopware.cookie_consent.log_storage` and give it a `storage` name:

```xml

Check warning on line 131 in guides/hosting/configurations/shopware/cookie-consent-logging.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/hosting/configurations/shopware/cookie-consent-logging.md#L131

File types are normally capitalized. (FILE_EXTENSIONS_CASE[1]) Suggestions: `XML` URL: https://languagetool.org/insights/post/spelling-capital-letters/ Rule: https://community.languagetool.org/rule/show/FILE_EXTENSIONS_CASE?lang=en-US&subId=1 Category: CASING
Raw output
guides/hosting/configurations/shopware/cookie-consent-logging.md:131:3: File types are normally capitalized. (FILE_EXTENSIONS_CASE[1])
 Suggestions: `XML`
 URL: https://languagetool.org/insights/post/spelling-capital-letters/ 
 Rule: https://community.languagetool.org/rule/show/FILE_EXTENSIONS_CASE?lang=en-US&subId=1
 Category: CASING
<service id="Swag\BasicExample\ConsentLog\ExampleConsentLogStorage">
<tag name="shopware.cookie_consent.log_storage" storage="example"/>
</service>
```

Then select it by this name:

```yaml
shopware:
cookie_consent:
log_storage: example
```

## Retention and cleanup

The daily scheduled task `cookie_consent_log.cleanup` deletes the decisions that are older than `retention_days`. The `filesystem` storage deletes whole hour directories, so it is accurate to one hour.

The task cleans every storage, not only the one that is selected. So the retention still applies to the records in a storage that you switched away from, including after you set `log_storage` back to `none`.

The retention period is a decision of the shop owner.

::: warning
When you change the storage, the existing records are not moved to the new storage. Each storage keeps its own records, and the cleanup task deletes them after the retention period.
:::

## Rate limiting

The consent log routes are rate limited per client IP address. The limiter `cookie_consent_log` allows 60 requests per 60 seconds, as a sliding window. The IP address is only the key of the limiter. Shopware does not store it with the decision.

Behind a CDN, a load balancer or a reverse proxy, configure the [trusted proxies](../../infrastructure/reverse-http-cache.md#trusted-proxies). Without it, all visitors get the IP address of the proxy and share one limit. Decisions over the limit are lost without a notice.

To change the limit, override the limiter in your `shopware.yaml`:

```yaml
shopware:
api:
rate_limiter:
cookie_consent_log:
enabled: true
policy: 'sliding_window'
limit: 120
interval: '60 seconds'
```

## Export

Export the log with this command. It writes JSON or CSV to standard output:

```bash
bin/console cookie:consent:export --from=2026-01-01 --to=2026-07-01 --format=csv > consents.csv
```

| Option | Description |
| -------------------- | -------------------------------------------------------------------------- |
| `--from` | Decisions recorded at or after this date and time, `1970-01-01` by default |
| `--to` | Decisions recorded before this date and time, now by default |
| `--sales-channel-id` | Only decisions of this sales channel |
| `--format` | `json` (default) or `csv` |

The command writes errors to standard error, so they do not end up in the exported file.

The export contains the decisions. To show the banner that a visitor saw, find the snapshot with the `configHash` of the decision: in the table `cookie_consent_config_snapshot`, or in `cookie-consent/snapshots/<configHash>.json`.

## Extension events

The Store API route publishes the extension `cookie-consent-log-route.log` with the payload, the request and the sales channel context. Plugins can react to it before and after a decision is logged.

<PageRef page="../../../plugins/plugins/framework/extension/" />

Check warning on line 199 in guides/hosting/configurations/shopware/cookie-consent-logging.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/hosting/configurations/shopware/cookie-consent-logging.md#L199

Unpaired symbol: ‘"’ seems to be missing (EN_UNPAIRED_QUOTES) URL: https://languagetool.org/insights/post/punctuation-guide/#what-are-parentheses Rule: https://community.languagetool.org/rule/show/EN_UNPAIRED_QUOTES?lang=en-US Category: PUNCTUATION
Raw output
guides/hosting/configurations/shopware/cookie-consent-logging.md:199:14: Unpaired symbol: ‘"’ seems to be missing (EN_UNPAIRED_QUOTES)
 URL: https://languagetool.org/insights/post/punctuation-guide/#what-are-parentheses 
 Rule: https://community.languagetool.org/rule/show/EN_UNPAIRED_QUOTES?lang=en-US
 Category: PUNCTUATION

Check warning on line 199 in guides/hosting/configurations/shopware/cookie-consent-logging.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/hosting/configurations/shopware/cookie-consent-logging.md#L199

Unpaired symbol: ‘"’ seems to be missing (EN_UNPAIRED_QUOTES) URL: https://languagetool.org/insights/post/punctuation-guide/#what-are-parentheses Rule: https://community.languagetool.org/rule/show/EN_UNPAIRED_QUOTES?lang=en-US Category: PUNCTUATION
Raw output
guides/hosting/configurations/shopware/cookie-consent-logging.md:199:60: Unpaired symbol: ‘"’ seems to be missing (EN_UNPAIRED_QUOTES)
 URL: https://languagetool.org/insights/post/punctuation-guide/#what-are-parentheses 
 Rule: https://community.languagetool.org/rule/show/EN_UNPAIRED_QUOTES?lang=en-US
 Category: PUNCTUATION
Loading