Multi-tenancy allows a single Credo agent instance to manage multiple isolated tenants, each with their own wallet, storage, and configuration.
Overview
Multi-tenancy is useful for:
SaaS platforms - One agent serving multiple customers
Enterprise deployments - Separate contexts for departments or users
Mediator services - Managing multiple edge agents
Wallet-as-a-Service - Providing wallet infrastructure
Each tenant has:
Isolated storage and wallet
Independent configuration
Separate DIDs and credentials
Own connection records
Installation
npm install @credo-ts/tenants
Basic Setup
Create a root agent with the TenantsModule:
import { Agent } from '@credo-ts/core'
import { TenantsModule } from '@credo-ts/tenants'
import { AskarModule } from '@credo-ts/askar'
import { agentDependencies } from '@credo-ts/node'
import { askar } from '@openwallet-foundation/askar-nodejs'
const rootAgent = new Agent ({
config: {
logger: new ConsoleLogger ( LogLevel . info ),
},
dependencies: agentDependencies ,
modules: {
// Root storage
askar: new AskarModule ({
askar ,
store: {
id: 'root-agent' ,
key: 'root-agent-key' ,
},
}),
// Enable multi-tenancy
tenants: new TenantsModule (),
},
})
await rootAgent . initialize ()
Creating Tenants
Create a new tenant with isolated storage:
const tenantRecord = await rootAgent . modules . tenants . createTenant ({
config: {
label: 'Alice Tenant' ,
},
})
console . log ( 'Tenant ID:' , tenantRecord . id )
console . log ( 'Tenant Label:' , tenantRecord . config . label )
Access the tenant’s agent instance:
const tenantAgent = await rootAgent . modules . tenants . getTenantAgent ({
tenantId: tenantRecord . id ,
})
// Use the tenant agent like a normal agent
const connections = await tenantAgent . connections . getAll ()
// Always end the session when done
await tenantAgent . endSession ()
Automatic session management:
await rootAgent . modules . tenants . withTenantAgent (
{ tenantId: tenantRecord . id },
async ( tenantAgent ) => {
// Work with the tenant agent
const invitation = await tenantAgent . oob . createInvitation ()
return invitation
}
)
// Session automatically ended
Tenant Configuration
Configure individual tenants:
const tenantRecord = await rootAgent . modules . tenants . createTenant ({
config: {
label: 'Corporate Tenant' ,
// Tenant-specific logger level
logger: new ConsoleLogger ( LogLevel . debug ),
},
})
Managing Tenants
Get Tenant by ID
const tenant = await rootAgent . modules . tenants . getTenantById ( tenantId )
console . log ( 'Tenant:' , tenant . config . label )
Find Tenants by Label
const tenants = await rootAgent . modules . tenants . findTenantsByLabel ( 'Alice' )
Get All Tenants
const allTenants = await rootAgent . modules . tenants . getAllTenants ()
console . log ( 'Total tenants:' , allTenants . length )
Query Tenants
const tenants = await rootAgent . modules . tenants . findTenantsByQuery (
{
config: {
label: 'Corporate Tenant' ,
},
},
{
limit: 10 ,
}
)
Update Tenant
const tenant = await rootAgent . modules . tenants . getTenantById ( tenantId )
tenant . config . label = 'Updated Label'
await rootAgent . modules . tenants . updateTenant ( tenant )
Delete Tenant
await rootAgent . modules . tenants . deleteTenantById ( tenantId )
Deleting a tenant permanently removes all its data, including credentials, connections, and DIDs.
Tenant Agent Usage
Once you have a tenant agent, use it like any other Credo agent:
DIDComm Operations
await rootAgent . modules . tenants . withTenantAgent (
{ tenantId: tenantRecord . id },
async ( tenantAgent ) => {
// Create connection invitation
const invitation = await tenantAgent . didcomm . oob . createInvitation ()
// Accept credential offer
await tenantAgent . didcomm . credentials . acceptOffer ({
credentialExchangeRecordId: credentialRecord . id ,
})
// Request proof
await tenantAgent . didcomm . proofs . requestProof ({
connectionId: connectionRecord . id ,
protocolVersion: 'v2' ,
proofFormats: { /* ... */ },
})
}
)
OpenID4VC Operations
await rootAgent . modules . tenants . withTenantAgent (
{ tenantId: tenantRecord . id },
async ( tenantAgent ) => {
// Resolve credential offer
const offer = await tenantAgent . openid4vc . holder . resolveCredentialOffer (
credentialOfferUri
)
// Request credentials
const credentials = await tenantAgent . openid4vc . holder . requestCredentials ({
resolvedCredentialOffer: offer ,
credentialConfigurationIds: [ 'UniversityDegree' ],
// ...
})
}
)
AnonCreds Operations
await rootAgent . modules . tenants . withTenantAgent (
{ tenantId: tenantRecord . id },
async ( tenantAgent ) => {
// Create link secret (once per tenant)
await tenantAgent . modules . anoncreds . createLinkSecret ()
// Get credentials
const credentials = await tenantAgent . modules . anoncreds . getCredentials ()
}
)
Tenant Modules Configuration
Share module configuration across all tenants:
import { DidCommModule } from '@credo-ts/didcomm'
import { AnonCredsModule } from '@credo-ts/anoncreds'
const rootAgent = new Agent ({
config: {},
dependencies: agentDependencies ,
modules: {
askar: new AskarModule ({
askar ,
store: { id: 'root' , key: 'root-key' },
}),
tenants: new TenantsModule (),
// These modules are available to all tenants
didcomm: new DidCommModule ({
endpoints: [ 'http://localhost:3000' ],
}),
anoncreds: new AnonCredsModule ({
registries: [ new IndyVdrAnonCredsRegistry ()],
anoncreds ,
}),
},
})
All tenants inherit the module configuration but maintain isolated data.
Storage Updates
Check for Outdated Storage
const outdatedTenants = await rootAgent . modules . tenants . getTenantsWithOutdatedStorage ()
for ( const tenant of outdatedTenants ) {
console . log ( `Tenant ${ tenant . id } needs storage update` )
}
Update Tenant Storage
await rootAgent . modules . tenants . updateTenantStorage ({
tenantId: tenantRecord . id ,
updateOptions: {
// Update options
},
})
Auto-Update on Startup
Enable automatic storage updates:
const tenantRecord = await rootAgent . modules . tenants . createTenant ({
config: {
label: 'Alice' ,
autoUpdateStorageOnStartup: true ,
},
})
Session Management
Manual Session Control
const tenantAgent = await rootAgent . modules . tenants . getTenantAgent ({
tenantId: tenantRecord . id ,
})
try {
// Perform operations
const connections = await tenantAgent . didcomm . connections . getAll ()
} finally {
// Always end the session
await tenantAgent . endSession ()
}
Automatic Session Management
// Preferred: automatic session cleanup
const result = await rootAgent . modules . tenants . withTenantAgent (
{ tenantId: tenantRecord . id },
async ( tenantAgent ) => {
return await tenantAgent . didcomm . connections . getAll ()
}
)
Always end tenant sessions to free resources. Use withTenantAgent for automatic cleanup.
Complete Example
Here’s a complete multi-tenant setup:
import { Agent , ConsoleLogger , LogLevel } from '@credo-ts/core'
import { TenantsModule } from '@credo-ts/tenants'
import { AskarModule } from '@credo-ts/askar'
import { DidCommModule } from '@credo-ts/didcomm'
import { AnonCredsModule } from '@credo-ts/anoncreds'
import { agentDependencies } from '@credo-ts/node'
import { askar } from '@openwallet-foundation/askar-nodejs'
import { anoncreds } from '@hyperledger/anoncreds-nodejs'
// Create root agent
const rootAgent = new Agent ({
config: {
label: 'Multi-Tenant Root Agent' ,
logger: new ConsoleLogger ( LogLevel . info ),
},
dependencies: agentDependencies ,
modules: {
askar: new AskarModule ({
askar ,
store: {
id: 'root-agent' ,
key: 'root-secure-key' ,
},
}),
tenants: new TenantsModule (),
didcomm: new DidCommModule ({
endpoints: [ 'http://localhost:3000' ],
}),
anoncreds: new AnonCredsModule ({
registries: [ new IndyVdrAnonCredsRegistry ()],
anoncreds ,
}),
},
})
await rootAgent . initialize ()
console . log ( 'Root agent initialized' )
// Create tenants
const aliceTenant = await rootAgent . modules . tenants . createTenant ({
config: { label: 'Alice Corporation' },
})
const bobTenant = await rootAgent . modules . tenants . createTenant ({
config: { label: 'Bob Enterprises' },
})
console . log ( 'Tenants created:' , aliceTenant . id , bobTenant . id )
// Work with Alice's tenant
await rootAgent . modules . tenants . withTenantAgent (
{ tenantId: aliceTenant . id },
async ( aliceAgent ) => {
const invitation = await aliceAgent . didcomm . oob . createInvitation ()
console . log ( 'Alice invitation:' , invitation . outOfBandInvitation . toUrl ({
domain: 'http://localhost:3000' ,
}))
}
)
// Work with Bob's tenant
await rootAgent . modules . tenants . withTenantAgent (
{ tenantId: bobTenant . id },
async ( bobAgent ) => {
await bobAgent . modules . anoncreds . createLinkSecret ()
console . log ( 'Bob link secret created' )
}
)
// List all tenants
const allTenants = await rootAgent . modules . tenants . getAllTenants ()
console . log ( 'Total tenants:' , allTenants . length )
For multi-tenant mediator setups:
import { DidCommMediatorModule } from '@credo-ts/didcomm'
const rootAgent = new Agent ({
config: {},
dependencies: agentDependencies ,
modules: {
tenants: new TenantsModule (),
didcomm: new DidCommModule ({
mediator: new DidCommMediatorModule ({
autoAcceptMediationRequests: true ,
}),
}),
},
})
Each tenant can act as a mediation recipient to the root agent’s mediator.
Best Practices
// Good: automatic cleanup
await rootAgent . modules . tenants . withTenantAgent ({ tenantId }, async ( agent ) => {
await agent . didcomm . connections . getAll ()
})
// Risky: manual cleanup required
const agent = await rootAgent . modules . tenants . getTenantAgent ({ tenantId })
try {
await agent . didcomm . connections . getAll ()
} finally {
await agent . endSession () // Don't forget!
}
Never share credentials, connections, or DIDs between tenants. Each tenant should operate independently.
Use meaningful labels and maintain a mapping between your application’s user IDs and tenant IDs.
Regularly check for outdated tenant storage: const outdated = await rootAgent . modules . tenants . getTenantsWithOutdatedStorage ()
for ( const tenant of outdated ) {
await rootAgent . modules . tenants . updateTenantStorage ({ tenantId: tenant . id })
}
Next Steps
Agent Config Configure tenant-specific settings
DIDComm Use DIDComm with multi-tenant agents
AnonCreds Issue credentials per tenant
API Reference Full Tenants API documentation