Skip to content

feat: Instance branding — white-label logos, org name, favicon, Powered by FreeFrame#90

Open
rubenxyz wants to merge 1 commit into
Techiebutler:mainfrom
rubenxyz:feature/instance-branding
Open

feat: Instance branding — white-label logos, org name, favicon, Powered by FreeFrame#90
rubenxyz wants to merge 1 commit into
Techiebutler:mainfrom
rubenxyz:feature/instance-branding

Conversation

@rubenxyz

@rubenxyz rubenxyz commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

What this does

Adds a full instance-level branding / white-labeling feature so self-hosters can replace FreeFrame branding with their own logos, organization name, and favicon. A togglable "Powered by FreeFrame" badge provides subtle attribution.

This was discussed and welcomed by the maintainer. A live demo is running at https://www.kamiko.xyz/freeframe/.

How it works

Single-row DB table instance_branding stores the entire instance brand identity. A default row is auto-seeded on first GET. Admins configure branding via the settings UI — no env vars needed.

Backend (~200 LOC)

  • Model InstanceBranding — 11 columns: org_name, 5 logo S3 keys, primary_color, powered_by_freeframe toggle
  • API routerGET /instance/branding (public), PUT /instance/branding (admin), presigned upload endpoints per logo slot, DELETE to reset logos
  • generate_presigned_put_url() — new helper in s3_service.py that uses the public endpoint so browser-uploaded logos work regardless of internal S3 host
  • Alembic migrationcdcf8e5a6437_add_instance_branding_table

Frontend (~500 LOC)

  • branding-store.ts — fetches from API on init, localStorage cache with fallback chain: API → cache → hardcoded defaults
  • BrandingHead — dynamically sets <title>, <link rel="icon">, <link rel="apple-touch-icon"> via document.head manipulation
  • PoweredByBadge — reusable component (FreeFrame icon + text), gated by poweredByFreeframe toggle
  • BrandingLogoUpload — quick upload (one logo applies to all slots) + advanced 6-slot fine-tune, with error states and loading indicators
  • Settings → Branding page — full rewrite: Quick Upload, Logos & Icons section (6 slots), Greeting text field, Powered toggle, Reset with confirm dialog
  • Sidebar — shows custom logo (theme-aware light/dark) + PoweredByBadge below org name
  • Login page — custom logo, org name greeting ("Welcome to {Org Name}"), powered badge
  • Share page — instance branding fallback when no per-project branding is set (PasswordGate, ErrorState, ShareTopBar)
  • Asset viewer — centered powered badge in top bar
  • Auth layout — removed static logo (login page handles it dynamically)
  • Login form — password-first default step, text sizing polish
  • 3 SVG logo assets — shipped as new FreeFrame defaults (logo-icon.svg, logo-icon-dark.svg, logo-full.svg)

Files changed

New (11 files):

  • apps/api/models/instance_branding.py
  • apps/api/schemas/instance_branding.py
  • apps/api/routers/instance_branding.py
  • apps/api/alembic/versions/cdcf8e5a6437_add_instance_branding_table.py
  • apps/web/components/settings/branding-logo-upload.tsx
  • apps/web/components/settings/branding-preview.tsx
  • apps/web/components/shared/branding-head.tsx
  • apps/web/components/shared/powered-by-badge.tsx
  • apps/web/public/logo-full.svg
  • apps/web/public/logo-icon.svg
  • apps/web/public/logo-icon-dark.svg

Modified (12 files):

  • apps/api/main.py — register instance_branding router
  • apps/api/models/__init__.py — add InstanceBranding import
  • apps/api/services/s3_service.py — add generate_presigned_put_url()
  • apps/web/stores/branding-store.ts — API fetch + new fields + localStorage partialize
  • apps/web/app/layout.tsx — add BrandingHead
  • apps/web/components/layout/sidebar.tsx — custom logo + PoweredByBadge
  • apps/web/app/(auth)/layout.tsx — remove static logo
  • apps/web/app/(auth)/login/page.tsx — custom branding header
  • apps/web/app/share/[token]/page.tsx — instance branding fallback
  • apps/web/app/(dashboard)/projects/[id]/assets/[assetId]/page.tsx — powered badge in top bar
  • apps/web/app/(dashboard)/settings/branding/page.tsx — full rewrite
  • apps/web/components/auth/login-form.tsx — password-first default + text polish

Testing

  • Backend: python -m pytest apps/api/tests/ -v
  • Frontend: pnpm --filter web build
  • Manual: Sign up at https://www.kamiko.xyz/freeframe/, go to Settings → Branding, upload a logo, verify it appears in sidebar, login page, share page, and browser tab

Screenshots

Live at https://www.kamiko.xyz/freeframe/ — branding settings page, sidebar with custom logo + powered badge, login page with custom branding, share page with instance fallback.

…ed by FreeFrame toggle

Adds a single-row InstanceBranding table with CRUD API, presigned
S3 upload slots, and full frontend integration across sidebar,
login page, share page, layout, and asset viewer.

Backend:
- InstanceBranding model + Alembic migration (cdcf8e5a6437)
- InstanceBranding CRUD router with presigned PUT URLs
- generate_presigned_put_url() helper in s3_service.py

Frontend:
- branding-store.ts — fetch from API, localStorage cache, new fields
- BrandingHead — dynamic favicon/apple-icon via document.head
- PoweredByBadge — reusable toggle-gated attribution component
- BrandingLogoUpload — quick upload + advanced 6-slot upload component
- settings/branding page — full rewrite with upload UI + powered toggle
- sidebar — shows custom logo + PoweredByBadge below org name
- login page — custom logo, org name greeting, powered badge
- share page — instance branding fallback when no project branding
- asset viewer — centered powered badge in top bar
- auth layout — removed static logo (login page handles branding)
- login form — password-first default, text sizing polish
- SVG logo assets (logo-icon.svg, logo-icon-dark.svg, logo-full.svg)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant