This document provides instructions for LLM agents working with the Orchard Core codebase. It covers building, testing, and creating new features.
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/
- .NET SDK: Version 10.0+ (see
global.jsonfor exact version requirements) - Node.js: Version 22.x (for asset compilation)
- Yarn: Version 4.x (package manager for frontend assets)
# 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# From repository root
dotnet build OrchardCore.sln
# Build with Release configuration
dotnet build OrchardCore.sln -c ReleaseThe default target framework is net10.0 as defined in src/OrchardCore.Build/TargetFrameworks.props.
cd src/OrchardCore.Cms.Web
dotnet run -f net10.0The application will be available at http://localhost:5000 (and https://localhost:5001).
Unit tests are located in the test/ directory and use xUnit v3.
# 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:
--filter-classRun 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.
--filter-methodRun 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.
End-to-end tests are located in test/OrchardCore.Tests.Functional/.
cd test/OrchardCore.Tests.Functional
npm install
npm run cms:testFor 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_navigateto navigate to pages - Interactions: Use tools like
mcp_playwright_browser_click,mcp_playwright_browser_typefor user actions - Verification: Use
mcp_playwright_browser_snapshotto capture page state - Console: Use
mcp_playwright_browser_console_messagesto check for JavaScript errors
Example workflow:
- Navigate to the application
- Complete setup wizard if needed
- Navigate to specific features (e.g.,
/Admin/Media) - Interact with UI elements
- Verify console has no errors
- Capture screenshots or snapshots for validation
test/OrchardCore.Tests/- Main unit test projecttest/OrchardCore.Abstractions.Tests/- Tests for abstractionstest/OrchardCore.Tests.Functional/- Cypress E2E teststest/OrchardCore.Tests.Modules/- Test modules used by tests
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
The following skills are available in .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-tester |
Browser-based testing | Testing features via Playwright automation |
These skills provide step-by-step guidance, code templates, and references for common tasks.
Orchard Core uses an asset manager for compiling SCSS, TypeScript, and JavaScript.
# Install Yarn
# Install dependencies (from repository root)
corepack enable
yarn
# Build all assets including gulp
yarn buildEach module with frontend assets needs an Assets.json file:
[
{
"action": "vite",
"name": "your-module",
"source": "Assets/",
"tags": ["js", "css"]
}
]{
"name": "@orchardcore/your-module",
"version": "1.0.0",
"dependencies": {
"vue": "3.5.13",
"bootstrap": "5.3.8"
}
}For detailed patterns including Content Parts, Content Part Drivers, Content Fields, and more, see the orchardcore-module-creator skill in .skills/.
- Follow ASP.NET Core Engineering guidelines
- Use
sealedfor classes that should not be inherited - Use file-scoped namespaces
- Prefer collection expressions (
[]) overnew List<T>() - Avoid use of primary constructors
- Classes:
PascalCase - Interfaces:
IPascalCase - Methods:
PascalCase - Properties:
PascalCase - Private fields:
_camelCase - Local variables:
camelCase - Constants:
PascalCase
- Suffix async methods with
Async - Use
TaskorValueTaskreturn types
The project uses:
- StyleCop.Analyzers for style enforcement
AnalysisLevelset tolatest-Recommended- Specific CA rules are suppressed (see
Directory.Build.props)
Orchard Core uses YesSql as its document database abstraction:
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);
}
}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,
});
}
}using Xunit;
namespace OrchardCore.Tests.Modules.OrchardCore.YourModule;
public class YourServiceTests
{
[Fact]
public async Task YourMethod_ShouldDoSomething_WhenCondition()
{
// 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_ShouldReturnExpected(string input, string expected)
{
// Test implementation
}
}public class YourIntegrationTests : IClassFixture<OrchardTestFixture>
{
private readonly OrchardTestFixture _fixture;
public YourIntegrationTests(OrchardTestFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task Feature_ShouldWork()
{
// Use _fixture to create test scenarios
}
}For browser-based manual testing using Playwright, see the orchardcore-tester skill in .skills/.
Quick start:
# 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 / admin@test.com / 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_DataDebugging: Check src/OrchardCore.Cms.Web/App_Data/logs/orchard-log-{date}.log
Create new functional tests under test/OrchardCore.Tests.Functional/cypress/ following the existing spec patterns.
Run the Cypress functional tests:
cd test/OrchardCore.Tests.Functional
# Required before first usage
npm install
# Run all functional tests
npm run cms:test// In Startup.cs
services.AddScoped<IYourService, YourService>();
services.AddSingleton<IYourSingleton, YourSingleton>();
services.AddTransient<IYourTransient, YourTransient>();public class YourContentHandler : ContentHandlerBase
{
public override Task PublishedAsync(PublishContentContext context)
{
// Handle content published event
return Task.CompletedTask;
}
}public class YourBackgroundTask : IBackgroundTask
{
public Task DoWorkAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken)
{
// Background work implementation
return Task.CompletedTask;
}
}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;
}
}- Enable detailed errors in development by setting
ASPNETCORE_ENVIRONMENT=Development - Check tenant logs in
App_Data/Sites/{TenantName}/logs/ - Use MiniProfiler module for performance analysis
- Enable SQL logging by configuring YesSql logging
- Follow existing code style and conventions
- Include unit tests for new functionality
- Update documentation if adding new features
- Run asset build if modifying CSS/JS:
yarn build - Ensure all tests pass:
dotnet test - Link related GitHub issues using
Fixes #IssueId - Add release notes for significant changes in
src/docs/releases/
# 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