A client app is a separate tenant inside an org's namespace — its own configuration, its own users, its own URL. Use client apps to run multiple distinct SaaS products, embedded extensions, or per-customer white-labels off a single AppEngine deployment without spinning up new orgs. The Client Apps module at /client-app/* exposes CRUD over these tenant records.
What a client app is
A client app sits one level above a site and one level below an org:
- Org — your top-level tenant (your platform). One org per business.
- Client App — a configured product/tenant inside the org. Multiple per org.
- Site — a deployable web property serving a client app. Multiple per app.
For a typical SaaS where every customer is a User and the storefront serves all of them, you don't need client apps — one site at one domain is enough. For a platform that hosts multiple distinct products under one corporate identity (e.g. an agency running 50 client micro-SaaS, or a parent company with a portfolio of brands), client apps map cleanly.
Surface
/client-appJWT/client-app/:idJWT/client-appJWT/client-app/:idJWT/client-app/:idJWTStandard REST. GET /client-app accepts a ?status= filter; GET /client-app/:id returns one. Permissions follow the platform's RBAC: read, create, update, delete on the content type.
const app = await fetch('/api/client-app', {
method: 'POST',
headers: { orgid: ORG_ID, Authorization: `Bearer ${jwt}` },
body: JSON.stringify({
name: 'Acme Tools - Client A',
appCode: 'acme-clienta',
status: 'active',
config: {
brandColor: '#cc0000',
logo: 'https://cdn.../clienta-logo.svg',
domain: 'tools.clienta.com',
},
features: ['storefront', 'crm', 'support-tickets'],
}),
}).then(r => r.json());
Tenant isolation
Client apps within an org share the underlying data layer but use the appCode as a filter on every record. Application code (or the platform's automatic scoping) appends where: { appCode: 'acme-clienta' } to every query, so data created in one client app isn't visible in another.
This is a softer boundary than org-level tenancy:
- Org boundary — enforced at the auth-and-data layer. Organisation A literally cannot read organisation B's data.
- Client app boundary — enforced by application convention. A bug in your code that omits the
appCodefilter would cross the boundary.
For PHI/PII-sensitive multi-tenancy, prefer separate orgs. For brand/UX multi-tenancy where the operator team is shared, client apps work.
Configuration
The config field on a client app is a free-form JSON blob — branding, feature flags, custom copy, integration overrides. The platform's site rendering layer reads config when a request hits a domain mapped to a client app and applies it to the response (theme tokens, logo, favicon).
Common config keys:
brandColor,logo,favicon— visual brandingdomain— the primary public hostname for the appsupportEmail— destination for support ticket creationdisabledFeatures— list of platform features to hide from this app's userscustomCSS/customJS— inline overrides applied to served pages
Wiring a domain
A client app doesn't have its own domain registration. It points at one or more sites:
- 1
Create the client app
POST /client-appas above. Note theidandappCode. - 2
Create or claim a site
POST /site/add(see Site and deployment). PassclientAppIdso the site is associated. - 3
Attach a domain to the site
POST /site/attach-domainwith the domain. The site's container starts serving on that domain with the client app's branding applied.
Updating
PUT /client-app/:id accepts a partial — send only the fields you're changing. The platform writes a modifyuser and modifydate so changes are auditable.
Deletion
DELETE /client-app/:id is a soft delete by default — the record is marked state: deleted so historical data attribution still resolves. Sites attached to the app become orphaned (still serving, but not associated with a live app); the platform's site management UI surfaces these for cleanup.
For a hard delete (purge data and tear down sites), pair with the org-management delete endpoint or use the data-layer's purge utilities.
When not to use client apps
- Single SaaS, multiple customers — your customers are Customer principals; you don't need client apps. One site, one product.
- Multi-region of the same product — use multiple sites, not multiple client apps. Region is a routing concern, not a tenancy concern.
- A/B testing — same site, different feature flags via the rule engine. Don't fork into a new client app for an experiment.
The client-app abstraction is most useful for agency-style platforms and white-label resellers. If you're building a single product, ignore this module entirely — the platform works fine without it.
Related
- Site and deployment — the layer that actually serves the client app's HTTP traffic
- Org management — the layer above client apps (the tenant boundary)