Skip to main content

Overview

Fataplus supports multi-tenant brand management, allowing each client project to maintain its unique visual identity while leveraging the shared design system infrastructure. Brand assets are stored in Cloudflare R2 and synchronized across all tenant touchpoints.
Brand management enables agencies to deliver fully white-labeled experiences for their clients while maintaining central design system consistency.

Multi-Tenant Architecture

Tenant Isolation

Each tenant gets its own subdomain and isolated brand configuration:

Tenant Configuration

export interface TenantBrandConfig {
  id: string;
  name: string;
  subdomain: string;
  branding: {
    logo: {
      primary: string;      // URL to primary logo
      secondary?: string;   // Optional secondary/dark logo
      favicon: string;      // Favicon URL
    };
    colors: {
      primary: string;      // Main brand color (hex)
      secondary?: string;   // Secondary brand color
      accent?: string;      // Accent color for CTAs
    };
    typography: {
      fontFamily?: string;  // Custom font (Google Fonts)
      headingFont?: string; // Optional heading font
    };
    assets: {
      heroImage?: string;
      backgroundPattern?: string;
      illustrations?: string[];
    };
  };
  domain?: string;         // Optional custom domain
  createdAt: string;
  updatedAt: string;
}

// Example tenant configuration
export const tenants: Record<string, TenantBrandConfig> = {
  'agritech-demo': {
    id: 'agritech-demo',
    name: 'AgriTech Solutions',
    subdomain: 'agritech-demo',
    branding: {
      logo: {
        primary: 'https://assets.fata.plus/agritech-demo/logo.svg',
        favicon: 'https://assets.fata.plus/agritech-demo/favicon.ico',
      },
      colors: {
        primary: '#18CF4C',
        secondary: '#27ae60',
        accent: '#3498db',
      },
      typography: {
        fontFamily: 'Inter',
      },
    },
    createdAt: '2026-01-15T10:00:00Z',
    updatedAt: '2026-03-03T10:00:00Z',
  },
};

Logo and Assets Management

Uploading Brand Assets

Tenants can upload their brand assets through the admin interface or API:
1

Access Brand Settings

Navigate to your tenant dashboard at https://[tenant].fata.plus/admin/branding
2

Upload Logo Files

Upload your logo in multiple formats:
  • Primary Logo (SVG preferred, PNG acceptable)
  • Dark Mode Logo (optional, for dark backgrounds)
  • Favicon (32x32 PNG or ICO)
  • App Icon (512x512 PNG for PWA)
3

Configure Asset URLs

Assets are automatically uploaded to R2 and URLs are generated:
https://assets.fata.plus/[tenant-id]/logo-primary.svg
https://assets.fata.plus/[tenant-id]/logo-dark.svg
https://assets.fata.plus/[tenant-id]/favicon.ico

R2 Storage Configuration

# R2 Buckets for tenant assets
[[r2_buckets]]
binding = "TENANT_ASSETS"
bucket_name = "fataplus-tenant-assets"
preview_bucket_name = "fataplus-tenant-assets-preview"

[[r2_buckets]]
binding = "DESIGN_ASSETS"
bucket_name = "fataplus-design-assets"

Color Management

Brand Color Customization

Tenants can customize their brand colors while maintaining design system consistency:
export function generateTenantTheme(brandColors: {
  primary: string;
  secondary?: string;
  accent?: string;
}) {
  return `
    :root {
      /* Tenant Brand Colors */
      --tenant-primary: ${brandColors.primary};
      --tenant-primary-light: ${lighten(brandColors.primary, 10)};
      --tenant-primary-dark: ${darken(brandColors.primary, 10)};
      
      ${brandColors.secondary ? `
        --tenant-secondary: ${brandColors.secondary};
      ` : ''}
      
      ${brandColors.accent ? `
        --tenant-accent: ${brandColors.accent};
      ` : ''}
      
      /* Override design system tokens */
      --primary-green: var(--tenant-primary);
      --primary-green-light: var(--tenant-primary-light);
      --primary-green-dark: var(--tenant-primary-dark);
    }
  `;
}

// Color manipulation utilities
function lighten(hex: string, percent: number): string {
  // Convert hex to RGB
  const num = parseInt(hex.replace('#', ''), 16);
  const r = (num >> 16) + Math.round(2.55 * percent);
  const g = ((num >> 8) & 0x00FF) + Math.round(2.55 * percent);
  const b = (num & 0x0000FF) + Math.round(2.55 * percent);
  
  return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
}

function darken(hex: string, percent: number): string {
  return lighten(hex, -percent);
}

Color Accessibility

The system automatically validates color contrast ratios:
export function checkContrastRatio(
  foreground: string,
  background: string
): { ratio: number; passes: boolean; level: 'AAA' | 'AA' | 'fail' } {
  const fgLuminance = getRelativeLuminance(foreground);
  const bgLuminance = getRelativeLuminance(background);
  
  const ratio = (Math.max(fgLuminance, bgLuminance) + 0.05) /
                (Math.min(fgLuminance, bgLuminance) + 0.05);
  
  return {
    ratio,
    passes: ratio >= 4.5,
    level: ratio >= 7 ? 'AAA' : ratio >= 4.5 ? 'AA' : 'fail',
  };
}

function getRelativeLuminance(hex: string): number {
  const rgb = hex.match(/\w\w/g)?.map(x => parseInt(x, 16) / 255) || [0, 0, 0];
  const [r, g, b] = rgb.map(val => {
    return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4);
  });
  return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
All brand colors are automatically validated for WCAG 2.1 AA compliance. If your colors don’t meet accessibility standards, the system will suggest adjustments.

Typography Customization

Custom Font Integration

Tenants can use Google Fonts or upload custom fonts:
export async function loadGoogleFont(fontFamily: string) {
  const fontUrl = `https://fonts.googleapis.com/css2?family=${
    fontFamily.replace(' ', '+')
  }:wght@300;400;500;600;700;800&display=swap`;
  
  return `
    <link href="${fontUrl}" rel="stylesheet" />
    <style>
      :root {
        --font-sans: '${fontFamily}', -apple-system, BlinkMacSystemFont, sans-serif;
      }
      body {
        font-family: var(--font-sans);
      }
    </style>
  `;
}

Design Assets Storage

Asset Organization

Tenant assets are organized in a structured hierarchy:
fataplus-tenant-assets/
├── tenant-a/
│   ├── branding/
│   │   ├── logo-primary.svg
│   │   ├── logo-dark.svg
│   │   ├── favicon.ico
│   │   └── app-icon.png
│   ├── fonts/
│   │   ├── custom-font.woff2
│   │   └── custom-font.woff
│   ├── images/
│   │   ├── hero-background.jpg
│   │   ├── pattern.svg
│   │   └── illustrations/
│   └── documents/
│       ├── brand-guidelines.pdf
│       └── style-guide.pdf
├── tenant-b/
│   └── ...
└── shared/
    └── default-assets/

Asset Delivery

Assets are served through Cloudflare’s global CDN with optimized caching:
export async function onRequest(
  context: EventContext<Env, any, any>
) {
  const { tenant, path } = context.params;
  const assetKey = `${tenant}/${path}`;
  
  // Try to get from R2
  const object = await context.env.TENANT_ASSETS.get(assetKey);
  
  if (!object) {
    return new Response('Asset not found', { status: 404 });
  }
  
  // Return with proper headers
  return new Response(object.body, {
    headers: {
      'Content-Type': object.httpMetadata.contentType || 'application/octet-stream',
      'Cache-Control': 'public, max-age=31536000, immutable',
      'ETag': object.etag,
      'Access-Control-Allow-Origin': '*',
    },
  });
}

Image Optimization

Automatic image optimization for faster loading:
export async function optimizeImage(
  imageBuffer: ArrayBuffer,
  options: {
    width?: number;
    height?: number;
    quality?: number;
    format?: 'webp' | 'avif' | 'jpeg' | 'png';
  } = {}
) {
  // Use Cloudflare Images or external service
  const optimized = await fetch('https://api.cloudflare.com/client/v4/images/v1', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.CLOUDFLARE_API_KEY}`,
    },
    body: new FormData({
      file: new Blob([imageBuffer]),
      ...options,
    }),
  });
  
  return optimized;
}

Tenant Routing

Subdomain-based Routing

Fataplus uses subdomain routing to isolate tenant experiences:
export async function onRequest(
  context: EventContext<Env, any, any>
) {
  const url = new URL(context.request.url);
  const hostname = url.hostname;
  
  // Extract tenant from subdomain
  // Format: [tenant].fata.plus or custom domain
  const parts = hostname.split('.');
  let tenant: string | null = null;
  
  if (hostname.endsWith('.fata.plus') && parts.length > 2) {
    tenant = parts[0];
  } else if (hostname !== 'fata.plus') {
    // Check if custom domain is mapped to a tenant
    tenant = await lookupCustomDomain(context.env, hostname);
  }
  
  if (tenant) {
    // Load tenant configuration
    const config = await getTenantConfig(context.env, tenant);
    
    // Add to context
    context.data.tenant = tenant;
    context.data.tenantConfig = config;
  }
  
  return context.next();
}

Branding API

Retrieve Tenant Branding

curl -X GET https://bknd.fata.plus/api/tenants/agritech-demo/branding \
  -H "Authorization: Bearer YOUR_API_KEY"

Update Tenant Branding

curl -X PATCH https://bknd.fata.plus/api/tenants/agritech-demo/branding \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "colors": {
      "primary": "#2ecc71",
      "accent": "#e74c3c"
    }
  }'

Best Practices

Consistent Branding

Maintain brand consistency across all touchpoints by using the centralized brand configuration

Asset Optimization

Always optimize images and use appropriate formats (SVG for logos, WebP for photos)

Accessibility First

Validate all color combinations for WCAG compliance before deployment

Performance

Leverage R2 and CDN caching to ensure fast asset delivery globally

Next Steps

Figma Integration

Connect Figma designs to automatically sync brand assets

UI Components

Apply your branding to the component library