Skip to main content
Feature ID: PF-95
Status: ✅ Complete (Phase 1–3; Phase 4 email branding deferred)
Spec: PF-95-tenant-white-labeling-theming.md (v1.2.0) Last Updated: 2026-03-25

Overview

PF-95 adds per-tenant visual theming (CSS custom properties), optional pf_organizations.subdomain, subdomain_verified, prefer_subdomain_redirect, and coordination with email branding. It extends the platform foundation without introducing cross-core imports. Consumers use @/platform/theming (planned) for theme resolution and CSS variable injection.

Integration Points (from Spec)


Platform Layer / Event / API Contracts

  • Platform layer (planned): @/platform/themingTenantThemeProvider, useTenantTheme, useTenantThemeForLogin (or equivalent) calling RPCs below for pre-auth, shared types aligned with pf_tenant_themes rows.
  • Pre-auth (database): Call pf_public_login_branding_by_subdomain(subdomain_label) when the request host is a tenant subdomain; call pf_public_login_branding_by_slug(slug) when resolving branding from PF-74 path slug (e.g. login with org context). Do not expose anonymous SELECT on pf_tenant_themes. Functions return a fixed JSON projection (login-safe fields only); see spec Data Model.
  • Authenticated: Standard Supabase CRUD on pf_tenant_themes with RLS; org subdomain flags updated via existing org update patterns and pf.subdomain.manage.
  • Event contracts: None planned for MVP.
  • Single active theme: Partial unique index (organization_id) WHERE is_active + BEFORE trigger to deactivate sibling rows (spec DDL).

Canonical redirect matrix (normative summary)

Eligibility: subdomain IS NOT NULL and subdomain_verified = true and prefer_subdomain_redirect = true. Full text and notes (308 vs 302, loop prevention, PF-77 boundary) are in the spec section Canonical redirect matrix (PF-74 / PF-81). PF-74 owns implementation (middleware, edge, or client bootstrap — PF-74 choice).

Security & Tenancy

  • All theme rows scoped by organization_id; RLS via pf_has_org_access (SECURITY DEFINER helper; no inline role-assignment queries in policies).
  • Permission keys (PF-30): pf.theme.view, pf.theme.manage, pf.subdomain.manage — constants + seeds with feature UI.
  • No arbitrary CSS/HTML injection; colors validated to safe formats; assets via approved upload paths.
  • Pre-auth: Only pf_public_login_branding_* RPCs; fixed column allowlist; search_path pinned inside functions.
  • Redirects: Gated on subdomain_verified to avoid redirect loops to hosts without valid TLS.