Recipes (OrchardCore.Recipes)¶
Overview¶
The OrchardCore.Recipes module allows you to automate Orchard Core tenant setup and configuration using JSON-based recipe files. Recipes can install features, set themes, create content types, provision content, and much more.
What is a Recipe?¶
A recipe is a .recipe.json file placed in a Recipes folder within your module or theme. Recipes are picked up by Orchard Core and can be executed via the admin panel or automatically during tenant setup.
Key Properties¶
| Property | Type | Description |
|---|---|---|
name |
string | The unique internal name of the recipe. Used for identifying the recipe in code, including when executing it from other recipes. |
displayName |
string | The friendly name shown in the admin UI or during setup. |
description |
string | A short description of what the recipe does. Displayed in the admin and setup UIs. |
author |
string | The name of the recipe creator or organization. |
website |
string | URL to the website or documentation for the recipe. |
version |
string | Semantic version (e.g., 1.0.0) representing the recipe version. |
issetuprecipe |
boolean | Indicates if this recipe should be available during tenant setup. |
tags |
array | Keywords for categorizing the recipe in the UI (e.g., ["blog", "theme"]). |
variables |
object | Key-value pairs to define reusable values throughout the recipe. |
steps |
array | An ordered list of step objects that define the actions the recipe will perform. Each step has a name and step-specific parameters. |
Example:¶
{
"name": "Blog",
"displayName": "Blog Site",
"description": "Creates a simple blog with custom content types, widgets, and pages.",
"author": "Orchard Core Team",
"website": "https://orchardcore.net",
"version": "1.0.0",
"issetuprecipe": true,
"tags": [ "blog", "content", "theme" ],
"variables": {
"siteId": "[js:uuid()]"
},
"steps": [
// Here you can add your steps which will be executed in the provided order.
]
}
Note
Recipes, despite being JSON files, may contain comments: // This is a comment.
Recipe Helpers¶
These helpers allow dynamic expressions inside recipe values using a special syntax.
| Helper | Example Usage | Description |
|---|---|---|
js |
"ContentItemId": "[js:variables('homePageId')]" |
Evaluates a JavaScript expression. Common for referencing variables. |
file |
"Content": "[file:text('Snippets/homepage.liquid')]" |
Loads content from an external file. Often used for Liquid templates. |
env |
"value": "[env:MyEnvironmentVariable]" |
Injects values from environment variables. |
appsettings |
"value": "[appsettings:OrchardCore:SiteName]" |
Reads configuration from appsettings.json. |
localization |
"value": "[localization:WelcomeTitle]" |
Retrieves localized strings by key. |
uuid |
"Id": "[js:uuid()]" |
Generates a new unique identifier (UUID/GUID). |
base64 |
"data": "[js:base64('ew0KICAgICJ0eXBlIjogIkNvbnRlbnRJdGVtL0Jsb2dQb3N0Ig0KfQ==')]" |
Decodes the specified string from Base64 encoding. Use https://www.base64-image.de/ to convert your files to base64. |
html |
"html": "[js:html('<p>Hello & welcome</p>')]" |
Decodes the specified string from HTML encoding.. |
gzip |
"data": "[js:gzip('data')]" |
Decodes the specified string from gzip/base64 encoding. Use http://www.txtwizard.net/compression to gzip your strings. |
Custom Recipes¶
To create a new recipe step, implement the IRecipeStepHandler interface and its ExecuteAsync method:
public async Task ExecuteAsync(RecipeExecutionContext context)
Alternatively, you can extend NamedRecipeStepHandler and implement the required abstract method, providing additional functionality for named steps.
Built-in Recipe Steps¶
Each step is a JSON object in the steps array. Here are all built-in types:
feature¶
Enables or disables features (modules/themes).
{
"name": "feature",
"enable": [ "OrchardCore.Admin", "MyCustomTheme" ],
"disable": []
}
Warning
If you want to use your own theme (e.g., YourTheme), make sure to enable its feature; otherwise, the theme layout will not work after the recipe is executed.
themes¶
Sets the active frontend and admin themes.
{
"name": "themes",
"admin": "TheAdmin",
"site": "MyCustomTheme"
}
settings¶
Configures core site settings (like homepage route, culture, time zone, etc).
{
"name": "settings",
"HomeRoute": {
"Action": "Display",
"Controller": "Item",
"Area": "OrchardCore.Contents",
"ContentItemId": "[js:variables('homeId')]"
},
"LayerSettings": {
"Zones": [ "Content", "Footer" ]
}
// You may add other settings here
}
ContentDefinition¶
Defines or updates content types and content parts.
{
"name": "ContentDefinition",
"ContentTypes": [ { "Name": "Article", ... } ],
"ContentParts": [ { "Name": "BodyPart", ... } ]
}
lucene-index¶
Creates or configures Lucene search indexes.
[
{
// Create the indices before the content items so they are indexed automatically.
"name": "lucene-index",
"Indices": [
{
"Search": {
"AnalyzerName": "standardanalyzer",
"IndexLatest": false,
"IndexedContentTypes": [
"Blog",
"BlogPost"
]
}
}
]
},
{
// Create the search settings.
"name": "Settings",
"LuceneSettings": {
"SearchIndex": "Search",
"DefaultSearchFields": [
"Content.ContentItem.FullText"
]
}
}
]
lucene-index-reset¶
Clears index content.
{
"name": "lucene-index-reset",
"includeAll": true
}
The includeAll property indicates whether to include all available Lucene indices. When set to true, the Indices property can be omitted.
lucene-index-rebuild¶
Rebuilds Lucene indexes to reflect current content.
{
"name": "lucene-index-rebuild",
"Indices": [ "Search" ]
}
content¶
Imports content items such as pages, blogs, or menus.
{
"name": "content",
"Data": [ { "ContentType": "Page", "DisplayText": "About", ... } ]
}
Note
There is also QueryBasedContentDeploymentStep which produces exactly the same output as the Content Step, but based on a provided Query.
media¶
Uploads files into the Media library.
{
"name": "media",
"Files": [
{ "TargetPath": "logo.jpg", "SourcePath": "../wwwroot/img/logo.jpg" }
]
}
layers¶
Defines layer rules for conditional widget placement.
{
"name": "layers",
"Layers": [
{ "Name": "Always", "Rule": "true" },
{ "Name": "Homepage", "Rule": "isHomepage()" }
]
}
queries¶
Adds Lucene or SQL queries to be reused by widgets or APIs.
{
"name": "queries",
"Queries": [
{
"Source": "Lucene",
"Name": "RecentPosts",
"Index": "Search",
"Template": "[file:text('Snippets/recentPosts.json')]",
"ReturnContentItems": true
}
]
}
AdminMenu¶
Defines items in the admin menu for organizing admin tools.
{
"name": "AdminMenu",
"data": [
{
"Id": "[js:uuid()]",
"Name": "Tools",
"MenuItems": [ ... ]
}
]
}
Roles¶
Creates user roles and assigns permissions.
{
"name": "Roles",
"Roles": [
{
"Name": "Editor",
"Permissions": [ "EditOwnContent", "PublishContent" ]
}
]
}
Templates¶
Defines or updates Liquid templates.
{
"name": "Templates",
"Templates": {
"Content__LandingPage": {
"Description": "Landing page layout",
"Content": "[file:text('Snippets/landingpage.liquid')]"
}
}
}
WorkflowType¶
Defines custom workflows to automate user or content events.
{
"name": "WorkflowType",
"data": [
{
"WorkflowTypeId": "[js:variables('workflowTypeId')]",
"Name": "User Registration"
}
]
}
deployment¶
Defines deployment plans to export/import content and settings. Also see Deployment.
{
"name": "deployment",
"Plans": [
{
"Name": "ExportSite",
"Steps": [
{
"Type": "CustomFileDeploymentStep",
"Step": {
"FileName": "Export",
"FileContent": "Export",
"Id": "[js: uuid()]",
"Name": "CustomFileDeploymentStep"
}
},
{
"Type": "AllContentDeploymentStep",
"Step": {
"Id": "[js: uuid()]",
"Name": "AllContent"
}
}
]
}
]
}
custom-settings¶
Updates content-based settings stored in a custom content item.
{
"name": "custom-settings",
"MySiteSettings": {
"ContentType": "MySiteSettings",
"MySettingsPart": {
"SomeTextField": { "Text": "Hello World" }
}
}
}
recipes¶
Runs additional recipes within the current one, allowing modular reuse.
{
"name": "recipes",
"Values": [
{ "executionid": "MyApp", "name": "MyApp.Pages" }
]
}
executionid use a custom identifier to distinguish these recipe executions from others. As name use the name field from the given recipe's head (this is left blank when you export to recipes).
Recipe Migrations¶
Recipe migrations allow you to perform updates using Orchard Core recipe files. These migrations are especially useful for updating metadata such as content types, workflows, settings, or any other component that can be updated via a recipe.
While many changes can be made through the admin UI, recipe migrations provide a repeatable and versioned way to apply updates, ideal for deployment automation or environment setup.
Basic Concept¶
A recipe migration is implemented by creating a DataMigration class in your module or theme. Inside this class, you call into the IRecipeMigrator service to execute recipe files.
Recipe files must be stored in a Migrations folder within your project, and they are typically written in JSON format using the standard Orchard recipe schema.
Setup¶
- Create a migration class:
- Inherit from
OrchardCore.Data.Migration.DataMigration(in theOrchardCore.Data.Abstractionspackage). - Inject the
IRecipeMigratorservice. -
Implement one or more of the following methods:
CreateAsync()– the first migration, must return1UpdateFrom<version>Async()– used for incremental migrations
-
Create migration recipe files:
- Place them in a
Migrationsfolder (same level as your migration class). - Name them clearly to reflect the version or purpose.
Example: Media Asset Migration¶
Let's say we want to deploy media assets as part of a module. Here’s how we’d structure this:
Migration Class¶
public sealed class Migrations : DataMigration
{
private readonly IRecipeMigrator _recipeMigrator;
public Migrations(IRecipeMigrator recipeMigrator)
{
_recipeMigrator = recipeMigrator;
}
public async Task<int> CreateAsync()
{
await _recipeMigrator.ExecuteAsync("migration.recipe.json", this);
return 1;
}
public async Task<int> UpdateFrom1Async()
{
await _recipeMigrator.ExecuteAsync("migrationV2.recipe.json", this);
return 2;
}
}
Note
Important: Method names like UpdateFrom1Async() are case-sensitive and must follow the naming convention exactly in order to be discovered and executed.
Recipe Files¶
Place the following JSON files in a folder named Migrations.
Migrations/migration.recipe.json¶
Initial migration adds two media files:
{
"steps": [
{
"name": "media",
"Files": [
{
"TargetPath": "about/1.jpg",
"SourcePath": "../wwwroot/img/about/1.jpg"
},
{
"TargetPath": "about/2.jpg",
"SourcePath": "../wwwroot/img/about/2.jpg"
}
]
}
]
}
Migrations/migrationV2.recipe.json¶
Second migration adds another image:
{
"steps": [
{
"name": "media",
"Files": [
{
"TargetPath": "about/1.jpg",
"SourcePath": "../wwwroot/img/about/1.jpg"
},
{
"TargetPath": "about/2.jpg",
"SourcePath": "../wwwroot/img/about/2.jpg"
},
{
"TargetPath": "about/3.jpg",
"SourcePath": "../wwwroot/img/about/3.jpg"
}
]
}
]
}