OrchardCore Tenants (Multi-tenancy)
SkillDev toolsLets your agent create and manage OrchardCore tenants and run code inside them.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the OrchardCore Tenants (Multi-tenancy) skill
About this capability
Works with OrchardCore multi-tenancy — shells, ShellSettings, creating/configuring tenants programmatically, running code in a tenant scope, feature profiles, and data isolation. Use when the user needs to create or manage tenants, run code inside a tenant, pre-configure tenants, or understand shell
What this skill tells your AI
The instructions your AI receives, as published by orchardcms/orchardcore in .agents/skills/orchardcore-tenants/SKILL.md and read by ahel’s review.
This skill guides you through OrchardCore multi-tenancy following project conventions.
OrchardCore runs many isolated sites ("tenants") in one process. Each tenant = a shell: its own DI container, configuration, enabled features, and (optionally) its own database or table prefix. The Default tenant is special — it's created first and manages the others.
Core concepts
| Type | Role |
|---|---|
ShellSettings | a tenant's identity + config (Name, State, RequestUrlHost, RequestUrlPrefix, DB info) |
ShellContext | a running tenant: its ServiceProvider, blueprint, request pipeline |
ShellScope | a unit-of-work scope inside a tenant (resolve services here) |
IShellHost | manages all shells: create, reload, list, get scopes |
IShellSettingsManager | persists ShellSettings |
Tenant states
Uninitialized → (setup) → Running. Also Initializing, Disabled, Invalid. A tenant configured but not yet set up is Uninitialized and appears in the Tenants list awaiting setup.
Decide what you're doing
| Goal | Approach |
|---|---|
| Create a tenant in code | IShellSettingsManager.CreateDefaultSettings() + IShellHost.UpdateShellSettingsAsync |
| Run code inside a tenant | IShellHost.GetScopeAsync(name) + scope.UsingAsync(...) |
| Pre-declare tenants without UI | appsettings/configuration tenant section |
| Restrict a tenant's features | Feature Profiles |
| Isolate tenant data | separate connection string, or shared DB + table prefix/schema |
Workflow A: create a tenant programmatically
using var shellSettings = _shellSettingsManager
.CreateDefaultSettings()
.AsUninitialized()
.AsDisposable();
shellSettings.Name = "acme";
shellSettings.RequestUrlPrefix = "acme"; // site at /acme
shellSettings.RequestUrlHost = ""; // or a host header
shellSettings["ConnectionString"] = connectionString;
shellSettings["DatabaseProvider"] = "Sqlite";
shellSettings["TablePrefix"] = "acme";
shellSettings["RecipeName"] = "Blog"; // recipe run at setup
shellSettings["Secret"] = Guid.NewGuid().ToString();
await _shellHost.UpdateShellSettingsAsync(shellSettings);
UpdateShellSettingsAsync stamps a new VersionId, saves, and reloads the shell. The tenant is now Uninitialized — it still needs setup (admin user, run recipe), done via the Setup UI/API or ISetupService.
Or via the Tenants API: POST /api/tenants/create then POST /api/tenants/setup.
Workflow B: run code in a tenant scope
To resolve tenant-scoped services (content manager, session, etc.) for a specific tenant:
var shellScope = await _shellHost.GetScopeAsync("acme");
await shellScope.UsingAsync(async scope =>
{
var contentManager = scope.ServiceProvider.GetRequiredService<IContentManager>();
// ... work inside the "acme" tenant ...
});
Inside the callback, ShellScope.Services / ShellScope.Current resolve to this tenant. Deferred tasks run on scope disposal. Never resolve tenant services off the root provider — you'll get the wrong (or no) tenant.
Workflow C: pre-configure tenants via configuration
In appsettings.json (or any config source), declare a tenant so it exists before any UI interaction:
{
"OrchardCore": {
"acme": {
"State": "Uninitialized",
"RequestUrlPrefix": "acme",
"ConnectionString": "...",
"DatabaseProvider": "SqlConnection"
}
}
}
Per-tenant module config goes under the tenant name too:
{ "OrchardCore": { "Default": { "OrchardCore_Media": { /* ... */ } } } }
Quick Reference
IShellHost
| Member | Purpose |
|---|---|
GetOrCreateShellContextAsync(settings) | get/build a tenant's context |
GetScopeAsync(settings|name) | a scope to run code in |
UpdateShellSettingsAsync(settings) | save + reload a tenant |
ReloadShellContextAsync(settings) | rebuild after config change |
ListShellContexts() / TryGetSettings(name, out) | enumerate / look up |
ShellSettings state extensions
AsUninitialized(), AsRunning(), AsDisabled(), plus IsRunning(), IsUninitialized(), etc.
Settings keys (bag)
ConnectionString, DatabaseProvider, TablePrefix, Schema, RecipeName, FeatureProfile, Secret, Category, Description.
Data isolation options
| Strategy | How |
|---|---|
| Separate database | distinct ConnectionString per tenant |
| Shared DB, table prefix | same DB + unique TablePrefix |
| Shared DB, schema | Schema per tenant (SQL Server) |
OrchardCore_Tenants config can enforce: RequireTablePrefix, TablePrefixPattern, SchemaPattern (templated with ShellSettings).
Feature Profiles
Feature OrchardCore.Tenants.FeatureProfiles. Profiles are include/exclude rules limiting which features a tenant may enable:
{
"name": "FeatureProfiles",
"FeatureProfiles": {
"minimal": { "FeatureRules": [ { "Rule": "Exclude", "Expression": "OrchardCore.MiniProfiler" } ] }
}
}
Assign via shellSettings["FeatureProfile"].
Gotchas
- Creating settings ≠ a usable site. A new tenant is
Uninitialized; it must be set up (recipe + admin user) before it runs. - Always
using/dispose theShellSettingsfromCreateDefaultSettings()(.AsDisposable()). - Only the
Defaulttenant (ShellSettings.DefaultShellName) can manage other tenants. - Resolve services through a
ShellScope, not the application root provider — tenant DI is a child container per shell. - Each tenant runs each module's
Startup.ConfigureServicesindependently; services are per-tenant singletons, not app-wide.
References
references/shells.md— shell lifecycle, scope mechanics, container factory, config sourcessrc/docs/reference/modules/Tenants/README.md(repo)src/docs/reference/modules/Configuration/README.md(repo)src/docs/reference/modules/Shells/README.md(repo)AGENTS.md(repo root) — build commands
Signals
- GitHub stars
- 8k
- Forks
- 3k
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
orchardcore-tenants- Source
- github.com/orchardcms/orchardcore