Skip to content

Cross-Schema Queries

Join tenant and shared tables with type safety.

When to Use

ApproachUse When
withShared()Default choice. Automatic schema detection, less boilerplate
createCrossSchemaQuery()Need explicit control over schema names, dynamic schemas

Common Use Cases

  • E-commerce: Join tenant orders with shared product catalog
  • SaaS Billing: Join tenant usage with shared pricing plans
  • Workflows: Join tenant tasks with shared workflow definitions
  • Multi-region: Join tenant data with shared configuration

Automatic schema detection based on your configuration:

typescript
import { withShared } from 'drizzle-multitenant';
import { eq } from 'drizzle-orm';
import * as tenantSchema from './schemas/tenant';   // { orders, customers }
import * as sharedSchema from './schemas/shared';   // { plans, workflowSteps }

const tenantDb = tenants.getDb('tenant-123');
const sharedDb = tenants.getSharedDb();

const result = await withShared(tenantDb, sharedDb, {
  tenant: tenantSchema,
  shared: sharedSchema,
})
  .from(orders)                    // Auto-detected as tenant table
  .leftJoin(plans,                 // Auto-detected as shared table
    eq(orders.planId, plans.id)
  )
  .innerJoin(customers,            // Auto-detected as tenant table
    eq(orders.customerId, customers.id)
  )
  .select({
    orderId: orders.id,
    customerName: customers.name,
    planName: plans.name,
    planPrice: plans.price,
  })
  .where(eq(orders.status, 'active'))
  .limit(50)
  .execute();

Features

  • Automatic detection of tenant vs shared tables
  • All join types: leftJoin, innerJoin, rightJoin, fullJoin
  • Full query builder: where, orderBy, limit, offset
  • Type-safe results inferred from select fields

createCrossSchemaQuery() - Manual Control

For explicit schema control:

typescript
import { createCrossSchemaQuery } from 'drizzle-multitenant';

const query = createCrossSchemaQuery({
  tenantDb: tenants.getDb('tenant-123'),
  sharedDb: tenants.getSharedDb(),
  tenantSchema: 'tenant_123',
  sharedSchema: 'public',
});

const result = await query
  .from('tenant', orders)
  .leftJoin('shared', plans, eq(orders.planId, plans.id))
  .select({
    orderId: orders.id,
    planName: plans.name,
  })
  .execute();

Complex Example

typescript
const salesReport = await withShared(tenantDb, sharedDb, {
  tenant: tenantSchema,
  shared: sharedSchema,
})
  .from(orders)
  .innerJoin(customers, eq(orders.customerId, customers.id))
  .innerJoin(plans, eq(orders.planId, plans.id))
  .leftJoin(workflowSteps, eq(orders.currentStep, workflowSteps.id))
  .select({
    orderId: orders.id,
    orderDate: orders.createdAt,
    customerName: customers.name,
    customerEmail: customers.email,
    planName: plans.name,
    planPrice: plans.price,
    currentStep: workflowSteps.name,
  })
  .where(
    and(
      eq(orders.status, 'completed'),
      gte(orders.createdAt, startDate),
      lte(orders.createdAt, endDate)
    )
  )
  .orderBy(desc(orders.createdAt))
  .limit(100)
  .execute();

Released under the MIT License.