Alastair
05/04/2026, 2:41 PMMarvin
05/04/2026, 2:41 PMMarvin
05/04/2026, 2:43 PMvalidate_parameters is runtime validation, and it is on by default.
Short version:
- @flow(validate_parameters=True) is the default
- Prefect validates/coerces parameters when the flow actually runs
- This applies to:
- direct Python calls
- deployment/API-triggered runs
- subflow calls
- It is not only for generating the deployment/UI parameter form
A couple important distinctions:
1. Runtime flow validation (validate_parameters)
This controls whether the flow engine validates parameters against the flow function signature/types before running the flow body.
So for something like:
python
from prefect import flow
@flow
def my_flow(x: int):
print(x)
with default behavior, passing x="abc" will fail validation before the flow body runs.
Prefect uses Pydantic-backed validation/coercion here, so some values may be coerced:
- "5" -> 5
- dicts -> Pydantic models, if annotated that way
2. Schema/UI generation
Prefect also generates a parameter schema for deployments/UI forms based on the flow signature and annotations.
That schema generation is related, but it is not the same thing as validate_parameters.
3. Deployment-side parameter enforcement
For deployment-triggered runs, there is also deployment schema enforcement (enforce_parameter_schema) which is separate from the flow’s own runtime validate_parameters.
So the rough split is:
- validate_parameters
- runtime flow execution behavior
- happens in the flow engine
- parameter schema / UI form
- used to describe inputs in the UI/API
- enforce_parameter_schema
- deployment/API-side schema checking
Subflows
Subflows are validated too. A subflow is still a flow run, so its own validate_parameters setting applies when it is called.
So if you have:
python
from prefect import flow
@flow
def child(x: int):
return x
@flow
def parent():
return child("oops")
the child flow will validate "oops" against int at runtime and fail by default.
How to disable it
If you want Prefect to skip runtime parameter validation:
python
from prefect import flow
@flow(validate_parameters=False)
def my_flow(x: int):
print(x)
Then Prefect will pass values through as-is, and any type issues would show up later in your own code instead of at flow start.
Answering your exact question directly:
- Does Prefect do runtime parameter checking by default?
Yes.
- Is validate_parameters only for inspecting flow shape / building UI inputs?
No. It affects actual runtime execution.
- Does it apply in flow/subflow calls?
Yes. Each flow/subflow validates its own inputs by default.
If you want, I can also show the exact difference between validate_parameters and deployment enforce_parameter_schema with a concrete example.Alastair
05/04/2026, 2:48 PMif TYPE_CHECKING?Marvin
05/04/2026, 2:48 PMMarvin
05/04/2026, 2:51 PMpython
from prefect import flow
@flow
def my_flow(x):
return x
In practice, Prefect treats x like Any.
That means:
- Runtime validation: effectively no type checking for that parameter
- Schema/UI generation: Prefect can still include the parameter, but without meaningful type information
So with validate_parameters=True, an unannotated parameter does not get strict validation. Almost anything will pass through.
Conceptually:
- annotated param: "this should be an `int`"
- unannotated param: "this can be anything"
2. Annotation hidden behind if TYPE_CHECKING
Example:
python
from typing import TYPE_CHECKING
from prefect import flow
if TYPE_CHECKING:
from pathlib import Path
@flow
def my_flow(p: "Path"):
return p
This is trickier.
Because Path only exists for static type checkers and not at runtime, Prefect/Pydantic may not be able to resolve that annotation when the flow actually runs.
The behavior is roughly:
- Schema/UI generation: Prefect often falls back and treats it like Any, so the UI may not show a real type
- Runtime validation: this can fail because Pydantic tries to resolve the annotation and discovers the type is not actually defined at runtime
So unlike the "no type" case, this is not a clean "skip validation" path — it's more like:
- schema generation may degrade gracefully
- runtime validation may error
Practical rule of thumb
If a type appears in a flow signature, it should usually be importable at runtime, not only under TYPE_CHECKING.
So this is safer:
python
from pathlib import Path
from prefect import flow
@flow
def my_flow(p: Path):
return p
If you want to avoid importing a heavy type at runtime, you have a few options:
- annotate with a simpler runtime-safe type (str, dict, etc.)
- disable parameter validation for that flow with @flow(validate_parameters=False)
- convert inside the flow body yourself
For example:
python
from prefect import flow
@flow
def my_flow(p: str):
# convert to your richer type inside the flow
return p
Bottom line
- No annotation: treated basically as `Any`; runtime validation does not enforce much
- Type only under `TYPE_CHECKING`: may look okay for static typing, but can break Prefect runtime validation
If you want, I can show a safe pattern for custom classes / heavy imports / TYPE_CHECKING-only types that still works nicely with Prefect deployments and the UI.