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
src/config/tenants.ts
Backend Collection Schema
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:
Access Brand Settings
Navigate to your tenant dashboard at https://[tenant].fata.plus/admin/branding
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)
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
wrangler.toml
src/api/brand/upload.ts
# 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:
src/utils/theme-generator.ts
src/layouts/TenantLayout.astro
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:
src/utils/accessibility.ts
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:
Google Fonts Integration
Custom Font Upload
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:
src/api/assets/[tenant]/[...path].ts
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:
src/utils/image-optimization.ts
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:
middleware.ts
Custom Domain Mapping
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
PATCH Request
TypeScript SDK
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