Orchard Core Display Management - Prompt Templates

SkillDev tools

Lets your agent change how OrchardCore pages render, like positioning, hiding, or moving content parts and fields.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Orchard Core Display Management - Prompt Templates skill

About this capability

Controls how OrchardCore renders content — placement.json rules, display drivers, shapes, zones, alternates, and editor groupings (tabs/cards/columns). Use when the user needs to position or hide a part/field, move a shape to another zone, add alternates or wrappers, build admin editor layouts, or w

What this skill tells your AI

The instructions your AI receives, as published by orchardcms/orchardcore in .agents/skills/orchardcore-display-management/SKILL.md and read by ahel’s review.

Create Display Drivers and Shapes

You are an Orchard Core expert. Generate display drivers, shapes, and display management code for Orchard Core.

Guidelines

  • Every content part that needs custom rendering requires a DisplayDriver.
  • Display drivers inherit from ContentPartDisplayDriver<TPart>.
  • Drivers handle three operations: Display, Edit, and Update.
  • Each operation returns IDisplayResult (shapes to render).
  • View models are used to pass data between drivers and views.
  • Shape names follow the convention {PartName} for display and {PartName}_Edit for editor.
  • Use Initialize<TModel> to create shapes with a view model.
  • Register drivers in Startup.cs using services.AddContentPart<TPart>().UseDisplayDriver<TDriver>().
  • For non-content-item models rendered through DisplayDriver<TModel>, the root shape still needs its own wrapper template for each display type you build (for example, CampaignAction.Edit.cshtml for CampaignAction_Edit and CampaignAction.SummaryAdmin.cshtml for CampaignAction_SummaryAdmin).
  • When driver results are placed into zones with .Location("Content:1"), .Location("Actions:5"), or similar, the wrapper template must render those zones (Model.Content, Model.Actions, Model.Meta, etc.) or the child shapes will never appear.
  • For ALL admin editor views (*.Edit.cshtml including *Settings.Edit.cshtml), use the ocat-* (Orchard Core Admin Theme) CSS classes instead of raw mb-3, form-label, or hard-coded grid classes. Use ocat-wrapper for the outer row, ocat-label for labels, ocat-end for the input column, ocat-end-offset for checkbox-only rows/headings/buttons with no left label, ocat-limited-wrapper + ocat-limited for narrow-width fields. Do NOT apply these classes to frontend views (Login, Register, etc.) or Admin Menu node editing (needs full width).
  • The former @Orchard.GetWrapperClasses(), @Orchard.GetLabelClasses(), @Orchard.GetEndClasses() helper methods and TheAdminThemeOptions class have been removed. Use the static ocat-* CSS classes directly.
  • Always seal classes.

Content Part Display Driver Pattern

using OrchardCore.ContentManagement.Display.ContentDisplay;
using OrchardCore.ContentManagement.Display.Models;
using OrchardCore.DisplayManagement.ModelBinding;
using OrchardCore.DisplayManagement.Views;

public sealed class {{PartName}}DisplayDriver : ContentPartDisplayDriver<{{PartName}}>
{
    public override IDisplayResult Display({{PartName}} part, BuildPartDisplayContext context)
    {
        return Initialize<{{PartName}}ViewModel>("{{PartName}}", model =>
        {
            model.{{PropertyName}} = part.{{PropertyName}};
            model.ContentItem = part.ContentItem;
        })
        .Location("Detail", "Content:5")
        .Location("Summary", "Content:5");
    }

    public override IDisplayResult Edit({{PartName}} part, BuildPartEditorContext context)
    {
        return Initialize<{{PartName}}ViewModel>("{{PartName}}_Edit", model =>
        {
            model.{{PropertyName}} = part.{{PropertyName}};
            model.ContentItem = part.ContentItem;
        });
    }

    public override async Task<IDisplayResult> UpdateAsync({{PartName}} part, UpdatePartEditorContext context)
    {
        var model = new {{PartName}}ViewModel();

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

        part.{{PropertyName}} = model.{{PropertyName}};

        return Edit(part, context);
    }
}

View Model Pattern

using OrchardCore.ContentManagement;

public class {{PartName}}ViewModel
{
    public string {{PropertyName}} { get; set; }
    public ContentItem ContentItem { get; set; }
}

Display Shape View (Views/{{PartName}}.cshtml)

@model {{Namespace}}.ViewModels.{{PartName}}ViewModel

<p>@Model.{{PropertyName}}</p>

Editor Shape View (Views/{{PartName}}_Edit.cshtml)

@model {{Namespace}}.ViewModels.{{PartName}}ViewModel

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

Registering a Display Driver

using OrchardCore.ContentManagement;
using OrchardCore.ContentManagement.Display.ContentDisplay;

public sealed class Startup : StartupBase
{
    public override void ConfigureServices(IServiceCollection services)
    {
        services.AddContentPart<{{PartName}}>()
            .UseDisplayDriver<{{PartName}}DisplayDriver>();
    }
}

Content Part with Handler Pattern

using OrchardCore.ContentManagement;
using OrchardCore.ContentManagement.Handlers;

public sealed class {{PartName}}Handler : ContentPartHandler<{{PartName}}>
{
    public override Task InitializingAsync(InitializingContentContext context, {{PartName}} part)
    {
        part.{{PropertyName}} = "default value";
        return Task.CompletedTask;
    }
}

Display Types

Orchard Core uses display types to differentiate how content is rendered:

  • Detail — Full content display (e.g., a blog post page).
  • Summary — Abbreviated display (e.g., in a list).
  • SummaryAdmin — Admin-specific summary view.
  • Edit — Editor form for the content part.

Wrapper Templates for DisplayDriver<TModel>

CRITICAL: When you use DisplayDriver<TModel> (not ContentPartDisplayDriver), Orchard Core resolves a root shape for each display type. If this root shape template does not exist, you get InvalidOperationException: The shape type '{ModelName}_Edit' is not found. You must create wrapper templates for every display type the driver builds shapes for.

Required wrapper templates per display type:

Display type built by driverRequired wrapper template file
Edit (via Edit() / BuildEditorAsync)Views/{ModelName}.Edit.cshtml
SummaryAdmin (via DisplayAsync + Location("Content:1"))Views/{ModelName}.SummaryAdmin.cshtml
DetailViews/{ModelName}.cshtml
SummaryViews/{ModelName}.Summary.cshtml

The wrapper template renders the zones that child shapes are placed into. If the wrapper doesn't render a zone (e.g., Model.Content, Model.Actions, Model.Meta), the child shapes placed in those zones will be silently dropped.

Example driver:

internal sealed class CampaignActionDisplayDriver : DisplayDriver<CampaignAction>
{
    public override Task<IDisplayResult> DisplayAsync(CampaignAction model, BuildDisplayContext context)
    {
        return CombineAsync(
            View("CampaignAction_Fields_SummaryAdmin", model)
                .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, "Content:1"),
            View("CampaignAction_Buttons_SummaryAdmin", model)
                .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, "Actions:5")
        );
    }

    public override IDisplayResult Edit(CampaignAction model, BuildEditorContext context)
        => Initialize<CampaignActionViewModel>("CampaignActionFields_Edit", m => { })
            .Location("Content:1");
}

Required wrapper templates:

  • Views/CampaignAction.Edit.cshtml for the root CampaignAction_Edit shape.
  • Views/CampaignAction.SummaryAdmin.cshtml for the root CampaignAction_SummaryAdmin shape.

Minimal edit wrapper:

@if (Model.Content != null)
{
    @await DisplayAsync(Model.Content)
}

Summary admin wrapper with zones:

<div class="row g-0">
    <div class="col-lg col-12">
        @if (Model.Content != null)
        {
            @await DisplayAsync(Model.Content)
        }

        @if (Model.Meta != null)
        {
            @await DisplayAsync(Model.Meta)
        }
    </div>
    <div class="col-lg-auto col-12">
        @if (Model.Actions != null)
        {
            @await DisplayAsync(Model.Actions)
        }
    </div>
</div>

Child shape file-name mapping:

  • CampaignAction_Fields_SummaryAdminViews/Items/CampaignAction.Fields.SummaryAdmin.cshtml
  • CampaignAction_Buttons_SummaryAdminViews/Items/CampaignAction.Buttons.SummaryAdmin.cshtml
  • CampaignActionFields_EditViews/CampaignActionFields.Edit.cshtml

Placing Shapes in Zones

Use .Location() to place shapes in zones with positions:

return Initialize<MyViewModel>("MyShape", model => { ... })
    .Location("Detail", "Content:5")      // Detail view, Content zone, position 5
    .Location("Summary", "Meta:5")        // Summary view, Meta zone, position 5
    .Location("SummaryAdmin", "Actions:5"); // Admin summary, Actions zone, position 5

Content Field Display Driver

using OrchardCore.ContentManagement.Display.ContentDisplay;
using OrchardCore.ContentManagement.Display.Models;

public sealed class {{FieldName}}FieldDisplayDriver : ContentFieldDisplayDriver<{{FieldName}}Field>
{
    public override IDisplayResult Display({{FieldName}}Field field, BuildFieldDisplayContext context)
    {
        return Initialize<{{FieldName}}FieldViewModel>(
            GetDisplayShapeType(context),
            model =>
            {
                model.Field = field;
                model.Part = context.ContentPart;
                model.PartFieldDefinition = context.PartFieldDefinition;
            })
            .Location("Detail", "Content")
            .Location("Summary", "Content");
    }
}

Shape Table Provider

Override shape rendering behavior:

using OrchardCore.DisplayManagement.Descriptors;

public sealed class MyShapeTableProvider : IShapeTableProvider
{
    public ValueTask DiscoverAsync(ShapeTableBuilder builder)
    {
        builder.Describe("Content")
            .OnDisplaying(context =>
            {
                // Add alternates, wrappers, etc.
                context.Shape.Metadata.Alternates.Add("Content__{{ContentType}}");
            });

        return ValueTask.CompletedTask;
    }
}

Shape Debug Information

You can instruct Orchard Core to write HTML comments around rendered shapes. This makes it easier to identify which Razor or Liquid template produced a specific fragment in the page output.

Enable directly through options:

services.Configure<ShapeRenderingOptions>(options =>
    options.WriteShapeDebugInformation = true);

When OrchardCore.Settings is enabled, the site's debug settings configure the same ShapeRenderingOptions.WriteShapeDebugInformation value.

When enabled, rendered shapes are wrapped with HTML comments:

<!--shape-start type:Menu bindings:Menu__Main=>Themes/Contoso/Views/Menu-Main.cshtml (razor)-->
...
<!--shape-end type:Menu-->

The start comment contains the shape type and the binding that was used. Razor bindings report the .cshtml path, while Liquid bindings report a virtual .liquid source. This is useful during development to trace which template is responsible for each fragment of the rendered page.

Signals

GitHub stars
8k
Forks
3k
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
orchardcore-display-management
Source
github.com/orchardcms/orchardcore