> Document Version: 1.0 Document Owner: Acme Inc Next Review Date: 2027-03-19

Last updated: 2026-03-19

Project: nextjs-saas-example

Company: Acme Inc

## Related Documents

- Privacy Policy (`PRIVACY_POLICY.md`)
- Cookie Policy (`COOKIE_POLICY.md`)
- Lawful Basis Assessment (`LAWFULBASISASSESSMENT.md`)

> Purpose: This guide provides actionable implementation steps for managing user consent across all detected services in your project. It covers GDPR, ePrivacy Directive, and CCPA requirements.

## 1. Legal Basis Classification

Based on the services detected in your codebase, here is how each service maps to a legal basis under GDPR Article 6:

### Consent Required (Article 6(1)(a))

These services collect non-essential data and require explicit opt-in consent before activation:

Service | Category | Data Collected | Action Required
--- | --- | --- | ---
openai | ai | user prompts, conversation history, generated content | Must not load until user consents
posthog | analytics | user behavior, session recordings, feature flag usage | Must not load until user consents

### Legitimate Interest (Article 6(1)(f))

These services may operate under legitimate interest with appropriate documentation and user objection mechanism:

Service | Category | Data Collected | Notes
--- | --- | --- | ---
@sentry/node | monitoring | error data, stack traces, user context | Document in LIA; provide opt-out
resend | email | email addresses, email content | Document in LIA; provide opt-out

### Contractual Necessity (Article 6(1)(b))

These services are required to fulfill contractual obligations and do not require separate consent:

Service | Category | Data Collected | Notes
--- | --- | --- | ---
@supabase/supabase-js | auth | email, password hash, session data | Essential for service delivery
prisma | database | user data as defined in schema | Essential for service delivery
stripe | payment | payment information, billing address, email | Essential for service delivery
stripe-ios | payment | payment information, billing address, email | Essential for service delivery

## 2. Cookie Consent Banner Requirements

Under the ePrivacy Directive and GDPR, your cookie banner must:

- Block non-essential cookies before consent is given (no pre-ticked boxes)
- Provide granular choices (analytics, marketing, functional — not just "accept all")
- Include a reject-all option equally prominent as "accept all"
- Not use dark patterns (e.g., hiding the reject option, making it harder to decline)
- Record proof of consent (timestamp, version, choices made)
- Allow withdrawal at any time, as easily as giving consent
- Re-prompt when consent purposes change or after 12 months

### Consent Categories for Your Project

Based on detected services, configure your consent banner with these categories:

Category | Services | Default State
--- | --- | ---
Strictly Necessary | @supabase/supabase-js, prisma, stripe, stripe-ios | Always active (no toggle)
Analytics | posthog | Off until consented
AI Services | openai | Off until consented

### Example: Minimal Cookie Consent Banner

    <!-- Cookie Consent Banner -->
    <div id="cookie-consent" style="
      position: fixed; bottom: 0; left: 0; right: 0;
      background: #1a1a2e; color: #fff; padding: 1rem;
      display: flex; justify-content: space-between; align-items: center;
      z-index: 9999; font-family: system-ui, sans-serif;
    ">
      <div>
        <p style="margin: 0 0 0.5rem 0; font-weight: 600;">We value your privacy</p>
        <p style="margin: 0; font-size: 0.875rem; opacity: 0.9;">
          We use cookies to improve your experience. You can customize your preferences.
        </p>
      </div>
      <div style="display: flex; gap: 0.5rem; flex-shrink: 0;">
        <button onclick="manageConsent()" style="
          background: transparent; color: #fff; border: 1px solid #fff;
          padding: 0.5rem 1rem; cursor: pointer; border-radius: 4px;
        ">Customize</button>
        <button onclick="rejectAll()" style="
          background: transparent; color: #fff; border: 1px solid #fff;
          padding: 0.5rem 1rem; cursor: pointer; border-radius: 4px;
        ">Reject All</button>
        <button onclick="acceptAll()" style="
          background: #4CAF50; color: #fff; border: none;
          padding: 0.5rem 1rem; cursor: pointer; border-radius: 4px;
        ">Accept All</button>
      </div>
    </div>

    <script>
    function getConsentState() {
      try {
        return JSON.parse(localStorage.getItem('cookie_consent') || 'null');
      } catch { return null; }
    }

    function saveConsent(choices) {
      const record = {
        choices,
        timestamp: new Date().toISOString(),
        version: '1.0',
      };
      localStorage.setItem('cookie_consent', JSON.stringify(record));
      document.getElementById('cookie-consent').style.display = 'none';
      applyConsent(choices);
    }

    function acceptAll() {
      saveConsent({ analytics: true, marketing: true, functional: true });
    }

    function rejectAll() {
      saveConsent({ analytics: false, marketing: false, functional: false });
    }

    function manageConsent() {
      // Open a modal with granular toggles per category
      // Implementation depends on your UI framework
    }

    function applyConsent(choices) {
      if (choices.analytics) {
        // Load analytics scripts here
      }
      if (choices.marketing) {
        // Load marketing scripts here
      }
    }

    // On page load: check existing consent
    (function() {
      const existing = getConsentState();
      if (existing) {
        document.getElementById('cookie-consent').style.display = 'none';
        applyConsent(existing.choices);
      }
    })();
    </script>

## 3. Global Privacy Control (GPC) Signal

The Global Privacy Control is a browser-level signal that indicates a user's privacy preferences. Under CCPA/CPRA, honoring GPC is legally required for California residents. Under GDPR, it serves as a valid objection signal.

### Implementation

    // Check for GPC signal on page load
    function checkGPC() {
      if (navigator.globalPrivacyControl) {
        // User has opted out via GPC
        // Treat as: reject analytics + advertising cookies
        saveConsent({ analytics: false, marketing: false, functional: true });
        console.log('[Consent] GPC signal detected — non-essential tracking disabled');
        return true;
      }
      return false;
    }

    // Call before showing consent banner
    if (!checkGPC()) {
      // Show consent banner if no GPC and no prior consent
      const existing = getConsentState();
      if (!existing) {
        document.getElementById('cookie-consent').style.display = 'flex';
      }
    }

## 4. Service-Specific Consent Patterns

### PostHog — Opt-In / Opt-Out

    import posthog from 'posthog-js';

    // Initialize with opt-out by default (GDPR-compliant)
    posthog.init('YOUR_PROJECT_KEY', {
      api_host: 'https://app.posthog.com',
      opt_out_capturing_by_default: true,  // No tracking until consent
      persistence: 'localStorage',
    });

    // When user gives consent:
    function onAnalyticsConsent() {
      posthog.opt_in_capturing();
    }

    // When user withdraws consent:
    function onAnalyticsRevoke() {
      posthog.opt_out_capturing();
      posthog.reset();  // Clear any stored user data
    }

## 5. Consent Storage Recommendations

### Where to Store Consent Records

Method | Pros | Cons | Recommended
--- | --- | --- | ---
localStorage | Simple, client-side only | Lost on clear, not shared across subdomains | Development / simple sites
First-party cookie | Shared across subdomains, server-readable | 4KB limit, sent with every request | Production sites
Server-side database | Auditable, tamper-proof, survives device changes | Requires API endpoint | Enterprise / regulated

### Consent Record Schema

Store the following data for each consent record:

    {
      "userId": "anonymous-uuid-or-user-id",
      "timestamp": "2026-03-15T10:30:00.000Z",
      "version": "1.0",
      "choices": {
        "analytics": true,
        "marketing": false,
        "functional": true,
        "ai": false
      },
      "gpcDetected": false,
      "method": "banner-click",
      "ipCountry": "DE"
    }

### Retention

- Keep consent records for the duration of consent + 3 years (to demonstrate compliance under GDPR Article 7(1))
- Update records when the user changes preferences
- Re-collect consent when purposes change or annually

## 6. Consent Withdrawal Process

Under GDPR Article 7(3), withdrawing consent must be as easy as giving it.

### Required Steps

1. Provide a persistent link (footer, settings page, or floating widget) to manage cookie preferences
2. When consent is withdrawn:

- Stop all non-essential tracking immediately
- Clear any locally stored tracking identifiers
- Send a server-side signal to stop processing (if applicable)
- Update the consent record with the withdrawal timestamp

### Implementation

    function withdrawConsent(category) {
      const consent = getConsentState();
      if (!consent) return;

      consent.choices[category] = false;
      consent.timestamp = new Date().toISOString();
      localStorage.setItem('cookie_consent', JSON.stringify(consent));

      // Category-specific cleanup
      switch (category) {
        case 'analytics':
          // Clear analytics cookies and local storage
          document.cookie.split(';').forEach(cookie => {
            const name = cookie.split('=')[0].trim();
            if (name.startsWith('_ga') || name.startsWith('ph_') || name.startsWith('mp_')) {
              document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/';
            }
          });
          // PostHog cleanup
          if (window.posthog) {
            window.posthog.opt_out_capturing();
            window.posthog.reset();
          }
          break;
        case 'marketing':
          // Clear advertising cookies
          document.cookie.split(';').forEach(cookie => {
            const name = cookie.split('=')[0].trim();
            if (name.startsWith('_fb') || name.startsWith('ads_')) {
              document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/';
            }
          });
          break;
      }
    }

## 7. Technical Implementation Checklist

### General

- Cookie consent banner loads before any non-essential scripts
- Non-essential scripts are blocked until consent is given
- GPC signal (navigator.globalPrivacyControl) is checked on load
- Consent record is stored with timestamp, version, and choices
- Consent withdrawal is available from every page (footer link or widget)
- Consent is re-collected when purposes change
- Consent banner is accessible (keyboard navigable, screen reader compatible)
- "Reject All" button is equally prominent as "Accept All"
- No pre-ticked checkboxes for non-essential categories

### Per-Service Checklist

#### openai (ai)

- Script/SDK does not load before consent
- Consent category mapped: AI Services
- Opt-out mechanism implemented
- Data cleared on consent withdrawal
- Third-party cookies documented in cookie policy

#### posthog (analytics)

- Script/SDK does not load before consent
- Consent category mapped: Analytics
- Opt-out mechanism implemented
- Data cleared on consent withdrawal
- Third-party cookies documented in cookie policy

#### @sentry/node (monitoring) — Legitimate Interest

- Legitimate Interest Assessment (LIA) documented
- User objection mechanism provided
- Data minimization applied
- Processing documented in privacy policy

#### resend (email) — Legitimate Interest

- Legitimate Interest Assessment (LIA) documented
- User objection mechanism provided
- Data minimization applied
- Processing documented in privacy policy

## 8. Recommended Consent Management Platforms

If you prefer a managed solution over a custom implementation:

Platform | Open Source | GDPR | CCPA | GPC Support | Notes
--- | --- | --- | --- | --- | ---
Cookiebot | No | Yes | Yes | Yes | Auto-scans cookies
Osano | No | Yes | Yes | Yes | Consent + data mapping
Klaro | Yes | Yes | Yes | Partial | Self-hosted, lightweight
CookieConsent | Yes | Yes | Yes | Manual | Popular open-source option
Consent Manager | No | Yes | Yes | Yes | IAB TCF 2.2 support

## 9. Contact

For questions about consent management or data processing:

- Email: legal@acme.com

This guide was auto-generated by Codepliant based on code analysis of nextjs-saas-example. It provides implementation guidance only and does not constitute legal advice. Consult with a qualified privacy professional for your specific compliance requirements.