Skip to content

Multi-Tenancy ​

Cognipeer Console is a fully multi-tenant platform with complete data isolation per company. Each tenant gets a dedicated database — either a separate SQLite file (default) or a separate MongoDB database.

Database Backends ​

SQLite (default) ​

Each tenant gets its own .sqlite file. No external services required:

data/
├── console_main.sqlite       ← Shared database
│   ├── tenants               ← Tenant registry
│   └── tenant_user_directory ← Cross-tenant email lookup
├── tenant_acme.sqlite        ← Tenant "acme"
│   ├── users
│   ├── api_tokens
│   ├── models
│   ├── providers
│   ├── vector_indexes
│   ├── guardrails
│   ├── prompts
│   ├── memory_stores
│   ├── rag_modules
│   └── ...
└── tenant_globex.sqlite      ← Tenant "globex"

Enable with: DB_PROVIDER=sqlite (default — no env var needed).

MongoDB ​

MongoDB Server
├── console_main              ← Shared/Main database
│   ├── tenants               ← Tenant registry
│   └── tenant_user_directory ← Cross-tenant email lookup
├── tenant_acme               ← Tenant "acme" database
│   ├── users
│   ├── api_tokens
│   ├── models
│   ├── providers
│   ├── vector_indexes
│   ├── guardrails
│   ├── prompts
│   ├── memory_stores
│   ├── rag_modules
│   └── ...
├── tenant_globex             ← Tenant "globex" database
│   └── ...
└── ...

Enable with: DB_PROVIDER=mongodb and set MONGODB_URI.

Note: The database abstraction layer ensures all application code works identically with both backends. Switch by changing a single environment variable.

Tenant Model ​

Each tenant has:

FieldDescription
companyNameDisplay name
slugURL-friendly identifier (unique)
dbNameDatabase name (tenant_{slug})
licenseTypeActive license tier
ownerIdOwner user ID

Projects & Members ​

Inside a tenant, work is scoped into projects and access is granted to members. Both are managed from the dashboard.

Projects (Configure → Projects) carry the active-project context that every feature reads — switching the header project pill changes the scope of models, vector stores, agents, and usage:

Projects

Members (Configure → Members) lists the tenant's users and pending invitations, each with a role (owner, user, …) and status (active, invited). A user created as a Programmatic User (canLogin: false — no password, email optional) shows a "No login" status badge instead, plus a "Programmatic" badge next to its role; see Authentication for what that flag means. The Groups tab manages group-based tenant/project grants:

Members

Add User modal replaces invite-only (September 2026)

Configure → Members → Add User now opens a modal that can create a member directly (with a password) or a Programmatic User (no password) in one flow, instead of the old invite-only entry point. A Send invite toggle controls whether the new member gets an email to set their own password. Members can also trigger a password-reset for any user from their row, independent of the invite flow.

Add User modal — the "Can log in" toggle turns a normal member into a Programmatic User

Access inside a project ​

Each member (a person, or a group assigned to the project) can carry a level per service for a project. These project service permissions only ever lower what the workspace level allows, and API tokens run under the project limits of the project they are bound to. Groups raise access at the workspace level and open the projects they are assigned to. The rules, and the custom policies that add exceptions, are in Authorization (IAM).

Bulk import ​

For onboarding many members at once, Configure → Members → Import accepts a CSV upload instead of adding rows one at a time. Failed rows are reported individually rather than aborting the whole import, so a handful of bad emails don't block the rest of the batch.

Database Abstraction ​

All database access goes through the abstraction layer — never import MongoDB directly:

typescript
import { getDatabase, getTenantDatabase } from '@/lib/database';

// Main database (tenant registry)
const db = await getDatabase();
const tenant = await db.findTenantBySlug('acme');

// Tenant database (user data, configs, etc.)
const tenantDb = await getTenantDatabase('tenant_acme');
const users = await tenantDb.listUsers();

switchToTenant Pattern ​

typescript
const db = await getDatabase();
await db.switchToTenant(`tenant_${slug}`);
// All subsequent queries are scoped to this tenant
const user = await db.findUserByEmail(email);

Tenant Context in Routes ​

Middleware injects tenant identity into request headers:

HeaderDescription
x-tenant-idTenant ObjectId
x-tenant-slugTenant slug
x-tenant-db-nameFull database name (tenant_acme)

Dashboard Routes ​

typescript
export const GET = withRequestContext(async (request) => {
  const tenantDbName = request.headers.get('x-tenant-db-name')!;
  const tenantId = request.headers.get('x-tenant-id')!;
  
  // All queries scoped to this tenant
  const db = await getTenantDatabase(tenantDbName);
  const models = await db.listModels();
  
  return NextResponse.json({ data: models });
});

Client API Routes ​

typescript
export async function POST(request: NextRequest) {
  const ctx = await requireApiToken(request);
  // ctx.tenantDbName, ctx.tenantId, ctx.tenantSlug — all available
  // Database already switched to tenant
}

Tenant Creation ​

When a user registers, the system:

  1. Creates a tenant record in console_main.tenants
  2. Creates a new database tenant_{slug}
  3. Creates the user as owner in the tenant database
  4. Initializes default collections and indexes
  5. Generates a JWT with tenant info embedded

Security Rules ​

  • Never create query patterns that can return cross-tenant data
  • Always call switchToTenant() before user/token operations
  • Use middleware-injected headers as the source of truth for tenant identity
  • Never trust client-supplied tenant identifiers without verification

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub