- check runtime parameter values against the schema (from the UI or the SDK)
- build a user-friendly form in the Prefect UI
- easily reuse parameter types in similar workflows
Motivation
Let’s say you have a workflow that triggers a marketing email blast which looks like:
- there are only a few valid values for
mailing_lists - the
subjectmust not exceed 30 characters - no more than 5
attachmentsare allowed
- you have to spin up the infrastructure associated with your flow in order to check the constraints, which is wasteful if it turns out that bad parameters were provided
- this might get duplicative, especially if you have similarly constrained parameters for different workflows
pydantic to build a convenient, self-documenting, and reusable flow signature that the Prefect UI can build a better form from.
Building a convenient flow signature
Let’s address the constraints onmailing_lists, subject, and attachments.
Using Literal to restrict valid values
there are only a few valid values for mailing_lists
Say our valid mailing lists are: ["newsletter", "customers", "beta-testers"]
We can define a Literal to specify the valid values for the mailing_lists parameter.
Using a BaseModel subclass to group and constrain parameters
Both the subject and attachments parameters have constraints that we want to enforce.
the subject must not exceed 30 characters
the attachments must not exceed 5 items
Additionally, the subject, body, and attachments parameters are all related to the same thing: the content of the email.
We can define a BaseModel subclass to group these parameters together and apply these constraints.
Rewriting the flow signature
Now that we have defined theMailingList and EmailContent types, we can use them in our flow signature:

mailing_lists parameter renders as a multi-select dropdown that only allows the Literal values from our MailingList type.

EmailContent fields will be enforced before the run is submitted.

Full example code so far
Full example code so far
Using json_schema_extra to order fields in the form
By default, your flow parameters are rendered in the order defined by your @flow function signature.
Within a given BaseModel subclass, parameters are rendered in the following order:
- parameters with a
defaultvalue are rendered first, alphabetically - parameters without a
defaultvalue are rendered next, alphabetically
BaseModel subclass by passing json_schema_extra to the Field constructor with a position key.
Taking our EmailContent model from the previous example, let’s enforce that subject should be displayed first, then body, then attachments.

Requiring fields based on other fields
Some forms need constraints that depend on multiple fields. For example, you may want to requirediscount_reason and approver whenever a user provides a
coupon_code.
The Prefect UI cannot run arbitrary Python validators, such as a Pydantic
model_validator, before submitting a flow run. However, if you can express the
constraint in the generated JSON schema, Prefect can validate it before the run
is submitted.
Use json_schema_extra on a BaseModel subclass to add a JSON Schema
dependencies rule:
coupon_code but without
discount_reason or approver fails parameter validation before flow run
infrastructure starts. A run without coupon_code can still omit those fields.
JSON Schema
dependencies checks whether a property is present. The schema above
uses a schema dependency, rather than only "coupon_code": ["discount_reason", "approver"], so discount_reason and approver must also be non-empty strings
when coupon_code is present.Using callable and class parameters
If your parameter model includesCallable or Type fields, Prefect can’t serialize them
to JSON. The UI shows a placeholder like <MyParams> instead of actual values, and
automation templates can’t access individual fields.
Pydantic’s ImportString
type solves this. It accepts a dotted import path as a string (e.g. "mymodule.my_func"),
resolves it to the real Python object at validation time, and serializes back to a string
for JSON.
For example, an order ingestion flow that needs a different normalizer per vendor:
vendors.py
ImportString in the parameter model so the normalizer and raw schema are editable
strings in the UI, but resolve to real Python objects at runtime:
pipeline.py
ImportString requires that the referenced object is importable by dotted path. Lambdas,
closures, and objects defined in __main__ won’t work — move them to a named module
instead.Recap
We have now embedded the constraints on our parameters in the types that describe our flow signature, which means:- the UI can enforce these constraints before the run is submitted - less wasted infra cycles
- workflow inputs are self-documenting, both in the UI and in the code defining your workflow
- the types used in this signature can be easily reused for other similar workflows
Debugging and related resources
As you craft a schema for your flow signature, you may want to inspect the raw OpenAPI schema thatpydantic generates, as it is what the Prefect UI uses to build the form.
Call model_json_schema() on your BaseModel subclass to inspect the raw schema.
pydantic, see their documentation on models and types.