Backend — Admin Dashboard
Consenti ships a full admin SPA (Preact + Tailwind CSS) baked directly into the @consenti/api package. No separate deployment or build step is needed — it is served from dist/dashboard/ inside the package.
Enabling the dashboard
createConsenti({
storage: { driver: 'node:sqlite', path: './consenti-data' },
auth: { mode: 'local', adminEmail: '[email protected]', adminPassword: process.env.CONSENTI_ADMIN_PASSWORD! },
dashboard: true, // ← enables the SPA
})The dashboard is then available at /consenti/ (or your configured basePath).
Dashboard sections
| Section | Description | Access |
|---|---|---|
| Dashboard | Consent overview, timeline chart, country breakdown, GPC stats | All users |
| Profiles | Create / edit / copy / delete / activate / deactivate consent profiles | All users |
| Profile History | Edit history viewer — every save increments the profile's version in place (stable id); compare locale JSON across past versions | All users |
| Consent Templates | Reusable parameter definitions plus the categories that own their legal basis | All users |
| UI Templates | Reusable banner + modal layout settings | All users |
| Consents | Browse, filter, and export consent records; per-visitor history | All users |
| Visitors | Visitor list with geographic data (IPs are SHA-256 hashed, never raw) | All users |
| Users | Admin user management with tenant scoping | All users |
| Roles | RBAC roles and fine-grained permission assignment | All users |
| Sites | Multi-tenant site management | Superadmin only |
| TCF Vendors | IAB Global Vendor List (when tcf.enabled: true) | All users |
| Audit Log | Append-only log of all admin actions, never deleted by Consenti | All users |
| Settings / API | API keys, branding, OpenAPI docs | Superadmin only |
| Setup Wizard | One-time first-run welcome / config / default-profiles / confirmation flow, shown once after install | All users |
tcf.enabled: true.First-run setup wizard
The first time any admin logs in, the dashboard shows a 4-step setup wizard at #/setup instead of the regular dashboard — welcome, a read-only view of the resolved server config (secrets redacted), an accordion to install any of the 8 default compliance profiles, and a confirmation step with a production-readiness checklist (JSON storage driver / default credentials warnings, otherwise only visible in the server console). It is gated by tenant_settings.setup_completed, set once the wizard finishes or is skipped — it never reappears afterward, and there is no way to re-open it from the dashboard.
Auth modes
| Mode | Config | Description |
|---|---|---|
local | auth: { mode: 'local', adminEmail, adminPassword } | Built-in email + password. Passwords hashed with scrypt via node:crypto. Brute-force protection built in. |
jwt | auth: { mode: 'jwt', masterSecret } | Validate externally issued JWTs (HS256). Your auth service issues tokens; Consenti verifies them. |
oidc | auth: { mode: 'oidc', oidc: { issuer, clientId, ... } } | OpenID Connect Authorization Code + PKCE. Supports RS256 / ES256 from any OIDC provider (Auth0, Keycloak, Google). |
saml | auth: { mode: 'saml', saml: { issuer, entryPoint, cert, ... } } | SAML 2.0 SP-initiated SSO. Requires samlify peer dep. |
custom | auth: { mode: 'custom', validateUser: async (req) => AdminUser | null } | Bring your own authentication. Consenti still enforces RBAC on the returned user's roles. |
Profile creation wizard
Creating a profile follows a 4-step wizard:
Step 1 — Profile metadata
| Field | Type | Description |
|---|---|---|
name | string | Human-readable profile name |
defaultLocale | BCP 47 (searchable select) | Locale used when visitor locale is unavailable; written as default.json |
complianceGroup | radio card grid | Regulation group for geo-routing — selects one of the 8 built-in groups |
customComplianceGroup | string (lower-kebab-case) | Required under the "None / Custom" option — the identifier the widget's compliance.type targets to fetch this profile. Drives activation and one-active-per-group conflict detection like complianceGroup; no GPC/compliance-rule defaults apply since none exist for a custom name |
gpcMode | 'ignore' | 'honor' | 'strict' | GPC signal handling. Overrides the compliance group default. |
expiryDays | number | Days until consent expires and the visitor is asked again — profile-wide. Default: 365. |
darkMode | boolean | Enable dark mode in the consent banner |
hidePoweredBy | boolean (default: checked/hidden) | Hide "Powered by Consenti" badge — checked by default, matching the widget's own default for a profile that never sets this field |
allowReceipt | boolean | Allow visitors to download a PDF consent receipt |
allowedOrigins | string[] | Allowlisted domains for CORS on this profile's consent endpoints |
complianceConfig | Record<string, string> | Per-compliance extra config (e.g. DPDPA data fiduciary name). Only shown when the compliance group requires it. |
Step 2 — Consent Template
Select or create a Consent Template — parameters and the categories that own their legal basis, edited together. Clicking Next calls POST /admin/profiles/validatewith the template's cookies, categories, and the chosen compliance group:
- Compliance errors (red): block advancing to Step 3 until resolved
- Compliance warnings (amber): show an acknowledgment checkbox — must be checked to proceed, but do not block saving
The parameter table's Pre Grant column is editable per-profile — toggling it writes a delta into ProfileConfig.cookiesOverriderather than changing the Consent Template itself, so other profiles sharing that template are unaffected. It's locked checked-and-disabled for any parameter whose category's legal basis isn't consent (mandatory/legitimate-interest parameters are already effectively pre-granted, matching the same lock used at template-authoring time).
Selecting a compliance group (or a Consent Template, if the group is already selected) auto-defaults every eligible parameter's pre-grant:
opt-out/opt-out-strict: force off — an amber banner explains pre-grant was overridden for compliance- every other group: force onfor parameters the template didn't already pre-grant
Only parameters whose effective value actually differs from the template's authored one get a delta — the override stays minimal. Manually toggling a checkbox afterward works the same way and is preserved independently of the auto-default.
Step 3 — UI Template and locale authoring
Select or create a UI Template. New templates start blank; click Load Defaults in the amber callout to populate a starter structure.
- + Add locale opens a searchable BCP 47 locale selector
- Non-default locale tabs show the default locale's copy as placeholder text in heading, subheading, and category-heading inputs
- Import / Export: available above the wizard card on every content step (Main Banner / GPC Banner / Preference Modal), covering the whole profile regardless of which step is active. JSON exports
{ "en": { "mainBanner.heading": "...", "category.necessary.heading": "...", ... }, "fr-FR": {...} }— a flat dot-path key/value map per locale the profile has content for, with button/category columns keyed by their template ids. CSV exports one row per every supported BCP 47 locale (not just ones with content) so translators never hand-type a locale code, with the same dot-path keys as CSV columns; blank rows on import are skipped
Step 4 — Content and readability
Enter localised copy (heading, body HTML) for the main banner, GPC banner, and preference modal categories. Inline advisory warnings appear when:
- Heading exceeds 80 characters
- Average sentence length in body exceeds 25 words
- Total word count in body exceeds 150 words
These are informational only — the profile can still be saved.
Profile activation and hot-serve
Profiles are inactive after creation. To serve them via geo-routing:
- Click Activate in the profile list or call
POST /admin/profiles/:id/activate - Consenti copies locale JSON files from
${profileId}/${version}/to${complianceGroup}/ - The static file route serves these files immediately — zero DB on the hot path
Only one profile per compliance group can be active. Activating a new profile automatically deactivates the existing one.
Profile edit history
A profile's id is stable across every edit — each save increments its version in place and writes a new resolved-JSON snapshot under that version number, so the full edit history stays inspectable on disk without keeping old DB rows around. The history page shows:
- Left panel: list of all versions, newest first, with dates
- Right panel: prettified JSON for the selected version; locale switcher dropdown
Deleting a profile removes its DB row but not its on-disk version snapshots — the Archived Profiles page (linked from the Profiles list) finds those orphaned profile-id directories from a directory listing alone and opens the same history viewer for them.
Template save safety
When saving a Consent Template or UI Template that is used by one or more profiles, a confirmation dialog lists the affected profiles. You must confirm before changes are applied.
- Deleting a Consent Template used by active profiles is blocked (422) — deactivate the profiles first
- Removing a parameter from a Consent Template, or leaving a parameter in zero/multiple categories, is blocked by save-time validation
Serving behind a reverse proxy
location /consenti/ {
proxy_pass http://localhost:3001/consenti/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}trustedProxies in createConsenti() so that IP hashing and rate limiting use the real client IP from X-Forwarded-For.