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
| Element | Configurable | How |
|---|---|---|
| App name | Yes | opsdash.config.ts branding.name |
| Logo | Yes | Drop logo.svg + logo-icon.svg in public/logos/, point branding.logo.{full,icon} at them |
| Favicon | Yes | Replace public/favicon.ico |
| Accent color / brand palette | Yes | branding.accentColor ({ light, dark } HSL triplets) in config |
| Email sender name | Yes | RESEND_FROM env var |
| Domain | Yes | Deploy to their domain |
| Pricing plates | Yes | packages/config/src/plates.ts |
| Enabled modules | Yes | modules.* in opsdash.config.ts |
| Feature gating per plate | Yes | features.* (minPlate) in opsdash.config.ts |
| Per-workspace branding | Yes (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.