Orchard Core is an open-source modular and multi-tenant application framework built with ASP.NET Core, and a content management system (CMS) built on top of...
# Orchard Core - Agent Guidelines
This document provides instructions for LLM agents working with the Orchard Core codebase. It covers building, testing, and creating new features.
## Project Overview
Orchard Core is an open-source, modular, multi-tenant application framework and CMS for ASP.NET Core. It consists of:
- **Orchard Core Framework**: An application framework for building modular, multi-tenant applications
- **Orchard Core CMS**: A Web Content Management System built on top of the framework
**Repository**: <https://github.com/OrchardCMS/OrchardCore>
**Documentation**: <https://docs.orchardcore.net/>
## Prerequisites
- **.NET SDK**: Version 10.0+ (see `global.json` for exact version requirements)
- **Node.js**: Version 24.x LTS (for asset compilation)
- **Yarn**: Version 4.x (package manager for frontend assets)
## Building the Project
### Command Line
```bash
# Navigate to the web project
cd src/OrchardCore.Cms.Web
# Run the application
dotnet run
# Or build with specific configuration
dotnet build -c Debug -f net10.0
```
### Full Solution Build
```bash
# From repository root
dotnet build OrchardCore.sln
# Build with Release configuration
dotnet build OrchardCore.sln -c Release
```
### Target Framework
The default target framework is `net10.0` as defined in `src/OrchardCore.Build/TargetFrameworks.props`.
## Running the Application
```bash
cd src/OrchardCore.Cms.Web
dotnet run -f net10.0
```
The application will be available at `http://localhost:5000` (and `https://localhost:5001`).
## Running Tests
### Unit Tests
Unit tests are located in the `test/` directory and use xUnit v3.
```bash
# Run all tests
dotnet test
# Run tests for a specific project
dotnet test test/OrchardCore.Tests/OrchardCore.Tests.csproj
# Run tests with a filter
dotnet test --filter-method "*.YourTest"
```
Arguments for tests:
```bash
--filter-class
```
Run all methods in a given test class. Pass one or more fully qualified type names (i.e.,
'MyNamespace.MyClass' or 'MyNamespace.MyClass+InnerClass'). Wildcard '*' is supported at
the beginning and/or end of each filter.
Note: Specifying more than one is an OR operation.
This is categorized as a simple filter. You cannot use both simple filters and query filters.
```bash
--filter-method
```
Run a given test method. Pass one or more fully qualified method names (i.e.,
'MyNamespace.MyClass.MyTestMethod'). Wildcard '*' is supported at the beginning and/or end
of each filter.
Note: Specifying more than one is an OR operation.
This is categorized as a simple filter. You cannot use both simple filters and query filters.
### Functional Tests (Playwright)
End-to-end tests are located in `test/OrchardCore.Tests.Functional/`.
```bash
# Run CMS functional tests
dotnet test test/OrchardCore.Tests.Functional/OrchardCore.Tests.Functional.csproj --filter-class "*Cms*"
# Run MVC functional tests
dotnet test test/OrchardCore.Tests.Functional/OrchardCore.Tests.Functional.csproj --filter-class "*Mvc*"
```
### Automated Browser Testing (Playwright MCP)
For AI agents, the Playwright MCP (Model Context Protocol) provides automated browser testing capabilities:
- **Setup**: Ensure the application is running at `http://localhost:5000`
- **Navigation**: Use `mcp_playwright_browser_navigate` to navigate to pages
- **Interactions**: Use tools like `mcp_playwright_browser_click`, `mcp_playwright_browser_type` for user actions
- **Verification**: Use `mcp_playwright_browser_snapshot` to capture page state
- **Console**: Use `mcp_playwright_browser_console_messages` to check for JavaScript errors
**Example workflow:**
1. Navigate to the application
2. Complete setup wizard if needed
3. Navigate to specific features (e.g., `/Admin/Media`)
4. Interact with UI elements
5. Verify console has no errors
6. Capture screenshots or snapshots for validation
### Test Organization
- `test/OrchardCore.Tests/` - Main unit test project
- `test/OrchardCore.Abstractions.Tests/` - Tests for abstractions
- `test/OrchardCore.Tests.Functional/` - Playwright E2E functional tests
- `test/OrchardCore.Tests.Modules/` - Test modules used by tests
## Project Structure
```
OrchardCore/
โโโ src/
โ โโโ OrchardCore/ # Core framework libraries
โ โ โโโ OrchardCore/ # Main framework
โ โ โโโ OrchardCore.Abstractions/ # Core interfaces
โ โ โโโ ... # Other abstractions
โ โโโ OrchardCore.Modules/ # Built-in modules
โ โโโ OrchardCore.Themes/ # Built-in themes
โ โโโ OrchardCore.Cms.Web/ # Main CMS web application
โ โโโ docs/ # Documentation source
โโโ test/ # Test projects
โโโ .scripts/ # Build and asset scripts
```
## Available Skills
The following skills are available in `.agents/skills/` for guided workflows:
| Skill | Description | Use When |
|-------|-------------|----------|
| `orchardcore-module-creator` | Create new modules | Adding modules, content parts, fields, handlers |
| `orchardcore-theme-creator` | Create new themes | Adding themes, layouts, frontend assets |
| `orchardcore-recipe-creator` | Create setup recipes | Provisioning a tenant: features, themes, content, roles, settings |
| `orchardcore-asset-manager` | Build/manage frontend assets | Modifying SCSS, JS, TS, Vue or troubleshooting the asset pipeline |
| `orchardcore-admin-edit-views` | Build admin edit views | Creating/updating `*.Edit.cshtml` with `ocat-*` classes |
| `orchardcore-admin-index-views` | Build admin index (list) pages | Any admin page listing items: the `AdminList` shape, `<admin-list>` columns, `SummaryAdmin`/`SummaryAdminGrid` rows, column providers, row actions |
| `orchardcore-data-migration` | Write data migrations | Altering content definitions, index tables, patching content items |
| `orchardcore-display-management` | Control rendering | placement.json, drivers, shapes, zones, alternates, editor layouts |
| `orchardcore-workflow-activity` | Custom workflow activities | New workflow task/event, outcomes, activity editor, input/output |
| `orchardcore-query-indexing` | Queries & search indexing | SQL/Lucene queries, index profiles, index handlers, search |
| `orchardcore-localization` | Localize apps & content | IStringLocalizer S/T/H, PO files, content translation, cultures |
| `orchardcore-tenants` | Multi-tenancy | Shells, creating tenants, tenant scopes, feature profiles, isolation |
| `orchardcore-unit-test` | Write & run tests | xUnit, SiteContext integration, Moq, Playwright functional |
| `orchardcore-docs-writer` | Author docs | MkDocs pages, module README, nav, admonitions, redirects |
| `orchardcore-tester` | Browser-based testing | Testing features via Playwright automation |
| `orchardcore-nswag-regenerate` | Regenerate NSwag API clients | Updating `Services/OpenApiClient.cs`/`OpenApiClient.ts`, noisy NSwag regeneration diffs |
| `orchardcore-video-producer` | Produce narrated videos | Feature walkthroughs, module overviews, PR demos, videos in the docs |
These skills provide step-by-step guidance, code templates, and references for common tasks.
## Videos
Every video of Orchard Core (documentation, pull requests, walkthroughs) follows one specification: 1920x1080 at 30 fps, the Orchard Core logo, colors and Open Sans font on a dark background, recordings at 1536x864 in a green frame, the `en-US-AndrewMultilingualNeural` voice, WebVTT captions, and an H.264 encode under 10 MB. Always produce videos with the `orchardcore-video-producer` skill, and don't change its specification for a single video. Narration describes the current behavior, with no "new", "now", or version wording, so videos stay accurate. In the docs, keep a video in a `videos/` folder next to its page.
## Frontend Assets
### Asset Management
Orchard Core uses an asset manager for compiling SCSS, TypeScript, and JavaScript.
```bash
# Install Yarn
# Install dependencies (from repository root)
corepack enable
yarn
# Build all assets including gulp
yarn build
```
### Assets.json Configuration
Each module with frontend assets needs an `Assets.json` file:
```json
[
{
"action": "vite",
"name": "your-module",
"source": "Assets/",
"tags": ["js", "css"]
}
]
```
### Asset Dependencies (package.json)
```json
{
"name": "@orchardcore/your-module",
"version": "1.0.0",
"dependencies": {
"vue": "3.5.13",
"bootstrap": "5.3.8"
}
}
```
## Content Management Patterns
For detailed patterns including Content Parts, Content Part Drivers, Content Fields, and more, see the `orchardcore-module-creator` skill in `.agents/skills/`.
## Coding Conventions
### General Guidelines
- Follow [ASP.NET Core Engineering guidelines](https://github.com/dotnet/aspnetcore/wiki/Engineering-guidelines)
- Use `sealed` for classes that should not be inherited
- Use file-scoped namespaces
- Prefer collection expressions (`[]`) over `new List<T>()`
- Avoid use of primary constructors
### Naming Conventions
- Classes: `PascalCase`
- Interfaces: `IPascalCase`
- Methods: `PascalCase`
- Properties: `PascalCase`
- Private fields: `_camelCase`
- Local variables: `camelCase`
- Constants: `PascalCase`
### Configuration Sections
- Name configuration sections without the redundant `OrchardCore_` prefix, and group them by service with a nested section per provider, e.g. `_shellConfiguration.GetSection("Email:Smtp")` for the `OrchardCore:Email:Smtp` section, next to `OrchardCore:Email:Azure`.
- When renaming a section that has been released, keep supporting its legacy name by binding `GetSectionCompat("New:Name", "OrchardCore_Legacy")`, with a `// The 'OrchardCore_Legacy' section is deprecated and will be removed in a future major version, use 'New:Name' instead.` comment above it. The values of the new section win, and its arrays replace the legacy ones. Don't bind the two sections one after the other, as arrays would be appended.
- Describe the sections read by a project in a `ConfigurationSchema.json` file at the project root, which is packed with a `JsonSchemaSegment` to provide IntelliSense in `appsettings.json` files.
### Async Conventions
- Suffix async methods with `Async`
- Use `Task` or `ValueTask` return types
### Code Analysis
The project uses:
- StyleCop.Analyzers for style enforcement
- `AnalysisLevel` set to `latest-Recommended`
- Specific CA rules are suppressed (see `Directory.Build.props`)
### Documentation
- Update the canonical page under `src\docs` whenever a change affects user-facing behavior, configuration, setup, public APIs, stereotypes, or extension points.
- Add or update XML `<summary>` documentation for new or modified public interfaces, domain models, enums, and other public members that are part of the change.
- Document each enum member individually when its behavior is relevant to users or downstream developers.
- When XML documentation already exists, revise the existing block in place and keep `<param>` tags accurate and in signature order.
## Database Patterns
### YesSql Usage
Orchard Core uses YesSql as its document database abstraction:
```csharp
public class YourService
{
private readonly ISession _session;
public YourService(ISession session)
{
_session = session;
}
public async Task<YourDocument> GetAsync(string id)
{
return await _session.Query<YourDocument, YourIndex>()
.Where(x => x.DocumentId == id)
.FirstOrDefaultAsync();
}
public async Task SaveAsync(YourDocument document)
{
await _session.SaveAsync(document);
}
}
```
### Index Definition
```csharp
public class YourIndex : MapIndex
{
public string DocumentId { get; set; }
public string Name { get; set; }
}
public class YourIndexProvider : IndexProvider<YourDocument>
{
public override void Describe(DescribeContext<YourDocument> context)
{
context.For<YourIndex>()
.Map(doc => new YourIndex
{
DocumentId = doc.Id,
Name = doc.Name,
});
}
}
```
## Testing Patterns
### Unit Test Structure
Name test methods with the `{Action}_{Condition}_{ExpectedResult}` format, for example `Write_WithinLimit_Succeeds`.
```csharp
using Xunit;
namespace OrchardCore.Tests.Modules.OrchardCore.YourModule;
public class YourServiceTests
{
[Fact]
public async Task YourMethod_Condition_DoesSomething()
{
// Arrange
var service = new YourService();
// Act
var result = await service.YourMethodAsync();
// Assert
Assert.NotNull(result);
}
[Theory]
[InlineData("input1", "expected1")]
[InlineData("input2", "expected2")]
public void YourMethod_Input_ReturnsExpected(string input, string expected)
{
// Test implementation
}
}
```
### Integration Test with Host
```csharp
public class YourIntegrationTests : IClassFixture<OrchardTestFixture>
{
private readonly OrchardTestFixture _fixture;
public YourIntegrationTests(OrchardTestFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task Feature_DefaultRecipe_Works()
{
// Use _fixture to create test scenarios
}
}
```
### Manual Testing
For browser-based manual testing using Playwright, see the `orchardcore-tester` skill in `.agents/skills/`.
**Quick start:**
```powershell
# Build
dotnet build src/OrchardCore.Cms.Web -c Debug -f net10.0
# Generate/get port and start in background
$port = if (Test-Path .orchardcore-port) { Get-Content .orchardcore-port } else { $p = Get-Random -Min 5000 -Max 6000; $p | Out-File .orchardcore-port -NoNewline; $p }
$proc = Start-Process dotnet -ArgumentList "run","-f","net10.0","--no-build","--urls","http://localhost:$port" -WorkingDirectory "src/OrchardCore.Cms.Web" -PassThru -NoNewWindow
$proc.Id | Out-File .orchardcore-pid -NoNewline
# URL: http://localhost:$port
# Test credentials: admin / [email protected] / Password1!
# Stop when done
Stop-Process -Id (Get-Content .orchardcore-pid) -Force; Remove-Item .orchardcore-pid
# Reset state: Remove-Item -Recurse -Force src/OrchardCore.Cms.Web/App_Data
```
**Debugging**: Check `src/OrchardCore.Cms.Web/App_Data/logs/orchard-log-{date}.log`
### Functional Testing with Playwright
Create new functional tests under `test/OrchardCore.Tests.Functional/Tests/` following the existing C# test class patterns.
Run the Playwright functional tests:
```bash
# Run CMS functional tests
dotnet test test/OrchardCore.Tests.Functional/OrchardCore.Tests.Functional.csproj --filter-class "*Cms*"
# Run MVC functional tests
dotnet test test/OrchardCore.Tests.Functional/OrchardCore.Tests.Functional.csproj --filter-class "*Mvc*"
```
## Common Extension Points
### Registering Services
```csharp
// In Startup.cs
services.AddScoped<IYourService, YourService>();
services.AddSingleton<IYourSingleton, YourSingleton>();
services.AddTransient<IYourTransient, YourTransient>();
```
### Event Handlers
```csharp
public class YourContentHandler : ContentHandlerBase
{
public override Task PublishedAsync(PublishContentContext context)
{
// Handle content published event
return Task.CompletedTask;
}
}
```
### Background Tasks
```csharp
public class YourBackgroundTask : IBackgroundTask
{
public Task DoWorkAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken)
{
// Background work implementation
return Task.CompletedTask;
}
}
```
### Navigation/Admin Menu
```csharp
public sealed class AdminMenu : AdminNavigationProvider
{
private readonly IStringLocalizer S;
public AdminMenu(IStringLocalizer<AdminMenu> localizer)
{
S = localizer;
}
protected override ValueTask BuildAsync(NavigationBuilder builder)
{
builder
.Add(S["Your Menu"], menu => menu
.Add(S["Your Item"], S["Your Item"], item => item
.Action("Index", "Admin", "OrchardCore.YourModule")
.Permission(YourPermissions.ManageYourFeature)
.LocalNav()
)
);
return ValueTask.CompletedTask;
}
}
```
## Debugging Tips
1. **Enable detailed errors** in development by setting `ASPNETCORE_ENVIRONMENT=Development`
2. **Check tenant logs** in `App_Data/Sites/{TenantName}/logs/`
3. **Use MiniProfiler** module for performance analysis
4. **Enable SQL logging** by configuring YesSql logging
## Pull Request Guidelines
1. Follow existing code style and conventions
2. Include unit tests for new functionality
3. Update the canonical documentation page when adding or changing user-facing features, public APIs, configuration, stereotypes, or extension points, and keep the docs aligned with the shipped code and behavior
4. Run asset build if modifying CSS/JS: `yarn build`
5. Ensure all tests pass: `dotnet test`
6. Link related GitHub issues using `Fixes #IssueId`
7. Add release notes for significant changes in `src/docs/releases/`, but do not rely on release notes as the only documentation for a feature; release notes describe what changed, while the canonical docs describe the current behavior without transitional or versioned wording
## Useful Commands
```bash
# Restore packages
dotnet restore
# Clean build artifacts
dotnet clean
# Run
dotnet run --project src/OrchardCore.Cms.Web
# Build assets
yarn build
# Lint JavaScript/TypeScript
yarn lint
# Type check Vue/TypeScript
yarn check
```
## Resources
- [Documentation](https://docs.orchardcore.net/)
- [Contributing Guide](https://docs.orchardcore.net/en/latest/contributing/)
- [Discord Community](https://orchardcore.net/discord)
- [Issue Tracker](https://github.com/OrchardCMS/OrchardCore/issues)
- [API Reference](https://docs.orchardcore.net/en/latest/reference/)
## Admin Edit View Conventions
When creating or updating Orchard Core admin edit views, always use the `ocat-*` admin theme classes so the UI stays consistent across the site and works with custom admin theme overrides.
This applies to admin-facing Razor edit views such as:
- `*.Edit.cshtml`
- `*.Fields.Edit.cshtml`
- Admin editor templates rendered in the admin theme
### Required patterns
| Scenario | Required structure |
|---|---|
| Standard admin field row (default for all inputs) | `ocat-wrapper` + `ocat-label` + `ocat-end` |
| Required field label | `ocat-label ocat-label-required` |
| Checkbox or toggle with no left-column label | `ocat-wrapper` + `ocat-end-offset` |
| Standalone alert, notice, or section headline inside an edit form | `ocat-wrapper` + `ocat-end-offset` |
| Limited-width row **only** for `type="number"` inputs or when the user explicitly requests compact width | `ocat-limited-wrapper` + `ocat-label` + `ocat-limited` |
> **Default rule:** Always use the standard `ocat-wrapper` + `ocat-label` + `ocat-end` pattern unless the input is a `type="number"` field or the user explicitly asks for limited/compact width.
### Standard field example
```cshtml
<div class="ocat-wrapper" asp-validation-class-for="DisplayText">
<label asp-for="DisplayText" class="ocat-label">@T["Display text"]</label>
<div class="ocat-end">
<input asp-for="DisplayText" class="form-control" />
<span asp-validation-for="DisplayText"></span>
<span class="hint">@T["Shown to editors in the admin UI."]</span>
</div>
</div>
```
### Limited-width field example (number inputs)
```cshtml
<div class="ocat-limited-wrapper" asp-validation-class-for="PageSize">
<label asp-for="PageSize" class="ocat-label">@T["Page size"]</label>
<div class="ocat-limited">
<input asp-for="PageSize" type="number" class="form-control" />
<span asp-validation-for="PageSize"></span>
<span class="hint">@T["The default page size."]</span>
</div>
</div>
```
### Limited-width field inside a content field or content part wrapper
If the row also needs Orchard-specific wrapper classes such as `field-wrapper-*` or `content-part-wrapper-*`, keep the outer `ocat-wrapper` and place the compact control inside `ocat-end`:
```cshtml
<div class="ocat-wrapper field-wrapper @($"field-wrapper-{Model.PartFieldDefinition.PartDefinition.Name.HtmlClassify()}-{Model.PartFieldDefinition.Name.HtmlClassify()}")">
<label asp-for="Value" class="ocat-label">@T["Value"]</label>
<div class="ocat-end">
<div class="ocat-limited-wrapper">
<div class="ocat-limited">
<input asp-for="Value" class="form-control" />
<span asp-validation-for="Value"></span>
</div>
</div>
<span class="hint">@T["Keeps a compact editor width without losing the field wrapper row."]</span>
</div>
</div>
```
### Standalone alert or headline row
Use `ocat-end-offset` for alerts, notices, legends, and headings that belong to the form but do not have a left-column label:
```cshtml
<div class="ocat-wrapper">
<div class="ocat-end-offset">
<h5>@T["Section heading"]</h5>
<div class="alert alert-warning" role="alert">
@T["Important guidance for this form section."]
</div>
</div>
</div>
```
### Avoid
- `mb-3`, `form-group`, or `form-label` as the row layout pattern
- Legacy helper methods such as `@Orchard.GetWrapperClasses()` or `@Orchard.GetLimitedWidthWrapperClasses()`
- Using `ocat-limited-wrapper` for text inputs, selects, paths, IDs, or other non-number fields unless the user explicitly requests compact width
- Mixing `ocat-end` and `ocat-end-offset` in the same row; choose exactly one based on whether the row has a left-column label
## Admin Index View Conventions
Every admin page that lists items (an index page, e.g. the content items, users, queries or workflows) renders them through the shared `AdminList` shape, following the `orchardcore-admin-index-views` skill in `.agents/skills/`. Read that skill before creating or changing an admin listing, so every list renders the same way, in the `List` and `Grid` layouts the site and the user choose.
### Required patterns
| Layer | Required pattern |
|---|---|
| Controller | Query the raw items and pass them in an `AdminListContext<TModel>` (`Source`, `Pager`, `Header` or `BulkActions`, ...). Never build the list shape or the row shapes. |
| View | Render the list with `<admin-list name="..." context="Model.List">` inside the page form, declaring the grid columns with `<admin-list-grid>` and `<admin-list-column>`. Import the tag helpers in the module's `_ViewImports.cshtml` with `@addTagHelper *, OrchardCore.Admin.Abstractions`. |
| Row display driver | Place every shape for both display types: `.Location(OrchardCoreConstants.DisplayType.SummaryAdmin, "Content:1")` for the `List` layout and `.Location(OrchardCoreConstants.DisplayType.SummaryAdminGrid, "Name:1")` for the column of the `Grid` layout. Register it in the feature owning the list. |
| Row template | `{Model}.SummaryAdmin.cshtml` renders the zones of the row, and its actions with `@await DisplayAsync(await Factory.CreateAdminListActionsAsync((IShape)Model))`. |
| Other modules | Add or change the columns of a list with an `IAdminListColumnsProvider` registered with `services.AddAdminListColumnsProvider<T>(listName)`, and fill them from their display driver with a `SummaryAdminGrid` location. |
| Tests | Add the page to `_lists` in `AdminListLayoutTests`, which renders every list as a grid and fails on a column that renders nothing. |
### Avoid
- A hand-written `<ul class="list-group">`, `<table>`, item count, select-all checkbox, bulk-action dropdown, empty state or pager for an admin listing
- Building row shapes in the controller, or passing prebuilt rows to the list
- A shape location without a display type, e.g. `.Location("Tags:5")`, or only a `SummaryAdmin` one: the shape is missing from its grid column
- A layout or actions layout of your own: the layouts are `List` and `Grid`, the actions layouts `Buttons` and `Menu`; customize them with templates and alternates such as `AdminList-{Name}.cshtml` and `AdminListCell-{Name}-{Column}.cshtml`