# Consent

Source: https://docs.mirafive.io/guides/consent

> Choose between consentless and full mode, pass consent answers from your banner, and honour Do Not Track, GPC and your own visits.

MIRA FIVE collects in one of two modes. **Consentless mode**, the default in the browser, sends no identifiers, stores nothing on the device and needs no consent banner. **Full mode** adds ids and device details, and waits for the visitor's consent. Pick the mode per page with the SDK, and wire your consent manager only if you use full mode.

## Consentless mode

The script tag and the browser SDKs start in consentless mode. Each batch carries:

- the batch id, the send time and the SDK name and version (`context.sdk`);
- per event: the name, the time, the page (the [cleaned URL](https://docs.mirafive.io/guides/track-events#page-urls), title and referrer) and your properties.

It never carries an anonymous id, a session id, a user id, the browser's language, time zone or screen size. The SDK does not read those values at all, sets no cookies and writes nothing to `localStorage` or any other storage. The server refuses a consentless batch that carries ids or device details (`400 validation_failed`), so a misconfigured client shows up at once.

What you give up:

- `identify()`, site search and experiment exposures: `$identify`, `$search` and `$exposure` are dropped from consentless batches.
- Per-visitor flags. A consentless page receives only the answer each flag gives everyone, so percentage rollouts, targeting rules and experiments do not run. See [Feature flags](https://docs.mirafive.io/guides/feature-flags#consent-and-flags).

Nothing to set up: this is the default. On the script tag, `consent`, `identify` and `reset` do nothing in this mode, silently, and `anonymousId` answers `undefined`, so consent wiring can stay in place when you switch modes.

## Full mode

Full mode is for visitors who consented. Turn it on with `data-mode="full"` on the script tag, or `mode: 'full'` and the `identity()` plugin in the browser SDK:

```ts
import { createMira } from '@mirafive/sdk-browser'
import { identity } from '@mirafive/sdk-browser/identity'
import { pageviews } from '@mirafive/sdk-browser/pageviews'

export const mira = createMira({
  key: import.meta.env.VITE_MIRAFIVE_KEY,
  mode: 'full',
  plugins: [pageviews(), identity()],
})
```

Until the visitor answers, full mode sends nothing and stores nothing. It remembers the landing pageview and sends it when statistics consent is first granted, so a visitor who accepts on the first page still counts that page. A visitor who declines, or never answers, is not counted.

With statistics consent, each batch carries everything consentless mode sends, plus:

| Field | Value |
| --- | --- |
| `anonymousId` | A UUID for the browser, stored for 365 days since last seen |
| `sessionId` | A UUID for the visit, stored until 30 minutes without an event |
| `userId` | Your id, after [`identify()`](https://docs.mirafive.io/guides/identify-users) |
| `context.locale` | The browser language, for example `de-DE` |
| `context.timezone` | The IANA time zone, for example `Europe/Berlin` |
| `context.screen` | The screen width and height |
| `$boot: 1` on `$pageview` | The consent answer was known when the page first drew |

Ids are stored in `localStorage` only, never in cookies. [Identify users](https://docs.mirafive.io/guides/identify-users#what-is-stored-where) lists the entries. Full mode also allows site search, experiments and segment targeting.

## Source mode

Each website source in MIRA FIVE also has a mode, and it is a ceiling:

| Source mode | Consentless batches | Full batches | Flags served |
| --- | --- | --- | --- |
| Full | Accepted | Accepted | Per-visitor rules; segment lookups with targeting consent |
| Consentless | Accepted | Refused, `400 collection_mode_not_allowed` | The answer for everyone only; a segment lookup is refused with `403 lookup_not_allowed` |

A full source is the flexible choice: the page decides per visitor. Set the page to full mode only when the source is full too.

## Consent answers

In full mode, pass your consent manager's answer to `consent()`. There are three scopes:

| Scope | Allows |
| --- | --- |
| `statistics` | Sending events with ids and device details, and storing the ids |
| `experiments` | Using the anonymous id for flag rollouts and experiments, and sending `$exposure` events |
| `targeting` | Looking up which of your segments the visitor is in, to target flags |

The answer is one of:

| Answer | Meaning |
| --- | --- |
| `true` | Statistics only |
| `{ statistics, experiments, targeting }` | By scope. A scope you leave out keeps its last answer |
| `false` | Forget the ids and the user, remove the stored entries and clear the queue |

Events need statistics consent: without it, full mode sends nothing, whatever the other scopes say. Exposures need statistics and experiments. Call `consent()` again whenever the visitor changes their choice.

## Wire a consent manager

Map your consent manager's categories to the three scopes. Statistics is usually the "analytics" or "statistics" category, targeting the "marketing" one.

**Script tag**

```html
<script>window.mirafive=window.mirafive||function(){(mirafive.q=mirafive.q||[]).push(arguments)}</script>
<script defer src="https://cdn.mirafive.io/mira.js" data-key="mf_…" data-mode="full"></script>
<script>
  window.addEventListener('CookiebotOnConsentReady', () => {
    const { statistics, preferences, marketing } = Cookiebot.consent
    mirafive('consent', { statistics, experiments: preferences, targeting: marketing })
  })
</script>
```

The identity code downloads on the first grant. A decline downloads it only when the browser still holds ids from an earlier visit, to remove them.

**Browser**

```ts
import { mira } from './mira'

document.querySelector('#accept')?.addEventListener('click', () => {
  mira.consent({ statistics: true, experiments: true, targeting: false })
})

document.querySelector('#decline')?.addEventListener('click', () => {
  mira.consent(false)
})
```

With a consent manager, call `mira.consent()` from its change callback with its categories mapped to the scopes.

**React**

```tsx
import { useMira } from '@mirafive/sdk-react'

export function CookieBanner() {
  const mira = useMira()

  return (
    <div role="dialog">
      <button onClick={() => mira.consent({ statistics: true, experiments: true, targeting: false })}>Accept</button>
      <button onClick={() => mira.consent(false)}>Decline</button>
    </div>
  )
}
```

## Answer before the SDK loads

A consent manager that already knows the stored answer can set it before the SDK starts:

```html
<script>
  window.__mirafive_consent = { statistics: true, experiments: true, targeting: false }
</script>
<script defer src="https://cdn.mirafive.io/mira.js" data-key="mf_…" data-mode="full"></script>
```

`window.__mirafive_consent` is `false` or an object of the three booleans. The SDK applies it at start, so the landing pageview is sent with the answer and marked `$boot: 1`, and the script tag loads its identity code at once. The [page-experiment snippet](https://docs.mirafive.io/guides/experiments#page-experiments) reads it too. On the script tag, a `mirafive('consent', …)` call queued before `mira.js` has run counts the same way.

## Do Not Track and Global Privacy Control

When `navigator.doNotTrack` is `"1"` or `navigator.globalPrivacyControl` is true, the browser SDKs send nothing, in both modes. Flags still answer, but without the anonymous id, without a segment lookup and without exposures, so the visitor sees what someone without an id sees.

On the server side:

- A browser request with `Sec-GPC: 1` or `DNT: 1` turns a full batch into a consentless one: the server keeps the events without their ids and drops `$identify`, `$search` and `$exposure`.
- Server flag reads take the visitor's opt-out as `optedOut: true`. `flagsFor()` from `@mirafive/sdk-next/server` and the Laravel package read both headers for you. See [Feature flags](https://docs.mirafive.io/guides/feature-flags#read-flags-on-a-server).
- For server events about an opted-out visitor, leave the ids out, or send them with a consentless client.

## Ignore your own visits

Set `window.__mirafive_ignore = true` and the page sends nothing from this browser: no events, no flag ids, no exposures, and page experiments show the original. Render it only for your own team, for example when an admin is signed in:

```html
<script>
  window.__mirafive_ignore = true
</script>
```

The SDK checks it on every event, so it also works when set after the SDK has loaded.

## Server events and mode

Server SDKs default to full mode: `userId`, `anonymousId` and `sessionId` are allowed, and you hold the consent or other lawful basis for them. Switch a server client to consentless mode to count without ids, for example invoices or anonymous totals:

**Node.js**

```ts
import { Mira } from '@mirafive/sdk-server'

const mira = new Mira({ key: process.env.MIRAFIVE_SECRET_KEY, mode: 'consentless' })

mira.track('invoice paid', { properties: { revenue: 99, currency: 'EUR' } })
```

Passing an id then throws a `TypeError`.

**PHP**

```php
use MiraFive\Mira;
use MiraFive\Mode;

$mira = new Mira(mode: Mode::Consentless);

$mira->track('invoice paid', properties: ['revenue' => 99, 'currency' => 'EUR']);
```

Passing an id then throws an `InvalidArgumentException`.

**Laravel**

```sh title=".env"
MIRAFIVE_MODE=consentless
MIRAFIVE_SCRIPT_MODE=consentless
```

`MIRAFIVE_MODE` sets server events, `MIRAFIVE_SCRIPT_MODE` the script tag that `@mirafiveScript` prints. They are separate on purpose: a consentless site can still send full server events.

The source's mode is a ceiling here too: a consentless server source refuses full batches. More in [Server-side events](https://docs.mirafive.io/guides/server-side#mode).
