
Teams and members
A directory of your customers, who belongs to each and what they may do. The thing the rest of your app asks “whose data is this?”
One customer never sees another’s data, not because you remembered a filter, but because the boundary is enforced underneath every query. Teams, roles and invitations included, over the same database connection your app already has.


A directory of your customers, who belongs to each and what they may do. The thing the rest of your app asks “whose data is this?”

Isolation sits underneath your queries rather than inside each one, so a forgotten filter cannot turn into a customer seeing someone else’s data.

Owner, admin and member. Enough to run a real team, few enough that everyone already knows what they mean.

It uses the database connection your app already has. Adding multi-tenancy does not add a service to run or a bill to pay.

Offboarding archives rather than destroys, so the record of who belonged when survives the person leaving.

Single sign-on, directory provisioning and role sync are ready when a big customer wants them: opt-in, not overhead you carry from day one.
Owner, admin, member. The whole permission surface, on purpose.
Work out which customer a request belongs to, from the subdomain, a header, or the path.
Check the person actually belongs to that customer. Only a real member of a real team gets through.
Run the work with the boundary in place, so only that customer’s data is reachable at all.
import { resolve, protect } from '@quxkit/tenant-kit';
// whose request is this, and do they actually belong?
const tenant = await resolve(sql, { host: req.headers.host, user });
// everything inside this block can only reach that customer's data
await protect(sql, tenant, async (db) => {
return db.query('select * from invoices'); // only this tenant's rows
});Isolation you can switch off by forgetting one line was never isolation.