Guides

Whitelabel guide

OpsDash can be fully white-labeled and resold as your own product — your logo, your colors, your domain, your pricing. This guide covers the complete configuration path.

What can be customized

ElementConfigurableHow
App nameYesopsdash.config.ts branding.name
LogoYesDrop logo.svg + logo-icon.svg in public/logos/, point branding.logo.{full,icon} at them
FaviconYesReplace public/favicon.ico
Accent color / brand paletteYesbranding.accentColor ({ light, dark } HSL triplets) in config
Email sender nameYesRESEND_FROM env var
DomainYesDeploy to their domain
Pricing platesYespackages/config/src/plates.ts
Enabled modulesYesmodules.* in opsdash.config.ts
Feature gating per plateYesfeatures.* (minPlate) in opsdash.config.ts
Per-workspace brandingYes (whitelabel only)Admin → Branding, gated by platform:custom-branding (agency plate)

Branding configuration

// opsdash.config.ts
import { defineConfig } from '@opsdash/config';

export default defineConfig({
  distribution: 'whitelabel', // enables per-workspace branding overrides
  branding: {
    name: 'AgencyOS Pro',
    logo: { full: '/logos/agencyos.svg', icon: '/logos/agencyos-icon.svg' }, // SVGs in /public/logos/
    accentColor: { light: '262 83% 58%', dark: '263 70% 60%' }, // HSL triplets, no hsl() — drives all primary UI
  },
});

The app name appears in: sidebar header, browser tab title, invitation emails, notifications.

The primary color drives all highlighted UI elements: buttons, active nav items, badges, focus rings.

Per-workspace branding override

The config above sets one brand for the entire deployment. When distribution: 'whitelabel', each workspace can additionally override its own name, logo, and accent color from Admin → Branding. This is stored in organizations.branding_override (JSONB) and gated behind the platform:custom-branding feature (the agency plate in shipped defaults) and distribution: 'whitelabel' — SaaS and self-hosted deployments never see the settings row and the server action rejects writes.

Overrides apply only inside /org/{slug}; the landing page, login, and other non-workspace routes always use the deployment-wide branding. Empty override fields inherit from the deployment default. Every branding change is audit-logged (platform.branding.updated / platform.branding.cleared).

Module configuration

modules: {
  crm: { enabled: true },
  projects: { enabled: true },
  automation: { enabled: true },
  invoicing: { enabled: false }, // disable a module the client doesn't need
}

Disabled modules are completely hidden — they do not appear in the sidebar, search, or command palette.

Custom pricing plates

Edit packages/config/src/plates.ts — the single source of truth for the plate ladder (free | studio | sales | growth | full_loop | agency; the public columns are Free $0 / Studio $24 / Growth $59 / Full Loop $99 / Agency “Contact us”, with sales a dormant internal rung). Set your own labels, prices, seats, and caps, then update the Stripe Price IDs in .env(STRIPE_PRICE_{PLATE}_{INTERVAL}) to match your own products.

// packages/config/src/plates.ts — amounts must match your Stripe products
export const platesCatalog: Record<PlateId, PlateDefinition> = {
  studio: {
    label: 'Studio',
    pricing: { monthlyUsd: 24, yearlyUsd: 240 },
    limits: { /* includedSeats, storageBytes, … */ },
    entityCaps: { /* contacts, deals, projects, … */ },
  },
  // … growth, full_loop, agency
};

To unlock a feature on an earlier plate, override its minPlate under features.* in opsdash.config.ts (e.g. 'crm:export': { minPlate: 'studio' }). To raise an entity cap, edit that plate's entityCaps in plates.ts.

Emails

Set RESEND_FROM in .env:

RESEND_FROM="AgencyOS Pro <noreply@yourdomain.com>"

This sets the sender name and address for all transactional emails (invitations, notifications, password resets).

Hiding the OpsDash attribution

If your license includes removal of the “Powered by OpsDash” attribution, remove or replace it in the footer component where it renders. No other components reference the product name directly — everywhere else it comes from branding.name in config, so setting that value white-labels the rest of the app automatically.

Deployment

Deploy the whitelabeled instance exactly as a private instance — see the Private instance guide. The only difference is the branding config and, if applicable, the reseller's own Stripe account.

Whitelabel — OpsDash Docs | OpsDash