# How the White-Label Branding Resolver Merges platform_branding and organizations.settings.branding in DeskcommCRM

> Learn how the DeskcommCRM white-label branding resolver merges platform branding and organization settings. Discover its layered configuration system that prioritizes organization specifics over environment variables.

- Repository: [Rafael Melgaço/DeskcommCRM](https://github.com/melgarafael/DeskcommCRM)
- Tags: internals
- Published: 2026-09-13

---

**The white-label branding resolver prioritizes organization-specific settings over installation defaults and environment variables by stacking three configuration layers into a unified brand resolution system.**

DeskcommCRM implements a cascading configuration system that allows multi-tenant deployments to override branding at the organization level while maintaining fallbacks to installation-wide settings and `.env` defaults. The resolver treats each source as a **CamadaDeMarca** (brand layer), merging them through field-level precedence rather than wholesale replacement.

## The Three-Layer Configuration Stack

The resolver in [`lib/branding/resolve.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/branding/resolve.ts) never reads configuration sources directly. Instead, dedicated helpers transform each source into a standardized layer object with an `origem` field identifying its source.

### Environment Layer (`.env`)

The `camadaDoAmbiente` function processes standard environment variables into the base layer:

- **Source**: `NEXT_PUBLIC_APP_NAME`, `NEXT_PUBLIC_APP_LOGO_URL`, `NEXT_PUBLIC_APP_ACCENT_HEX`
- **Behavior**: Returns a layer with `origem: "env"`; empty `APP_ACCENT_HEX` values omit the `cor` field entirely, forcing the resolver to skip this layer for color selection
- **Location**: Defined in [`lib/branding/resolve.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/branding/resolve.ts)

### Installation Layer (`platform_branding`)

The `camadaDaInstalacao` function in [`lib/branding/instalacao.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/branding/instalacao.ts) queries the database table `platform_branding`:

```typescript
// lib/branding/instalacao.ts
export async function camadaDaInstalacao(linha: LinhaDeMarcaDeInstalacao): Promise<CamadaDeMarca> {
  const { app_name, logo_url, accent_hex } = linha;
  
  return {
    origem: "instalacao",
    nome: app_name || null,
    logoUrl: logo_url || null,
    cor: accent_hex ? envelopeDeSemente(accent_hex) : undefined
  };
}

```

If `accent_hex` is empty, the layer excludes the `cor` property, allowing the resolver to fall through to the environment layer.

### Organization Layer (`organizations.settings.branding`)

The `camadaDaOrganizacao` function in [`lib/branding/organizacao.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/branding/organizacao.ts) extracts branding from the JSONB column `organizations.settings.branding`:

- **Fields parsed**: `app_name`, `accent_hex`, `logo_path`
- **Validation**: Missing or malformed fields resolve to `null`, triggering fallback to lower layers
- **Precedence**: This layer sits at the top of the stack, making organization settings the highest priority

## How the Resolver Stacks Configuration Layers

The `resolverMarcaDaOrganizacao` function (called from [`app/layout.tsx`](https://github.com/melgarafael/DeskcommCRM/blob/main/app/layout.tsx)) constructs an ordered array representing the precedence hierarchy:

```typescript
// lib/branding/organizacao.ts
export async function resolverMarcaDaOrganizacao(
  settings: OrganizationSettings,
  linha: LinhaDeMarcaDeInstalacao,
  ambiente: VariaveisDeAmbiente
): Promise<MarcaResolvida> {
  const camadas = [
    camadaDaOrganizacao(marcaDaOrganizacaoDeSettings(settings)), // Highest priority
    camadaDaInstalacao(linha),                                   // Middle priority
    camadaDoAmbiente(ambiente)                                   // Lowest priority
  ];
  
  return resolverMarca(camadas);
}

```

This stack ensures that organization-specific branding overrides installation defaults, which in turn override static `.env` values.

## Field-Level Precedence and Graceful Fallbacks

The core `resolverMarca` function implements **"precedência por campo"** (field-level precedence). Rather than replacing the entire configuration when a higher layer exists, it evaluates each field independently:

1. **Name and Logo**: Uses `primeiroDefinido` to select the first non-empty `nome` and `logoUrl` from the stack
2. **Color**: Iterates through layers calling `resolverCor` on each until finding a valid `CorResolvida`

```typescript
// lib/branding/resolve.ts
for (const camada of camadas) {
  if (!("cor" in camada)) continue;
  const tentativa = resolverCor(camada.cor, camada.origem, regua);
  motivos.push(...tentativa.motivos);
  
  if (tentativa.cor) {
    cor = tentativa.cor;          // First defined colour wins
    origemDaCor = camada.origem;
    break;
  }
}

```

This granular approach allows an organization to customize only its accent color while retaining the installation's default logo and application name, or vice versa.

## Error Handling Without Exceptions

The resolver guarantees **never throwing** during brand resolution. All validation errors—malformed hex codes, invalid envelope shapes, or missing required fields—are captured as `MotivoDaMarca` objects:

- **Validation location**: Inside `resolverCor` function
- **Error propagation**: Collected into the `motivos` array and returned with the resolved brand
- **UI safety**: [`app/layout.tsx`](https://github.com/melgarafael/DeskcommCRM/blob/main/app/layout.tsx) receives a complete `MarcaResolvida` object even when branding configuration is partially invalid

This design prevents configuration errors from crashing the Next.js layout, ensuring the application remains accessible even with incomplete or corrupted branding settings.

## Integration in the Application Layout

The entry point at [`app/layout.tsx`](https://github.com/melgarafael/DeskcommCRM/blob/main/app/layout.tsx) orchestrates the resolution by fetching all three sources before invoking the resolver:

```typescript
// app/layout.tsx
import { marcaDaInstalacao } from "@/lib/branding/instalacao";
import { resolverMarcaDaOrganizacao } from "@/lib/branding/organizacao";

async function getBranding(orgId: string): Promise<MarcaResolvida> {
  // Load layer sources
  const linhaInstalacao = await marcaDaInstalacao();
  const ambiente = {
    APP_NAME: process.env.NEXT_PUBLIC_APP_NAME,
    APP_LOGO_URL: process.env.NEXT_PUBLIC_APP_LOGO_URL,
    APP_ACCENT_HEX: process.env.NEXT_PUBLIC_APP_ACCENT_HEX,
  };
  const orgSettings = await fetchOrgSettings(orgId);

  // Resolve with precedence
  return resolverMarcaDaOrganizacao(orgSettings, linhaInstalacao, ambiente);
}

```

Because each layer is wrapped in a pure data object, the resolver remains fully testable without database or environment dependencies.

## Summary

- **Three-layer architecture**: Environment variables form the base, `platform_branding` provides installation defaults, and `organizations.settings.branding` offers tenant-specific overrides
- **Field-level precedence**: The resolver in [`lib/branding/resolve.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/branding/resolve.ts) evaluates `nome`, `logoUrl`, and `cor` independently rather than replacing entire configurations
- **Defensive programming**: Validation errors become `MotivoDaMarca` objects that propagate without throwing exceptions, protecting the UI from configuration crashes
- **Pure data layers**: Each source transforms into a `CamadaDeMarca` with standardized fields, enabling isolated unit testing and clear source attribution via the `origem` property

## Frequently Asked Questions

### How does the resolver handle missing accent colors in higher priority layers?

If `organizations.settings.branding` contains no `accent_hex` value, the organization layer excludes the `cor` field entirely. The resolver continues iterating through the stack, checking the installation layer's `platform_branding.accent_hex`, and finally falling back to `.env` variables. Empty or null color values never cause errors—they simply trigger the fallback mechanism to the next layer.

### Can an organization override just the logo without changing the application name?

Yes. Because the resolver uses field-level precedence, you can set only `logo_path` in `organizations.settings.branding` while leaving `app_name` undefined. The resolver will use the organization's logo but continue using the `app_name` from `platform_branding` or `.env`, depending on availability.

### What happens if the hexadecimal color code in platform_branding is malformed?

The `resolverCor` function validates hex format internally. Malformed values generate a `MotivoDaMarca` error object that documents the validation failure, but the function returns `undefined` for that layer's color. The resolver then attempts to source the color from the next layer in the stack (typically `.env`). This ensures that invalid database entries never crash the application layout.

### Where should I configure global branding for a single-tenant deployment?

For single-tenant installations, configure branding in the `platform_branding` database table rather than relying solely on `.env` files. This approach allows runtime updates without rebuilding the application, while the [`instalacao.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/instalacao.ts) module handles memoization and cache invalidation to maintain performance.