OrchardCore Display Management

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 OrchardCore Display Management 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.

This skill guides you through OrchardCore's display system following project conventions.

OrchardCore renders content by building shapes (dynamic view models) from drivers, then placing each shape into a zone of the layout. Drivers decide what shapes exist and their default location; placement.json overrides where they go, whether they show, and which template renders them.

Mental model

ContentItem ──drivers──▶ shapes ──placement.json──▶ zones ──templates──▶ HTML
  • Shape — a dynamic object with a ShapeType and Metadata. The shape type (e.g. TextField, Parts_Contents_Publish, ContentPart) selects the template and the placement rule.
  • DriverDisplayDriver / ContentPartDisplayDriver / ContentFieldDisplayDriver. Returns IDisplayResults from Display, Edit, UpdateAsync, each with a default .Location(...).
  • Placementplacement.json at the root of a module or theme. Filters shapes and rewrites their location/visibility/template.
  • Zone — named region of the layout (Content, Header, Footer, Actions, …) or an editor group (tab/card/column).

Decide: driver vs placement

Want to…Use
Reposition / hide a shape someone else builtplacement.json
Move a shape to a layout zoneplacement.json (place starts with /)
Swap the template or add alternatesplacement.json (shape / alternates)
Lay out the admin editor (tabs/cards/columns)placement.json groupings
Produce a new shape from your part/fielddisplay driver
Read/write the editor modeldisplay driver (Edit / UpdateAsync)

Prefer placement.json for layout — no rebuild needed, theme can override.

Workflow A: place a shape

Step 1: Find the shape type and differentiator

The placement property name is the shape type, not the part name. A part without its own driver renders as ContentPart (differentiator = part name). A field renders as its field type (TextField) with differentiator {PartName}-{FieldName}.

See references/placement.md for the full differentiator table.

Step 2: Add a rule to placement.json

{
  "TextField": [
    {
      "differentiator": "Article-Subtitle",
      "displayType": "Detail",
      "place": "Content:2"
    }
  ]
}

Step 3: Verify

Run the site, view the content item. Use ?display debug shapes or the template's shape tracing to confirm the shape type. Wrong type → no rule matches.

Workflow B: write a display driver

Step 1: Inherit the right base

Base classFor
ContentPartDisplayDriver<TPart>a content part
ContentFieldDisplayDriver<TField>a content field
SectionDisplayDriver<TModel, TSection>settings sections
DisplayDriver<TModel>anything else

Step 2: Implement Display / Edit / Update

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

    public override IDisplayResult Edit(TextField field, BuildFieldEditorContext context)
    {
        return Initialize<EditTextFieldViewModel>(GetEditorShapeType(context), model =>
        {
            model.Field = field;
            model.Part = context.ContentPart;
            model.PartFieldDefinition = context.PartFieldDefinition;
        });
    }

    public override async Task<IDisplayResult> UpdateAsync(TextField field, UpdateFieldEditorContext context)
    {
        await context.Updater.TryUpdateModelAsync(field, Prefix, f => f.Text);
        return Edit(field, context);
    }
}

Step 3: Register the driver

In Startup.cs:

services.AddContentPartDisplayDriver<MyPart, MyPartDisplayDriver>();
// or for a field:
services.AddContentFieldDisplayDriver<TextField, TextFieldDisplayDriver>();

See references/drivers.md for shape-type helpers, the fluent .Location API, and registration variants.

Quick Reference

Placement rule keys

KeyPurpose
placeZone:Position. - hides, /Zone targets a layout zone
displayTypeMatch Detail, Summary, SummaryAdmin, DetailAdmin, Edit
differentiatorDistinguish reused shapes (field/part name)
contentTypeMatch content type / stereotype (Art* wildcard, or array)
contentPartMatch content items containing a part
pathMatch request path
alternatesAdd alternate shape types
wrappersWrap the shape in other shapes
shapeSubstitute a different shape type

Position values

before < 0/empty < 1 < 1.1 < 2 < 10 < after. Numeric, not alphabetic. Dot notation inserts between (5.1 is between 5 and 6).

Editor grouping modifiers

ModifierGroupExample
#TabParts:0#Media;1
%CardParts:0%Details;2
|ColumnParts:0|Left_9;1 (_9 = width, ;1 = order)

;N after a group name = group order. _N after a column name = Bootstrap grid width (12-col).

Common zones

ZonePurpose
ContentMain content shapes
Header / FooterLayout regions
ActionsAdmin list row buttons
PartsEditor parts container
MetaMetadata block

Gotchas

  • Hiding a whole editor row → target ContentPart_Edit with differentiator {ContentType}-{PartName}, not the inner XxxPart_Edit.
  • Shape type ≠ part type. No driver → shape is ContentPart. Summary display of a dynamic part → ContentPart_Summary.
  • Field display modes are stricter: shape type needs _Display, differentiator is {Part}-{Field}-{FieldType}_Display__{Mode}.
  • Placement precedence: startup project → active theme → modules (by dependency). A theme can override any module's placement.
  • Same position value → registration order decides; use distinct positions when order matters.

References

  • references/placement.md — placement.json filters, differentiators, layout zones, editor groupings
  • references/drivers.md — driver base classes, shape helpers, fluent Location API, registration
  • src/docs/reference/modules/Placement/README.md (repo) — official placement reference
  • src/docs/reference/modules/DisplayManagement/README.md (repo) — display management reference
  • src/docs/reference/modules/Templates/README.md (repo) — shape differentiators and alternates
  • AGENTS.md (repo root) — build commands

Signals

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