PyHannes
02/17/2026, 9:23 AM"reason": "Validation error occurred for 'run-deployment' - Error creating flow run: Validation failed for field 'cfg.dry_run'. Failure reason: 'true' is not of type 'boolean'"
How can i properly map the datatype to boolean?
When removing the "" from the yaml line (``dry_run: {{ event.payload.data.dry_run|lower }}`` ) the yaml parsing fails.Marvin
02/17/2026, 9:23 AMMarvin
02/17/2026, 9:26 AM{{ event.payload.data.dry_run }} becomes the string "True"/"False" and validation rejects it since your parameter schema expects a boolean.
In Prefect 3.x, the fix is to use the JSON hydration wrapper so the rendered string is parsed back into a real boolean:
- Wrap the leaf parameter with __prefect_kind: "json" and render a JSON boolean (`true`/`false`) in value.
Example for your config structure:
triggers:
- type: event
name: Request Trigger
enabled: true
expect:
- my-external.trigger
parameters:
cfg:
dry_run:
__prefect_kind: "json"
value: '{% if event.payload.data.dry_run %}true{% else %}false{% endif %}'
Notes:
- Always quote Jinja in YAML (use single quotes) to avoid YAML parse errors.
- The json hydration handler will run json.loads on the rendered value, so `true`/`false` become actual booleans.
- If your payload sometimes comes as a string `"true"`/`"false"`, you can coerce safely like this:
value: '{% if event.payload.data.dry_run|string|lower == "true" %}true{% else %}false{% endif %}'
References:
- Hydration handlers (JSON/Jinja): prefect.utilities.schema_tools.hydration
- Jinja filters available in automations: prefect.server.events.jinja_filters
If you prefer, you can also wrap the entire cfg dict with __prefect_kind: "json" and render a full JSON object, but the leaf-level approach above is simplest for a single boolean.PyHannes
02/17/2026, 9:30 AM__prefect_kind: "json" and __prefect_kind: "jinja" ? In which context do i use what?Marvin
02/17/2026, 9:30 AMMarvin
02/17/2026, 9:35 AM"{{ event.payload... }}") are auto-upgraded to a jinja template under the hood. The result is still a string.
Example:
parameters:
note:
__prefect_kind: "jinja"
template: "Run requested by {{ event.resource.id }}"
(or simply note: "Run requested by {{ event.resource.id }}" for RunDeployment, since it’s auto-upgraded)
What __prefect_kind: "json" does
- Purpose: Parse a JSON string into a real (non-string) Python type via json.loads.
- Required key: "value"
- Output type: JSON → Python types (booleans, numbers, lists, dicts, null).
- When to use:
- Your parameter must be a boolean, number, list, or object (not a string).
- You want type-safe coercion (e.g., "true" → True, "3" → 3, '{"a":1}' → {"a":1}).
- Important: If you need templating inside the JSON, nest jinja inside json so the template renders first, then the result is parsed as JSON.
Examples
- Boolean from event payload:
parameters:
dry_run:
__prefect_kind: "json"
value: '{% if event.payload.data.dry_run %}true{% else %}false{% endif %}'
- Number from event payload:
parameters:
max_retries:
__prefect_kind: "json"
value: '{{ event.payload.data.max_retries }}'
(json.loads('3') → 3)
- Object/dict parameter:
parameters:
cfg:
__prefect_kind: "json"
value:
__prefect_kind: "jinja"
template: '{"source": "{{ event.resource.id }}", "dry_run": {% if event.payload.data.dry_run %}true{% else %}false{% endif %}}'
How they compose in RunDeployment automations
- RunDeployment does a two-pass hydration:
1) Detects/validates templates (plain strings with {{ }} are auto-wrapped as jinja for this action)
2) Renders with full context, then json handler parses any JSON wrappers
- Key takeaway: jinja gives you a string; json turns a valid JSON string into real types. Use json (optionally wrapping a jinja template) whenever you need non-string parameter types.
YAML quoting tip
- Always quote Jinja in YAML (use single quotes) to avoid parse errors:
- Good: value: '{% if ... %}true{% else %}false{% endif %}'
- Removing quotes often triggers YAML parse failures.
Source for deeper dive:
- Hydration handlers: prefect.utilities.schema_tools.hydration
- RunDeployment param rendering (two-pass, auto-upgrade of v1-style Jinja strings): prefect.server.events.actionsPyHannes
02/17/2026, 9:47 AMdry_run:
__prefect_kind: "json"
template: "{{ event.payload.data.dry_run|lower }}"PyHannes
02/17/2026, 9:49 AMdry_run:
__prefect_kind: "json"
template: "{{ event.payload.data.dry_run|lower }}"
But then the dry_run parameter in the automation UI shows as "empty", it doesn't seem to recognize it.
When starting the flow run, this parameter is just skipped and the flow run uses the default value.Marvin
02/17/2026, 9:49 AMMarvin
02/17/2026, 9:50 AMdry_run:
__prefect_kind: "json"
template: "{{ ... }}"
the handler won’t find “value” and returns RemoveValue(). That’s why the UI shows the parameter as empty and the flow run falls back to the default — Prefect effectively dropped that field.
Why “value” can fail
- If you used:
value: "{{ event.payload.data.dry_run }}"
Jinja will usually render a Python-style boolean (“True”/“False”). JSON requires lowercase true/false. Then json.loads("True") fails with InvalidJSON. That’s likely the “deploy step fails” you saw.
Working fix (boolean -> real boolean)
- Emit a valid JSON boolean literal (lowercase) from Jinja, then let the JSON handler parse it:
triggers:
- type: event
name: Request Trigger
enabled: true
expect:
- my-external.trigger
parameters:
cfg:
dry_run:
__prefect_kind: "json"
value: '{% if event.payload.data.dry_run %}true{% else %}false{% endif %}'
- If your payload can be the string "true"/"false", coerce it safely:
value: '{% if (event.payload.data.dry_run|string|lower) == "true" %}true{% else %}false{% endif %}'
Important YAML/Jinja tip
- Always quote the Jinja template in YAML (use single quotes) so the YAML parser doesn’t choke on the braces.
Recap: when to use what
- __prefect_kind: "jinja" → render to a string (for string parameters).
- __prefect_kind: "json" → parse a JSON string into real types (booleans, numbers, objects). If you need templating inside, put the Jinja template in “value” so it renders first, then is parsed by json.
If switching to “value” still fails, can you paste the exact error Prefect returns during the deploy step? That’ll help confirm if it’s the uppercase boolean issue or something else. For reference, handlers are here:
- Hydration handlers: prefect.utilities.schema_tools.hydrationPyHannes
02/17/2026, 9:53 AMdry_run:
__prefect_kind: "json"
value: "{% if event.payload.data.dry_run %}true{% else %}false{% endif %}"Marvin
02/17/2026, 9:53 AMPyHannes
02/17/2026, 9:53 AMMarvin
02/17/2026, 9:53 AMMarvin
02/17/2026, 9:54 AM__prefect_kind: "json" on a dict, Prefect’s auto-upgrade of v1-style templates (plain strings with {{ ... }}) does not recurse into that dict. So your Jinja inside the value string is never recognized/rendered, and the JSON handler tries to parse the raw Jinja string — which fails.
You need to nest a Jinja wrapper inside the JSON wrapper so Jinja renders first, then JSON parses the result:
triggers:
- type: event
name: Request Trigger
enabled: true
expect:
- my-external.trigger
parameters:
cfg:
dry_run:
__prefect_kind: "json"
value:
__prefect_kind: "jinja"
template: '{% if event.payload.data.dry_run %}true{% else %}false{% endif %}'
Notes:
- Always quote the Jinja string in YAML (use single quotes).
- If your payload might be the string "true"/"false", coerce it:
template: '{% if (event.payload.data.dry_run|string|lower) == "true" %}true{% else %}false{% endif %}'
- Alternatively, set the whole cfg in one go:
cfg:
__prefect_kind: "json"
value:
__prefect_kind: "jinja"
template: '{"dry_run": {% if event.payload.data.dry_run %}true{% else %}false{% endif %}}'
Why your earlier “template under json” looked like it worked but was skipped:
- Using template directly under __prefect_kind: "json" (without a value key) causes the JSON handler to return RemoveValue(), so the parameter shows “empty” in the UI and your flow uses its default.
References (source):
- Hydration handlers: prefect.utilities.schema_tools.hydration
- RunDeployment param rendering (auto-upgrade behavior and two-pass rendering): prefect.server.events.actions
Give the nested version a try. If anything still errors, please share the exact error message and we’ll dig in.Marvin
02/17/2026, 9:54 AMtriggers:
- type: event
name: Request Trigger
enabled: true
expect:
- my-external.trigger
parameters:
cfg:
dry_run:
__prefect_kind: "json"
value:
__prefect_kind: "jinja"
template: '{% if event.payload.data.dry_run %}true{% else %}false{% endif %}'
Why this works
- At deploy-time:
- The JSON handler hydrates the dict in value, which becomes a ValidJinja placeholder (no rendering yet), so it does NOT call json.loads and does NOT error.
- At runtime:
- Jinja renders to the JSON literal true or false, then the JSON handler parses it into a real boolean True/False. Your parameter type validation passes.
About the UI showing “empty”
- That’s expected for placeholders. The UI can’t evaluate event context ahead of time, so it shows blank. It will be populated when the automation fires.
- If you see the value skipped at runtime, it usually means either:
- The structure didn’t match exactly (e.g., you used template directly under json instead of nesting under value), which causes the handler to drop the field, or
- The parameter path doesn’t match your flow’s parameters. Please double‑check your flow expects cfg with a dry_run boolean inside. If your flow expects a top-level dryrun boolean, map that instead.
About the “do-nothing” error line
- That line typically shows up as part of the validation union when the primary validation fails. Fixing the invalid JSON error above should make it disappear. If it persists, please share your Prefect version (prefect version). There were fixes in recent 3.x releases that improved this validation UX.
References (source)
- Hydration handlers (JSON/Jinja): prefect.utilities.schema_tools.hydration
- RunDeployment parameter rendering (two-pass with placeholders): prefect.server.events.actions
If you try the nested structure above and still hit issues, please paste:
- Your flow signature (the parameters and types)
- Your Prefect version
- The exact error from the deploy command
I’ll help you zero in from there.PyHannes
02/17/2026, 10:30 AMMarvin
02/17/2026, 10:30 AMMarvin
02/17/2026, 10:30 AM{{ event.payload.data.dry_run }} becomes "True"/"False" and fails boolean validation.
- Use JSON hydration to coerce to a real boolean, with Jinja nested under JSON so deploy-time validation defers parsing:
dry_run:
__prefect_kind: "json"
value:
__prefect_kind: "jinja"
template: '{% if event.payload.data.dry_run %}true{% else %}false{% endif %}'
- Quote the Jinja string (use single quotes).
- UI may show empty (it’s a placeholder); it will resolve at runtime.
- Ensure your flow expects cfg.dry_run as a boolean (or adjust the path).PyHannes
04/21/2026, 6:47 PMcfg:
config:
__prefect_kind: "json"
value:
__prefect_kind: "jinja"
template: "{{ event.payload.data.config }}"
The event payload will be a dict {"a": 123}.
When the event appears the automation actions fails with "
Unable to create flow run from deployment: InvalidJSON()
This comes from
@handler("json")
def json_handler(obj: dict[str, Any], ctx: HydrationContext):
if "value" in obj:
if isinstance(obj["value"], dict):
dehydrated_json = _hydrate(obj["value"], ctx)
else:
dehydrated_json = obj["value"]
# If the result is a Placeholder, we should return it as is
if isinstance(dehydrated_json, Placeholder):
return dehydrated_json
try:
return json.loads(dehydrated_json)
except (json.decoder.JSONDecodeError, TypeError) as e:
return InvalidJSON(detail=str(e))
in prefect\utilities\schema_tools\hydration.pyMarvin
04/21/2026, 6:47 PMMarvin
04/21/2026, 6:50 PM{'a': 123} (or a non-string), and the outer json handler then tries to json.loads(...) it. That fails with InvalidJSON because JSON requires double quotes and proper JSON formatting.
Fix: render the payload as valid JSON first, then let the json handler parse it back to a dict. Add the tojson filter to your template.
Change your parameters to:
cfg:
config:
__prefect_kind: "json"
value:
__prefect_kind: "jinja"
template: "{{ event.payload.data.config | tojson }}"
Why this works:
- | tojson converts the dict in the event payload to a valid JSON string (e.g., {"a": 123}).
- The outer json handler then parses that JSON string back into a real dict for your flow parameter.
Notes:
- If you omit | tojson, Jinja will produce a Python repr (single quotes) that is not valid JSON.
- If you want to pass the entire event payload as a dict param, you can do the same pattern:
payload:
__prefect_kind: "json"
value:
__prefect_kind: "jinja"
template: "{{ event.payload | tojson }}"
If this still errors, let me know your Prefect version and share the exact event payload example you’re testing with so I can reproduce.