Multi-Tenant Data Isolation
User Intent
"How do I isolate data for multiple tenants/users in my SaaS application?"
Operation
Concept: Per-user data isolation within a single Graphlit project
SDK Method: createUser() + userId parameter in client initialization
Use Case: SaaS applications with multiple users/tenants
Multi-Tenant Pattern (Recommended)
Strategy: Per-User Scoping in Single Project
Graphlit supports multi-tenancy by creating users within your project and scoping SDK operations to each user.
import { Graphlit, Types } from 'graphlit-client';
// 1. Admin client (no user scoping) for user management
const adminClient = new Graphlit({
organizationId: process.env.GRAPHLIT_ORGANIZATION_ID!,
environmentId: process.env.GRAPHLIT_ENVIRONMENT_ID!,
jwtSecret: process.env.GRAPHLIT_JWT_SECRET!,
});
// 2. When a user signs up (e.g., via Clerk, Supabase, Auth0)
async function onUserSignUp(authUser: { id: string; name: string; email: string }) {
// Create user in Graphlit (maps to your auth provider's user)
const user = await adminClient.createUser({
name: authUser.name,
identifier: authUser.id, // Your auth provider's user ID (Clerk, Supabase, etc.)
type: Types.UserTypes.Human,
});
// Store user.createUser.id in your database alongside auth user
await db.users.update(authUser.id, {
graphlitUserId: user.createUser.id
});
}
// 3. On each user request, create scoped client
async function handleUserRequest(authUserId: string, request: any) {
// Get Graphlit user ID from your database
const graphlitUserId = await db.users.getGraphlitUserId(authUserId);
// Create user-scoped client
const userClient = new Graphlit({
organizationId: process.env.GRAPHLIT_ORGANIZATION_ID!,
environmentId: process.env.GRAPHLIT_ENVIRONMENT_ID!,
jwtSecret: process.env.GRAPHLIT_JWT_SECRET!,
userId: graphlitUserId, // ← ALL operations now scoped to this user
});
// All content, conversations, and feeds are automatically isolated
const results = await userClient.queryContents({
filter: { search: request.query },
});
return results; // Only returns this user's content
}What Gets Scoped?
When you pass userId to the Graphlit client:
User-Scoped (Isolated per User):
✅ Content - Documents, files, web pages ingested by this user
✅ Conversations - Chat history and AI interactions
✅ Feeds - Connected data sources (Slack, Gmail, etc.)
Project-Scoped (Shared Across Users):
📋 Specifications - AI model configurations
📋 Workflows - Content processing pipelines
This means you define AI models and workflows once at the project level, and each user gets isolated data automatically.
Complete Example: Multi-Tenant SaaS
import { Graphlit, Types } from 'graphlit-client';
export class GraphlitService {
private adminClient: Graphlit;
constructor() {
// Admin client for user management
this.adminClient = new Graphlit({
organizationId: process.env.GRAPHLIT_ORGANIZATION_ID!,
environmentId: process.env.GRAPHLIT_ENVIRONMENT_ID!,
jwtSecret: process.env.GRAPHLIT_JWT_SECRET!,
});
}
// Create user on signup
async createUser(authUser: { id: string; name: string; email: string }) {
// Check if user already exists
try {
const existing = await this.adminClient.getUserByIdentifier(authUser.id);
if (existing.userByIdentifier) {
return existing.userByIdentifier.id;
}
} catch (error) {
// User doesn't exist, create new one
}
const user = await this.adminClient.createUser({
name: authUser.name,
identifier: authUser.id, // Clerk/Supabase/Auth0 user ID
type: Types.UserTypes.Human,
});
return user.createUser.id;
}
// Get user-scoped client
getUserClient(graphlitUserId: string): Graphlit {
return new Graphlit({
organizationId: process.env.GRAPHLIT_ORGANIZATION_ID!,
environmentId: process.env.GRAPHLIT_ENVIRONMENT_ID!,
jwtSecret: process.env.GRAPHLIT_JWT_SECRET!,
userId: graphlitUserId,
});
}
}
// Usage in your API routes
const graphlitService = new GraphlitService();
// POST /api/signup
async function handleSignup(req: Request) {
const { clerkUserId, name, email } = req.body;
// Create Graphlit user
const graphlitUserId = await graphlitService.createUser({
id: clerkUserId,
name,
email,
});
// Store in your database
await db.users.create({
clerkUserId,
graphlitUserId,
name,
email,
});
}
// POST /api/documents/upload
async function handleUpload(req: Request) {
const { userId } = req.auth; // From your auth middleware
const { documentUrl } = req.body;
// Get user's Graphlit client
const dbUser = await db.users.findByClerkId(userId);
const client = graphlitService.getUserClient(dbUser.graphlitUserId);
// Upload document (automatically scoped to this user)
const content = await client.ingestUri(
documentUrl,
'User Document',
undefined,
undefined,
true
);
return { contentId: content.ingestUri.id };
}
// POST /api/chat
async function handleChat(req: Request) {
const { userId } = req.auth;
const { message, conversationId } = req.body;
const dbUser = await db.users.findByClerkId(userId);
const client = graphlitService.getUserClient(dbUser.graphlitUserId);
// Chat only accesses this user's content
const response = await client.promptConversation(
message,
conversationId
);
return { reply: response.promptConversation.message?.message };
}Multi-Tenant Benefits
Complete Data Isolation: Each user's content/conversations/feeds are isolated Single Project Management: One project, shared specifications/workflows Scalable: Thousands of users per project Auth Integration: Works with Clerk, Supabase, Auth0, custom auth Usage Tracking: Per-user usage tracking available
Implementation Checklist
Alternative: Legacy ownerId (Deprecated)
The legacy ownerId parameter is still supported but deprecated. Use userId for new applications.
Last updated
Was this helpful?