--- 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.Flows` module. - **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 as `BagPart_Empty`. - Use placement to hide empty parts by placing `FlowPart_Empty` or `BagPart_Empty` to `"-"`. - `FlowPart` and `BagPart` display shapes use `{PartName}` as their differentiator. - To hide or move a whole FlowPart or BagPart editor row in the admin UI, use `ContentPart_Edit` with differentiator `{ContentType}-{PartName}`. - Inner shapes such as `FlowPart_Edit` and `BagPart_Edit` target 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 `sealed` modifier except View Models. - Use file-scoped namespaces in C# examples. ### Enabling Flow Features ```json { "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: ```csharp public sealed class Migrations : DataMigration { private readonly IContentDefinitionManager _contentDefinitionManager; public Migrations(IContentDefinitionManager contentDefinitionManager) { _contentDefinitionManager = contentDefinitionManager; } public async Task 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: ```csharp public sealed class Migrations : DataMigation { private readonly IContentDefinitionManager _contentDefinitionManager; public Migrations(IContentDefinitionManager contentDefinitionManager) { _contentDefinitionManager = contentDefinitionManager; } public async Task 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 ```csharp await _contentDefinitionManager.AlterTypeDefinitionAsync("LandingPage", type => type .WithPart("FlowPart", part => part .WithEditor("Blocks") ) ); ``` ```csharp await _contentDefinitionManager.AlterTypeDefinitionAsync("AgencyPage", type => type .WithPart("Services", "BagPart", part => part .WithEditor("Blocks") ) ); ``` #### Via Admin UI 1. Navigate to **Content Definition** → **Content Types**. 2. Edit the content type containing the FlowPart or BagPart. 3. Click **Edit** on the FlowPart or BagPart. 4. 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: ```json { "FlowPart_Empty": [ { "place": "-" } ], "BagPart_Empty": [ { "place": "-" } ] } ``` To render empty containers with the same template as populated ones: ```json { "FlowPart_Empty": [ { "shape": "FlowPart" } ] } ``` ### Templating BagPart - Decoupled Approach Access content items directly through the named BagPart, bypassing display management: #### Liquid ```liquid {% for service in Model.ContentItem.Content.Services.ContentItems %}

{{ service.DisplayText }}

{{ service.HtmlBodyPart.Html | raw }}

{% endfor %} ``` #### Razor ```html @foreach (var item in Model.ContentItem.Content.Services.ContentItems) {

@item.DisplayText

@Html.Raw(item.Content.HtmlBodyPart.Html)

} ``` ### Templating BagPart - Display Management Approach Use display management to build and render child shapes with proper alternate resolution: #### Liquid ```liquid
{% for item in Model.ContentItems %} {{ item | shape_build_display: "Detail" | shape_render }} {% endfor %}
``` #### Razor ```html @using OrchardCore.Flows.ViewModels @model BagPartViewModel @inject OrchardCore.ContentManagement.Display.IContentItemDisplayManager ContentItemDisplayManager
@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) }
``` ### 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`: ```json { "BagPart": [ { "differentiator": "Services", "place": "Content:1" } ] } ``` For a standard `FlowPart` on the front end: ```json { "FlowPart": [ { "differentiator": "FlowPart", "place": "-" } ] } ``` To hide the whole editor row in the admin UI, use `ContentPart_Edit` and the `{ContentType}-{PartName}` differentiator: ```json { "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 ```json { "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": "

Welcome to our site!

" } } ] } } ] } ] } ```