Branding API
Organization branding management — get, update, and preview custom branding configuration
Branding API
The branding API provides per-tenant brand customization management. Organizations can configure custom colors, upload logos and favicons, customize the login page, and optionally inject custom CSS (Enterprise plan). All procedures require organization-level authentication (orgProcedure).
Plan Requirements
Custom branding requires the Pro plan or higher (plan level >= 2). The router checks the organization’s subscription plan before allowing updates. Plan levels:
| Plan | Level | Branding Access |
|---|---|---|
| Free | 0 | No branding customization |
| Starter | 1 | No branding customization |
| Pro | 2 | Full branding (colors, logos, login page) |
| Enterprise | 3 | Full branding + custom CSS + “Powered by” removal |
Default Branding Values
When no custom branding is configured, the system uses these defaults:
| Property | Default Value |
|---|---|
primaryColor | #115D9C |
accentColor | #9AC443 |
sidebarColor | #1E293B |
sidebarTextColor | #F8FAFC |
branding.get
Retrieve the current branding configuration for the authenticated organization. Available to all members because the layout renderer needs branding data for every page load.
Type: Query
Input: None (uses organization context from session)
Output:
{
branding: {
primaryColor?: string; // Hex color (e.g., "#115D9C")
accentColor?: string; // Hex color
sidebarColor?: string; // Hex color for sidebar background
sidebarTextColor?: string; // Hex color for sidebar text
headerLogo?: string; // S3 key or URL
loginLogo?: string; // S3 key or URL
favicon?: string; // S3 key or URL
loginWelcomeText?: string; // Max 500 characters
loginBackground?: string; // S3 key or URL
poweredByVisible?: boolean; // Default true
} | null;
customCss: string | null; // Enterprise plan only, max 10,000 chars
plan: string; // Organization's subscription plan name
}
branding.update
Update the organization’s branding configuration. Performs a partial merge with the existing configuration: provided fields overwrite the current values, omitted fields are preserved unchanged.
Type: Mutation
Input Schema:
{
branding?: {
primaryColor?: string; // Hex color matching /^#[0-9A-Fa-f]{6}$/
accentColor?: string; // Hex color
sidebarColor?: string; // Hex color
sidebarTextColor?: string; // Hex color
headerLogo?: string; // S3 key after upload
loginLogo?: string; // S3 key after upload
favicon?: string; // S3 key after upload
loginWelcomeText?: string; // Max 500 characters
loginBackground?: string; // S3 key after upload
poweredByVisible?: boolean; // Enterprise plan only for false
};
customCss?: string | null; // Max 10,000 chars, sanitized for XSS
}
Output:
{
branding: { ... }; // Merged branding configuration
customCss: string | null;
}
Error Codes:
| Code | Condition |
|---|---|
FORBIDDEN | Caller is not admin/owner, or plan level is below Pro |
NOT_FOUND | Organization not found |
Custom CSS Validation:
The customCss field is sanitized to prevent XSS attacks. The following patterns are rejected:
<scripttags (case-insensitive)- Event handler attributes (
onload=,onerror=, etc.) javascript:URLs
Side Effects:
- Logs
branding.updatedactivity with the list of changed field names.
branding.getUploadUrl
Generate a presigned S3 upload URL for branding assets. The client uses this URL to upload images directly to S3, bypassing the API server for the file transfer.
Type: Mutation
Input Schema:
{
type: "logo" | "favicon" | "loginBackground";
contentType: "image/jpeg" | "image/png" | "image/svg+xml";
}
Output:
{
uploadUrl: string; // Presigned PUT URL (15-minute expiry)
fileUrl: string; // Permanent S3 key to store in branding config
}
S3 Key Format:
branding/{organizationId}/{type}-{timestamp}.{extension}
Error Codes:
| Code | Condition |
|---|---|
FORBIDDEN | Caller is not admin/owner, or plan level is below Pro |
NOT_FOUND | Organization not found |
Upload Workflow:
- Call
branding.getUploadUrlwith the desired asset type and content type. - Use the returned
uploadUrlto PUT the file directly to S3. - Call
branding.updatewith thefileUrlin the appropriate branding field.
branding.preview
Compute CSS custom properties from a branding configuration without persisting anything to the database. This enables real-time preview in the admin UI: as the user adjusts color pickers, the client calls this procedure and applies the returned CSS variables instantly.
Type: Query
Input Schema:
{
primaryColor?: string;
accentColor?: string;
sidebarColor?: string;
sidebarTextColor?: string;
}
Output:
{
"--color-primary": string; // e.g., "#115D9C"
"--color-accent": string; // e.g., "#9AC443"
"--sidebar-bg": string; // e.g., "#1E293B"
"--sidebar-text": string; // e.g., "#F8FAFC"
}
Three-Layer Merge: The preview procedure uses a three-layer priority system for each CSS variable:
- Input values (user’s unsaved preview) — highest priority
- Current org branding (persisted config) — middle priority
- System defaults — lowest priority (ensures no variable is undefined)
Permissions
| Procedure | Required Role |
|---|---|
branding.get | Any authenticated member |
branding.update | Admin or Owner (Pro+ plan) |
branding.getUploadUrl | Admin or Owner (Pro+ plan) |
branding.preview | Any authenticated member |
Security
- All procedures are scoped via
orgProcedurefor tenant isolation. - Custom CSS is sanitized to reject script injection, event handlers, and
javascript:URLs. - Upload URLs are presigned with 15-minute expiry and scoped to the organization’s S3 prefix.
- Only image MIME types (JPEG, PNG, SVG) are accepted for branding asset uploads.
Was this page helpful?