---
title: Multi-Tenancy
description: How Sentinel scopes suites, cases, runs, and all entities to app and tenant contexts.
---

Every Sentinel entity is scoped to an **app** (logical application boundary) and optionally to a **tenant** (user/organization boundary). This scoping is enforced at the store layer — cross-app access is structurally impossible.

## Context injection

Scope identifiers are injected into the Go context using helper functions from the root `sentinel` package:

```go
import "github.com/xraph/sentinel"

ctx = sentinel.WithTenant(ctx, "org-123")
ctx = sentinel.WithApp(ctx, "myapp")
```

## Extraction

Retrieve scope values from any context:

```go
tenantID := sentinel.TenantFromContext(ctx) // "org-123"
appID    := sentinel.AppFromContext(ctx)    // "myapp"
```

Both functions return an empty string if no value is set.

## AppID vs TenantID

| Scope | Purpose | Required | Example |
|-------|---------|----------|---------|
| **AppID** | Logical application boundary. All domain entities (suites, cases, baselines, prompt versions) carry an `AppID` field. | Yes | `"myapp"`, `"staging"` |
| **TenantID** | User or organization boundary. Used for execution entities (runs, results) to isolate per-user data. | Optional | `"org-123"`, `"user-456"` |

## Store enforcement

The store enforces app scoping on every query:

- **Suite** — `GetSuiteByName` and `ListSuites` filter by `app_id`
- **Case** — all queries scoped through the parent suite's app
- **Run** — queries filter by `app_id` and optionally by `target_tenant_id`
- **Baseline** — scoped through the parent suite's app
- **Cross-scope access** — returns `ErrNotFound` even if the entity exists under a different app

## API integration

When using the Forge extension, the API layer extracts the app ID from the request context (typically set by Forge middleware) and passes it to all engine operations. This means:

- Each API request is automatically scoped to the caller's app
- Suites in app A cannot see or modify suites in app B
- Run history is isolated per tenant within each app

## Example: multi-tenant setup

```go
// App A creates a suite
ctxA := sentinel.WithApp(context.Background(), "app-a")
eng.CreateSuite(ctxA, suiteA)

// App B creates a different suite
ctxB := sentinel.WithApp(context.Background(), "app-b")
eng.CreateSuite(ctxB, suiteB)

// App A cannot see App B's suites
suites, _ := eng.ListSuites(ctxA, &suite.ListFilter{AppID: "app-b"})
// suites is empty — app isolation enforced
```
