
Orchard Core Theming
- 18 installs
- 8 repo stars
- Updated February 3, 2026
- lombiq/orchard-core-agent-skills
Helps with ai & agent building tasks during AI-assisted development.
About
orchard-core-theming is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- orchard-core-theming
- AI & Agent Building
- AI-coding skill
Orchard Core Theming by the numbers
- 18 all-time installs (skills.sh)
- Ranked #10,736 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/lombiq/orchard-core-agent-skills --skill orchard-core-themingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 18 |
|---|---|
| repo stars | ★ 8 |
| Last updated | February 3, 2026 |
| Repository | lombiq/orchard-core-agent-skills ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Orchard Core Theming
Use this skill for Orchard Core theming and content-definition/recipe work.
How to use
- Path A (task match): scan the Tasks list; if the request matches, open
references/TASK-MAP.mdand go directly to the leaf files. - Path B (exploration): use the section cues to pick a reference section, open that section's
INDEX.md, then choose the leaf file it points to. - Determine Razor vs Liquid early using the workflow in
references/TASK-MAP.md. If it cannot be decided, fall back to Liquid and confirm with the user. - Open only the necessary leaf files; use a section
INDEX.mdonly for orientation and discovery. - Prefer examples and ready-to-copy patterns.
Evidence rules
- Prefer repo evidence over assumptions: active theme, base theme,
placement.json, and existing templates. - Trace the shape model first when it is unclear; use
references/TASK-MAP.mdto find shape tracing guidance and ask the user if needed. - Confirm unknown part/field properties in
ContentDefinition.json(orOrchardCore.db) and the relevant field references. - Ask for missing identifiers (content type, part name, field name, display type) instead of inventing them.
- Do not invent recipe steps or feature IDs; use
references/TASK-MAP.mdto find the right recipe references.
Scripts
Use these scripts instead of hand-building extracts.
scripts/extract-content-definitions.pyto extract content types/parts/fields fromContentDefinition.jsonorOrchardCore.db, with optional related-type expansion and Markdown/JSON output. Seereferences/50-content-model/CONTENT-DEFINITIONS-EXTRACTOR.md.scripts/extract-content-items.pyto get content items fromOrchardCore.db, filter by type/IDs/text, and optionally emit a recipecontentstep or a Markdown/JSON extract. Seereferences/50-content-model/CONTENT-ITEMS-EXTRACTOR.md.scripts/generate-orchard-ids.pyto generate Orchard Core IDs that match theDefaultIdGeneratoralphabet for stableContentItemIdvalues in recipes. Seereferences/70-recipes/ID-GENERATION.md.scripts/sync-skill.pyto refresh the entire skill folder from the Lombiq/Orchard-Core-Agent-Skills repo (references, scripts, SKILL.md, assets). The running script is not overwritten until the next sync.
Sync example:
python skills/orchard-core-theming/scripts/sync-skill.pyReference section cues
Use these cues to decide which reference section to open.
- Use
references/10-understand-structure/to confirm solution and theme structure (manifests, base theme), create themes, and locate layouts/zones. - Use
references/20-shapes-placement/to find shape names, alternates, placement rules, and override workflow steps. - Use
references/30-razor/to implement Razor theme changes with tag helpers, shape rendering, and IOrchardHelper. - Use
references/40-liquid/to implement Liquid theme changes with tags, filters, and shape helpers. - Use
references/50-content-model/to inspect content definitions/items, parts/fields, settings/containers, and the extractors. - Use
references/60-assets-resources/to include scripts/styles and manage resources and static files. - Use
references/70-recipes/to author, validate, and reuse recipes for setup, definitions, and content import. - Use
references/80-debugging-discovery/to trace shapes, inspect logs, and find evidence in source. - Use
references/90-glossary/to resolve terms and acronyms in Orchard Core theming docs.
Tasks
Use this list to decide whether to open references/TASK-MAP.md for the exact leaf files.
- Determine template language (Razor vs Liquid).
- Add or update a content type/part/field in ContentDefinition.json.
- Extract a focused content definition slice (large JSON).
- Create a setup recipe.
- Add content types and sample content to a recipe.
- Create or update workflows in a recipe.
- Create or override a content item shape template.
- Implement or override OrchardCore.Forms widgets and Form content.
- Inspect real content items (SQLite).
- Update a shape after adding fields.
- Render BagPart/FlowPart/ListPart items.
- Add scripts/styles and include them in the layout.
- Find shape alternates and placement rules.
- Find evidence in Orchard Core source.
- Work on theme structure or layout.
- Understand solution structure.
Create a Theme
Steps to scaffold a new Orchard Core theme and what files matter.
Preferred: dotnet template
1) Ensure templates are installed (match your Orchard Core version):
dotnet new install OrchardCore.ProjectTemplates::<version>- If already installed, skip.
2) Scaffold:
dotnet new octheme -n MyTheme -o src/Themes/MyTheme
3) Add the project to your solution/host project and build.
Template output (key files):
MyTheme.csproj(SDK-style, references OrchardCore theme targets)Manifest.cs(theme metadata)Startup.cs(optional; add services/resources if needed)Views/Layout.cshtmlorLayout.liquid(fall back to Liquid if not specified by the user)Views/_ViewImports.cshtml(for if Razor is used)wwwroot/for static assets
If templates are unavailable
1) Create a new Razor Class Library:
dotnet new razorclasslib -n MyTheme -o src/Themes/MyTheme
2) Edit .csproj to use the OrchardCore theme SDK:
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OrchardCore.Theme.Targets" Version="<matching-oc-version>" PrivateAssets="All" />
<PackageReference Include="OrchardCore.DisplayManagement" Version="<matching-oc-version>" />
<PackageReference Include="OrchardCore.ResourceManagement" Version="<matching-oc-version>" />
<PackageReference Include="OrchardCore.Contents" Version="<matching-oc-version>" />
</ItemGroup>
</Project>(Adjust package path/version to your solution.) 3) Add Manifest.cs:
using OrchardCore.DisplayManagement.Manifest;
[assembly: Theme(
Name = "MyTheme",
Author = "Org",
Website = "https://example.com",
Version = "1.0.0",
Description = "Site theme",
BaseTheme = "TheTheme" // optional
)]4) Add Views/Layout.cshtml (or Layout.liquid) and Views/_ViewImports.cshtml (Razor). 5) Add wwwroot/ for assets; add ResourceManifest.cs if you need named resources.
Minimal files checklist
Manifest.cs: required metadata (Name, Version, optionally BaseTheme).Layoutview: page shell with zones/sections.Views/_ViewImports.cshtml(Razor): include Orchard tag helpers.Viewsoverrides: shapes you need to customize.wwwroot/assets: styles/scripts/images.Startup.cs(optional): register services, adjust options if needed.
Scaffold examples
_ViewImports.cshtml(Razor):
@inherits OrchardCore.DisplayManagement.Razor.RazorPage<TModel>
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, OrchardCore.DisplayManagement
@addTagHelper *, OrchardCore.ResourceManagement
@addTagHelper *, OrchardCore.ContentsViews/Layout.cshtml(minimal):
<!DOCTYPE html>
<html lang="@Orchard.CultureName()" dir="@Orchard.CultureDir()">
<head>
<meta charset="utf-8" />
<title>@RenderTitleSegments(Site.SiteName, \"before\")</title>
<style asp-name=\"MyTheme\" at=\"Head\"></style>
<resources type=\"Header\" />
</head>
<body>
<main class=\"container\">
@await RenderSectionAsync(\"Messages\", required: false)
@await RenderBodyAsync()
</main>
<resources type=\"FootScript\" />
</body>
</html>After scaffolding
- Add the project to the solution and reference it from the host app.
- Enable the theme feature in admin or via recipe (
feature+themessteps). - If using a base theme, override views/resources rather than editing the base.
Theme Basics
Purpose: Identify the active/base theme, confirm manifests, and locate layouts/zones.
Files:
SOLUTION-STRUCTURE.md- repo layout, host web project, App_Data/tenants, AutoSetup, recipes, and content definitions.THEME-STRUCTURE.md- views and rendering, Razor vs Liquid,_ViewImports, manifests, base theme inheritance.LAYOUTS-ZONES.md- layout template global zones, where to confirm zones in source, content item shape local zones.CREATE-THEME.md- dotnet template flow, fallback scaffolding, minimal files checklist, scaffold examples, and post-setup steps.
Layouts and Zones
How the layout template exposes zones/sections that placement can target.
Layout template global zones
- The active theme's layout (
Views/Layout.cshtmlfor Razor orViews/Layout.liquidfor Liquid) controls the page shell. If it's missing and a base theme is set, the base theme layout is applied. - Common zones:
HeadMeta,Header,Messages,Content,Footer, plus any theme-specific ones. - Razor: render sections with
@await RenderSectionAsync("<Zone>", required: false); render the main body with@await RenderBodyAsync(). - Placement
placevalues (e.g.,Content:1,/Footer) must match zones rendered in the layout. See20-shapes-placement/PLACEMENT.mdfor details. - The
<zone>Razor tag helper or{% zone %}Liquid tag can also place ad-hoc shapes into zones. See30-razor/TAG-HELPERS-SHAPES.mdor40-liquid/LIQUID-TAGS.mdif needed.
Where to confirm in source
- Check the active theme's layout file first (child themes can override base theme layouts).
- If a base theme is set and the layout is not overridden, use the base theme layout to confirm zones.
Content item shape local zones
- When overriding content item shapes (e.g.,
Content-Page.cshtml,Widget-Hero.Summary.liquid), local zones are available on the dynamic view model. - Render them with
@await DisplayAsync(Model.Content)(Razor) or{{ Model.Content | shape_render }}(Liquid). - These contain pre-rendered part/field shapes you can override individually.
- Use these only when needed; see
20-shapes-placement/SHAPE-WORKFLOW.mdto decide. - Common local zones:
Model.Header,Model.Metadata,Model.Content,Model.Footer.
Solution Structure
This section captures where to find the core pieces of a typical Orchard Core solution. Keep it generic and use it as a checklist for discovery in any repo.
Repository layout (typical)
src/: application code.src/Modules/: Orchard Core modules.src/Themes/: Orchard Core themes.src/Libraries/: shared libraries that are not Orchard Core extensions.src/Utilities/: optional utilities or support projects.test/: automated tests and UI testing projects.tools/: repo tooling (analyzers, scripts, build helpers).node_modules/: JS tooling dependencies (if front-end tooling is used).
Host web project (the running app)
Look for the primary *.Web project under src/. Typical contents:
appsettings*.json: environment settings and Orchard configuration.Program.cs: application startup.wwwroot/: static assets, but typically not used, because assets comes from the themes and modules.App_Data/: tenant data, logs, and runtime state, see below.NLog.config(or other logging config).
App_Data for understanding the state of the app and tenants after setup
App_Data/logs/: logs, useful for debugging runtime errors, help the user with providing errors found here when needed.App_Data/tenants.json: tenant registry; keys are tenant names with values likeTenantId,VersionId,RequestUrlPrefix,State.App_Data/Sites/<TenantName>/: tenant-specific storage.appsettings.json: per-tenant settings.Media/: media files for local development environments.DataProtection-Keys/: data protection keys.- SQLite database files (e.g.,
OrchardCore.db) when using file-based providers. - optional
ContentDefinition.jsonwhen definitions are stored to file; prefer the extractor (50-content-model/CONTENT-DEFINITIONS-EXTRACTOR.md) to inspect types/parts
and fall back to OrchardCore.db if the file is missing.
Auto-setup when user wants to skip the setup screen
In appsettings.Development.json (or other environment files), check:
OrchardCore:OrchardCore_AutoSetup:Tenants[]Each tenant typically includes:
ShellNameSiteNameSiteTimeZoneAdminUsername,AdminEmail,AdminPasswordDatabaseProvider,DatabaseConnectionString,DatabaseTablePrefixRecipeName- optional
RequestUrlHost,RequestUrlPrefix,FeatureProfile
Recipes
Recipes convey data, configuration, content items, content types either during setup or when needed. Recipes can be located in:
HostProject/Recipes/(common but not required).ModuleOrTheme/Recipes/(custom or sample recipes)ModuleOrTheme/Migrations/Recipes/- Test projects may also include recipes for automation.
Built outputs may contain bin/.../Migrations/Recipes folders; ignore these for source edits.
Content definitions (optional file storage)
If present, ContentDefinition.json provides the content type/part/field definitions for a tenant and is useful for understanding ContentItem shape data. Use the extractor first; it can also read from OrchardCore.db when the file does not exist.
Theme Structure
Understanding the theme structure helps you locate templates and confirm inheritance.
Views and rendering, Razor vs Liquid
- Site theme views usually live under the theme's
Views/. - Admin UI views live in module
Views/or the active admin theme. - If multiple themes exist, confirm which you need to work with, unless specifically asked by the user.
- View files are either
.cshtmlor.liquid. Determine this for the active theme and use the matching guidance. If none are present, check the base theme or ask the user. - Razor guidance:
30-razor/INDEX.md; Liquid guidance:40-liquid/INDEX.md. - Prefer shapes over MVC partials for UI composition.
Views/Layout.cshtmlis treated as the site layout automatically; do not setLayout = nullinside it.
_ViewImports for Razor themes
If a theme uses Razor, _ViewImports.cshtml should exist with at least:
@inherits OrchardCore.DisplayManagement.Razor.RazorPage<TModel>
If it's missing, it can be a typical build error. Read CREATE-THEME.md to verify what should be present.
Manifest file, Theme inheritance (base theme)
- Theme manifest lives in
Manifest.csdefining metadata for the theme. - Look for a
Themeattribute with metadata likeName,Description, andBaseTheme. BaseThemeindicates theme inheritance; child themes can override base theme templates.- Base themes supply views/resources that the child theme inherits.
- Override base theme views in the child theme rather than editing the base theme unless explicitly requested.
Shape Alternates
Shape alternates are the template name candidates that Orchard Core tries in order. They let you target a specific content type, display type, part, field, or zone without changing drivers.
Naming rules (high level)
__separates alternate segments; filenames use-in place of__(both work, but-is standard).- A single
_in shape type maps to.in file names. - Display types are inserted with
_DisplayTypebetween the base shape and the alternate segments. - Display modes append
_Displayto the shape type for parts/fields that support display modes. - Part and field "differentiators" use
-inside the alternate segment (e.g.,Blog-MyField).
- Shape type ↔ file name examples:
Component__Header->Component-Header.cshtmlContent_Summary__Page->Content-Page.Summary.cshtml
Content item alternates
Content__[ContentType]- content item shape for a specific content type.Content_[DisplayType]__[ContentType]- display-type-specific override (e.g., Summary).Content__Alias__[Alias]Content_[DisplayType]__Alias__[Alias]Content__Slug__[Slug]Content_[DisplayType]__Slug__[Slug]
Filename mapping examples:
Content__Article->Content-Article.cshtml(or same with .liquid)Content_Summary__Article->Content-Article.Summary.cshtml(or same with .liquid)
Stereotype alternates for content items
- A content type might have a stereotype set. If you are unsure, use the extractor (
50-content-model/CONTENT-DEFINITIONS-EXTRACTOR.md) or fall back toContent. Use the stereotype value as the base shape name instead ofContent. - Example:
Section->Section__[ContentType]->Section-Hero.cshtml. - Example:
Block->Block__[ContentType]->Block-TextAndImage.cshtml. - Example:
Widget->Widget__[ContentType]->Widget-Image.cshtml. - Same alternates apply; replace
Contentwith the stereotype.
Part alternates if granular overrides are required
[ShapeType](often the part type name)[ShapeType]_[DisplayType][ContentType]_[DisplayType]__[PartType][ContentType]_[DisplayType]__[PartName][ContentType]_[DisplayType]__[PartType]__[ShapeType][ContentType]_[DisplayType]__[PartName]__[ShapeType]- If a custom part only has fields, a field override may be enough.
Display mode variants (for parts with display modes):
[ShapeType]_[DisplayType]__[DisplayMode]_Display[ContentType]_[DisplayType]__[PartType]__[DisplayMode]_Display[ContentType]_[DisplayType]__[PartName]__[DisplayMode]_Display
Taxonomy term alternates
- Term landing pages render the
TermPartshape for the term content type. - Alternate name:
<TermContentType>__TermPart(file example:Tag-TermPart.liquidorTag__TermPart.liquid). - Use
Model.ContentItemsfor the related content list andModel.Pagerfor paging (useshape_pagerandshape_render). - For the term header/body, override the term content item with
Content__<TermContentType>(e.g.,Content__Tag.liquid).
Field alternates if granular overrides are required
[ShapeType](often the field type name)[ShapeType]_[DisplayType](field type with display type)[PartType]__[FieldName][ContentType]__[PartName]__[FieldName][ContentType]__[FieldType][FieldType]__[ShapeType][PartType]__[FieldName]__[ShapeType][ContentType]__[PartName]__[FieldName]__[ShapeType][ContentType]__[FieldType]__[ShapeType]
Field display mode variants require _Display on the shape type and a full differentiator.
Zone alternates (wrap or override a zone)
Zone__ZoneName(e.g.,Zone__Footer->Zone-Footer.cshtml)
User alternates
UserDisplayName_DisplayTypeUserDisplayName_DisplayType__UserName(when available)
Practical tips
- Use
console_logor the RazorConsoleLoghelper to inspect a shape's alternates list. - When changing shape type or display type in Liquid/Razor, clear alternates first.
- Most-specific alternates win; keep templates targeted to avoid surprising overrides.
Forms widgets (OrchardCore.Forms)
Purpose: implement and override Form widgets in themes and recipes without breaking FlowPart layout or losing form metadata.
Source of truth (Orchard Core)
- Module:
src/OrchardCore.Modules/OrchardCore.Forms - Feature id:
OrchardCore.Forms(Manifest.cs). Depends onOrchardCore.WidgetsandOrchardCore.Flows. - Content types (Migrations.cs):
Form,Input,TextArea,Select,Button,Label,
ValidationSummary, Validation (all Stereotype: Widget).
- Default display templates:
Views/Form.Wrapper.cshtml(form wrapper, renders<form>+ child content)Views/Items/InputPart.cshtml,SelectPart.cshtml,TextAreaPart.cshtmlViews/Items/ButtonPart.cshtmlViews/Items/FormElementLabelPart.cshtmlViews/Items/FormElementValidationPart.cshtmlViews/Items/ValidationSummaryPart.cshtml
Recipe checklist
- Enable the feature in setup recipe:
OrchardCore.Forms(also pulls Widgets/Flows dependencies).- Add a
Formwidget to the page/container (BagPart or FlowPart). - BagPart: include
ForminContainedContentTypes. - The Form widget contains a
FlowPartwith child form element widgets. - Use
FlowMetadataon each widget for size/alignment. - Alignment is
FlowAlignment(Left/Center/Right/Justify/Inherit) serialized as numbers. - Export from admin to capture correct numeric values.
Rendering strategy (Liquid)
- Override the widget templates for form elements:
Views/Widget__Form.liquidViews/Widget__Input.liquidViews/Widget__Select.liquidViews/Widget__TextArea.liquidViews/Widget__Button.liquidViews/Widget__ValidationSummary.liquid(if needed)- Label and validation are separate parts. Render them explicitly in the widget templates:
Model.Content.FormElementLabelPartModel.Content.FormElementValidationPartModel.Content.InputPart/SelectPart/TextAreaPart/ButtonPart
Example pattern (Input widget):
{% assign widget_classes = Model.Classes | join: " " | strip %}
<div class="form-field{% if widget_classes != blank %} {{ widget_classes }}{% endif %}">
{% if Model.Content.FormElementLabelPart %}
{{ Model.Content.FormElementLabelPart | shape_render }}
{% endif %}
{% if Model.Content.InputPart %}
{{ Model.Content.InputPart | shape_render }}
{% endif %}
{% if Model.Content.FormElementValidationPart %}
{{ Model.Content.FormElementValidationPart | shape_render }}
{% endif %}
</div>Keep FlowPart logic intact
FlowPartadds classes and runs authorization checks per widget.- If you override
Widget__Form, do NOT loopFlowPart.Widgetsmanually.
Render the content zone instead so FlowPart can do its work:
{% assign widget_classes = Model.Classes | join: " " | strip %}
{% if widget_classes != blank %}
<div class="{{ widget_classes }}">
{{ Model.Content | shape_render }}
</div>
{% else %}
{{ Model.Content | shape_render }}
{% endif %}FlowPart classes and layout
OrchardCore.Flows/Views/FlowPart.cshtmladds sizing/alignment classes to each widget shape.- See
50-content-model/CONTAINERS.mdfor the exact class list and size variants to style. - Always apply
Model.Classeson widget wrappers to preserve sizing/alignment metadata. - The FlowPart wrapper renders
<section class="flow">. Style.flowand the widget classes
to get the desired grid or flex layout.
Form wrapper override
FormContentDisplayDriveradds wrapperForm_Wrapper__{ContentType}for display type Detail.- Default wrapper (
Form.Wrapper.cshtml) builds the<form>element and anti-forgery token. - If you override it, keep
FormPartfields (Action,Method,EncType,
EnableAntiForgeryToken, SaveFormLocation) and render child content.
Content type collisions
- OrchardCore.Forms defines a widget content type named
Button. - Avoid naming your own content type
Button; rename it (for example,ActionButton).
Shapes and Placement
Purpose: Find shape names, alternates, placement rules, and override workflow steps.
Files:
SHAPES.md- what shapes are, when to create them, shape model, lifecycle, and where to find shape data.ALTERNATES.md- naming rules, content item and stereotype alternates, part/field alternates, zone/user alternates, tips.PLACEMENT.md- file format, filters, placement info, precedence, differentiators, field display modes, editor grouping, dynamic parts.SHAPE-WORKFLOW.md- step-by-step override flow: identify type/tenant, determine template name, map fields, render helpers, placement, validate.FORMS-WIDGETS.md- OrchardCore.Forms widgets, FlowPart class handling, Liquid overrides, recipes.MENU-SHAPES.md- feature dependency, shapes involved, default rendering, tag helpers, manual rendering, overrides, built-in templates, model properties, tips.PAGER-SHAPES.md- shapes involved, common properties, shape-building flow, alternates, customization options, Razor overrides, Liquid helpers, tips.
Menu Shapes and Rendering
How Orchard Core renders menus and how to override or hand-render them.
Feature dependency
- Enable
OrchardCore.Menufor menu content types and shapes.
Shapes involved
Menu: root shape for a menu.MenuItem: one per menu item (recursive).MenuItemLink: the link portion of a menu item.- Metadata/alternates (from
MenuShapes.cs): - Level alternates:
MenuItem__level__{n},MenuItemLink__level__{n}. - By content type:
MenuItem__<ContentType>,MenuItemLink__<ContentType>, with level variants. - By menu name (differentiator):
MenuItem__<MenuName>,MenuItem__<MenuName>__level__{n}, and combined with content type; same forMenuItemLink.
Default rendering patterns
- Built-in shape drivers create
MenuItemandMenuItemLinkshapes per item; child items recurse. - Permissions:
MenuItemPermissionPartcan hide items from users lacking permissions. - Menu item kinds (content types with stereotype
MenuItem): LinkMenuItem(LinkMenuItemPart: Url/Target)HtmlMenuItem(HtmlMenuItemPart: Html)ContentMenuItem(ContentMenuItemPart: selected content item)- Nested items are stored via
MenuItemsListPart.MenuItems.
Tag helpers (Razor)
<menu>tag helper:<menu alias="alias:main-menu" cache-id="main-menu" cache-tag="alias:main-menu" cache-context="user.roles" />- Parameters:
alias(e.g.,alias:main-menu), caching attributes (cache-id,cache-tag,cache-context,cache-fixed-duration),cache-expires-after, etc. - Use the tag helper to get default rendering, then override shapes if you need different markup.
Rendering manually (Liquid or Razor)
- Liquid: load by alias and build shapes:
{% assign menu = "alias:main-menu" | menu %}
{{ menu | shape_render }}- Liquid (content item access for in-place rendering):
{% assign menu_item = Content["alias:main-menu"] %}
{% assign menu_items = menu_item.Content.MenuItemsListPart.MenuItems %}
{% for item in menu_items %}
{{ item.DisplayText }}
{% endfor %}- Razor: inject
IShapeFactoryor useNewto build:
@inject OrchardCore.DisplayManagement.IShapeFactory ShapeFactory
@{
var menu = await ShapeFactory.New.Menu(Alias: "alias:main-menu");
}
@await DisplayAsync(menu)- To fully custom render, iterate items:
- Liquid:
menu.MenuItemsis a list of content items (with parts like LinkMenuItemPart, HtmlMenuItemPart, ContentMenuItemPart, MenuItemsListPart for children). - Razor: iterate
Model.MenuItems(from Menu shape) orContentItem.As<MenuItemsListPart>().MenuItems.
Overriding templates
- Place overrides in the active theme:
Views/Menu.liquidorViews/Menu.cshtmlViews/MenuItem.liquid/Views/MenuItemLink.liquid(use alternates for menu name/level/content type to target specific cases).- Use alternates for per-menu or per-level styling:
MenuItem__MainMenu,MenuItem__MainMenu__level__2MenuItemLink__LinkMenuItem,MenuItemLink__MainMenu__level__1
Built-in templates (Razor, simplified)
Menu.cshtml:
TagBuilder tag = Tag(Model, "ul");
tag.AddCssClass("list-group");
foreach (var item in Model.Items) { tag.InnerHtml.AppendHtml(await DisplayAsync(item)); }
<nav>@tag</nav>MenuItem.cshtml:
TagBuilder tag = Tag(Model, "li");
tag.InnerHtml.AppendHtml(await DisplayAsAsync(Model, "MenuItemLink"));
if ((bool)Model.HasItems) {
tag.InnerHtml.AppendHtml("<ul>");
foreach (var item in Model.Items) { tag.InnerHtml.AppendHtml(await DisplayAsync(item)); }
tag.InnerHtml.AppendHtml("</ul>");
}
@tagMenuItemLink(base):
<a href="@(Model.Href ?? "#")" target="@(!string.IsNullOrEmpty(Model.Target) ? Model.Target : "_self")">@Model.Text</a>MenuItemLink-LinkMenuItem.cshtml(LinkMenuItemPart):
var part = Model.ContentItem.As<LinkMenuItemPart>();
var url = part.Url.StartsWith('/') ? "~" + part.Url : part.Url;
url = url.StartsWith("~/") ? Url.Content(part.Url) : url;
if (!string.IsNullOrEmpty(part.Target)) tag.Attributes["target"] = part.Target;
tag.Attributes["href"] = url;
tag.InnerHtml.Append(Model.ContentItem.DisplayText);MenuItemLink-HtmlMenuItem.cshtml(HtmlMenuItemPart):
var part = Model.ContentItem.As<HtmlMenuItemPart>();
var url = part.Url.StartsWith('/') ? "~" + part.Url : part.Url;
url = url.StartsWith("~/") ? Url.Content(part.Url) : url;
tag.Attributes["href"] = url;
if (!string.IsNullOrEmpty(part.Target)) tag.Attributes["target"] = part.Target;
tag.InnerHtml.AppendHtml(Html.Raw(part.Html));MenuItemLink-ContentMenuItem.cshtml(ContentMenuItemPart):
var part = Model.ContentItem.As<ContentMenuItemPart>();
string id = part.ContentItem.Content.ContentMenuItemPart.SelectedContentItem.ContentItemIds[0];
var routeValues = new RouteValueDictionary(AutorouteOptions.Value.GlobalRouteValues);
routeValues[AutorouteOptions.Value.ContentItemIdKey] = id;
tag.Attributes["href"] = Url.RouteUrl(routeValues);
tag.InnerHtml.Append(Model.ContentItem.DisplayText);Useful models/properties (per menu item)
ContentItemwith parts:LinkMenuItemPart:Url,TargetHtmlMenuItemPart:HtmlContentMenuItemPart:SelectedContentItem.ContentItemIds[]MenuItemsListPart:MenuItems(children)MenuItemPermissionPart: permission data (hide if unauthorized)- For display, the
MenuItemshape includes the content item and computed alternates;MenuItemLinkfocuses on the link rendering. - Use
ContentItem.DisplayTextfor link labels; avoidTitlePart.Titlein front-end rendering since it's admin UX metadata and not reliable for content handling.
Tips
- Start with the tag helper for default behavior; override
MenuItem/MenuItemLinkfor custom markup. - Use level and menu-name alternates to target specific menu instances without affecting others.
- When hand-rendering, respect
MenuItemPermissionPartif present (check permissions) to avoid showing unauthorized links.
Pager Shapes and Rendering
Pager rendering in Orchard Core (Navigation module) and how to override/customize.
Shapes involved (C#-created via [Shape])
Pager: main pager shape built from child shapes (not just a view).Pager_Links: builds/aggregates link shapes for standard pager.- Sub-shapes:
Pager_Gap,Pager_First,Pager_Previous,Pager_Next,Pager_Last,Pager_CurrentPage(all morph toPager_Link). Pager_Link: morphs toActionLink(Razor view) to render<a>.Pager_Gap: renders a disabled link.PagerSlim: two-link pager; usesPager_Previous/Pager_Next.
Properties (common)
- Core:
Page,PageSize,TotalItemCount,Quantity,PagerId,ShowNext, text labels (FirstText,PreviousText,NextText,LastText,GapText). - Shape base:
TagName,Attributes,Classes. - List inherited (for link items):
ItemTagName,ItemClasses,ItemAttributes,FirstClass,LastClass. - Slim-specific:
PreviousClass,NextClass,PreviousText,NextText,UrlParams.
Shape-building flow (from PagerShapesTableProvider/PagerShapes)
Pager/PagerSlimare created with defaultItemClasses/ItemAttributes. Alternates added perPagerId(Pager__{id}).Pager_Linksbuilds the list:- Computes page count; if < 2 pages, morphs pager to
Listand renders children directly. - Creates
Pager_First,Pager_Previous, optionalPager_Gap, per-page items (Pager_CurrentPageorPager_Link), optional trailing gap,Pager_Next,Pager_Last. - Adds
relattributes (next/prev/no-follow) as needed. - Sets
shape.Metadata.Type = "List"; children render via list/item rendering. - Sub-shapes (
Pager_First,Pager_Previous,Pager_CurrentPage,Pager_Next,Pager_Last,Pager_Gap) all: - Clear alternates, set
Metadata.Type = "Pager_Link"(exceptPager_Linksets toActionLink), then render. Pager_CurrentPageaddsactiveclass to parent tag;Pager_Gapaddsdisabled.Pager_Linkmorphs toActionLink(RazorActionLinkshape renders<a>withhrefunless disabled).- Alternates per
PagerIdare added for all shapes (Pager__id,Pager_First__id, etc.).
Alternates
Pageralternates byPagerId:Pager__{Id},Pager_Previous__{Id}, etc.- You can add your own in code or via placement.
Customization approaches
- Override
Pager.cshtml/Pager.liquidin your theme to change wrapper/markup. - Override sub-shapes (e.g.,
Pager_Link.cshtml,Pager_Previous.cshtml,Pager_CurrentPage.cshtml) for link-level markup. - For
PagerSlim, overridePager_Previous/Pager_Next. - Use
shape_pager(Liquid) or configure the pager shape in Razor controller/view to set classes/attributes/texts.
Override examples (Razor)
- Minimal
Views/Pager.cshtml(forces C# shape pipeline to runPager_Links):
@{
Model.Metadata.Alternates.Clear();
Model.Metadata.Type = "Pager_Links";
}
@await DisplayAsync(Model)Views/Pager.cshtmlwith wrapper/Bootstrap classes:
TagBuilder tag = Tag(Model, "ul");
tag.AddCssClass("pagination");
foreach (var item in Model) { tag.InnerHtml.AppendHtml(await DisplayAsync(item)); }
<nav aria-label="Pager">@tag</nav>Views/Pager_Link.cshtml(bootstrap-style link items):
@*
Model has Attributes (href, rel, etc.), Classes, and Value.
Add active/disabled classes if set on parent tags.
*@
var li = Tag(Model, "li");
li.AddCssClass("page-item");
if (Model.Tag?.HasCssClass("disabled") == true) { li.AddCssClass("disabled"); }
if (Model.Tag?.HasCssClass("active") == true) { li.AddCssClass("active"); }
var a = Tag(Model, "a");
a.AddCssClass("page-link");
foreach (var attr in Model.Attributes) { a.Attributes[attr.Key] = attr.Value?.ToString(); }
a.InnerHtml.AppendHtml(CoerceHtmlString(Model.Value));
li.InnerHtml.AppendHtml(a);
@liViews/Pager_Previous.cshtml(optional custom glyph/text):
Model.Value = Html.Raw("«");
Model.Metadata.Type = "Pager_Link";
@await DisplayAsync(Model)- Example glyph-based overrides (from a working theme):
Pager_First: setModel.Metadata.Type = "Pager_Link",Model.Valueto««(Font Awesome icons), addtitle, setRouteValues["action"]="Index"; then@await DisplayAsync(Model).Pager_Last: same pattern with»».Pager_Next: setModel.Valueto», addtitle,Model.Metadata.Type = "Pager_Link".Pager_Previous: setModel.Valueto«, addtitle, setModel.RouteValues["action"]="Index"whenpageNumis null;Model.Metadata.Type = "Pager_Link".
Use PagerId alternates (Pager__Blog) to scope overrides to a specific pager instance if needed.
Liquid helpers (from docs)
shape_pager Model.Pager attributes: "{\"rel\": \"no-follow\"}"to adjust attributes.- Alternates with
PagerIdcan target specific pagers (e.g.,Pager-Blog.cshtml).
Tips
- Pager is just a specialized navigation/list; overriding the navigation templates changes pager rendering too.
- Use
PagerIdto scope overrides to a specific pager instance to avoid global impact. - When adding attributes (e.g.,
rel="nofollow"), set them onPagerorPager_Linkvia shape properties orshape_pager.
Placement
Placement controls where shapes render, whether they render, and which alternates/wrappers apply. Themes and modules can supply placement.json at their root.
File location and format
- File name:
placement.jsonat the root of a theme or module. - JSON object: keys are shape types; values are arrays of placement rules.
Example skeleton:
{
"TextField": [
{ "place": "Content:1", "displayType": "Detail" }
]
}Filters (rule matching)
displayType:Detail,Summary,SummaryAdmin, etc.differentiator: used to target a specific part/field instance.contentType: single or array, supports*wildcard prefixes.contentPart: single or array.path: single or array of request paths.
Placement info
place: target zone/position.-hides the shape./ZoneNamemoves to a layout zone.alternates: list of alternates to add.wrappers: list of wrapper shapes.shape: replace shape type.
Removing shapes from zones (Liquid)
When you render a local zone like Model.Content, you can remove a specific shape before rendering:
{% shape_remove_item Model.Content "Blog-Summary" %}
{% shape_remove_item Model.Content "HtmlBodyPart" %}Use the shape differentiator as listed on the shape (e.g., Blog-Summary for a field on the Blog part). This is the default way to suppress a shape from a zone in Liquid.
Placement rules (place: "-") are still valid when you want to hide shapes globally or by contentType/displayType.
Placement precedence
1) Startup project (acts like a super-theme) 2) Active theme (front-end or admin depending on request) 3) Modules (dependency order)
Differentiators
Differentiators uniquely identify shapes that share the same type. Common patterns:
- Part shapes:
[PartName]or[PartName]-[ShapeType] - Field shapes:
[PartName]-[FieldName]or[PartName]-[FieldName]-[ShapeType]
Field display modes (strict rules)
If a field uses a display mode, the shape type changes and the differentiator must include it:
- Shape type:
TextField_Display(example) - Differentiator:
[PartType]-[FieldName]-[FieldType]_Display__[DisplayMode]
Example:
{
"TextField_Display": [
{
"place": "Content:1",
"differentiator": "Blog-MyField-TextField_Display__Header"
}
]
}Editor grouping (tabs/cards/columns)
Editor shapes can be grouped using modifiers in place:
- Tabs:
# - Cards:
% - Columns:
| - Group position:
;(e.g.,#Media;0) - Column width:
_(e.g.,|Content_9;1)
Example:
{
"MediaField_Edit": [
{ "place": "Parts:0#Media;0", "contentType": [ "Article" ] }
],
"HtmlField_Edit": [
{ "place": "Parts:0#Content;1", "contentType": [ "Article" ] }
]
}Dynamic parts (no driver)
Dynamic parts render with ContentPart shape and use the part name as differentiator. For non-detail displays, use ContentPart_Summary (or the display type suffix).
Example:
{
"ContentPart": [
{ "place": "MyGalleryZone", "differentiator": "GalleryPart" }
],
"ContentPart_Summary": [
{ "place": "MyGalleryZone", "differentiator": "GalleryPart" }
]
}Shape Build Workflow (Checklist)
Use this to build/override a shape safely (e.g., TextAndImage section).
1) Identify content type and tenant
- If
ContentDefinition.jsonexists: use50-content-model/CONTENT-DEFINITIONS-EXTRACTOR.mdto extract the type/part slice (avoid reading the full JSON). - Locate the content type and note:
Stereotype(Widget, Section, etc.) - drives base shape name.- Parts attached, and fields on those parts.
- Field types and settings (see
50-content-model/FIELDS.md).
2) Determine template name
- Base on stereotype:
- Use the stereotype value as the prefix (e.g.,
Widget->Widget-<ContentType>,Section->Section-<ContentType>,Block->Block-<ContentType>). - Default content ->
Content-<ContentType> - Add display type if needed:
Content-<Type>.Summary.cshtml(or.liquid). - Alternates patterns: see
20-shapes-placement/ALTERNATES.md. - If unsure, use the extractor to check
ContentTypeSettings.Stereotypebefore creating the template.
3) Map fields to properties
- Use the field type to know the value property (see
50-content-model/FIELDS.md): TextField.TextMediaField.Paths[]andMediaTexts[]- etc.
- Access pattern in Razor:
Model.ContentItem.Content.<Part>.<Field>.<Property>. - For fields on the type itself, the part name equals the type name.
- Content JSON values are dynamic; in Razor, avoid explicit casts like
(string)on field values. Inline the expression or call.ToString()/Convert.ToString()when you need a string. - Avoid
Model?.ContentItem?.Contentnull chains in shape templates; use null checks only at the field/property level when data can be missing (e.g.,MediaField.Paths).
4) Render helpers (Razor/Liquid)
- These are examples; for other field types use
50-content-model/FIELDS.mdand the tag/helper catalogs. - In Razor, keep simple fields inline and only assign locals when reusing values or handling field-level nulls; in Liquid,
assignis fine for readability. - Media (Razor, MediaField first path):
@{
var imgPath = Model.ContentItem.Content.PartName.Image.Paths?[0]?.ToString();
}
@if (!string.IsNullOrEmpty(imgPath))
{
<img asset-src="@imgPath" asp-append-version="true" alt="">
}- Media (Liquid):
{% assign img = Model.ContentItem.Content.PartName.Image.Paths[0] %}
<img src="{{ img | asset_url | resize_url: width: 1200 }}" alt="">- Text field (Razor):
@Model.ContentItem.Content.PartName.Text.Text(no explicit cast needed) - Text field (Liquid):
{{ Model.ContentItem.Content.PartName.Text.Text }} - Other fields: see
50-content-model/FIELDS.mdfor the property to use (e.g., NumericField.Value, BooleanField.Value, LinkField.Url/Text/Target, TaxonomyField.TermContentItemIds, ContentPickerField.ContentItemIds, etc.). - Use
<shape>orshape_renderto embed other shapes if needed. - Helper catalogs:
- Tag helpers:
30-razor/TAG-HELPERS-SHAPES.md - Liquid tags/filters:
40-liquid/LIQUID-TAGS.md,40-liquid/LIQUID-FILTERS.md - Orchard helper extensions:
30-razor/ORCHARD-HELPER.md - Editor-aware rendering:
- Check
50-content-model/FIELDS.mdfor editor options that affect data shape (notably TextField editors likeIconPicker-> Font Awesome class;PredefinedList-> selected option value). HtmlField rendersHtmlregardless of editor flavor.
- When overriding a content item template and you just want a wrapper, prefer
@await DisplayAsync(Model.Content)and let parts (including BagPart) render with their own templates. - Override
BagPartonly when you need custom item-level markup (e.g., FAQ accordion).
5) Placement and differentiator
- If scoping to a specific instance/part, use placement with
differentiator(seePLACEMENT.md). - For Section/Widget stereotypes, alternates often suffice without placement changes.
6) Validate (manual checks)
- Optional debugging when values look wrong/missing:
- Liquid:
{{ Model.Metadata.Alternates | json | console_log }}and{{ Model.ContentItem | json | console_log }}to the browser console. - Razor: temporarily render a
<pre>withModel.Metadata.Type,Model.Metadata.Alternates, orSystem.Text.Json.JsonSerializer.Serialize(Model.ContentItem). - Remove these snippets after verifying to keep output clean.
- Verify the target display types you care about (
Detail,Summary, etc.).
Shapes Overview
What is a shape
- A shape is a renderable object with metadata and properties (often dynamic).
- Shapes are rendered via templates (Razor or Liquid) and resolved using alternates.
When to create shapes
- Prefer shapes over MVC partials for UI composition and theming.
- Shapes can be invoked from other shapes using the
<shape>tag helper (Razor) orshape_render/shapetags (Liquid).
Determining the shape model
- If the view model is unclear, find where the shape is created or invoked to see which properties are passed.
- If that fails, treat it as a Content Item shape (see
50-content-model/CONTENT-ITEMS.md) or ask for the expected model.
Shape lifecycle (high level)
- Creation -> metadata/alternates -> placement -> rendering.
Finding shape data
- Inspect the driver/tag helper/Liquid invocation that created the shape to see what properties are set.
- If unsure, log
Model.Metadata.AlternatesandModel(Liquid| console_log, Razor serialize in dev) to learn the model. - Content item shapes usually expose
ContentItem,ContentItem.Content, and the part/field being rendered; see50-content-model/CONTENT-ITEMS.md.
Razor Templating
Purpose: Implement Razor theme changes with tag helpers, shape rendering, and IOrchardHelper.
Files:
TAG-HELPERS.md- overview and links to the tag helper catalogs.TAG-HELPERS-SHAPES.md-<shape>,<metadata>, alternates/wrappers/classes/properties, and<zone>.TAG-HELPERS-RESOURCES.md- script/style tags,<resources>, meta, and link helpers.TAG-HELPERS-MEDIA.md- asset-src/asset-href and image resize/format helpers.TAG-HELPERS-CONTENT-MENUS.md-<contentitem>, display/edit/admin/remove/create links, and<menu>.TAG-HELPERS-CACHING.md-<dynamic-cache>and<cache-dependency>.TAG-HELPERS-UTILITIES.md- user display name, validation class, disabled inputs, datetime/timespan helpers, captcha.ORCHARD-HELPER.md- content queries, display helpers, culture/localization, users, CSS helpers, media/CDN, sanitization, tips.
Use this section only when the active theme uses .cshtml templates.
IOrchardHelper Extensions (Razor)
Quick catalog of useful IOrchardHelper extension methods in Razor views.
Content queries (OrchardCore.Contents.Core)
GetContentItemIdByHandleAsync(handle)GetContentItemByHandleAsync(handle, VersionOptions option = null)GetContentItemByIdAsync(id, VersionOptions option = null)GetContentItemsByIdAsync(ids, VersionOptions option = null)GetContentItemByVersionIdAsync(versionId)QueryContentItemsAsync(Func<IQuery<ContentItem, ContentItemIndex>, IQuery<ContentItem>> query)GetRecentContentItemsByContentTypeAsync(contentType, max = 10)
Display helpers (OrchardCore.ContentManagement.Display/Razor)
DisplayAsync(ContentItem, displayType = "", groupId = "", IUpdateModel updater = null)(onIOrchardDisplayHelper, which implementsIOrchardHelper).ConsoleLog(object content)(logs JSON to browser console; no-op in production/null content).LiquidToHtmlAsync(string liquid, object model = null)- render a Liquid string to HTML from Razor (e.g., for HtmlField content).
Culture helpers (OrchardCore.DisplayManagement/Extensions)
CultureDir()->"rtl"or"ltr"IsRightToLeft()-> boolCultureName()-> culture name (e.g.,en-US)
Localization (OrchardCore.ContentLocalization.Abstractions)
GetContentCultureAsync(ContentItem)->CultureInfo
Users (OrchardCore.Users.Core/Razor)
GetUserByIdAsync(userId)GetUsersByIdsAsync(userIds)
CSS helpers (wrappers/classes)
- Content wrappers (OrchardCore.ContentManagement.Display/Razor):
GetPartWrapperClasses(ContentTypePartDefinition, params string[] extra)GetFieldWrapperClasses(ContentPartFieldDefinition, params string[] extra)- Theme/admin CSS helpers (OrchardCore.DisplayManagement/Html):
GetLimitedWidthWrapperClasses(...)GetLimitedWidthClasses(...)GetStartClasses(...)GetEndClasses(...)(+ overload with offset)GetLabelClasses(bool inputRequired = false, params string[] extra)GetWrapperClasses(...)GetOffsetClasses(...)GetThemeOptions()->TheAdminThemeOptions
Media/CDN and sanitization
ResourceUrl(resourcePath, bool? appendVersion = null)(OrchardCore.ResourceManagement.Core) - maps~/to app base, applies CDN and versioning.SanitizeHtml(string html)(OrchardCore.Infrastructure) - returns sanitized HTML.
Tips
- Add
@inject IOrchardHelper Orchard(or use the built-inOrchardproperty in Razor views) to access these. - For shape rendering in Razor, prefer the
<shape>tag helper for ad-hoc shapes andDisplayAsyncfor content items.
Tag Helpers - Caching
Tag helpers for dynamic caching and cache dependencies.
<dynamic-cache cache-id="...">
- Purpose: Cache rendered output with dynamic invalidation.
- Attributes:
cache-id(required)vary-by,dependenciesexpires-on,expires-after,expires-slidingenabled(bool)- Example:
<dynamic-cache cache-id="home-hero" vary-by="culture">
...
</dynamic-cache><cache-dependency dependency="...">
- Purpose: Add a cache dependency inside a cache scope.
- Example:
<cache-dependency dependency="contentitem:123" />Tag Helpers - Content and Menus
Tag helpers for content item rendering and menus.
<contentitem>
- Purpose: Render a
ContentItemshape. - Attributes: same as
<shape>, plusprop-*etc. - Example:
<contentitem content-item="@Model.ContentItem" display-type="Summary" /><a display-for="...">, <a edit-for="...">, <a admin-for="...">, <a remove-for="...">, <a create-for="...">
- Purpose: Generate links for content items.
- Attributes:
display-for,edit-for,admin-for,remove-for,create-for(each expects aContentItem).asp-route-*adds route values.- Example:
<a display-for="@Model.ContentItem" class="btn" asp-route-returnUrl="@Context.Request.Path" /><menu>
- Purpose: Render a menu shape.
- Attributes: same as
<shape>, plusprop-*etc. - Example:
<menu prop-name="main" />Tag Helpers - Media
Tag helpers for media library paths and image resizing.
<img asset-src="...">
- Purpose: Resolve a media library path to a public URL.
- Attributes:
asset-src(media path)asp-append-version(bool)- Example:
<img asset-src="/media/hero.jpg" asp-append-version="true" /><a asset-href="...">
- Purpose: Resolve a media library path to a public URL.
- Attributes:
asset-href(media path)asp-append-version(bool)- Example:
<a asset-href="/media/file.pdf">Download</a><img img-width="..." img-height="..." img-resize-mode="..." img-quality="..." img-format="..." img-profile="..." img-anchor="..." img-bgcolor="...">
- Purpose: Apply image resizing to an existing
src. - Attributes (prefix
img-): img-width,img-height,img-resize-mode,img-qualityimg-format,img-profile,img-anchor,img-bgcolor- Example:
<img src="/media/hero.jpg" img-width="800" img-resize-mode="Crop" />Tag Helpers - Resources and Metadata
Tag helpers for scripts, styles, and head metadata.
<script ...> and <style ...>
- Purpose: Register or inline scripts/styles with resource management.
- Key attributes:
asp-name,asp-src,at,asp-append-versioncdn-src,debug-src,debug-cdn-srcuse-cdn,condition,culture,debug,depends-on,version- Notes:
atcan beHead,Foot, orInline.- Example:
<script asp-name="bootstrap" at="Foot"></script>
<style asp-src="~/MyTheme/site.css" at="Head" asp-append-version="true"></style><resources type="...">
- Purpose: Render all registered resources of a given type.
- Attributes:
type(enum; e.g.,HeadLink,HeadScript,FootScript,Stylesheet,Meta)- Example:
<resources type="Stylesheet"></resources><meta asp-name="..." content="...">, <meta asp-property="..." content="...">
- Purpose: Register meta entries.
- Attributes:
asp-nameorasp-propertycontent,http-equiv,charset,separator- Example:
<meta asp-name="description" content="..." /><link asp-src="...">
- Purpose: Register a link resource.
- Attributes:
asp-src,asp-append-versionrel,title,type,condition- Example:
<link asp-src="~/MyTheme/icons/favicon.ico" rel="icon" />Tag Helpers - Shapes and Zones
Tag helpers used to render and customize shapes.
<shape>
- Purpose: Render any shape by type name with optional props and caching metadata.
- Attributes:
type(optional): shape type. If omitted, uses tag name. Use the internal shape type, not the file name.
File name mapping: - -> __, . -> _. E.g. Component-Header.cshtml renders with type="Component__Header" and Content-Page.Summary.cshtml renders with type="Content_Summary__Page".
prop-*: passes additional shape properties; values keep original type.- Any other attributes become shape properties (string).
id,alternate,wrapper,display-typemap to shape metadata.cache-id,cache-context,cache-tag,cache-fixed-duration,cache-sliding-duration.- Example:
<shape type="Card" prop-title="Hello" class="hero" cache-id="card-1" /><metadata>
- Purpose: Set shape metadata inside a shape tag.
- Attributes:
display-type(sets metadata display type).- Example:
<shape type="Card">
<metadata display-type="Summary" />
</shape><add-alternate name="...">, <remove-alternate name="...">, <clear-alternates>
- Purpose: Manage shape alternates.
- Location: inside a shape tag; alternates and clears require
<metadata>as parent. - Example:
<shape type="Card">
<metadata>
<add-alternate name="Card__Featured" />
<remove-alternate name="Card__Old" />
<clear-alternates />
</metadata>
</shape><add-wrapper name="...">, <remove-wrapper name="...">, <clear-wrappers>
- Purpose: Manage shape wrappers.
- Location: inside a shape tag; wrappers and clears require
<metadata>as parent. - Example:
<shape type="Card">
<metadata>
<add-wrapper name="Card_Wrapper" />
</metadata>
</shape><add-class name="...">, <remove-class name="...">, <clear-classes>
- Purpose: Manage
shape.Classes. - Location: inside a shape tag.
- Example:
<shape type="Card">
<add-class name="featured" />
</shape><add-property name="...">
- Purpose: Adds a property to the current shape; value can come from inner content or a
valueattribute. - Location: inside a shape tag.
- Attributes:
name(required)value(optional)- Example:
<shape type="Card">
<add-property name="Intro">Short intro text</add-property>
</shape><zone name="..." position="...">
- Purpose: Add child content to a layout zone.
- Attributes:
name(required)position(optional)- Example:
<zone name="Header" position="1">...</zone>Tag Helpers - Utilities
Helpers for user display, validation, date/time, and other utilities.
<user-display-name user-name="...">
- Purpose: Render a user display name shape with caching hints.
- Attributes:
user-name- inherits
<shape>attributes and cache metadata. - Example:
<user-display-name user-name="@user.UserName" />asp-validation-class-for
- Purpose: Adds
has-validation-error is-invalidto any element when the specified model field has errors. - Attribute:
asp-validation-class-for="Model.Property"- Example:
<div asp-validation-class-for="Model.Email">...</div><input asp-is-disabled="true">
- Purpose: Adds
disabled="disabled"whenasp-is-disabledis true. - Attribute:
asp-is-disabled(bool)- Example:
<input asp-for="Model.Name" asp-is-disabled="true" /><datetime utc="..." format="...">
- Purpose: Render a
DateTimeshape. - Attributes:
utc(DateTime?)format(string)- Example:
<datetime utc="@Model.PublishedUtc" format="g" /><timespan utc="..." origin="...">
- Purpose: Render a
TimeSpanshape relative toorigin. - Attributes:
utc(DateTime?)origin(DateTime?)- Example:
<timespan utc="@Model.PublishedUtc" origin="@DateTime.UtcNow" /><captcha language="..." onload="...">
- Purpose: Render a ReCaptcha shape.
- Attributes:
language(ISO code)onload(callback)- Example:
<captcha language="en" onload="onCaptchaLoaded" />Tag Helpers
Razor tag helpers used in Orchard Core themes. Open only the file you need.
- Shapes and zones:
TAG-HELPERS-SHAPES.md - Content and menus:
TAG-HELPERS-CONTENT-MENUS.md - Resources and metadata:
TAG-HELPERS-RESOURCES.md - Media helpers:
TAG-HELPERS-MEDIA.md - Caching:
TAG-HELPERS-CACHING.md - Utilities (user, validation, time, captcha):
TAG-HELPERS-UTILITIES.md
Liquid Templating
Purpose: Implement Liquid theme changes with tags, filters, and shape helpers.
Files:
LIQUID.md- syntax quick ref, Orchard-specific tips, safety/debugging, and related files.LIQUID-TAGS.md- Orchard Liquid tag catalog.LIQUID-FILTERS.md- built-in filter catalog.LIQUID-SHAPES.md- build/render shapes, render content items by ID, zone content, shape metadata helpers.
Use this section only when the active theme uses .liquid templates.
Liquid Filters
This is a source-derived catalog of Liquid filters commonly used for Orchard Core theme work.
Core formatting and utilities
t: localize a string with optional parameters. Example:{{ "Hello {0}" | t: user.Name }}html_class: converts text to a CSS class (e.g., spaces -> dashes). Example:{{ "My Title" | html_class }}json: serialize to JSON; passtrueto indent. Example:{{ Model | json: true }}jsonparse: parse JSON into a Liquid object/array. Example:{% assign obj = my_json | jsonparse %}
Dates and times
local: convert a date/time to local timezone; accepts"now"/"today". Example:{{ "now" | local }}utc: convert a date/time to UTC; accepts"now"/"today". Example:{{ Model.PublishedUtc | utc }}
Strings and templating
slugify: slugifies text. Example:{{ Model.Title | slugify }}liquid: renders a Liquid string template; first argument is an optional model. Example:{{ "{{ user.Name }}" | liquid: Model }}
Shapes
shape_new: create a shape; named args become shape properties. Example:{% assign s = "Card" | shape_new: title: "Hi" %}shape_render: renders a shape to HTML. Example:{{ s | shape_render }}shape_stringify: renders a shape to a string (not HTML-encoded). Example:{{ s | shape_stringify }}shape_properties: set shape properties from named args. Example:{{ s | shape_properties: title: "Hi" }}
Resources and URLs
href: convert a virtual path to an app-relative URL. Example:{{ "~/css/site.css" | href }}absolute_url: convert a URL to absolute. Example:{{ "~/about" | absolute_url }}append_version: append file version to a URL. Example:{{ "~/css/site.css" | append_version }}resource_url: prefix a CDN base URL if configured. Example:{{ "~/css/site.css" | resource_url }}
HTML safety
sanitize_html: sanitize HTML content. Example:{{ Model.Html | sanitize_html }}supported_cultures: returns supported cultures (obsolete). Example:{% assign cultures = "" | supported_cultures %}
Content
display_url: get display URL for a content item or id. Example:{{ Model.ContentItem | display_url }}shape_build_display: build a display shape for a content item. Example:{{ Model.ContentItem | shape_build_display: "Summary" | shape_render }}content_item_id: load content item(s) by id(s). Example:{{ "42" | content_item_id }}full_text: get full-text segments for indexing/search. Example:{{ Model.ContentItem | full_text }}
Lists
list_items: get contained items for a list content item (or id). Example:{{ Model.ListItem | list_items }}list_count: get count of items in a list content item (or id). Example:{{ Model.ListItem | list_count }}container: get the list container of a contained item. Example:{{ Model.ContentItem | container }}
Media
asset_url: resolve a media path to a public URL. Example:{{ "/media/hero.jpg" | asset_url }}resize_url: build a resized image URL. Example:{{ "/media/hero.jpg" | resize_url: width: 800, mode: "crop" }}.
Use a media profile with named args: {{ "/media/hero.jpg" | resize_url: profile: "site-default" }}
Localization
localization_set: get localized item(s) for a set; optional culture arg. Example:{{ Model.LocalizationSet | localization_set: "fr-FR" }}switch_culture_url: build a URL that switches to the given culture. Example:{{ "fr-FR" | switch_culture_url }}
Queries
query: execute a query with parameters. Example:{{ Query | query: category: "news" }}
Taxonomies
taxonomy_terms: resolve term items from a taxonomy field. Example:{{ Model.TaxonomyField | taxonomy_terms }}inherited_terms: get term hierarchy for a term in a taxonomy. Example:{{ Term | inherited_terms: TaxonomyId }}
Users and permissions
users_by_id: load user(s) by user id(s). Example:{{ "user-id" | users_by_id }}has_permission: check current user permission. Example:{{ User | has_permission: "ViewContent" }}is_in_role: check current user role. Example:{{ User | is_in_role: "Administrator" }}user_email: get email for current user or a user. Example:{{ User | user_email }}
Markdown and shortcodes
markdownify: convert markdown to HTML. Example:{{ Model.Body | markdownify }}shortcode: process shortcodes in text. Example:{{ Model.Body | shortcode }}
Workflows
signal_url: build a workflow signal URL. Example:{{ "MySignal" | signal_url }}
Debug
console_log: outputs a<script>console.log(...)in non-production. Example:{{ Model | console_log }}
Liquid Shape Tags
Build and render shapes
- Tag:
{% shape type: "Card", title: "Hello" %}creates and renders a shape immediately. - Filters:
shape_build_display: build a display shape for a content item.shape_build_editor: build an editor shape for a content item.shape_render: render a built shape.- Example (content item already available):
{% assign display = Model.ContentItem | shape_build_display: "Detail" %}
{{ display | shape_render }}Render content items by ID
{{ Content.ContentItemId["<id>"] | shape_build_display: "Summary" | shape_render }}Render content items by handle/alias
{% assign menu = Content["alias:main-menu"] %}
{{ menu | shape_build_display: "Summary" | shape_render }}Zone content
{% zone "Header", position: "1" %}...{% endzone %}pushes content into a zone/section.- Use
render_sectionin the layout to output the zone.
Shape metadata helpers
{% shape_add_alternates shape, "Alt1 Alt2" %}{% shape_clear_alternates shape %}{% shape_add_wrappers shape, "Wrapper1" %}{% shape_type shape, "MyType" %}
See 40-liquid/LIQUID-TAGS.md for the full tag list and 20-shapes-placement/ALTERNATES.md for naming patterns.
Liquid Tags
This is a source-derived catalog of Liquid tags commonly used for Orchard Core theme work. Liquid tag arguments are typically snake_case (e.g., append_version, cache_id).
Layout and sections
{% layout "LayoutName" %}: set the view layout. Example:{% layout "Layout" %}{% render_body %}: render the mainContentzone.{% render_section "ZoneName", required: true %}: render a zone by name. Example:{% render_section "Header" %}{% page_title "Segment", position: "0", separator: " - " %}: render full page title.{% page_title_add_segment "Segment", position: "0" %}: add a title segment only.{% antiforgerytoken %}: render a hidden anti-forgery input.
Shapes (rendering)
{% shape type: "Card", title: "Hi" %}: render a shape; extra args become properties. Use the internal shape type,
not the file name. File name mapping: - -> __, . -> _. E.g. Component-Header.cshtml renders as Component__Header and Content-Page.Summary.cshtml renders as Content_Summary__Page.
{% contentitem content_item: Model.ContentItem %}: render a content item shape.{% zone "Header", position: "1" %}...{% endzone %}: add content to a zone.
Shapes (metadata and manipulation)
{% shape_add_alternates shape, "Alt1 Alt2" %}: add alternates.{% shape_clear_alternates shape %}: clear alternates.{% shape_add_wrappers shape, "Wrapper1 Wrapper2" %}: add wrappers.{% shape_clear_wrappers shape %}: clear wrappers.{% shape_add_classes shape, "class1 class2" %}: add CSS classes.{% shape_clear_classes shape %}: clear classes.{% shape_add_attributes shape, data_id: "42" %}: add HTML attributes (underscores become dashes).{% shape_clear_attributes shape %}: clear attributes.{% shape_type shape, "MyType" %}: set shape type.{% shape_display_type shape, "Summary" %}: set display type.{% shape_position shape, "1" %}: set position.{% shape_tab shape, "Content" %}: set editor tab.{% shape_cache shape, cache_id: "id", cache_tag: "tag", cache_context: "ctx" %}: set cache metadata.{% shape_add_properties shape, title: "Hi" %}: set properties.{% shape_remove_property shape, "Title" %}: remove a property.{% shape_remove_item shape, "MyItem" %}: remove an item from aShape.{% shape_pager shape, classes: "a b", item_classes: "x y" %}: customize pager properties.
Anchors and tag helpers
{% a action: "Index", controller: "Home" %}Home{% enda %}: route-based anchor.{% a route: "MyRoute", route_id: "42" %}Link{% enda %}: route-name anchor withroute_*.{% form method: "post", asp_action: "Save" %}...{% endform %}: call a Razor tag helper for<form>.{% helper "input", asp_for: "Model.Name", class: "form-control" %}: call any tag helper.{% block "textarea", asp_for: "Model.Body" %}...{% endblock %}: tag helpers with block content.
Resources
{% script name: "bootstrap", at: "Foot" %}: require a script resource.{% script src: "~/theme/app.js", append_version: true %}: include a script URL.{% scriptblock name: "app", at: "Foot" %}...{% endscriptblock %}: inline script block.{% style name: "site", at: "Head" %}: require a style resource.{% style src: "~/theme/site.css", append_version: true %}: include a style URL.{% styleblock name: "site", at: "Head" %}...{% endstyleblock %}: inline style block.{% meta name: "description", content: "..." %}: register meta tags.{% link src: "~/favicon.ico", rel: "icon" %}: register link tags.{% resources type: "Stylesheet" %}: render registered resources.
Caching
{% cache "id", vary_by: "culture", dependencies: "contentitem:123", expires_after: "00:01:00" %}...{% endcache %}{% cache_dependency "contentitem:123" %}: add cache dependency inside a cache scope.{% cache_expires_on "2024-01-01T00:00:00Z" %}: set absolute expiration.{% cache_expires_after "00:05:00" %}: set absolute duration.{% cache_expires_sliding "00:00:30" %}: set sliding expiration.
HttpContext items
{% httpcontext_add_items key: value, another: 1 %}: add items toHttpContext.Items.{% httpcontext_remove_items "key" %}: remove an item by key.
Liquid Basics
Syntax quick ref
- Output:
{{ value }}; statements:{% ... %}. - Variables:
assign,capture,increment,decrement. - Flow:
if/unless,case/when,forwithlimit/offset/reverse,break/continue. - Strings/arrays/objects follow Shopify Liquid semantics plus Orchard filters/tags.
Orchard-specific tips
- Use
href/img_tag/asset_urlfilters to resolve paths. - Build and render shapes:
shape_build_display,shape_build_editor,shape_render. - Access content items in scope:
Model.ContentItem,Content.ContentItemId["id"],Content["alias:my-handle"], or values passed in the shape's model. - Inline menu rendering: load a menu by alias (
{% assign menu = Content["alias:main-menu"] %}) and iteratemenu.MenuItemsListPart.MenuItems. - For BagPart
ContentItems, access parts directly (item.PartName.Field) instead ofitem.Content.PartName. - Localization:
| tfilter.
Safety and debugging
- Avoid heavy logic in templates; push logic to drivers when possible.
- Debug quickly with
| console_logor| jsonin development.
Related files
- Tags:
40-liquid/LIQUID-TAGS.md - Filters:
40-liquid/LIQUID-FILTERS.md - Shape helpers:
40-liquid/LIQUID-SHAPES.md
Containers: BagPart, FlowPart, ListPart
How Orchard Core stores and renders contained items.
BagPart
- Data: embeds items under
ContentItem.Content.<PartName>.ContentItems(array of fullContentItemobjects). - Shape:
BagPartwith a differentiator matching the part name. - Render (Liquid):
{% for item in Model.ContentItems %}
{{ item | shape_build_display: "Detail" | shape_render }}
{% endfor %}- When you need direct field access on contained items in Liquid, use
item.PartName.Field(no.Contentprefix). - Render (Razor):
@using OrchardCore.ContentManagement.Display
@inject IContentItemDisplayManager DisplayManager
@foreach (var item in Model.ContentItems)
{
var shape = await DisplayManager.BuildDisplayAsync(item, "Detail");
@await DisplayAsync(shape);
}- Placement differentiator: the part name (e.g.,
ContentPartrules useBagPartName). - If a content type just needs a wrapper (Page with sections), render
@await DisplayAsync(Model.Content)in the content template and let the default BagPart render; overrideBagPartonly for custom item-level markup.
FlowPart
- Data:
ContentItem.Content.<PartName>.WidgetswithFlowMetadata(Alignment, Size). - Shape:
FlowPartrenders widgets in order; widgets are just content items. - Custom rendering:
- Access
Model.Widgets(already shapes in display mode) or rebuild like BagPart. - Use
FlowMetadata.Sizeto apply grid classes if the base theme uses them. - To change layout rules globally, override the
FlowPartshape. - Typical usage: FlowPart is meant for Widget stereotypes. It is usually rendered as part of
@await DisplayAsync(Model.Content); manual rendering is rare compared to BagPart.
- FlowPart adds widget classes. These are the exact class patterns:
widgetwidget-<contenttype>(content type HTML-classified, ex:widget-input)widget-align-left,widget-align-center,widget-align-right,widget-align-justify,widget-align-inheritwidget-size-25,widget-size-33,widget-size-50,widget-size-66,widget-size-75,widget-size-100- Sizes are integers and can be any value, but FlowPart UI usually sets the above defaults.
- If you override widget templates, render
Model.Classeson the wrapper or you lose sizing/alignment metadata.
ListPart and ContainedPart
- ListPart stores no items; items live as regular content items with
ContainedPart. ContainedPartproperties:ListContentItemId,ListContentType,Order.- The
ListPartdisplay shape usually queries contained items and renders them as a list. - Custom rendering:
- Override
ListPartshape to change listing markup. - Use placement on
ListPart(differentiatoris the part name) to move/hide. - If you need direct access to contained items, query by
ContainedPart.ListContentItemId.
Quick placement patterns
- Bag/Flow/List part shapes:
BagPart,FlowPart,ListPart(add display type suffix as needed). - Differentiator is the part name: target with placement
differentiator: "<PartName>". - Widget/contained item alternates still follow content-type rules (see
20-shapes-placement/ALTERNATES.md).
Content Definition Examples
Large reference examples. Load only when you need working JSON patterns.
Examples from a real tenant (anonymized)
- Page with FlowPart and Autoroute:
{
"Name": "Page",
"Settings": { "ContentTypeSettings": { "Creatable": true, "Listable": true, "Draftable": true, "Versionable": true, "Securable": true } },
"ContentTypePartDefinitionRecords": [
{ "PartName": "TitlePart", "Name": "TitlePart", "Settings": { "ContentTypePartSettings": { "Position": "0" } } },
{
"PartName": "AutoroutePart",
"Name": "AutoroutePart",
"Settings": {
"ContentTypePartSettings": { "Position": "1" },
"AutoroutePartSettings": { "AllowCustomPath": true, "Pattern": "{{ ContentItem.DisplayText | slugify }}", "ShowHomepageOption": true }
}
},
{ "PartName": "FlowPart", "Name": "FlowPart", "Settings": { "ContentTypePartSettings": { "Position": "2" } } }
]
}- Widget with FlowPart:
{
"Name": "ContainerWidget",
"Settings": { "ContentTypeSettings": { "Stereotype": "Widget", "Securable": true } },
"ContentTypePartDefinitionRecords": [
{ "PartName": "TitlePart", "Name": "TitlePart", "Settings": { "ContentTypePartSettings": { "Position": "0" } } },
{ "PartName": "FlowPart", "Name": "FlowPart", "Settings": { "ContentTypePartSettings": { "Position": "1" } } }
]
}- Form widget with TitlePart, FormElementPart, FormPart, FlowPart:
{
"Name": "Form",
"Settings": { "ContentTypeSettings": { "Stereotype": "Widget" } },
"ContentTypePartDefinitionRecords": [
{ "PartName": "TitlePart", "Name": "TitlePart", "Settings": { "TitlePartSettings": { "RenderTitle": false }, "ContentTypePartSettings": { "Position": "0" } } },
{ "PartName": "FormElementPart", "Name": "FormElementPart", "Settings": { "ContentTypePartSettings": { "Position": "1" } } },
{ "PartName": "FormPart", "Name": "FormPart", "Settings": {} },
{ "PartName": "FlowPart", "Name": "FlowPart", "Settings": {} }
]
}- Part with fields (ContentPicker, Text fields):
{
"Name": "ContentItemWidget",
"ContentPartFieldDefinitionRecords": [
{
"FieldName": "ContentPickerField",
"Name": "ContentToDisplay",
"Settings": {
"ContentPartFieldSettings": { "DisplayName": "Content to display" },
"ContentPickerFieldSettings": { "Multiple": true, "DisplayAllContentTypes": true, "TitlePattern": "{{ Model.ContentItem | display_text }}" }
}
},
{ "FieldName": "TextField", "Name": "DisplayType", "Settings": { "ContentPartFieldSettings": { "DisplayName": "Display type" } } },
{ "FieldName": "TextField", "Name": "GroupId", "Settings": { "ContentPartFieldSettings": { "DisplayName": "Group ID" } } }
]
}Example: Case Study type (with editors)
{
"ContentTypeDefinitionRecords": [
{
"Name": "CaseStudy",
"DisplayName": "Case Study",
"Settings": {
"ContentTypeSettings": {
"Creatable": true,
"Listable": true,
"Draftable": true,
"Versionable": true,
"Securable": true
}
},
"ContentTypePartDefinitionRecords": [
{
"PartName": "TitlePart",
"Name": "TitlePart",
"Settings": { "ContentTypePartSettings": { "Position": "0" } }
},
{
"PartName": "AutoroutePart",
"Name": "AutoroutePart",
"Settings": {
"ContentTypePartSettings": { "Position": "1" },
"AutoroutePartSettings": {
"AllowCustomPath": true,
"AllowUpdatePath": true,
"Pattern": "{{ ContentItem.DisplayText | slugify }}"
}
}
},
{ "PartName": "CaseStudy", "Name": "CaseStudy", "Settings": { "ContentTypePartSettings": { "Position": "2" } } }
]
}
],
"ContentPartDefinitionRecords": [
{
"Name": "CaseStudy",
"ContentPartFieldDefinitionRecords": [
{
"FieldName": "HtmlField",
"Name": "Body",
"Settings": {
"ContentPartFieldSettings": { "DisplayName": "Body", "Editor": "Trumbowyg", "Position": "0" },
"HtmlFieldSettings": { "SanitizeHtml": true },
"HtmlFieldTrumbowygEditorSettings": {
"Options": "{ \"autogrow\": true, \"removeformatPasted\": true, \"btns\": [[\"viewHTML\"],[\"undo\",\"redo\"],[\"formatting\"],[\"strong\",\"em\",\"del\"],[\"fontsize\"],[\"link\"],[\"align\"],[\"unorderedList\",\"orderedList\"],[\"removeformat\"]], \"btnsDef\": { \"align\": { \"dropdown\": [\"justifyLeft\",\"justifyCenter\",\"justifyRight\",\"justifyFull\"], \"ico\": \"justifyLeft\" } } }",
"InsertMediaWithUrl": false
}
}
},
{
"FieldName": "UserPickerField",
"Name": "Owner",
"Settings": {
"ContentPartFieldSettings": { "DisplayName": "Owner", "Position": "1" },
"UserPickerFieldSettings": { "Multiple": false, "DisplayAllUsers": true, "DisplayedRoles": [] }
}
},
{
"FieldName": "TaxonomyField",
"Name": "Tags",
"Settings": {
"ContentPartFieldSettings": { "DisplayName": "Tags", "Editor": "Tags", "Position": "2" },
"TaxonomyFieldSettings": { "TaxonomyContentItemId": "<replace-with-taxonomy-id>", "LeavesOnly": false, "Unique": false, "Open": true, "DisplayAllNodes": true, "Required": false },
"TaxonomyFieldTagsEditorSettings": { "Open": true }
}
}
]
}
]
}Content Definition Extractor
Use this by default instead of reading ContentDefinition.json directly. It returns a focused slice for a content type or part, including container-related types (Bag/Flow/List, content pickers, stereotypes).
Script: scripts/extract-content-definitions.py
Quick usage
Extract a type and its related container types (Markdown, stdout):
python scripts/extract-content-definitions.py \
--source <ContentDefinition.json|tenant-folder|OrchardCore.db> \
--type Page \
--include-related \
--format mdExtract a type as JSON (machine-friendly, stdout):
python scripts/extract-content-definitions.py \
--source <ContentDefinition.json|tenant-folder|OrchardCore.db> \
--type Page \
--format jsonExtract a reusable part definition (optional; type output already embeds attached part definitions):
python scripts/extract-content-definitions.py \
--source <ContentDefinition.json|tenant-folder|OrchardCore.db> \
--part BlogPost \
--format mdExtract from SQLite explicitly (Document table):
python scripts/extract-content-definitions.py \
--sqlite-db <OrchardCore.db> \
--type Page \
--format mdRelated-type expansion
--include-related pulls in types referenced by settings keys like:
ContainedContentTypes(Bag/Flow/List)DisplayedContentTypes(ContentPicker)ContainedStereotypes/DisplayedStereotypes/Stereotypes
Use --related-depth 2 if related types themselves contain nested containers.
Notes
- The script reads
ContentDefinition.jsondirectly when present. For SQLite-backed tenants without it,
the script reads from Document where Type is OrchardCore.ContentManagement.Metadata.Records.ContentDefinitionRecord, OrchardCore.ContentManagement.Abstractions.
- Use
--allonly when you truly need the full set; it can be large. - Omit
--outto write to stdout (preferred to avoid creating files tracked by git). - First extract the type without
--include-relatedto see attached parts. If it only has FlowPart
and you are not overriding FlowPart, skip --include-related because widget types are usually not rendered directly.
Content Definitions
ContentDefinition.json is a tenant-scoped snapshot of content type, part, and field definitions. It lives under App_Data/Sites/<TenantName>/ when file storage is enabled. Prefer CONTENT-DEFINITIONS-EXTRACTOR.md to pull focused slices; if the file is missing, use the SQLite-backed extractor mode. Read raw JSON only as a last resort.
Top-level structure
ContentTypeDefinitionRecords: all content types and the parts attached to them.ContentPartDefinitionRecords: reusable part definitions and their fields.Identifier: internal identifier for the definitions document.
Content type records
Each ContentTypeDefinitionRecord includes:
Name,DisplayNameSettings: usually includesContentTypeSettings(Creatable, Draftable, Stereotype, etc.).ContentTypePartDefinitionRecords: attached parts and their settings.
Each ContentTypePartDefinitionRecord includes:
PartName: the part's technical name.Name: the part instance name (often same asPartName).Settings: usually includesContentTypePartSettingsplus part-specific settings.
Part records
Each ContentPartDefinitionRecord includes:
NameSettings: usually includesContentPartSettings.ContentPartFieldDefinitionRecords: fields attached to this part.
Each ContentPartFieldDefinitionRecord includes:
FieldName: field type (e.g.,TextField).Name: field instance name.Settings: includesContentPartFieldSettingsplus field-specific settings.
Field and part settings
- Shared settings:
ContentTypeSettings,ContentPartSettings,ContentTypePartSettings,ContentPartFieldSettings.- Field-specific settings:
- Stored under
<FieldType>Settingsplus optional editor-specific settings. - Part-specific settings:
- Stored under
<PartType>Settingsor similar. - For type-scoped parts used only as a field container (e.g., part name matches the content type),
omit ContentPartSettings.Attachable unless you want it to show as a reusable part.
Access pattern from ContentItem
The JSON structure maps directly to ContentItem.Content:
ContentItem.Content.<PartName>.<FieldName>.<FieldProperty>Example:
ContentItem.Content.BlogPost.Summary.HtmlFields on the content type itself
If a field is attached directly to the content type (not via a named part), it is stored on a part named exactly like the content type:
ContentItem.Content.<ContentType>.<FieldName>.<FieldProperty>How to use this file
- Identify which parts and fields exist for a type.
- Read settings to know editor/display behavior and validation.
- Use the field type to determine which property holds the value (see
FIELDS.md).
Examples: see CONTENT-DEFINITIONS-EXAMPLES.md.
Editing tips (JSON)
- Types live under
ContentTypes, parts underContentParts. Attach parts viaContentTypePartDefinitionRecords. - Stereotypes matter for shape names (e.g.,
"Stereotype": "Widget"->Widget-<Type>;"Stereotype": "MenuItem"for menu items). - Set editors/display modes via field settings:
- TextField example with IconPicker:
{
"FieldName": "TextField",
"Name": "Icon",
"Settings": {
"ContentPartFieldSettings": { "DisplayName": "Icon", "Editor": "IconPicker" },
"TextFieldSettings": { "Hint": "Pick an icon" }
}
}- HtmlField with Trumbowyg (lean toolbar):
{
"FieldName": "HtmlField",
"Name": "Body",
"Settings": {
"ContentPartFieldSettings": { "DisplayName": "Body", "Editor": "Trumbowyg" },
"HtmlFieldSettings": { "SanitizeHtml": true },
"HtmlFieldTrumbowygEditorSettings": {
"Options": "{ \"autogrow\": true, \"removeformatPasted\": true, \"btns\": [[\"viewHTML\"],[\"undo\",\"redo\"],[\"formatting\"],[\"strong\",\"em\",\"del\"],[\"fontsize\"],[\"link\"],[\"align\"],[\"unorderedList\",\"orderedList\"],[\"removeformat\"]], \"btnsDef\": { \"align\": { \"dropdown\": [\"justifyLeft\",\"justifyCenter\",\"justifyRight\",\"justifyFull\"], \"ico\": \"justifyLeft\" } } }"
}
}
}- Keep
Positionstrings ordered if present; they control editor tab order. - For fields directly on the type, use the type name as the part name in
ContentPartFieldDefinitionRecords.
Content Items Extractor (SQLite)
Use this to inspect real content items when the definition alone is not enough (e.g., confirm actual field values, shape data, or sample items for recipes).
Script: scripts/extract-content-items.py
Common scenarios
- Understand sample items for a type (e.g.,
BlogPost,Page,Sectionwidgets). - Confirm real field values or JSON shape when templates are unclear.
- Pull latest/published items to prepare recipe seeds.
- Find items by display text when the content item ID is unknown.
Quick usage
Latest items for a content type (default limit 5):
python scripts/extract-content-items.py \
--source <tenant-folder|OrchardCore.db> \
--content-type BlogPost \
--latest \
--format mdPublished pages for a recipe seed:
python scripts/extract-content-items.py \
--source <tenant-folder|OrchardCore.db> \
--content-type Page \
--published \
--limit 10 \
--format jsonContent step for a recipe (defaults to published items):
python scripts/extract-content-items.py \
--source <tenant-folder|OrchardCore.db> \
--content-type SectionPage \
--output content-step \
--format jsonFind a specific item by display text:
python scripts/extract-content-items.py \
--source <tenant-folder|OrchardCore.db> \
--display-text "About" \
--latest \
--format mdFetch by content item ID:
python scripts/extract-content-items.py \
--source <tenant-folder|OrchardCore.db> \
--content-item-id <ContentItemId> \
--format jsonNotes
- Requires at least one filter to avoid dumping the full index.
- If
--latest/--publishedare omitted and you filter by type or IDs, it defaults toLatest = 1. --output content-stepdefaults toPublished = 1unless you set--latestor--any-version.- Omit
--outto write to stdout (preferred to avoid creating files tracked by git).
Content Items
Structure
- Content item graph
- Content type, parts, fields
Core properties (ContentItem)
ContentItemId,ContentItemVersionIdContentTypePublished,LatestCreatedUtc,ModifiedUtc,PublishedUtcOwner,AuthorDisplayTextId(database document id),Number(version number) may be present in some contexts.- Use
DisplayTextfor rendering;TitlePart.Titleis for editor UX/back-compat and should not be used as a rendering fallback.
Content JSON access
ContentItem.Contentis a dynamic JSON object.- Parts are stored under
ContentItem.Content.<PartName>. - Fields are stored under
ContentItem.Content.<PartName>.<FieldName>. - Field values live in a field-specific property (see
FIELDS.md). - In Razor, avoid explicit casts like
(string)on dynamic field values; inline the expression or call.ToString()/Convert.ToString()when you need a string. - Avoid
Model?.ContentItem?.Contentnull chains in shape templates; use null checks only at the field/property level when data can be missing (e.g.,MediaField.Paths).
Template conventions
- Content item templates (e.g.,
Content-Article.cshtmlorWidget-MyType.liquid) typically expose: Model.ContentItem: the underlying content item.Model.Content: a zone containing rendered parts and fields.- Other local zones like
Model.HeaderorModel.Footer. - When a task requires direct part/field access, avoid relying solely on
Model.Contentand inspect parts directly.
Content definitions (optional)
- If
ContentDefinition.jsonexists underApp_Data/Sites/<TenantName>/, it describes content types, parts, and fields. - Prefer the extractor (
CONTENT-DEFINITIONS-EXTRACTOR.md) to infer the shape ofContentItem.Contentwhen overriding templates,
including when definitions live in SQLite.
Sample content items from SQLite
When actual values are needed (e.g., to confirm field data or build recipe samples), use CONTENT-ITEMS-EXTRACTOR.md to pull items from OrchardCore.db.
Fields
Access pattern
Fields are stored under their parent part:
ContentItem.Content.<PartName>.<FieldName>.<FieldProperty>If a field is attached directly to the content type (not via a named part), the part name equals the content type.
Shared settings
All field settings derive from FieldSettings:
HintRequired
Editor placement
Field editor selection (e.g., Wysiwyg, PredefinedList, TextArea) is stored on ContentPartFieldSettings.Editor, not in the field-type settings object. Field-type settings like MarkdownFieldSettings, TextFieldSettings, or HtmlFieldSettings should not contain the editor value. Example:
{
"ContentPartFieldSettings": {
"DisplayName": "Body",
"Position": "2",
"Editor": "Wysiwyg"
},
"MarkdownFieldSettings": {
"SanitizeHtml": true
}
}Field types (built-in)
BooleanField
- Value:
Value(bool) - Settings:
BooleanFieldSettings(Label,DefaultValue) - Example:
ContentItem.Content.MyPart.Featured.ValueTextField
- Value:
Text(string) - Settings:
TextFieldSettings(DefaultValue,Type,Pattern,Placeholder) Type:Editable,GeneratedDisabled,GeneratedHidden- Editor options (affects data and display expectations):
- Standard: plain text input;
Textholds the string. TextArea: multiline input;Textholds the string.PredefinedList:TextFieldPredefinedListEditorSettings(Options,Editor,DefaultValue);Textis the selected option value.IconPicker:Textis typically a Font Awesome class (e.g.,fas fa-home) to use in<i class="...">.Color:Textis a color string (e.g.,#ffffff).Email,Tel,Url: specialized inputs;Textis the email/phone/url.Header: header-style input;Textis the heading; see display mode below.CodeMirror: code editor;Textis the code; uses CodeMirror UI.Monaco: code editor;Textis the code;TextFieldMonacoEditorSettings(Options).- Display modes:
TextFieldHeaderDisplaySettings(Level) for header display mode (e.g., template alternateTextField-Header.Display).- Example:
ContentItem.Content.MyPart.Title.TextHtmlField
- Value:
Html(string) - Settings:
HtmlFieldSettings(SanitizeHtml) - Editor options:
- Standard: simple HTML textarea.
Trumbowyg(HtmlFieldTrumbowygEditorSettings):Options(JSON string passed to Trumbowyg).InsertMediaWithUrl(bool).- Lean toolbar example (less bloat, keeps
removeformatPasted):
{
"autogrow": true,
"removeformatPasted": true,
"btns": [
["viewHTML"],
["undo","redo"],
["formatting"],
["strong","em","del"],
["fontsize"],
["link"],
["align"],
["unorderedList","orderedList"],
["removeformat"]
],
"btnsDef": {
"align": {
"dropdown": ["justifyLeft","justifyCenter","justifyRight","justifyFull"],
"ico": "justifyLeft"
}
}
}Monaco(HtmlFieldMonacoEditorSettings):Options.- Example:
ContentItem.Content.MyPart.Body.HtmlNumericField
- Value:
Value(decimal?) - Settings:
NumericFieldSettings(Scale,Minimum,Maximum,Placeholder,DefaultValue) - Example:
ContentItem.Content.MyPart.Price.ValueDateField
- Value:
Value(DateTime?) - Settings:
DateFieldSettings(inherits base only) - Example:
ContentItem.Content.MyPart.PublishDate.ValueDateTimeField
- Value:
Value(DateTime?) - Settings:
DateTimeFieldSettings(inherits base only) - Example:
ContentItem.Content.MyPart.EventTime.ValueTimeField
- Value:
Value(TimeSpan?) - Settings:
TimeFieldSettings(Step) - Example:
ContentItem.Content.MyPart.OpeningTime.ValueMultiTextField
- Value:
Values(string[]) - Settings:
MultiTextFieldSettings(Options[]withName,Value,Default) - Editor options:
- Standard: free text entries into
Values. CheckboxList: renders options as checkboxes; selected option values stored inValues.Picker: renders a picker UI; selected option values stored inValues.- Example:
ContentItem.Content.MyPart.Tags.ValuesLinkField
- Values:
Url,Text,Target - Settings:
LinkFieldSettings(HintLinkText,LinkTextMode,UrlPlaceholder,
TextPlaceholder, DefaultUrl, DefaultText, DefaultTarget)
LinkTextMode:Optional,Required,Static,Url- Example:
ContentItem.Content.MyPart.CTA.Url
ContentItem.Content.MyPart.CTA.TextContentPickerField
- Values:
ContentItemIds(string[]) - Settings:
ContentPickerFieldSettings Multiple,DisplayAllContentTypes,DisplayedContentTypes,
DisplayedStereotypes, Placeholder, TitlePattern
- Example:
ContentItem.Content.MyPart.Related.ContentItemIdsLocalizationSetContentPickerField
- Values:
LocalizationSets(string[]) - Settings:
LocalizationSetContentPickerFieldSettings(Multiple,DisplayedContentTypes) - Example:
ContentItem.Content.MyPart.RelatedLocalization.LocalizationSetsUserPickerField
- Values:
UserIds(string[]) - Settings:
UserPickerFieldSettings(Multiple,DisplayAllUsers,DisplayedRoles,Placeholder) - Example:
ContentItem.Content.MyPart.Authors.UserIdsYoutubeField
- Values:
RawAddress,EmbeddedAddress - Settings:
YoutubeFieldSettings(Label,Width,Height,Placeholder) - Example:
ContentItem.Content.MyPart.Video.EmbeddedAddressMediaField
- Values:
Paths(string[]),MediaTexts(string[]) - Settings:
MediaFieldSettings(Multiple,AllowMediaText,AllowAnchors,AllowedExtensions) - Example:
ContentItem.Content.MyPart.Image.PathsTaxonomyField
- Values:
TaxonomyContentItemId,TermContentItemIds(string[]) - Settings:
TaxonomyFieldSettings(TaxonomyContentItemId,Unique,LeavesOnly,Open,Placeholder) - Editor settings:
TaxonomyFieldTagsEditorSettings(Open) - Example:
ContentItem.Content.MyPart.Categories.TermContentItemIds- Getting taxonomy/terms:
- Razor:
var taxonomy = await Orchard.GetContentItemByIdAsync(field.TaxonomyContentItemId); - Terms are a flat list under
taxonomy.Content.TaxonomyPart.Terms; levels live onTermPartif needed. - Selected term IDs are in
TermContentItemIds(string[]). Filter the flat terms list byContentItemId. - Liquid: assign the taxonomy content item from the ID (
{% assign tax = Content.ContentItemId[field.TaxonomyContentItemId] %}), then iteratetax.Content.TaxonomyPart.Termsand filter byTermContentItemIds.
Content Model
Purpose: Inspect content definitions and access parts/fields while rendering.
Files:
CONTENT-ITEMS.md- content item structure, core properties, JSON access, template conventions, definitions, and SQLite samples.CONTENT-ITEMS-EXTRACTOR.md- common scenarios, quick usage, and notes for extracting items from SQLite.CONTENT-DEFINITIONS.md- top-level structure, type/part records, settings, access patterns (including fields on type), how to use, editing tips.CONTENT-DEFINITIONS-EXAMPLES.md- real-tenant examples and a case study type with editor settings.CONTENT-DEFINITIONS-EXTRACTOR.md- quick usage, related-type expansion, and notes.FIELDS.md- access pattern, shared settings, and built-in field types (Boolean, Text, Html, Numeric, Date/Time, MultiText, Link, ContentPicker, LocalizationSet, UserPicker, YouTube, Media, Taxonomy).PARTS.md- core parts, lists/flows, widgets/menus, forms, taxonomies, search/localization, SEO/sitemaps, and misc parts.CONTAINERS.md- BagPart, FlowPart, ListPart/ContainedPart, and quick placement patterns.SETTINGS.md- shared settings (type/part/field) plus common part-specific and field-specific settings.
Content Parts
This catalog lists built-in Orchard Core content parts and their stored properties. Parts with no properties are still useful for behaviors, routing, or display, but do not add fields to ContentItem.Content.
Core content parts
TitlePart
- Properties:
Title(string) - Often mirrored into
ContentItem.DisplayText.
AutoroutePart
- Properties:
Path,SetHomepage(bool),Disabled(bool),RouteContainedItems(bool),Absolute(bool)
AliasPart
- Properties:
Alias(string)
HtmlBodyPart
- Properties:
Html(string)
MarkdownBodyPart
- Properties:
Markdown(string)
LiquidPart
- Properties:
Liquid(string)
CommonPart
- No stored properties (uses
ContentItemmetadata likeCreatedUtc,Owner, etc.).
PreviewPart
- No stored properties (used for preview pipeline).
ArchiveLaterPart
- Properties:
ScheduledArchiveUtc(DateTime?)
PublishLaterPart
- Properties:
ScheduledPublishUtc(DateTime?)
AuditTrailPart
- Properties:
Comment(string),ShowComment(bool)
Lists and flows
ListPart
- No stored properties (list behavior; items are in index/query).
ContainedPart
- Properties:
ListContentItemId,ListContentType,Order
BagPart
- Properties:
ContentItems(array of embedded content items)
FlowPart
- Properties:
Widgets(array of flow widgets/content items)
Widgets and menus
WidgetsListPart
- No stored properties (controls widget placement in zones).
MenuPart
- No stored properties (menu container).
MenuItemsListPart
- Properties:
MenuItems(array of menu item content items)
MenuItemPermissionPart
- Properties:
PermissionNames(string[])
LinkMenuItemPart
- Properties:
Url,Target
HtmlMenuItemPart
- Properties:
Url,Target,Html
ContentMenuItemPart
- Properties:
CheckContentPermissions(bool)
Forms
FormPart
- Properties:
Action,Method,WorkflowTypeId,EncType,
EnableAntiForgeryToken, SaveFormLocation
FormElementPart
- Properties:
Id
FormInputElementPart
- Properties:
Name
FormInputElementVisibilityPart
- Properties:
Groups,Action
FormElementLabelPart
- Properties:
Option,Label
FormElementValidationPart
- Properties:
For
InputPart
- Properties:
Type,DefaultValue,Placeholder
TextAreaPart
- Properties:
DefaultValue,Placeholder,Rows
SelectPart
- Properties:
Options,DefaultValue,Editor,Text,Value
LabelPart
- Properties:
For
ButtonPart
- Properties:
Text,Type
ValidationPart
- Properties:
For
ValidationSummaryPart
- Properties:
ModelOnly(bool)
ReCaptchaPart
- No stored properties (runtime behavior only).
Taxonomies
TaxonomyPart
- Properties:
TermContentType,Terms(JSON array of embedded terms) - Routing tip: add
AutoroutePartto the taxonomy content item (RouteContainedItems: true) and to the term content type (e.g., Tag) so each term gets its own URL (useAutoroutePart.Pathon terms likestrategy).
TermPart
- Properties:
TaxonomyContentItemId
Search and localization
SearchFormPart
- Properties:
IndexName,Placeholder
LocalizationPart
- Properties:
LocalizationSet,Culture
SEO and sitemaps
SeoMetaPart
- Properties:
PageTitle,Render,MetaDescription,MetaKeywords,Canonical,
MetaRobots, CustomMetaTags, DefaultSocialImage, OpenGraphImage, OpenGraphType, OpenGraphTitle, OpenGraphDescription, TwitterImage, TwitterTitle, TwitterDescription, TwitterCard, TwitterCreator, TwitterSite, GoogleSchema
SitemapPart
- Properties:
OverrideSitemapConfig,ChangeFrequency,Priority,Exclude
Miscellaneous
DashboardPart
- Properties:
Position,Width,Height
FacebookPluginPart
- Properties:
Liquid(string)
UserNotificationPreferencesPart
- Properties:
Methods,Optout
Definition Settings
This file documents the settings objects used inside content definitions. Settings appear under the Settings property in ContentDefinition.json. Prefer using CONTENT-DEFINITIONS-EXTRACTOR.md to pull the exact settings slice you need.
Shared settings
ContentTypeSettings
CreatableListableDraftableVersionableStereotypeSecurableDescription
ContentPartSettings
AttachableReusableDisplayNameDescriptionDefaultPosition
ContentTypePartSettings
DisplayNameDescriptionPositionDisplayModeEditor
ContentPartFieldSettings
DisplayNameDescriptionEditorDisplayModePosition
FieldSettings (base)
HintRequired
Part-specific settings (common)
TitlePartSettings
Options(Editable,GeneratedDisabled,GeneratedHidden,EditableRequired)PatternRenderTitlePlaceholder
AutoroutePartSettings
AllowCustomPathPatternShowHomepageOptionAllowUpdatePathAllowDisabledAllowRouteContainedItemsManageContainedItemRoutesAllowAbsolutePath
AliasPartSettings
PatternOptions(Editable,GeneratedDisabled)
HtmlBodyPartSettings
SanitizeHtml
MarkdownBodyPartSettings
SanitizeHtml
ListPartSettings
PageSizeContainedContentTypesEnableOrderingShowHeader
WidgetsListPartSettings
Zones
BagPartSettings
ContainedContentTypesContainedStereotypesDisplayTypeCollapseContainedItems
FlowPartSettings
ContainedContentTypesCollapseContainedItemsDefaultAlignment
CommonPartSettings
DisplayDateEditorDisplayOwnerEditor
AuditTrailPartSettings
ShowCommentInput
PreviewPartSettings
Pattern
HtmlMenuItemPartSettings
SanitizeHtml
SeoMetaPartSettings
DisplayKeywordsDisplayCustomMetaTagsDisplayOpenGraphDisplayTwitterDisplayGoogleSchema
FacebookPluginPartSettings
Liquid
Field-specific settings
See FIELDS.md for each field type's settings and editor settings.
Assets and Resources
Purpose: Include scripts/styles and manage resources and static files.
Files:
RESOURCES.md- define resources, require them in Razor/Liquid, built-in Orchard resources, tips.STATIC-FILES.md- where to place assets, how to reference them, tips.
Resources
How to register and require scripts/styles via Orchard Core's resource manager.
Define resources
- Create a class implementing
IResourceManifestProviderin a module or theme. - Example:
using OrchardCore.ResourceManagement;
public class ResourceManifest : IResourceManifestProvider
{
public void BuildManifests(IResourceManifestBuilder builder)
{
var manifest = builder.Add();
manifest
.DefineStyle("MyTheme")
.SetUrl("~/MyTheme/styles/site.min.css", "~/MyTheme/styles/site.css")
.SetVersion("1.0")
.SetDependencies("bootstrap");
manifest
.DefineScript("MyTheme")
.SetUrl("~/MyTheme/scripts/site.min.js", "~/MyTheme/scripts/site.js")
.SetDependencies("jquery");
}
}Require resources in Razor
- From manifest:
<style asp-name="MyTheme" at="Foot"></style>or<script asp-name="MyTheme" at="Foot"></script>. - Direct file:
<style asp-src="~/MyTheme/styles/extra.css" at="Head"></style>. at="Head"orat="Foot"controls rendering location.- Use
depends-onto ensure order:<style asp-name="MyTheme" depends-on="bootstrap"></style>.
Built-in Orchard Core resources (from OrchardCore.Resources)
- Common style/script names you can require without adding CDNs yourself:
bootstrap(CSS/JS),bootstrap-theme,bootstrap-rtlfont-awesome(multiple versions defined)jQuery,jQuery-uitrumbowyg,trumbowyg-pluginscodemirror(plus addons likecodemirror-addon-display-fullscreen,codemirror-addon-hint-show-hint, thememonokai)bootstrap-select,nouislider,vue-multiselect- Media indexers and other module-specific resources may be available when features are enabled.
- Prefer using these names in
asp-name/depends-oninstead of adding CDN links manually.
Require resources in Liquid
- Use the same tag helpers inside Liquid templates via Razor-rendered shapes, or emit
<style asp-name="...">/<script asp-name="...">tags in Razor shapes that wrap Liquid content. - For simple cases in Liquid, link static assets directly with
hreffilter:<link rel="stylesheet" href="{{ '~/MyTheme/styles/site.css' | href }}">.
Tips
- Keep resource names stable; use versioning to bust caches.
- Prefer manifest resources so dependencies are tracked and deduplicated.
- Use
at="Head"for critical CSS/JS; default toFootScriptfor scripts. - For quick theme prototyping, you can use the Tailwind Play CDN by adding
<script src="https://cdn.tailwindcss.com"></script> in the theme Layout head. Prefer a build pipeline for production.
Static Files
Where to place assets
- Themes:
Themes/<ThemeName>/wwwroot/(e.g.,~/MyTheme/styles/site.css). - Modules:
Modules/<ModuleName>/wwwroot/(e.g.,~/MyModule/js/widget.js). - Keep build outputs (bundles/minified files) here so tag helpers can find them.
Referencing assets
- Razor:
<link rel="stylesheet" href="~/MyTheme/styles/site.css" asp-append-version="true" /> - Liquid:
<link rel="stylesheet" href="{{ '~/MyTheme/styles/site.css' | href }}"> - Use
asp-append-version="true"for cache-busting when hashes are available.
Tips
- Keep CDN overrides in the manifest (see
RESOURCES.md) while shipping local fallbacks inwwwroot. - Ensure static files are included in the project file or build pipeline if using custom SDK settings.
Admin Menu Recipe Step
Use the AdminMenu step to create or update admin menus with custom navigation links. Each menu entry is a tree of admin nodes under MenuItems.
Node types
LinkAdminNode: clickable link node.$type:OrchardCore.AdminMenu.AdminNodes.LinkAdminNode, OrchardCore.AdminMenu- Required:
LinkText,LinkUrl PlaceholderAdminNode: non-clickable parent container for children.$type:OrchardCore.AdminMenu.AdminNodes.PlaceholderAdminNode, OrchardCore.AdminMenu- Required:
LinkText
If a node has children, use PlaceholderAdminNode for the parent. A LinkAdminNode is not a reliable parent for sub-items.
Minimal shape
{
"name": "AdminMenu",
"data": [
{
"Id": "0f0b20d3a57b436b9e5edbbc053d50b5",
"Name": "Pages",
"Enabled": true,
"MenuItems": [
{
"$type": "OrchardCore.AdminMenu.AdminNodes.PlaceholderAdminNode, OrchardCore.AdminMenu",
"LinkText": "Pages",
"IconClass": "fas fa-file-alt",
"PermissionNames": [],
"UniqueId": "08e804c49cb64fc89198cb2d1ffed407",
"Enabled": true,
"Priority": 0,
"LinkToFirstChild": true,
"LocalNav": false,
"Items": [
{
"$type": "OrchardCore.AdminMenu.AdminNodes.LinkAdminNode, OrchardCore.AdminMenu",
"LinkText": "Landing Pages",
"LinkUrl": "~/Admin/Contents/ContentItems?contentTypeId=Page",
"IconClass": "far fa-file",
"PermissionNames": [],
"UniqueId": "a726d64589d3498e956dc0f95c72104d",
"Enabled": true,
"Priority": 0,
"LinkToFirstChild": false,
"LocalNav": false,
"Items": [],
"Classes": []
}
],
"Classes": []
}
]
}
]
}Notes
Id(menu) andUniqueId(node) values are GUIDs in "n" format (32 hex chars).- Keep IDs stable when updating an existing menu, otherwise new items will be added.
- For singleton pages or content types without a meaningful admin display view, link to
the edit page (e.g., ~/Admin/Contents/ContentItems/<id>/Edit) instead of the display view, which may render empty.
- List pages by content type use the
contentTypeIdroute. Orchard Core's list route is
~/Admin/Contents/ContentItems/{contentTypeId?} (generated by the built-in content type admin nodes). Example: ~/Admin/Contents/ContentItems/Page. Query-string (?contentTypeId=Page) also works but the route segment is the canonical form.
- Optional fields:
IconClass(Font Awesome),PermissionNames,Classes,Priority,
LinkToFirstChild, LocalNav.
Base Setup Recipes (Orchard Core)
Canonical setup recipes shipped with Orchard Core that can be referenced or mimicked.
Blank (full CMS skeleton)
- File:
src/OrchardCore.Themes/TheAdmin/Recipes/blank.recipe.json - Purpose: Blank CMS-ready site with admin, content management, media, flows, lists, templates, widgets, etc.
- Key features enabled:
- SaaS/admin:
OrchardCore.HomeRoute,OrchardCore.Admin,OrchardCore.Diagnostics,OrchardCore.DynamicCache,OrchardCore.Features,OrchardCore.Navigation,OrchardCore.Recipes,OrchardCore.Resources,OrchardCore.Roles,OrchardCore.Security,OrchardCore.Settings,OrchardCore.Themes,OrchardCore.Users - Content:
OrchardCore.Alias,OrchardCore.Autoroute,OrchardCore.Html,OrchardCore.ContentFields,OrchardCore.ContentPreview,OrchardCore.Contents,OrchardCore.ContentTypes,OrchardCore.CustomSettings,OrchardCore.Deployment,OrchardCore.Deployment.Remote,OrchardCore.Flows,OrchardCore.Indexing,OrchardCore.Layers,OrchardCore.Lists,OrchardCore.Markdown,OrchardCore.Media,OrchardCore.Menu,OrchardCore.Queries,OrchardCore.Shortcodes.Templates,OrchardCore.Title,OrchardCore.Templates,OrchardCore.Widgets - Theme:
TheAdminas admin theme; site theme left empty. - Roles scaffolded: Moderator, Editor, Author, Contributor (no permissions assigned by default).
Headless (API-first)
- File:
src/OrchardCore.Themes/TheAdmin/Recipes/headless.recipe.json - Purpose: Headless CMS with GraphQL and OpenID enabled; admin home route.
- Key features enabled:
- SaaS/admin: same core set as Blank (minus DynamicCache), plus GraphQL and OpenID server/validation.
- Content: similar to Blank (HTML/Markdown/Flows/Lists/Media/Queries/Widgets), plus
OrchardCore.Apis.GraphQL. - OpenID:
OrchardCore.OpenId,OrchardCore.OpenId.Management,OrchardCore.OpenId.Server,OrchardCore.OpenId.Validation. - Theme:
TheAdminas admin theme; site theme left empty. - Roles: Moderator, Editor, Author, Contributor, and
AuthenticatedwithViewContent,ExecuteGraphQL,ExecuteApiAll. - Settings: HomeRoute set to Admin dashboard.
When to reuse
- Use Blank as a "full CMS scaffold" for most sites; copy feature list or reference it in your setup recipe.
- Use Headless when API/GraphQL/OpenID are required; copy its features/roles and home route.
- Both are setup recipes (
issetuprecipe: true); you can reference them via therecipesstep or copy their feature blocks into your own setup recipe. - Example (reference Blank directly from TheAdmin):
{
"name": "recipes",
"Values": [
{ "executionid": "TheAdmin", "name": "Blank" }
]
}executionidmust match the extension containing the recipe.- If you want to keep composition local (host project), create a wrapper recipe in your project that references
TheAdminand then reference the wrapper by your project name.
Feature Catalog - Full List
Full feature list from module manifests. Use when you need a rarely used feature ID.
OrchardCore.AdminOrchardCore.AdminDashboardOrchardCore.AdminMenuOrchardCore.AdminTemplatesOrchardCore.AliasOrchardCore.Apis.GraphQLOrchardCore.ArchiveLaterOrchardCore.AuditTrailOrchardCore.AutoSetupOrchardCore.AutorouteOrchardCore.BackgroundTasksOrchardCore.ContentFieldsOrchardCore.ContentFields.Indexing.SQLOrchardCore.ContentFields.Indexing.SQL.UserPickerOrchardCore.ContentLocalizationOrchardCore.ContentLocalization.ContentCulturePickerOrchardCore.ContentLocalization.SitemapsOrchardCore.ContentPreviewOrchardCore.ContentTypesOrchardCore.ContentsOrchardCore.Contents.Deployment.AddToDeploymentPlanOrchardCore.Contents.Deployment.DownloadOrchardCore.Contents.Deployment.ExportContentToDeploymentTargetOrchardCore.Contents.FileContentDefinitionOrchardCore.CorsOrchardCore.CustomSettingsOrchardCore.DataLocalizationOrchardCore.DataProtection.AzureOrchardCore.Demo.FooOrchardCore.DeploymentOrchardCore.Deployment.RemoteOrchardCore.DiagnosticsOrchardCore.DynamicCacheOrchardCore.EmailOrchardCore.Email.AzureOrchardCore.Email.SmtpOrchardCore.FeedsOrchardCore.FlowsOrchardCore.FormsOrchardCore.HealthChecksOrchardCore.HomeRouteOrchardCore.HtmlOrchardCore.HttpsOrchardCore.LayersOrchardCore.LiquidOrchardCore.Liquid.CoreOrchardCore.ListsOrchardCore.LocalizationOrchardCore.Localization.AdminCulturePickerOrchardCore.Localization.ContentLanguageHeaderOrchardCore.MarkdownOrchardCore.MediaOrchardCore.Media.AmazonS3OrchardCore.Media.AmazonS3.ImageSharpImageCacheOrchardCore.Media.Azure.ImageSharpImageCacheOrchardCore.Media.Azure.StorageOrchardCore.Media.CacheOrchardCore.Media.IndexingOrchardCore.Media.Indexing.OpenXMLOrchardCore.Media.Indexing.PdfOrchardCore.Media.Indexing.TextOrchardCore.Media.SecurityOrchardCore.Media.SlugifyOrchardCore.MenuOrchardCore.MiniProfilerOrchardCore.Mvc.HelloWorldOrchardCore.NavigationOrchardCore.NotificationsOrchardCore.Notifications.EmailOrchardCore.PlacementsOrchardCore.Placements.FileStorageOrchardCore.PublishLaterOrchardCore.QueriesOrchardCore.Queries.CoreOrchardCore.Queries.SqlOrchardCore.ReCaptchaOrchardCore.ReCaptcha.UsersOrchardCore.RecipesOrchardCore.Recipes.CoreOrchardCore.RedisOrchardCore.Redis.BusOrchardCore.Redis.CacheOrchardCore.Redis.DataProtectionOrchardCore.Redis.LockOrchardCore.RemotePublishingOrchardCore.ResourcesOrchardCore.ResponseCompressionOrchardCore.ReverseProxyOrchardCore.RolesOrchardCore.Roles.CoreOrchardCore.RulesOrchardCore.ScriptingOrchardCore.SearchOrchardCore.Search.AzureAIOrchardCore.Search.ElasticsearchOrchardCore.Search.Elasticsearch.ContentPickerOrchardCore.Search.Elasticsearch.WorkerOrchardCore.Search.LuceneOrchardCore.Search.Lucene.ContentPickerOrchardCore.Search.Lucene.WorkerOrchardCore.SecurityOrchardCore.SeoOrchardCore.SettingsOrchardCore.SetupOrchardCore.ShortcodesOrchardCore.Shortcodes.TemplatesOrchardCore.SitemapsOrchardCore.Sitemaps.CleanupOrchardCore.Sitemaps.RazorPagesOrchardCore.SpatialOrchardCore.TaxonomiesOrchardCore.Taxonomies.ContentsAdminListOrchardCore.TemplatesOrchardCore.TenantsOrchardCore.Tenants.DistributedOrchardCore.Tenants.FeatureProfilesOrchardCore.Tenants.FileProviderOrchardCore.ThemesOrchardCore.TitleOrchardCore.UrlRewritingOrchardCore.Users.AuditTrailOrchardCore.Users.Authentication.CacheTicketStoreOrchardCore.Users.ChangeEmailOrchardCore.Users.CustomUserSettingsOrchardCore.Users.LocalizationOrchardCore.Users.TimeZoneOrchardCore.WidgetsOrchardCore.WorkflowsOrchardCore.Workflows.HttpOrchardCore.Workflows.SessionOrchardCore.Workflows.TimersOrchardCore.XmlRpc
Feature Catalog (Orchard Core feature IDs)
Feature IDs to use in recipes (feature step) or module enables. Module = feature name unless additional [Feature] attributes are defined.
Commonly used (setup/themes/content)
OrchardCore.SetupOrchardCore.ThemesOrchardCore.ContentsOrchardCore.ContentTypesOrchardCore.ContentFieldsOrchardCore.FlowsOrchardCore.ListsOrchardCore.MediaOrchardCore.Navigation(menus)OrchardCore.Layers(widgets on rules)OrchardCore.Localization(+OrchardCore.ContentLocalization)OrchardCore.TaxonomiesOrchardCore.SitemapsOrchardCore.ShortcodesOrchardCore.Queries(core) + provider (e.g.,OrchardCore.Queries.Sql,OrchardCore.Search.Lucene)OrchardCore.Resources(resource management)OrchardCore.Html,OrchardCore.MarkdownOrchardCore.Title,OrchardCore.Alias,OrchardCore.AutorouteOrchardCore.Users,OrchardCore.Roles(Users base feature is implicit with security packages)OrchardCore.Admin,OrchardCore.AdminMenuOrchardCore.SeoOrchardCore.Tenants(multi-tenant scenarios)OrchardCore.Deployment(export/import plans)OrchardCore.Workflows(+OrchardCore.Workflows.Http)
Full list
- See
FEATURE-CATALOG-ALL.md.
Orchard ID Generation
Use this when you need stable ContentItemId values in recipes (autoroutes, aliases, and cross-item references).
Script: scripts/generate-orchard-ids.py
Why this matters
Orchard Core generates IDs using a 26-character base32 alphabet: 0123456789abcdefghjkmnpqrstvwxyz
Do not invent IDs with the full a-z0-9 alphabet; Orchard intentionally excludes i, l, o, and u.
Quick usage
Generate one ID:
python scripts/generate-orchard-ids.pyGenerate multiple IDs:
python scripts/generate-orchard-ids.py --count 5Notes
- Use generated IDs for
ContentItemIdwhen you need stability across environments. - Keep
ContentItemVersionIdas[js:uuid()]unless you need deterministic versions.
Recipes
Purpose: Author, validate, and reuse recipes for setup, definitions, and content import.
Files:
RECIPE-STEPS.md- step groups and common first steps.RECIPE-STEPS-CORE.md-feature,themes,settings, andrecipessteps.RECIPE-STEPS-DEFINITIONS.md-ContentDefinition,ReplaceContentDefinition,DeleteContentDefinition.RECIPE-STEPS-CONTENT-MEDIA.md-content,media, andMediaProfilessteps.RECIPE-STEPS-SEARCH.md-Queries, Lucene (index/reset/rebuild), Elastic (index/reset/rebuild), Azure AI Search, index profile steps.RECIPE-STEPS-SECURITY.md-Roles,Users, custom user settings, OpenID/external auth, social providers.RECIPE-STEPS-MISC.md-Layers,Placements,AdminMenu,Sitemaps,UrlRewriting,custom-settings, tenants.ADMIN-MENU.md- Admin menu recipe structure, node types, and placeholder parents.RECIPE-STEPS-TEMPLATES-WORKFLOWS.md-Templates,AdminTemplates,ShortcodeTemplates,WorkflowType.WORKFLOWS.md- workflow recipe authoring, expressions, and activity catalog.RECIPE-CONTENT.md- content import structure, fields/parts, references, definitions vs items, Flow/Bag examples.ID-GENERATION.md- Orchard ID generation for stableContentItemIdvalues.RECIPE-COMMANDS.md- command step shape, known commands, discovery, and when to use vsUsers.RECIPE-EXAMPLES.md- ready-to-copy examples.RECIPE-EXAMPLES-WORKFLOWS.md- workflow examples (contact form, etc.).RECIPE-EXAMPLES-SETUP.md- minimal setup recipe and a page example with Summary/Autoroute settings.RECIPE-EXAMPLES-CONTENT.md- content package example with FlowPart and BagPart.FEATURE-CATALOG.md- common feature IDs (full list inFEATURE-CATALOG-ALL.md).FEATURE-CATALOG-ALL.md- full feature ID list.BASE-RECIPES.md- base setup recipes (Blank, Headless) and when to reuse.
Where recipes live
*/Recipes/*.recipe.json: reusable recipes in modules/themes.*/Migrations/*.recipe.json: recipes used by data migrations.HostProject/Recipes/: optional, common location for setup recipes.
Top-level schema
{
"name": "My.Recipe.Name",
"displayName": "My Recipe",
"description": "What this recipe does",
"author": "Org",
"website": "https://example.com",
"version": "1.0",
"issetuprecipe": true,
"tags": [ "setup", "content" ],
"variables": {
"homeId": "[js:uuid()]"
},
"steps": [
{ "name": "feature", "enable": [ "OrchardCore.Contents" ] }
]
}Recipe helpers (inline values)
js: execute JavaScript expressions (for example,"[js:uuid()]").file: load file contents (for example,"[file:text('Snippets/page.liquid')]").env: read environment variables (for example,"[env:MY_VAR]").appsettings: read configuration values (for example,"[appsettings:OrchardCore:SiteName]").localization: read localized strings (for example,"[localization:WelcomeTitle]").base64,html,gzip: decode content.- Content item IDs use Orchard's 26-character base32 alphabet; use
[js:uuid()]orscripts/generate-orchard-ids.pywhen you need stable IDs (seeID-GENERATION.md).
Execution and composition
- Use the
recipesstep to include other recipes byname. - Order matters; definitions and settings should usually come before content.
- Setup recipes (
issetuprecipe: true) are available during tenant setup and AutoSetup.
Recipe Commands
The Command step runs Orchard Core commands during recipe execution. Commands are provided by modules and are defined in */Commands/*Commands.cs.
Command step shape
{
"name": "Command",
"Commands": [
"createUser /UserName:admin /Password:Passw0rd! /Email:admin@example.com /Roles:Administrator"
]
}Known commands (built-in)
createUser(OrchardCore.Users)- Switches:
UserName,Password,Email,PhoneNumber,Roles recipes harvest(OrchardCore.Recipes)- Lists available recipes
How to discover commands
Search for [CommandName("...")] in */Commands/*Commands.cs. Commands use /Switch:Value syntax with [OrchardSwitch] properties.
When to use Command vs Users step
- Use
Usersstep for bulk import with full user properties. - Use
Commandwhen you need quick creation with explicit switches.
Recipe Content Import
Use the content step to import content items as JSON. Each item is a full ContentItem record, including parts and fields.
Basic structure
{
"name": "content",
"data": [
{
"ContentItemId": "[js:uuid()]",
"ContentItemVersionId": "[js:uuid()]",
"ContentType": "Page",
"DisplayText": "Home",
"Published": true,
"Latest": true,
"CreatedUtc": "2024-01-01T00:00:00Z",
"ModifiedUtc": "2024-01-01T00:00:00Z",
"PublishedUtc": "2024-01-01T00:00:00Z",
"Owner": "admin",
"Author": "admin",
"TitlePart": { "Title": "Home" },
"AutoroutePart": {
"Path": "home",
"SetHomepage": true,
"Disabled": false,
"RouteContainedItems": false,
"Absolute": false
},
"HtmlBodyPart": {
"Html": "<p>Welcome</p>"
}
}
]
}Notes:
- Some recipes use
Datainstead ofdata. Prefer lowercasedatafor consistency. - Content item IDs are 26-character strings generated from Orchard's base32 alphabet. Use
[js:uuid()]to generate them at import time, or generate stable IDs withscripts/generate-orchard-ids.py(see70-recipes/ID-GENERATION.md). Avoid inventing IDs with the fulla-z0-9alphabet. - The properties under each part match the part/field models in the content model docs.
Fields and parts
- Use the exact part name as the property name (e.g.,
TitlePart). - Fields live under their part:
<PartName>.<FieldName>.<FieldProperty>- Field properties are in
50-content-model/FIELDS.md(e.g.,TextField.Text,NumericField.Value). - Part properties are in
50-content-model/PARTS.md.
Referencing other content items
Use variables and IDs when content items reference each other:
{
"variables": { "pageId": "[js:uuid()]" },
"steps": [
{
"name": "content",
"data": [
{
"ContentItemId": "[js:variables('pageId')]",
"ContentType": "Page",
"TitlePart": { "Title": "Example" }
},
{
"ContentType": "Landing",
"MyPickerField": { "ContentItemIds": [ "[js:variables('pageId')]" ] }
}
]
}
]
}Content definitions vs content items
ContentDefinitiondefines the shape of types and parts.contentimports actual items and uses that definition.- Ensure definitions exist before importing items.
- Cross-reference:
50-content-model/CONTENT-DEFINITIONS.md.
FlowPart and BagPart examples
- FlowPart embeds widgets under
FlowPart.Widgets[]withFlowMetadata:
{
"FlowPart": {
"Widgets": [
{
"ContentItemId": "widget0001",
"ContentType": "HtmlWidget",
"TitlePart": { "Title": "Intro" },
"HtmlBodyPart": { "Html": "<p>Hello</p>" },
"FlowMetadata": { "Alignment": "Justify", "Size": 100 }
}
]
}
}- BagPart embeds items under
BagPart.ContentItems[]:
{
"BagPart": {
"ContentItems": [
{
"ContentItemId": "faqitem1",
"ContentType": "FaqItem",
"TitlePart": { "Title": "Q1" },
"MarkdownBodyPart": { "Markdown": "Answer." }
}
]
}
}- Ensure contained item definitions exist and match the parts/fields you populate.
Recipe Examples - Content Packages
Use these as starting points for content packages.
Content package with FlowPart and BagPart
{
"name": "My.ContentPackage",
"displayName": "Sample Content Package",
"steps": [
{
"name": "ContentDefinition",
"ContentTypes": [
{
"Name": "LandingPage",
"DisplayName": "Landing Page",
"Settings": { "ContentTypeSettings": { "Creatable": true, "Listable": true, "Draftable": true } },
"ContentTypePartDefinitionRecords": [
{ "PartName": "TitlePart", "Name": "TitlePart" },
{ "PartName": "AutoroutePart", "Name": "AutoroutePart" },
{ "PartName": "FlowPart", "Name": "FlowPart" }
]
},
{
"Name": "CalloutWidget",
"DisplayName": "Callout Widget",
"Settings": { "ContentTypeSettings": { "Stereotype": "Widget", "Creatable": true } },
"ContentTypePartDefinitionRecords": [
{ "PartName": "TitlePart", "Name": "TitlePart" },
{ "PartName": "HtmlBodyPart", "Name": "HtmlBodyPart" }
]
},
{
"Name": "FaqList",
"DisplayName": "FAQ List",
"Settings": { "ContentTypeSettings": { "Creatable": true } },
"ContentTypePartDefinitionRecords": [
{ "PartName": "BagPart", "Name": "BagPart", "Settings": { "BagPartSettings": { "ContainedContentTypes": [ "FaqItem" ] } } }
]
},
{
"Name": "FaqItem",
"DisplayName": "FAQ Item",
"Settings": { "ContentTypeSettings": { "Creatable": true } },
"ContentTypePartDefinitionRecords": [
{ "PartName": "TitlePart", "Name": "TitlePart" },
{ "PartName": "MarkdownBodyPart", "Name": "MarkdownBodyPart" }
]
}
]
},
{
"name": "content",
"data": [
{
"ContentItemId": "landing000000000000000000000001",
"ContentType": "LandingPage",
"DisplayText": "Landing",
"Published": true,
"Latest": true,
"TitlePart": { "Title": "Landing" },
"AutoroutePart": { "Path": "landing" },
"FlowPart": {
"Widgets": [
{
"ContentItemId": "callout00000000000000000000001",
"ContentType": "CalloutWidget",
"DisplayText": "Callout",
"Latest": true,
"Published": true,
"TitlePart": { "Title": "Callout" },
"HtmlBodyPart": { "Html": "<h3>Callout</h3><p>Details.</p>" },
"FlowMetadata": { "Alignment": "Justify", "Size": 100 }
}
]
}
},
{
"ContentItemId": "faqlist0000000000000000000001",
"ContentType": "FaqList",
"DisplayText": "FAQs",
"Published": true,
"Latest": true,
"BagPart": {
"ContentItems": [
{
"ContentItemId": "faqitem000000000000000000001",
"ContentType": "FaqItem",
"DisplayText": "Question 1",
"Published": true,
"Latest": true,
"TitlePart": { "Title": "What is this?" },
"MarkdownBodyPart": { "Markdown": "Sample answer." }
}
]
}
}
]
}
]
}Notes:
FlowPart.Widgetsitems carryFlowMetadatafor layout hints (Alignment, Size).BagPart.ContentItemsembeds contained items directly; they must match definitions (hereFaqItem).- Use deterministic IDs in content packages when other items reference them.
Recipe Examples (Ready-to-Copy)
Use these as starting points for setup and content packages. Adjust names/IDs as needed.
- Setup recipes:
RECIPE-EXAMPLES-SETUP.md - Content packages:
RECIPE-EXAMPLES-CONTENT.md - Workflows:
RECIPE-EXAMPLES-WORKFLOWS.md
Recipe Steps - Content Definitions
Steps that add, replace, or delete content type/part/field definitions.
ContentDefinition
- Adds or updates content types/parts (merge style).
- Shape:
{ "name": "ContentDefinition", "ContentTypes": [ ... ], "ContentParts": [ ... ] }- Use
ContentTypeSettingsfor type-level flags (Creatable, Draftable, Listable, Stereotype). - Attach parts via
ContentTypePartDefinitionRecords; add fields insideContentPartFieldDefinitionRecords. - Part/field settings mirror
ContentDefinition.json(see50-content-model/CONTENT-DEFINITIONS.md); use the extractor for existing definitions.
ReplaceContentDefinition
- Replaces content definitions (delete then recreate).
- Shape:
{ "name": "ReplaceContentDefinition", "ContentTypes": [ ... ], "ContentParts": [ ... ] }DeleteContentDefinition
- Deletes content types/parts by name.
- Shape:
{ "name": "DeleteContentDefinition", "ContentTypes": [ "Type" ], "ContentParts": [ "Part" ] }Debugging and Discovery
Purpose: Trace shapes, inspect logs, and find evidence in source.
Files:
SHAPE-TRACE.md- quick trace in Liquid/Razor, content item JSON, placement hints, cleanup.LOGS.md- logging tips and useful runtime data.