Skip to main content
DocsIntegration GuidesWhitelabel Setup
Back to docs

Whitelabel Setup

Custom branding, domains, tenant configuration

integration
whitelabel

Whitelabel Setup Guide

Overview

Creatiq supports full whitelabel customization for tenants on the Premium plan. Whitelabel features allow you to rebrand the Creatiq platform as your own, including:

  • Custom branding -- Logo, favicon, app name, primary color
  • Custom CSS -- Inject up to 50,000 characters of custom CSS
  • Custom domain -- Serve Creatiq from your own domain (e.g., content.yourschool.edu)
  • SDK theming -- Pass brand colors and fonts to the @creatiq/ui-sdk components

All whitelabel API endpoints require authentication and are gated behind the whitelabel feature flag, which is only available on Premium plans.


Prerequisites

  1. Premium plan -- Whitelabel features are exclusively available on the Premium subscription tier. Free and Pro plans do not have access to the branding or custom domain APIs.

  2. Tenant setup -- Your organization must be registered as a tenant in the Creatiq system. Each tenant has:

    • A unique slug (lowercase alphanumeric with hyphens, max 63 characters, pattern: ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$)
    • A primary domain for tenant resolution
    • An optional customDomain for vanity URLs
    • A brandConfig object with all visual customization
    • An optional smtpConfig for custom email delivery
  3. Admin access -- Only authenticated users with appropriate permissions can modify tenant branding and domain settings.


Branding

Brand Configuration Object

The brandConfig object controls the visual identity of your Creatiq instance:

FieldTypeConstraintsDefaultDescription
primaryColorstringHex color #RRGGBB#6366f1Primary brand color used across the UI
logoUrlstring | nullValid URL, max 1000 charsnullURL to your organization's logo
faviconUrlstring | nullValid URL, max 1000 charsnullURL to your custom favicon
appNamestring1-100 charactersCreatiqApplication name shown in the UI header, page titles, and emails
customCssstring | nullMax 50,000 charactersnullCustom CSS injected into all pages

Update Branding

PUT /api/tenant/brand
Authorization: Bearer <token>
Content-Type: application/json

Request body (all fields optional):

{
  "primaryColor": "#2563eb",
  "logoUrl": "https://cdn.yourschool.edu/logo.svg",
  "faviconUrl": "https://cdn.yourschool.edu/favicon.ico",
  "appName": "YourSchool Interactive",
  "customCss": ".header { background: linear-gradient(135deg, #2563eb, #7c3aed); }"
}

Response:

{
  "success": true,
  "data": {
    "brandConfig": {
      "primaryColor": "#2563eb",
      "logoUrl": "https://cdn.yourschool.edu/logo.svg",
      "faviconUrl": "https://cdn.yourschool.edu/favicon.ico",
      "appName": "YourSchool Interactive",
      "customCss": ".header { background: linear-gradient(135deg, #2563eb, #7c3aed); }"
    }
  }
}

Updates are merged with the existing configuration (JSONB || operator), so you only need to send the fields you want to change.

Validation Rules

  • primaryColor must be a valid 6-digit hex color (e.g., #ff5500). 3-digit shorthand and named colors are not accepted.
  • logoUrl and faviconUrl must be valid URLs. Pass null to remove them.
  • appName must be between 1 and 100 characters.
  • customCss must not exceed 50,000 characters. Pass null to remove it.

Resolve Current Branding

Any client (including unauthenticated embed pages) can resolve the current tenant's branding:

GET /api/tenant/current

Response (tenant found):

{
  "success": true,
  "data": {
    "isDefault": false,
    "id": "tenant-uuid",
    "slug": "yourschool",
    "name": "YourSchool",
    "brandConfig": {
      "primaryColor": "#2563eb",
      "logoUrl": "https://cdn.yourschool.edu/logo.svg",
      "faviconUrl": "https://cdn.yourschool.edu/favicon.ico",
      "appName": "YourSchool Interactive",
      "customCss": null
    },
    "plan": "premium"
  }
}

Response (no tenant / default):

{
  "success": true,
  "data": {
    "isDefault": true,
    "brandConfig": {
      "primaryColor": "#6366f1",
      "logoUrl": null,
      "faviconUrl": null,
      "appName": "Creatiq",
      "customCss": null
    }
  }
}

Tenant resolution is based on the request hostname. The system checks both the domain and custom_domain columns for a match.


Custom Domain

Setup Steps

  1. Choose your domain -- Select a subdomain or domain to serve Creatiq from (e.g., content.yourschool.edu or creatiq.yourschool.edu).

  2. Configure DNS -- Add a CNAME record pointing to the Creatiq platform:

    content.yourschool.edu  CNAME  creatiq.app
    

    If using an apex domain (no subdomain), use an ALIAS or ANAME record instead of CNAME, as CNAME records are not valid at the zone apex.

  3. Register the domain via API:

    PUT /api/tenant/domain
    Authorization: Bearer <token>
    Content-Type: application/json
    
    {
      "customDomain": "content.yourschool.edu"
    }
    

    Response:

    {
      "success": true,
      "data": {
        "customDomain": "content.yourschool.edu"
      }
    }
    
  4. SSL/TLS -- The platform automatically provisions TLS certificates for custom domains. Allow up to 10 minutes for certificate issuance after DNS propagation.

  5. Verify -- After DNS propagation (typically 5-30 minutes), visit https://content.yourschool.edu to confirm your branded Creatiq instance is accessible.

Domain Validation Rules

  • Must be at least 3 characters, maximum 500 characters
  • Must match pattern: ^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$
  • Must be unique across all tenants (enforced by a unique database constraint)
  • Cannot be a Creatiq platform domain

DNS Configuration Examples

Subdomain (recommended):

Record TypeHostValue
CNAMEcontentcreatiq.app

Apex domain (Cloudflare, Route53, etc.):

Record TypeHostValue
ALIAS@creatiq.app

Removing a Custom Domain

To remove a custom domain, update it to null via the tenant update endpoint or set a different domain.


Custom CSS Guide

The customCss field allows you to override any Creatiq styles. CSS is injected into every page rendered for your tenant.

Scoping

Custom CSS is applied globally. Use specific selectors to avoid unintended side effects:

/* Override the main header background */
.creatiq-header {
  background-color: #1e293b;
}

/* Custom button styling */
.creatiq-btn-primary {
  background: linear-gradient(135deg, #2563eb, #7c3aed);
  border-radius: 9999px;
}

/* Hide the Creatiq branding footer */
.creatiq-footer-brand {
  display: none;
}

/* Custom font */
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap');

body {
  font-family: 'Poppins', sans-serif;
}

Limitations

  • Maximum 50,000 characters
  • No <script> tags or JavaScript execution
  • External resources (fonts, images) must be served over HTTPS
  • CSS is sanitized before injection to prevent XSS

SDK Integration

When embedding Creatiq via the @creatiq/ui-sdk, pass your brand theme through the CreatiqConfig:

Basic Branded Setup

import { CreatiqProvider, CreatiqWorkspace } from '@creatiq/ui-sdk';
import '@creatiq/ui-sdk/styles.css';

function BrandedCreatiq() {
  return (
    <CreatiqProvider
      config={{
        baseUrl: 'https://content.yourschool.edu', // Your custom domain
        token: 'sdk-token',
        locale: 'en',
        tenantId: 'your-tenant-id',
        theme: {
          colorPrimary: '#2563eb',
          colorPrimaryForeground: '#ffffff',
          colorBackground: '#f8fafc',
          colorSurface: '#ffffff',
          colorText: '#0f172a',
          colorMuted: '#64748b',
          colorBorder: '#e2e8f0',
          borderRadius: '0.75rem',
          fontFamily: "'Inter', sans-serif",
        },
      }}
    >
      <CreatiqWorkspace />
    </CreatiqProvider>
  );
}

Theme CSS Variables

The SDK injects CSS custom properties on the <html> element. These variables are used by all SDK components:

CSS VariableTheme PropertyDescription
--crq-color-primarycolorPrimaryPrimary brand color
--crq-color-primary-foregroundcolorPrimaryForegroundText on primary
--crq-color-backgroundcolorBackgroundPage background
--crq-color-surfacecolorSurfaceCard/panel background
--crq-color-textcolorTextBody text color
--crq-color-mutedcolorMutedSecondary text
--crq-color-bordercolorBorderBorder color
--crq-color-destructivecolorDestructiveError/warning color
--crq-border-radiusborderRadiusGlobal border radius
--crq-font-familyfontFamilyFont stack

You can also use these variables in your own CSS to match the Creatiq theme:

.my-custom-card {
  background: var(--crq-color-surface);
  border: 1px solid var(--crq-color-border);
  border-radius: var(--crq-border-radius);
  color: var(--crq-color-text);
}

Dark Mode

The SDK supports dark mode via the dark class on the <html> element. The theme preference is persisted in localStorage under the key creatiq-theme. On mount, the provider automatically restores the saved preference.

Fetching Brand Config at Runtime

If your application needs to dynamically load the tenant's brand config (e.g., for a multi-tenant SaaS), fetch it from the API before rendering:

import { useEffect, useState } from 'react';
import { CreatiqProvider, CreatiqWorkspace } from '@creatiq/ui-sdk';
import type { CreatiqTheme } from '@creatiq/ui-sdk';

function DynamicBrandedApp({ token }: { token: string }) {
  const [theme, setTheme] = useState<Partial<CreatiqTheme> | undefined>();
  const [baseUrl, setBaseUrl] = useState('https://creatiq.app');

  useEffect(() => {
    fetch('/api/tenant/current')
      .then((res) => res.json())
      .then((data) => {
        if (data.success && data.data.brandConfig) {
          const brand = data.data.brandConfig;
          setTheme({
            colorPrimary: brand.primaryColor,
          });
        }
      });
  }, []);

  return (
    <CreatiqProvider
      config={{
        baseUrl,
        token,
        theme,
      }}
    >
      <CreatiqWorkspace />
    </CreatiqProvider>
  );
}

Tenant Database Schema

For reference, the tenant table structure:

ColumnTypeDescription
idUUIDPrimary key
slugVARCHAR(63)Unique URL-safe identifier
nameVARCHAR(255)Display name
domainVARCHAR(500)Primary domain (indexed)
custom_domainVARCHAR(500)Custom vanity domain (unique, indexed)
brand_configJSONBBrand configuration object
smtp_configJSONBCustom email delivery settings (nullable)
planVARCHAR(50)Subscription plan (free, pro, premium)
activeBOOLEANWhether the tenant is active
created_atTIMESTAMPCreation timestamp
updated_atTIMESTAMPLast update timestamp

Tenant resolution uses the indexed domain and custom_domain columns. The query filters for active = true to exclude disabled tenants.

Back to docsdocs/product/integration/whitelabel.md