OrchardCMS/OrchardCore · error · FormatException
Top-level JSON element must be an object. Instead
Error message
Top-level JSON element must be an object. Instead, '{doc.RootElement.ValueKind}' was found. What it means
RecipeExecutor.ExecuteAsync parses the recipe JSON file and requires the top-level element to be a JSON object (the recipe document with steps, variables, etc.). If the root is an array, string, number, or null, it throws this FormatException. It guards against malformed recipe files being executed as scripts.
Solutions
- Open the recipe .json file and wrap the root in an object with recipe properties (e.g., { "name": ..., "steps": [...] }).
- Validate the JSON root with a tool or code (JsonValueKind.Object) before deploying the recipe.
- Re-export or re-download the recipe from a trusted source if it was corrupted.
- Check for encoding/BOM or truncation issues that could break the object structure.
Example fix
// before (invalid root: bare array)
[
{ "name": "Settings", "type": "recipes", "values": { } }
]
// after (valid root: object with steps array)
{
"name": "MyRecipe",
"displayName": "My Recipe",
"steps": [
{ "name": "Settings", "type": "recipes", "values": { } }
]
} Defensive patterns
Strategy: validation
Validate before calling
using var doc = JsonDocument.Parse(File.ReadAllText(recipePath));
if (doc.RootElement.ValueKind != JsonValueKind.Object)
throw new Exception("Recipe root must be a JSON object"); Type guard
bool IsValidRecipe(string json, out JsonDocument doc)
{
doc = JsonDocument.Parse(json);
return doc.RootElement.ValueKind == JsonValueKind.Object;
} Try / catch
try
{
await recipeExecutor.ExecuteAsync(executionId, recipeDescriptor, environment, logger);
}
catch (FormatException ex)
{
logger.LogError(ex, "Recipe {Name} has an invalid top-level JSON structure", recipeDescriptor.Name);
} Prevention
- Always keep the recipe root an object with name/steps properties.
- Lint recipe JSON files in CI before deployment.
- Avoid hand-editing recipe files; regenerate from a working setup export.
- Verify files were not truncated or re-encoded during transfer.
When it happens
Trigger: Executing a recipe whose descriptor's RecipeFileInfo content parses as valid JSON but whose root is not an object — e.g., a recipe file containing a bare array [ ... ], a quoted string, or an accidental non-object root.
Common situations: A hand-edited recipe file where brackets were misplaced; a recipe generated/serialized incorrectly (array of steps at the root instead of an object with a 'steps' property); a file downloaded/exported with wrong root; copying a recipe fragment instead of a full recipe document.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Top level JSON element must be an object. Instead
- Can't use the numeric key
- Can't use the non numeric key
- Top-level JSON element must be an object. Instead
- The variable ' ' was used in the recipe but not defined…
AI-assisted analysis of OrchardCMS/OrchardCore@4306c0717f (2026-09-13).
Data as JSON: /api/errors/7efadbf74d4b8700.
Report an issue: GitHub.
Appendix: source
Thrown at src/OrchardCore/OrchardCore.Recipes.Core/Services/RecipeExecutor.cs:59
{
await _recipeEventHandlers.InvokeAsync((handler, executionId, recipeDescriptor) => handler.RecipeExecutingAsync(executionId, recipeDescriptor), executionId, recipeDescriptor, _logger);
try
{
var methodProviders = new List<IGlobalMethodProvider>();
_methodProviders.Add(executionId, methodProviders);
methodProviders.Add(new ParametersMethodProvider(environment));
methodProviders.Add(new ConfigurationMethodProvider(_shellSettings.ShellConfiguration));
var result = new RecipeResult { ExecutionId = executionId };
await using (var stream = recipeDescriptor.RecipeFileInfo.CreateReadStream())
{
using var doc = await JsonDocument.ParseAsync(stream, JOptions.Document, cancellationToken);
if (doc.RootElement.ValueKind != JsonValueKind.Object)
{
throw new FormatException($"Top-level JSON element must be an object. Instead, '{doc.RootElement.ValueKind}' was found.");
}
foreach (var property in doc.RootElement.EnumerateObject())
{
if (property.Name == "variables")
{
var variables = JsonObject.Create(property.Value);
methodProviders.Add(new VariablesMethodProvider(variables, methodProviders));
}
// Go to Steps, then iterate.
if (property.Name == "steps" && property.Value.ValueKind == JsonValueKind.Array)
{
foreach (var step in property.Value.EnumerateArray())
{
var child = JsonObject.Create(step);
var recipeStep = new RecipeExecutionContextView on GitHub (pinned to 4306c0717f)