TUTORIAL 05 · ADVANCED
Build a Multi-Tenant SaaS
Add Tenant identity and tenant-scoped Ticket access so one generated ServiceDesk can safely serve multiple organizations.
By the end of this tutorial
- Declare a Tenant entity
- Scope Ticket by tenantId
- Combine tenant isolation with ownership
- Understand the x-raiql-tenant-id request context
Add tenant identity
CREATE TENANT ENTITY establishes the tenant identity model. TENANT Ticket BY tenantId makes tenantId a server-managed scope in generated PostgreSQL APIs.
app.raiql
CREATE APPLICATION "RAIQL ServiceDesk";
TYPE WEB;
USE STACK
FRONTEND NEXTJS,
BACKEND NEXTJS,
DATABASE POSTGRESQL;
CREATE TENANT ENTITY Tenant;
CREATE USER ENTITY User;
CREATE ENTITY Ticket (
id UUID PRIMARY,
title STRING REQUIRED,
description STRING,
status STRING REQUIRED,
priority STRING REQUIRED,
ownerId UUID REQUIRED,
tenantId UUID REQUIRED
);
CREATE CRUD Tenant;
CREATE API Tenant;
CREATE CRUD Ticket;
CREATE API Ticket;
ADD AUTHENTICATION EMAIL_PASSWORD, JWT;
ADD ROLE ADMIN, AGENT, USER;
DEFAULT ROLE USER;
RELATE Ticket.ownerId TO User.id;
RELATE Ticket.tenantId TO Tenant.id;
OWN Ticket BY ownerId;
TENANT Ticket BY tenantId;
ALLOW ADMIN CREATE Ticket, READ Ticket, UPDATE Ticket, DELETE Ticket, CREATE Tenant, READ Tenant, UPDATE Tenant, DELETE Tenant;
ALLOW AGENT READ Ticket, UPDATE Ticket;
ALLOW USER CREATE OWN Ticket, READ OWN Ticket, UPDATE OWN Ticket;
ENVIRONMENT development, staging, production;
SECRET DATABASE_URL;
SECRET RAIQL_JWT_SECRET;
BUILD;
DEPLOY DOCKER;Tenant and ownership are different
- Tenant scope answers: which organization may see this record?
- Ownership answers: which user owns this record?
- A request can be both tenant-scoped and owner-scoped
- ADMIN permissions can still be broader within the generated policy
Generate the SaaS application
terminal
raiql validate
raiql generate
raiql dev --env developmentSend tenant context
Generated tenant-aware APIs use x-raiql-tenant-id. The generated TypeScript SDK can also carry tenant context so application callers do not hand-build the header on every request.
HTTP
GET /api/ticket
Authorization: Bearer <jwt>
x-raiql-tenant-id: <tenant-uuid>Isolation rule
RAIQL ruleTenant-scoped generated CRUD must never trust tenantId from the request body. The adapter applies tenant context server-side.