Skip to content
Packages Examples Agents Blog Get started

Multi-Tenancy

oridecon-tenancy adds first-class multi-tenancy — identifying the current tenant, isolating its data, and propagating its context safely across async calls.

Install it when you need it, not on day one:

Terminal window
uv add oridecon-tenancy
# or, from a scaffolded app:
oridecon add tenancy

Config lives in application.yaml under tenancy:. Feature types stay in domains/; the tenancy provider is app-root di/ or the module’s provider.py.

For the full configuration reference, see the oridecon-tenancy package docs.


  1. Resolution — determine which tenant a request belongs to.
  2. Isolation — separate tenant data (row, schema, or database).
  3. Enforcement — bind the current context to the resolved tenant and reject requests that violate it.

Resolution runs at the edge of the request pipeline. Resolvers are tried in order; the first match wins.

ResolverSourceUse case
headerX-Tenant-ID headerAPI integrations, mobile apps
jwt_claima claim in the auth tokenOAuth2 / OIDC requests
subdomaintenant.app.comclassic SaaS
path/api/v1/{tenant}/...multi-org public portals
from oridecon import Application
from oridecon.tenancy import TenancyModule, TenancyConfig, ResolutionConfig
app = Application(name="my-saas")
app.add_module(
TenancyModule.configure(
TenancyConfig(resolution=ResolutionConfig(resolvers=["header", "jwt_claim"]))
)
)

Add TenancyModule.configure(...) next to WebModule in create_app(). Pair JWT claims with Authentication.

For tests, TenancyModule.stub() provides an in-memory, header-only setup with no isolation overhead.


Once resolved, a TenantContextMiddleware stores the tenant id in a ContextVar, so it follows your code across await boundaries without being threaded through every function signature. Tenant-aware services and repositories read the current tenant from that context automatically.

See the oridecon-tenancy package docs for the exact context-accessor and tenant-scoping decorator APIs.


StrategyHowTrade-off
Row-level (shared table)every row carries a tenant_idcheapest; relies on consistent filtering
Schema (shared DB)one Postgres schema per tenantstronger isolation, moderate ops overhead
Database (separate DBs)a database per tenantstrongest isolation; highest ops cost

The isolation strategy is pluggable per tenant via the package’s strategy registry.


Mark routes as tenant-scoped so a request without a resolved, authorized tenant is rejected (401 if no tenant is present, 403 if the user doesn’t belong to it). The tenancy middleware validates the resolved tenant before the handler runs.