name: orchardcore-flow-bagpart description: Skill for building page layouts with FlowPart and BagPart in Orchard Core. Covers flow widgets, BagPart for named containers, Blocks Editor configuration, content type setup, Liquid and Razor templating, shape alternates, and layout building patterns. Use this skill when requests mention Orchard Core Flow & BagPart, Building Layouts with FlowPart and BagPart, Enabling Flow Features, Creating a Content Type with FlowPart, Creating a Content Type with Named BagParts, Enabling the Blocks Editor, or closely related Orchard Core implementation, setup, extension, or troubleshooting work. Strong matches include work with OrchardCore.Flows, OrchardCore.Widgets, OrchardCore.Flows.ViewModels, FlowPart, BagPart, DataMigration, IContentDefinitionManager, WithPart, TitlePart. It also helps with Creating a Content Type with Named BagParts, Enabling the Blocks Editor, Blocks Editor Settings (BagPart), plus the code patterns, admin flows, recipe steps, and referenced examples captured in this skill.
Orchard Core Flow & BagPart - Prompt Templates
Building Layouts with FlowPart and BagPart
You are an Orchard Core expert. Generate code and configuration for composing page layouts using FlowPart and BagPart.
Guidelines
- FlowPart and BagPart are provided by the
OrchardCore.Flowsmodule. - FlowPart allows embedding arbitrary widget content items inline within a content item (e.g., a page).
- BagPart is similar but lets you restrict which content types can be contained within it via its settings.
- BagPart can be added as a named part, allowing multiple BagParts on a single content type (e.g.,
Services,Portfolio,About). - BagPart items are stored as a single document in the database for efficient retrieval.
- Empty flows render with shape name
FlowPart_Empty; empty bags render asBagPart_Empty. - Use placement to hide empty parts by placing
FlowPart_EmptyorBagPart_Emptyto"-". FlowPartandBagPartdisplay shapes use{PartName}as their differentiator.- To hide or move a whole FlowPart or BagPart editor row in the admin UI, use
ContentPart_Editwith differentiator{ContentType}-{PartName}. - Inner shapes such as
FlowPart_EditandBagPart_Edittarget only the inner editor content, not the wrapper. - The Blocks Editor provides a modal-based content type picker as an alternative to the default dropdown. Enable it by setting the editor to
Blocks. - Always wrap recipe JSON in
{ "steps": [...] }. - All C# classes must use the
sealedmodifier except View Models. - Use file-scoped namespaces in C# examples.
Enabling Flow Features
{
"steps": [
{
"name": "Feature",
"enable": [
"OrchardCore.Flows",
"OrchardCore.Widgets"
],
"disable": []
}
]
}
Creating a Content Type with FlowPart
Use a data migration to create a content type (e.g., LandingPage) that contains a FlowPart for embedding widgets:
public sealed class Migrations : DataMigration
{
private readonly IContentDefinitionManager _contentDefinitionManager;
public Migrations(IContentDefinitionManager contentDefinitionManager)
{
_contentDefinitionManager = contentDefinitionManager;
}
public async Task<int> CreateAsync()
{
await _contentDefinitionManager.AlterTypeDefinitionAsync("LandingPage", type => type
.Creatable()
.Listable()
.Draftable()
.WithPart("TitlePart", part => part.WithPosition("0"))
.WithPart("FlowPart", part => part.WithPosition("1"))
.WithPart("AutoroutePart", part => part
.WithPosition("2")
.WithSettings(new AutoroutePartSettings
{
Pattern = "{{ Model.ContentItem | display_text | slugify }}",
AllowRouteContainedItems = true,
})
)
);
return 1;
}
}
Creating a Content Type with Named BagParts
Named BagParts allow multiple containers on a single content type, each restricted to specific child content types:
public sealed class Migrations : DataMigation
{
private readonly IContentDefinitionManager _contentDefinitionManager;
public Migrations(IContentDefinitionManager contentDefinitionManager)
{
_contentDefinitionManager = contentDefinitionManager;
}
public async Task<int> CreateAsync()
{
await _contentDefinitionManager.AlterTypeDefinitionAsync("AgencyPage", type => type
.Creatable()
.Listable()
.WithPart("TitlePart", part => part.WithPosition("0"))
.WithPart("Services", "BagPart", part => part
.WithPosition("1")
.WithDisplayName("Services")
.WithDescription("Services section")
.WithSettings(new BagPartSettings
{
ContainedContentTypes = ["ServiceWidget"],
})
)
.WithPart("Portfolio", "BagPart", part => part
.WithPosition("2")
.WithDisplayName("Portfolio")
.WithDescription("Portfolio section")
.WithSettings(new BagPartSettings
{
ContainedContentTypes = ["PortfolioWidget"],
})
)
);
return 1;
}
}
Enabling the Blocks Editor
Set the editor to Blocks for a modal-based content type picker UI:
Via Migration
await _contentDefinitionManager.AlterTypeDefinitionAsync("LandingPage", type => type
.WithPart("FlowPart", part => part
.WithEditor("Blocks")
)
);
await _contentDefinitionManager.AlterTypeDefinitionAsync("AgencyPage", type => type
.WithPart("Services", "BagPart", part => part
.WithEditor("Blocks")
)
);
Via Admin UI
- Navigate to Content Definition → Content Types.
- Edit the content type containing the FlowPart or BagPart.
- Click Edit on the FlowPart or BagPart.
- Set the Editor field to
Blocks.
Blocks Editor Settings (BagPart)
When the Blocks Editor is enabled, the following additional settings are available:
| Setting | Description |
|---|---|
| Add Button Text | Custom text for the "Add" button. Defaults to "Add Block". |
| Modal Title Text | Custom title for the content type picker modal. Defaults to "Select Block". |
Hiding Empty Flows and Bags
Use placement to suppress empty containers:
{
"FlowPart_Empty": [
{
"place": "-"
}
],
"BagPart_Empty": [
{
"place": "-"
}
]
}
To render empty containers with the same template as populated ones:
{
"FlowPart_Empty": [
{
"shape": "FlowPart"
}
]
}
Templating BagPart - Decoupled Approach
Access content items directly through the named BagPart, bypassing display management:
Liquid
{% for service in Model.ContentItem.Content.Services.ContentItems %}
<h4 class="service-heading">{{ service.DisplayText }}</h4>
<p class="text-muted">{{ service.HtmlBodyPart.Html | raw }}</p>
{% endfor %}
Razor
@foreach (var item in Model.ContentItem.Content.Services.ContentItems)
{
<h4 class="service-heading">@item.DisplayText</h4>
<p class="text-muted">@Html.Raw(item.Content.HtmlBodyPart.Html)</p>
}
Templating BagPart - Display Management Approach
Use display management to build and render child shapes with proper alternate resolution:
Liquid
<section class="flow">
{% for item in Model.ContentItems %}
{{ item | shape_build_display: "Detail" | shape_render }}
{% endfor %}
</section>
Razor
@using OrchardCore.Flows.ViewModels
@model BagPartViewModel
@inject OrchardCore.ContentManagement.Display.IContentItemDisplayManager ContentItemDisplayManager
<section class="flow">
@foreach (var item in Model.BagPart.ContentItems)
{
var itemContent = await ContentItemDisplayManager.BuildDisplayAsync(
item,
Model.BuildPartDisplayContext.Updater,
Model.Settings.DisplayType ?? "Detail",
Model.BuildPartDisplayContext.GroupId);
@await DisplayAsync(itemContent)
}
</section>
Template Alternates
BagPart supports standard shape alternates. For named BagParts, include the part name in the alternate:
| Alternate Template File | Description |
|---|---|
MyBag-BagPart.liquid |
Alternate for content type MyBag and BagPart |
MyBag-MyNamedBagPart.liquid |
Alternate for content type MyBag and named part MyNamedBagPart |
Use ConsoleLog (Razor) or console_log (Liquid) to inspect all available alternates for a shape.
Placement Differentiator
Use the named BagPart name as the differentiator in placement.json:
{
"BagPart": [
{
"differentiator": "Services",
"place": "Content:1"
}
]
}
For a standard FlowPart on the front end:
{
"FlowPart": [
{
"differentiator": "FlowPart",
"place": "-"
}
]
}
To hide the whole editor row in the admin UI, use ContentPart_Edit and the {ContentType}-{PartName} differentiator:
{
"ContentPart_Edit": [
{
"differentiator": "LandingPage-FlowPart",
"place": "-"
},
{
"differentiator": "LandingPage-Services",
"place": "-"
}
]
}
Use LandingPage-Services for a named BagPart called Services. Use LandingPage-BagPart for a standard non-named BagPart.
Recipe: Creating a Page with FlowPart Content
{
"steps": [
{
"name": "Content",
"data": [
{
"ContentItemId": "[js:uuid()]",
"ContentType": "LandingPage",
"DisplayText": "Welcome",
"Latest": true,
"Published": true,
"TitlePart": {
"Title": "Welcome"
},
"FlowPart": {
"Widgets": [
{
"ContentItemId": "[js:uuid()]",
"ContentType": "Paragraph",
"DisplayText": "Intro",
"TitlePart": {
"Title": "Intro"
},
"HtmlBodyPart": {
"Html": "<p>Welcome to our site!</p>"
}
}
]
}
}
]
}
]
}