Multi-tenancy
One deployment serves many clients. The tenant is resolved by hostname; everything client-specific hangs off that.
flowchart LR H[request host] --> R[_tenant.js resolveTenant] R --> J[tenants/<slug>.json<br/>branding · gateCodes · odooEnv] J --> BR[brand.js themes the page] J --> CAT[api/catalog serves that catalogue<br/>+ live Odoo prices] J --> SEC[getTenantSecrets → ODOO_URL_<ENV> …] SEC --> LEAD[api/lead → _odoo push to THAT tenant's Odoo]
What is per-tenant
Section titled “What is per-tenant”| Aspect | Where | Secret? |
|---|---|---|
| Branding (logo, colour, title, tagline) | tenants/<slug>.json → brand |
no — in Git |
| Hosts that map to the tenant | tenants/<slug>.json → hosts |
no |
| Access codes | tenants/<slug>.json → gateCodes |
no |
| Product catalogue | tenants/<slug>/catalog.js |
no |
| Logo image | tenants/<slug>/logo.png |
no |
| Odoo / CRM credentials | Vercel env, keyed by odooEnv |
yes — never in Git |
Tenant file shape
Section titled “Tenant file shape”{ "slug": "duratray", "name": "Duratray", "hosts": ["duratraydemo.goask.com.au", "duratray3dconfig.vercel.app"], "brand": { "title": "Duratray 3D Configurator", "logo": "/tenants/duratray/logo.png", "accent": "#E54441", "accentDark": "#c23a37", "tagline": "Complete Traybody Solutions", "legal": "Duratray" }, "gateCodes": ["demo"], "odooEnv": "DURATRAY"}Secrets
Section titled “Secrets”Per-tenant Odoo credentials live only in Vercel env vars, keyed by the tenant’s odooEnv:
ODOO_URL_<ENV> ODOO_DB_<ENV> ODOO_USER_<ENV> ODOO_PW_<ENV>getTenantSecrets() reads these, falling back to the unsuffixed ODOO_URL etc. for local
single-tenant dev.
Live tenants
Section titled “Live tenants”| Tenant | Domain | Odoo |
|---|---|---|
| Oz Chivalry | ozchivalrydemo.goask.com.au |
✅ ozchivalry-odoo.up.railway.app |
| Duratray | duratraydemo.goask.com.au |
❌ not yet (branding + catalogue only) |
To add a new one, see Add a tenant — it is data + config only, no code change.