---
title: Initialization Flow
description: What happens from provider mount to first render — the full consent lifecycle.
---
When the consent provider mounts, it creates a cached consent runtime, reads any stored consent from the browser, fetches the resolved policy from the backend (or uses SSR/offline data), and decides whether to show the banner. This entire sequence completes before the first meaningful consent-aware render.

## Lifecycle Sequence

**Simplified**

1. **Provider mounts** — creates (or retrieves from cache) a consent runtime and store
2. **Check stored consent** — reads existing consent from cookies / localStorage; if found and the policy fingerprint hasn't changed, the banner stays hidden
3. **Fetch init data** — calls the backend `GET /init` (or uses SSR/offline data) for the resolved policy, location, and translations
4. **Apply resolved policy** — the backend resolves the policy from your [policy pack](/docs/frameworks/react/concepts/policy-packs) based on visitor geo (region → country → fallback → default). The response includes the consent model, categories, UI mode, and a material fingerprint. If no policy pack is configured, the legacy jurisdiction-to-model mapping is used instead.
5. **Decide banner visibility** — shows the banner only if no prior consent exists, the resolved policy requires it (`ui.mode` is `banner` or `dialog`), or the policy fingerprint changed since last consent
6. **Gating enforced** — scripts, iframes, and network requests tagged with a consent category are blocked until that category is granted
7. **User interacts** — choices are persisted to storage, synced to the backend (with `policySnapshotToken` if configured), and blocked scripts/iframes load immediately after consent is granted

**Sequence Diagram**

```mermaid
sequenceDiagram
    participant Provider as ConsentManagerProvider
    participant Store as Consent Store
    participant Storage as localStorage / Cookie
    participant API as c15t Backend
    participant UI as Banner / Dialog
    participant Scripts as Script Loader

    Provider->>Store: getOrCreateConsentRuntime()
    Store->>Storage: getStoredConsent()
    alt Stored consent exists
        Storage-->>Store: consentInfo + consents
        Store->>Store: activeUI = 'none'
    else No stored consent
        Store->>Store: isLoadingConsentInfo = true
    end

    Store->>Store: initConsentManager()
    Store->>Storage: Check pending consent sync
    opt Pending sync from revocation reload
        Store->>API: Deferred setConsent() (non-blocking)
    end

    alt SSR data provided
        Store->>Store: tryUseSSRData()
    else No SSR data
        Store->>API: GET /init
        API-->>Store: policy, policyDecision, location, translations
    end

    Store->>Store: Apply resolved policy (model, categories, ui.mode)
    Store->>Store: Check fingerprint change → re-prompt if needed
    Store->>Store: Set activeUI, auto-grant if opt-out/none

    alt User has no prior consent or policy changed
        UI->>UI: Banner / Dialog appears (per policy ui.mode)
        UI->>Store: saveConsents({ type })
        Store->>Storage: Persist consent + subjectId + fingerprint
        Store->>Scripts: updateScripts(), updateIframes(), updateNetwork()
        Store->>API: POST /subjects + policySnapshotToken (non-blocking)
    end
```

## How It Works

**Mount** — When the provider renders, it creates (or retrieves from cache) a consent runtime and store. Any existing consent is read from localStorage/cookies immediately. If consent already exists and the policy fingerprint matches, the banner stays hidden and gating rules apply right away. See [Client Modes](/docs/frameworks/react/concepts/client-modes) for how the mode affects runtime creation.

**Init** — The store fetches the resolved policy, location, and translation data. In hosted mode this calls `GET /init` on your backend; in offline mode it resolves from `offlinePolicy.policyPacks` locally. If SSR data was passed to the provider, the network fetch is skipped entirely. See [Server-Side Utilities](/docs/frameworks/react/server-side) for SSR setup.

**Policy resolution** — When [policy packs](/docs/frameworks/react/concepts/policy-packs) are configured, the backend resolves the right policy for the visitor based on their geo-location (region → country → fallback → default). The resolved policy determines the consent model (`opt-in`, `opt-out`, `iab`, or `none`), which categories are in scope, and what UI to show. For `opt-out` and `none` models, all categories are auto-granted — unless the resolved policy has `consent.gpc: true` and the browser sends a Global Privacy Control signal, in which case `marketing` and `measurement` are denied. If no policy pack is configured, the legacy jurisdiction-to-model mapping is used instead. See [Consent Models](/docs/frameworks/react/concepts/consent-models) for details.

**Re-prompting** — If the resolved policy's material fingerprint differs from the fingerprint stored with the user's last consent, the banner is shown again. This happens automatically when you change consent-affecting fields (model, categories, scope mode, allowed actions). Presentation-only changes do not trigger re-prompts. See [Policy Packs — Re-Prompting](/docs/frameworks/react/concepts/policy-packs#re-prompting) for details.

**Save** — When the user interacts with the banner or dialog, their choices are persisted to localStorage/cookies and synced to the backend (along with the `policySnapshotToken` if snapshot signing is configured). Script, iframe, and network gating rules update immediately based on the new consent state. See the [Script Loader](/docs/frameworks/react/script-loader), [Iframe Blocking](/docs/frameworks/react/iframe-blocking), and [Network Blocker](/docs/frameworks/react/network-blocker) guides for gating details.

**Revocation** — If a user revokes a previously granted category, the page reloads by default to ensure a clean execution environment. The API sync is deferred to the fresh page load. See [Cookie Management](/docs/frameworks/react/concepts/cookie-management) for revocation and persistence details.

## When Does the Banner Show?

The banner appears when any of these conditions are true:

1. **No existing consent** — the user has never consented (or their consent was cleared), **and** the resolved policy requires a UI (`ui.mode` is `banner` or `dialog`, or the model is `opt-in` or `iab`)
2. **Policy changed** — the material policy fingerprint differs from the fingerprint stored with the user's last consent (re-prompting)
3. **Storage is accessible** — the browser allows localStorage (not blocked in private mode)

If the resolved model is `none` or `opt-out` (and `ui.mode` is `none`), consents are auto-granted and the banner never appears. See [Consent Models](/docs/frameworks/react/concepts/consent-models) and [Policy Packs](/docs/frameworks/react/concepts/policy-packs) for details.

## Debugging the Lifecycle

Use the DevTools panel and callbacks to inspect each step of the initialization flow. `onConsentSet` is the broad lifecycle signal; `onConsentChanged` and `subscribeToConsentChanges()` are the change-only signals for explicit post-init saves.

| Step                     | DevTools Panel    | Callback                                            | What to check                                                                                          |
| ------------------------ | ----------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| Init / SSR hydration     | Location          | `onBannerFetched`                                   | jurisdiction, countryCode, regionCode populated?                                                       |
| Policy resolution        | Policy            | `onBannerFetched`                                   | `policyId`, `matchedBy`, `fingerprint` in policyDecision                                               |
| Model resolution         | Location          | `onBannerFetched`                                   | `model` value matches the resolved policy                                                              |
| Banner visibility        | Consents          | —                                                   | `activeUI` in store state; does policy `ui.mode` require it?                                           |
| Re-prompting             | Policy            | —                                                   | Fingerprint mismatch between stored and resolved policy?                                               |
| Consent save             | Consents + Events | `onConsentSet`                                      | `preferences` object in callback payload                                                               |
| Change-only integrations | Events            | `onConsentChanged` or `subscribeToConsentChanges()` | `allowedCategories`, `deniedCategories`, and previous values only when a real save changed preferences |
| Script loading           | Scripts           | `onConsentSet`                                      | Script IDs and their load/blocked status                                                               |
| Reload on revocation     | Events            | `onBeforeConsentRevocationReload`                   | Fires before reload; check localStorage for `c15t:pending-consent-sync`                                |
| Deferred sync            | Events            | `onError` (if sync fails)                           | After reload, check Events panel for successful API call                                               |

Configure callbacks in the provider to log lifecycle events, and add DevTools for a visual inspector:

```tsx
import { type ReactNode } from 'react';
import { ConsentManagerProvider, ConsentBanner, ConsentDialog } from '@c15t/nextjs';
import { DevTools } from '@c15t/dev-tools/react';

export default function ConsentManager({ children }: { children: ReactNode }) {
  return (
    <ConsentManagerProvider
      options={{
        mode: 'hosted',
        backendURL: '/api/c15t',
        callbacks: {
          onBannerFetched: ({ jurisdiction, location }) => {
            console.log('Init complete:', { jurisdiction, location });
          },
          onConsentSet: ({ preferences }) => {
            console.log('Broad consent lifecycle event:', preferences);
          },
          onConsentChanged: ({ allowedCategories, deniedCategories }) => {
            console.log('Explicit consent change:', {
              allowedCategories,
              deniedCategories,
            });
          },
          onBeforeConsentRevocationReload: ({ preferences }) => {
            console.log('Reloading due to revocation:', preferences);
          },
          onError: ({ error }) => {
            console.error('Consent error:', error);
          },
        },
      }}
    >
      <ConsentBanner />
      <ConsentDialog />
      {process.env.NODE_ENV !== 'production' && <DevTools />}
      {children}
    </ConsentManagerProvider>
  );
}
```
