> ## Documentation Index
> Fetch the complete documentation index at: https://vlyai-1c28d863.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-tenancy

> Patterns for apps where multiple customers share infrastructure. Workspaces, isolation, per-tenant limits.

Most B2B SaaS apps are multi-tenant: many customers (workspaces / orgs / accounts) share one application, one database, but never see each other's data. The pattern:

## Schema-level isolation

Every domain table has a `workspaceId` field. Every query filters by it.

```typescript convex/schema.ts theme={null}
projects: defineTable({
  workspaceId: v.id("workspaces"),
  name: v.string(),
  // ...
}).index("by_workspace", ["workspaceId"])

tasks: defineTable({
  projectId: v.id("projects"),
  workspaceId: v.id("workspaces"),  // denormalized for query speed
  title: v.string(),
}).index("by_workspace", ["workspaceId"])
  .index("by_project", ["projectId"])
```

## Auth-aware scoping

In every query / mutation that returns workspace data, filter by the active workspace:

```typescript theme={null}
export const list = query({
  args: { workspaceId: v.id("workspaces") },
  handler: async (ctx, { workspaceId }) => {
    const u = await ctx.auth.getUserIdentity();
    if (!u) throw new Error("Not signed in");

    // Verify the user is a member of this workspace
    const membership = await ctx.db
      .query("memberships")
      .withIndex("by_user_and_workspace",
        q => q.eq("userId", u.subject).eq("workspaceId", workspaceId))
      .unique();
    if (!membership) throw new Error("Not a member");

    return await ctx.db
      .query("projects")
      .withIndex("by_workspace", q => q.eq("workspaceId", workspaceId))
      .collect();
  },
});
```

The membership check is the **gate**; the workspace filter is the **scope**.

## URL-scoped routing

Routes include the workspace slug: `/w/{slug}/projects`, `/w/{slug}/settings`. The slug → workspaceId resolution happens in a layout component.

## Per-tenant limits

For "free tier max 3 projects":

```typescript theme={null}
export const create = mutation({
  args: { workspaceId: v.id("workspaces"), name: v.string() },
  handler: async (ctx, { workspaceId, name }) => {
    const workspace = await ctx.db.get(workspaceId);
    if (!workspace) throw new Error("Not found");

    const projectCount = await ctx.db
      .query("projects")
      .withIndex("by_workspace", q => q.eq("workspaceId", workspaceId))
      .collect()
      .then(arr => arr.length);

    const limits = { free: 3, pro: Infinity, business: Infinity };
    if (projectCount >= limits[workspace.planTier]) {
      throw new ConvexError({ code: "limit_exceeded", message: "Upgrade to add more projects" });
    }

    return await ctx.db.insert("projects", { workspaceId, name });
  },
});
```

## Common patterns

<CardGroup cols={2}>
  <Card title="Workspaces + memberships" icon="users">
    Standard. A user can belong to many workspaces. See the [Multi-tenant SaaS recipe](/recipes/multi-tenant-saas).
  </Card>

  <Card title="Personal + team workspaces" icon="user">
    Each new user gets a personal workspace; can create / join shared ones later.
  </Card>

  <Card title="Subdomain per workspace" icon="globe">
    `acme.yourapp.com`, `betacorp.yourapp.com`. Adds setup complexity but feels premium.
  </Card>

  <Card title="Database-per-tenant (don't)" icon="x-circle">
    Common in legacy systems; rarely worth it on Convex. Schema-level isolation is simpler and just as secure.
  </Card>
</CardGroup>

## Related

<CardGroup cols={3}>
  <Card title="Multi-tenant SaaS recipe" icon="building" href="/recipes/multi-tenant-saas">
    Full walkthrough.
  </Card>

  <Card title="Roles & permissions" icon="shield-check" href="/features/auth/roles-and-permissions">
    Per-workspace roles.
  </Card>

  <Card title="Data access control" icon="users-round" href="/security/data-access-control">
    Defending against leaks.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.