# 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**: **Documentation**: ## 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, `` 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()` - 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 `` 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 `` 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 GetAsync(string id) { return await _session.Query() .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 { public override void Describe(DescribeContext context) { context.For() .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 { 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 / 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_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(); services.AddSingleton(); services.AddTransient(); ``` ### 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 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
@T["Shown to editors in the admin UI."]
``` ### Limited-width field example (number inputs) ```cshtml
@T["The default page size."]
``` ### 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
@T["Keeps a compact editor width without losing the field wrapper row."]
``` ### 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
@T["Section heading"]
``` ### 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` (`Source`, `Pager`, `Header` or `BulkActions`, ...). Never build the list shape or the row shapes. | | View | Render the list with `` inside the page form, declaring the grid columns with `` and ``. 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(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 `
    `, ``, 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`