How Branding Resolution Sources Are Prioritized in DeskcommCRM

DeskcommCRM resolves branding through a deterministic four-layer stack where organization settings take precedence over installation records, environment variables, and product defaults, applying field-by-field precedence in lib/branding/resolve.ts.

DeskcommCRM implements a sophisticated branding resolution system that merges configuration from multiple sources to produce a cohesive tenant identity. The architecture, maintained in the melgarafael/DeskcommCRM repository, evaluates branding resolution sources through a priority-based layer stack to determine the final application name, logo URL, and accent color. Understanding this precedence hierarchy is essential for administrators configuring multi-tenant deployments and white-label installations.

The Four-Layer Stack for Branding Resolution

The resolver constructs a stack of layers (CamadaDeMarca) ordered from most specific to most generic. The core logic walks this stack sequentially, accepting the first non-empty value for each branding field.

Organization Settings (Most Specific)

The top priority layer derives from organizations.settings.branding, a JSON column storing tenant-specific brand data. When present, values in this layer override all other sources. The function camadaDaOrganizacao in lib/branding/resolve.ts transforms the organization's branding payload into a structured layer object. This ensures that end-customer configurations always take precedence over reseller defaults.

Installation Records

The middle layer reads from the platform_branding database table, configured by resellers or installers via the administrative UI. Accessed through camadaDaInstalacao, this layer provides the default identity for a specific installation while allowing organizations to override it. The database row contains fields for name, logo references, and color seeds that persist across code deployments.

Environment Variables

The generic layer sources values from the host environment via camadaDoAmbiente. It reads .env variables including APP_NAME, APP_LOGO_URL, and APP_ACCENT_HEX. These seed values survive code upgrades and provide consistent branding for login screens and email templates when no higher-layer configuration exists.

Product Defaults (Base Layer)

The final fallback consists of hard-coded constants such as DEFAULT_APP_NAME, imported from lib/branding.ts as reguaDoProduto. This base layer guarantees that every branding field has a valid value even when no external configuration is provided.

Core Resolution Logic in lib/branding/resolve.ts

The resolution engine employs two complementary strategies for field extraction. The helper primeiroDefinido iterates through the supplied layer array and returns the first non-empty string for textual fields like nome and logoUrl. For color resolution, resolverCor traverses the same stack, stopping at the first layer that yields a valid accent_hex value.

This architecture implements field-by-field precedence: the resolver never deletes fields from lower layers. If a higher layer provides a name but omits a logo, the logo falls back silently to the next layer that defines it. This granular approach allows administrators to configure partial brands without losing inherited elements.

Implementing the Priority Stack in Code

The layer ordering is explicitly declared in app/layout.tsx, where the application constructs the stack before server-side rendering. The sequence passed to resolverMarca determines the evaluation order:

resolverMarca(
  [
    camadaDaOrganizacao(orgBranding),   // most specific
    camadaDaInstalacao(installRow),     // reseller configuration
    camadaDoAmbiente(process.env),      // generic environment
  ],
  reguaDoProduto,
);

The following example demonstrates building the complete stack and resolving the final brand object:

// 1️⃣ Build the layer stack
import { camadaDoAmbiente } from '@/lib/branding/resolve';
import { camadaDaInstalacao } from '@/lib/branding/resolve';
import { camadaDaOrganizacao } from '@/lib/branding/resolve';
import { resolverMarca } from '@/lib/branding/resolve';
import { reguaDoProduto } from '@/lib/branding/regua-do-produto';

const envLayer = camadaDoAmbiente({
  APP_NAME: process.env.APP_NAME,
  APP_LOGO_URL: process.env.APP_LOGO_URL,
  APP_ACCENT_HEX: process.env.APP_ACCENT_HEX,
});

const instalLayer = camadaDaInstalacao(await fetchPlatformBranding()); // DB row
const orgLayer   = camadaDaOrganizacao(await fetchOrgBranding(orgId)); // org JSON

// 2️⃣ Resolve the brand
const brand = resolverMarca([orgLayer, instalLayer, envLayer], reguaDoProduto);

// 3️⃣ Use the resolved brand in a component
function Header() {
  return (
    <header style={{ '--color-brand': brand.cor?.semente }}>
      {brand.logoUrl ? <img src={brand.logoUrl} alt={brand.name} /> : <span>{brand.initial}</span>}
    </header>
  );
}

How Each Branding Field Is Resolved

Name resolution follows the strict stack order: organization settings take priority, followed by installation records, then environment variables, finally falling back to DEFAULT_APP_NAME.

Logo URL resolution applies the same precedence with an additional business rule implemented in the installation layer: an uploaded file specified via logo_path wins over a plain URL string when both are present in the same layer.

Accent color resolution behaves differently for invalid values. While primeiroDefinido simply skips empty strings, resolverCor validates hex codes. Invalid or empty color values in a higher layer generate diagnostic entries but do not override valid colors defined in lower layers, ensuring visual consistency even when administrators enter malformed color codes.

Debugging Branding Resolution with Diagnostics

Every resolution operation returns a motivos array containing MotivoDaBranca objects that explain the decision process. These diagnostics identify which layer supplied each value and why specific colors were rejected. Administrators can inspect these logs to trace inheritance conflicts:

// Inspect the diagnostics (useful for admin screens)
brand.motivos.forEach(m => {
  console.log(`[${m.origem}] ${m.codigo}: ${m.detalhe}`);
});

The diagnostic system records the origin layer (origem), specific reason code (codigo), and detailed explanation (detalhe) for every fallback or override decision, enabling transparent troubleshooting across the branding resolution pipeline.

Summary

  • Organization settings (organizations.settings.branding) provide the highest priority branding resolution source, overriding all other configurations.
  • Installation records (platform_branding database) serve as the middle tier for reseller-defined defaults.
  • Environment variables (.env files) supply generic seeds that persist through code upgrades.
  • Product defaults (DEFAULT_APP_NAME) guarantee fallback values when no external configuration exists.
  • The resolverMarca function in lib/branding/resolve.ts implements field-by-field precedence, allowing partial brand inheritance.
  • The layer stack is constructed in app/layout.tsx and processed via primeiroDefinido and resolverCor helpers.
  • Diagnostic data (MotivoDaMarca) tracks every resolution decision for administrative transparency.

Frequently Asked Questions

What happens if an organization defines a name but leaves the logo empty?

The system applies field-by-field precedence. The organization layer supplies the name while the resolver falls back to the installation or environment layer for the logo URL. This partial inheritance allows flexible branding configurations without requiring complete redefinition of all assets.

Can environment variables override organization branding?

No. The stack order in app/layout.tsx places camadaDaOrganizacao at index 0 (most specific) and camadaDoAmbiente at the end. Because primeiroDefinido returns the first non-empty value it encounters, organization settings always take precedence over .env configurations.

How does DeskcommCRM handle invalid accent colors in high-priority layers?

Invalid hex codes or empty strings in higher layers are recorded as diagnostics via the MotivoDaMarca interface but do not override valid colors from lower layers. The resolverCor function continues iterating down the stack until it finds a valid color seed, ensuring the UI receives a properly formatted value even when administrators enter malformed data.

Where are the TypeScript types for branding layers defined?

The type definitions for installation layers reside in lib/branding/instalacao.ts (LinhaDaInstalacao), while organization branding types are located in lib/branding/organizacao.ts (MarcaDaOrganizacao). The public API and default constants are exported from lib/branding.ts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →