ADR-0004: Custom domains via Cloudflare for SaaS, dashboard on the core domain
Merchant storefronts are served on custom hostnames through Cloudflare for SaaS by the planned storefront app; the dashboard stays isolated on app.mahalli.sa.
ADR-0004: Custom domains via Cloudflare for SaaS, dashboard on the core domain
- Date: 2026-09-18
- Status: Proposed (seed — awaiting the owner's approval)
- Deciders: the owner, Claude (seed)
- Tags:
infradomain
Context
The original requirement: public pages on the merchant's domain, and the control panel on the project's domain. Thousands of domains need automatic SSL, fast routing, and security isolation from the dashboard.
Decision
The planned apps/storefront (a Cloudflare Worker) serves Custom Hostnames through Cloudflare for SaaS plus the wildcard *.mahalli.sa; apps/saas serves app.mahalli.sa only.
- An edge cache (KV) maps
hostname → { tenantId, siteId }; the source of truth is thedomainstable in Postgres. - Verification by CNAME / TXT, domain status shown in the dashboard, SSL managed by Cloudflare.
- Two different origins → independent cookies, authentication, and CSP.
Consequences
- Positive — zero-touch setup for the merchant, automatic SSL, security isolation of the dashboard.
- Negative — a per-hostname cost for custom domains (covered by the Growth plan and above).
- Revenue impact (SAR / month, confidence) — the custom domain is the lever for upgrading to Growth (+150 SAR per upgraded merchant, medium confidence).
Rejected alternatives
- A generic reverse proxy (Nginx / Caddy) on a VPS: manual certificate management that does not scale.
- Merchant sites on a subdomain only: loses the original requirement and the trust it brings.
References
docs/00-vision.md,docs/02-architecture.md
ADR-0003: Never hold funds — licensed PSP with a sub-merchant model
Every money flow goes through a licensed PSP in the merchant's name; platform revenue from payments comes from the PSP partner programme, never from custody.
ADR-0005: Modules as packages bound by one contract and unlocked by entitlements
Every module, first-party or third-party, exports a ModuleManifest; the registry is the single source of what loads, tables live only in packages/database, and entitlements gate access.