OrchardCore

GitHub

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 that framework.

RAW Rules

AGENTS.md

# 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-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 |

These skills provide step-by-step guidance, code templates, and references for common tasks.

## 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`

### 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