Weave
1.x
Docs/Weave/Multi-Tenancy
Open

Reading3 min
Updated31 Jul 2026
Sourcev1/concepts/multi-tenancy.mdx

Weave is tenant-scoped by design. Every entity — collection, document, chunk — carries a TenantID and AppID. These are automatically stamped from the context at every operation, and all store queries enforce them.

Setting the scope01

Use the weave.WithTenant and weave.WithApp helpers to inject scope into a context:

import "github.com/xraph/weave"

ctx = weave.WithTenant(ctx, "tenant-1")
ctx = weave.WithApp(ctx, "myapp")

Both values are propagated automatically — you do not need to pass them to individual engine.* calls. Every ingestion, retrieval, collection create, and delete operation reads the tenant and app from the context.

Scope enforcement02

The engine calls weave.TenantFromContext(ctx) and weave.AppFromContext(ctx) before every store operation and injects the values as WHERE clause filters:

-- MetadataStore query (internal)
SELECT * FROM weave_collections
WHERE tenant_id = $1 AND app_id = $2 AND id = $3

If TenantID is empty, the engine returns ErrInvalidInput — operations without a tenant scope are rejected.

If a caller passes a collection ID that belongs to a different tenant, the store returns no rows, and the engine surfaces ErrCollectionNotFound. No cross-tenant data is accessible, even if the caller knows an ID from another tenant.

AppID and TenantID03

FieldPurpose
TenantIDIsolates data between tenants in a multi-tenant SaaS
AppIDFurther namespace within a tenant (e.g. multiple apps per account)

Both are strings — use whatever format suits your application (UUID, slug, etc.). A typical setup:

// SaaS: per-customer isolation
ctx = weave.WithTenant(ctx, customer.ID)
ctx = weave.WithApp(ctx, "weave-service")

// Mono-tenant: use a constant tenant
ctx = weave.WithTenant(ctx, "default")
ctx = weave.WithApp(ctx, "myapp")

Vector store isolation04

The vector store layer also enforces tenant isolation. When upserting vectors, Weave stores tenant_id and collection_id as metadata on each vector entry. The VectorStore.Search call filters by both fields:

// Internal — callers never call this directly
vectorStore.Search(ctx, queryVector, &vectorstore.SearchOptions{
    Filter: map[string]string{
        "tenant_id":     tenantID,
        "collection_id": collectionID,
    },
    TopK: topK,
})

This ensures that even if two tenants have documents with identical content, their vector searches do not cross-contaminate.

Background jobs05

For background jobs (reindexing, cleanup), inject the tenant scope explicitly from a stored tenant ID rather than relying on an HTTP request context:

func reindexTenant(engine *weave.Engine, tenantID, colID string) error {
    ctx := context.Background()
    ctx = weave.WithTenant(ctx, tenantID)
    ctx = weave.WithApp(ctx, "myapp")
    return engine.Reindex(ctx, colID)
}