Multi-tenancy
How standards scopes every request to a tenant context automatically.
Every standards request runs inside a tenant context. The schema, hooks, and adapters all scope automatically — your code rarely passes a tenantId directly.
Backend (NestJS)
TenantResolverMiddleware extracts the tenant slug from the X-Stndrds-Tenant-Slug request header, looks up the corresponding tenant id via an LRU cache (30-second TTL), then writes X-Tenant-ID onto the request. The TenantContextInterceptor from @stndrds/adapter-nestjs reads that header and stores the id in AsyncLocalStorage. Every downstream service and adapter then reads the context implicitly — no argument threading required.
In development, the middleware falls back to the DEFAULT_TENANT_SLUG environment variable when no header is present.
Register the middleware globally in your AppModule:
// @noverify
configure(consumer: MiddlewareConsumer) {
consumer.apply(TenantResolverMiddleware).forRoutes("*");
}See the NestJS backend guide for the full wiring, including TenantCacheService registration and the cache-bust endpoint.
Database isolation
The Supabase adapter reads the tenant id from AsyncLocalStorage and passes it to Postgres row-level security policies on every query. Migrations generate tenant-scoped RLS policies so that every SELECT, INSERT, UPDATE, and DELETE is automatically filtered. There is no shared query path that bypasses these policies when you go through the adapter.
Frontend (React)
SchemaClientProvider attaches the tenant slug to every outbound fetch as the X-Stndrds-Tenant-Slug header. The slug is typically derived from the subdomain or the current session. The server-side middleware resolves it to a tenantId on every request.
To read the current user's identity and tenant from a component, use useMyProfile() from @stndrds/react:
// @noverify
import { useMyProfile } from "@stndrds/react";
const { data: me } = useMyProfile();
// me.id — current actor (UserProfile id)
// me.tenantId — current tenantNever access the database directly, bypassing the adapter. Direct queries skip AsyncLocalStorage and break tenant isolation — RLS policies will either reject the query or return rows from the wrong tenant depending on the Postgres role in use.