Skip to main content
AI/MLCrestApps

orchardcore-site-settings

Skill for accessing and extending Orchard Core site-level configuration. Covers ISiteService, custom settings sections with the CustomSettings stereotype, SiteSettingsDisplayDriver pattern, admin navigation registration, and recipe-based configuration. Use this skill when requests mention Orchard Core Site Settings, Access and Extend Site Settings, Core Services, Built-In Site Properties, Reading Site Settings, Updating Site Settings, or closely related Orchard Core implementation, setup, extension, or troubleshooting work. Strong matches include work with OrchardCore.Settings, ISiteService, CustomSettings, SiteSettingsDisplayDriver, ISite, SiteDisplayDriver, ContentPartDisplayDriver, INavigationProvider, IShellReleaseManager, IContentDefinitionManager, IANA. It also helps with site settings examples, Reading Site Settings, Updating Site Settings, Creating a Custom Settings Section, plus the code patterns, admin flows, recipe steps, and referenced examples captured in this skill.

Stars
13
Source
CrestApps/CrestApps.AgentSkills
Updated
2026-05-29
Slug
CrestApps--CrestApps.AgentSkills--orchardcore-site-settings
View on GitHubRaw SKILL.md

// install — copy + paste into any project

mkdir -p .claude/skills && curl -fsSL https://raw.githubusercontent.com/CrestApps/CrestApps.AgentSkills/HEAD/plugins/orchardcore/skills/orchardcore-site-settings/SKILL.md -o .claude/skills/orchardcore-site-settings.md

Drops the SKILL.md into .claude/skills/orchardcore-site-settings.md. Works with Claude Code, Cursor, and any agent that loads SKILL.md files from .claude/skills/.

Orchard Core Site Settings - Prompt Templates

Access and Extend Site Settings

You are an Orchard Core expert. Generate code and configuration for working with site-level settings, creating custom settings sections, and rendering them in the admin dashboard.

Guidelines

  • Use ISiteService to read and write site-level configuration at runtime.
  • ISiteService.GetSiteSettingsAsync() returns the cached site document for read-only access. When you intend to modify site settings and persist them with UpdateSiteSettingsAsync(), call LoadSiteSettingsAsync() instead.
  • Built-in site properties include SiteName, BaseUrl, TimeZoneId, Culture, PageSize, and UseCdn.
  • Custom settings sections are content types with the CustomSettings stereotype.
  • Each custom settings section is stored as a named JSON property inside the site document.
  • Use ISite.TryGet<TSettings>(out var settings) to read custom settings sections.
  • Render custom settings in admin using SiteDisplayDriver<TSettings> when possible (not ContentPartDisplayDriver).
  • Register an INavigationProvider to add settings entries to the admin navigation menu.
  • Define a dedicated permission to control who can manage each settings section.
  • If saving a settings editor should reload the tenant, call context.AddTenantReloadWarningWrapper() in Edit/EditAsync and inject IShellReleaseManager to call RequestRelease() when values change.
  • Do not gate RequestRelease() on ModelState.IsValid; the Orchard Core site settings controller only releases the shell after validation succeeds.
  • All C# classes must use the sealed modifier.
  • All recipe JSON must be wrapped in the root { "steps": [...] } format.

Core Services

Service Purpose
ISiteService Read and write the site settings document. Returns ISite.
ISite Read-only site settings object. Use .TryGet<T>(out ...) to read custom settings sections.
IContentDefinitionManager Define custom settings content types with the CustomSettings stereotype.
SiteDisplayDriver<TSettings> Preferred base class for rendering site settings sections in admin.
INavigationProvider Register admin menu entries for settings pages.
IShellReleaseManager Requests a tenant reload after runtime-affecting settings changes.

Built-In Site Properties

Property Type Description
SiteName string Display name of the site.
BaseUrl string Root URL used for absolute link generation.
TimeZoneId string IANA time zone identifier (e.g., America/New_York).
Culture string Default culture code (e.g., en-US).
PageSize int Default number of items per page in lists.
UseCdn bool Whether to serve static resources from a CDN.

Reading Site Settings

public sealed class MyService
{
    private readonly ISiteService _siteService;

    public MyService(ISiteService siteService)
    {
        _siteService = siteService;
    }

    public async Task<string> GetSiteNameAsync()
    {
        var site = await _siteService.GetSiteSettingsAsync();

        return site.SiteName;
    }
}

Updating Site Settings

public sealed class MyService
{
    private readonly ISiteService _siteService;

    public MyService(ISiteService siteService)
    {
        _siteService = siteService;
    }

    public async Task UpdateBaseUrlAsync(string newBaseUrl)
    {
        var site = await _siteService.LoadSiteSettingsAsync();
        site.BaseUrl = newBaseUrl;
        await _siteService.UpdateSiteSettingsAsync(site);
    }
}

Use LoadSiteSettingsAsync() when you intend to modify the site document and save it back with UpdateSiteSettingsAsync(). Use GetSiteSettingsAsync() only for read-only access because it returns a cached instance.

Creating a Custom Settings Section

Step 1: Define a Content Part for the Settings

public sealed class {{SettingsPartName}} : ContentPart
{
    public string {{PropertyName}} { get; set; }

    public bool {{BoolPropertyName}} { get; set; }
}

Step 2: Register the Content Type with the CustomSettings Stereotype

public sealed class Migrations : DataMigration
{
    private readonly IContentDefinitionManager _contentDefinitionManager;

    public Migrations(IContentDefinitionManager contentDefinitionManager)
    {
        _contentDefinitionManager = contentDefinitionManager;
    }

    public async Task<int> CreateAsync()
    {
        await _contentDefinitionManager.AlterTypeDefinitionAsync("{{SettingsTypeName}}", type => type
            .DisplayedAs("{{Settings Display Name}}")
            .Stereotype("CustomSettings")
            .WithPart("{{SettingsPartName}}", part => part
                .WithPosition("0")
            )
        );

        return 1;
    }
}

The CustomSettings stereotype tells Orchard Core this content type represents a site settings section rather than a regular content item.

Step 3: Create the Display Driver

The display driver should usually inherit from SiteDisplayDriver<TSettings>, not from ContentPartDisplayDriver<T>.

public sealed class {{SettingsPartName}}DisplayDriver : SiteDisplayDriver<{{SettingsPartName}}>
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly IAuthorizationService _authorizationService;
    private readonly IShellReleaseManager _shellReleaseManager;

    protected override string SettingsGroupId
        => "{{settingsGroupId}}";

    public {{SettingsPartName}}DisplayDriver(
        IHttpContextAccessor httpContextAccessor,
        IAuthorizationService authorizationService,
        IShellReleaseManager shellReleaseManager)
    {
        _httpContextAccessor = httpContextAccessor;
        _authorizationService = authorizationService;
        _shellReleaseManager = shellReleaseManager;
    }

    public override async Task<IDisplayResult> EditAsync(ISite site, {{SettingsPartName}} settings, BuildEditorContext context)
    {
        var user = _httpContextAccessor.HttpContext?.User;

        if (!await _authorizationService.AuthorizeAsync(user, Permissions.Manage{{SettingsPartName}}))
        {
            return null;
        }

        context.AddTenantReloadWarningWrapper();

        return Initialize<{{SettingsPartName}}ViewModel>("{{SettingsPartName}}_Edit", viewModel =>
        {
            viewModel.{{PropertyName}} = settings.{{PropertyName}};
            viewModel.{{BoolPropertyName}} = settings.{{BoolPropertyName}};
        }).Location("Content:5")
        .OnGroup(SettingsGroupId);
    }

    public override async Task<IDisplayResult> UpdateAsync(ISite site, {{SettingsPartName}} settings, UpdateEditorContext context)
    {
        var user = _httpContextAccessor.HttpContext?.User;

        if (!await _authorizationService.AuthorizeAsync(user, Permissions.Manage{{SettingsPartName}}))
        {
            return null;
        }

        var viewModel = new {{SettingsPartName}}ViewModel();

        await context.Updater.TryUpdateModelAsync(viewModel, Prefix);

        if (settings.{{PropertyName}} != viewModel.{{PropertyName}} ||
            settings.{{BoolPropertyName}} != viewModel.{{BoolPropertyName}})
        {
            _shellReleaseManager.RequestRelease();
        }

        settings.{{PropertyName}} = viewModel.{{PropertyName}};
        settings.{{BoolPropertyName}} = viewModel.{{BoolPropertyName}};

        return await EditAsync(site, settings, context);
    }
}

Important: The OnGroup() call ties the editor shape to a specific group identifier. This must match the group used in the admin controller and navigation entry. Use context.AddTenantReloadWarningWrapper() together with IShellReleaseManager.RequestRelease() for runtime-affecting settings.

Step 4: Create the View Model

public class {{SettingsPartName}}ViewModel
{
    public string {{PropertyName}} { get; set; }

    public bool {{BoolPropertyName}} { get; set; }
}

Step 5: Create the Razor View

Create a view at Views/{{SettingsPartName}}_Edit.cshtml:

@model {{SettingsPartName}}ViewModel

<div class="ocat-wrapper" asp-validation-class-for="{{PropertyName}}">
    <label asp-for="{{PropertyName}}" class="ocat-label">{{Property Display Name}}</label>
    <div class="ocat-end">
        <input asp-for="{{PropertyName}}" class="form-control" />
        <span asp-validation-for="{{PropertyName}}"></span>
    </div>
</div>

<div class="ocat-wrapper" asp-validation-class-for="{{BoolPropertyName}}">
    <div class="ocat-end-offset">
        <div class="form-check">
            <input asp-for="{{BoolPropertyName}}" class="form-check-input" />
            <label asp-for="{{BoolPropertyName}}" class="form-check-label">{{Bool Display Name}}</label>
        </div>
    </div>
</div>

Step 6: Register Admin Navigation

public sealed class AdminMenu : INavigationProvider
{
    internal readonly IStringLocalizer S;

    public AdminMenu(IStringLocalizer<AdminMenu> localizer)
    {
        S = localizer;
    }

    public Task BuildNavigationAsync(string name, NavigationBuilder builder)
    {
        if (!NavigationHelper.IsAdminMenu(name))
        {
            return Task.CompletedTask;
        }

        builder
            .Add(S["Settings"], settings => settings
                .Add(S["{{Settings Display Name}}"], S["{{Settings Display Name}}"].PrefixPosition(), entry => entry
                    .AddClass("{{iconCssClass}}")
                    .Id("{{settingsMenuId}}")
                    .Permission(Permissions.Manage{{SettingsPartName}})
                    .Action("Index", "Admin", new { area = "OrchardCore.Settings", groupId = "{{settingsGroupId}}" })
                    .LocalNav()
                )
            );

        return Task.CompletedTask;
    }
}

The groupId route value must match the group used in the display driver's OnGroup() call. The OrchardCore.Settings area provides the built-in admin controller that handles rendering settings groups.

Note: Settings pages are registered directly under the "Settings" top-level menu group. The "Configuration" menu group is no longer used in Orchard Core.

Step 7: Register Services in Startup

public sealed class Startup : StartupBase
{
    public override void ConfigureServices(IServiceCollection services)
    {
        services.AddContentPart<{{SettingsPartName}}>();
        services.AddScoped<IDisplayDriver<ISite>, {{SettingsPartName}}DisplayDriver>();
        services.AddScoped<INavigationProvider, AdminMenu>();
        services.AddScoped<IPermissionProvider, Permissions>();
    }
}

Reading Custom Settings in Views

Use the ISite object available through ISiteService or the Orchard helper in Liquid templates:

@inject ISiteService SiteService

@{
    var site = await SiteService.GetSiteSettingsAsync();
}

@if (site.TryGet<{{SettingsPartName}}>(out var settings))
{
    <p>@settings.{{PropertyName}}</p>
}

Reading Custom Settings in Services

public sealed class MyService
{
    private readonly ISiteService _siteService;

    public MyService(ISiteService siteService)
    {
        _siteService = siteService;
    }

    public async Task<{{SettingsPartName}}?> GetSettingsAsync()
    {
        var site = await _siteService.GetSiteSettingsAsync();

        return site.TryGet<{{SettingsPartName}}>(out var settings)
            ? settings
            : null;
    }
}

Configuring Site Settings via Recipes

Use the Settings recipe step to configure built-in site properties:

{
    "steps": [
        {
            "name": "Settings",
            "SiteName": "My Orchard Core Site",
            "BaseUrl": "https://www.example.com",
            "TimeZoneId": "America/New_York",
            "Culture": "en-US",
            "PageSize": 10,
            "UseCdn": true
        }
    ]
}

Configuring Custom Settings via Recipes

Use the custom-settings recipe step to set values for a custom settings section:

{
    "steps": [
        {
            "name": "custom-settings",
            "{{SettingsTypeName}}": {
                "ContentItemId": "[js:uuid()]",
                "ContentType": "{{SettingsTypeName}}",
                "{{SettingsPartName}}": {
                    "{{PropertyName}}": "{{value}}",
                    "{{BoolPropertyName}}": true
                }
            }
        }
    ]
}

Adding Content Fields to Custom Settings

Custom settings parts can include content fields for richer configuration:

public sealed class Migrations : DataMigration
{
    private readonly IContentDefinitionManager _contentDefinitionManager;

    public Migrations(IContentDefinitionManager contentDefinitionManager)
    {
        _contentDefinitionManager = contentDefinitionManager;
    }

    public async Task<int> CreateAsync()
    {
        await _contentDefinitionManager.AlterTypeDefinitionAsync("{{SettingsTypeName}}", type => type
            .DisplayedAs("{{Settings Display Name}}")
            .Stereotype("CustomSettings")
            .WithPart("{{SettingsPartName}}", part => part
                .WithPosition("0")
            )
        );

        await _contentDefinitionManager.AlterPartDefinitionAsync("{{SettingsPartName}}", part => part
            .WithField("Logo", field => field
                .OfType("MediaField")
                .WithDisplayName("Site Logo")
                .WithPosition("0")
            )
            .WithField("FooterText", field => field
                .OfType("HtmlField")
                .WithDisplayName("Footer Text")
                .WithPosition("1")
                .WithEditor("Wysiwyg")
            )
            .WithField("SocialLink", field => field
                .OfType("LinkField")
                .WithDisplayName("Social Media Link")
                .WithPosition("2")
            )
        );

        return 1;
    }
}

When using content fields in custom settings, the fields are rendered automatically by the content field display drivers. You do not need to handle them manually in the site settings display driver.