Skip to main content
AI/MLCrestApps

orchardcore-flow-bagpart

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.

Stars
13
Source
CrestApps/CrestApps.AgentSkills
Updated
2026-05-29
Slug
CrestApps--CrestApps.AgentSkills--orchardcore-flow-bagpart
View on GitHubRaw SKILL.md

// install — copy + paste into any project

mkdir -p .claude/skills && curl -fsSL https://raw.githubusercontent.com/CrestApps/CrestApps.AgentSkills/HEAD/plugins/orchardcore/skills/orchardcore-flow-bagpart/SKILL.md -o .claude/skills/orchardcore-flow-bagpart.md

Drops the SKILL.md into .claude/skills/orchardcore-flow-bagpart.md. Works with Claude Code, Cursor, and any agent that loads SKILL.md files from .claude/skills/.

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

{
  "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

  1. Navigate to Content DefinitionContent 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:

{
    "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>"
                }
              }
            ]
          }
        }
      ]
    }
  ]
}