Ben Griffiths
10/23/2025, 11:50 AMBaseModel for it's parameters. This flow is called by another flow as a subflow, but I don't want all of the subflows parameters to be editable in the Prefect UI when configuring a run, as some are set programatically. How can I have some parameters in the BaseModel hidden in the UI?Marvin
10/23/2025, 11:50 AMMarvin
10/23/2025, 11:54 AMField(exclude=True), PrivateAttr, or SecretStr don’t remove a field from the form (they affect serialization or masking, not visibility). See Form building.
Recommended patterns to keep some values programmatic/non-editable:
- Don’t expose them as flow parameters
- Put only “public” inputs in the flow signature/model; derive/load the rest inside the flow (from blocks, env vars, or runtime context).
- Example:
from pydantic import BaseModel, Field
from prefect import flow
# from my_blocks import MyConfig # a Prefect block you define
class PublicParams(BaseModel):
dataset: str
date: str
config_block: str = Field(default="etl-config/dev") # only editable field
@flow
def subflow(params: PublicParams):
cfg = MyConfig.load(params.config_block) # holds api keys, endpoints, etc.
# derive private/internal config here
retries = 5
api_key = cfg.api_key
# ...rest of your logic using derived/private values...
- This keeps internal fields out of the UI entirely; users only edit config_block or other public inputs.
- Don’t deploy the subflow
- If you deploy only the parent (or a thin public wrapper flow) and keep the subflow undecorated-as-deployment, users won’t get a UI form for the subflow at all. The parent can set programmatic values and pass them down in code.
- Use a wrapper flow for UI and keep a richer internal model
- The wrapper flow takes a minimal “public” model and calls an internal implementation (task or plain function) with an “internal” model built programmatically. Only deploy the wrapper.
from pydantic import BaseModel
from prefect import flow
class PublicParams(BaseModel):
dataset: str
date: str
class InternalParams(BaseModel):
dataset: str
date: str
api_key: str
retries: int = 5
def _impl(p: InternalParams):
# actual work here
...
@flow
def subflow(params: PublicParams):
# compute/load private values here
api_key = MyConfig.load("etl-config/dev").api_key
_impl(InternalParams(**params.model_dump(), api_key=api_key))
- Consider environment-driven config
- If the values are environment-specific, load them from env vars or settings models instead of parameters. You can also pull context like flow run info via runtime if needed: Runtime context.
Notes:
- JSON Schema tricks like readOnly/writeOnly or json_schema_extra do not provide a supported “hidden” control in the Prefect UI today; only ordering is documented.
- Field(exclude=True) affects model_dump but not the generated parameter form.
If you can share:
- Whether the subflow itself has a deployment you want to keep, and
- Which fields you want to keep programmatic,
I can suggest the cleanest refactor for your case.
References:
- Form building: Prefect 3.x form building
- Runtime context: Prefect runtime context
- Pydantic serialization notes: Pydantic model_dump()Ben Griffiths
10/23/2025, 12:18 PMparam1: ClassVar[None] = None, this successfully hides them in the UI. Is this approach acceptable?Marvin
10/23/2025, 12:19 PMMarvin
10/23/2025, 12:20 PMfrom pydantic import BaseModel
from prefect import flow
class PublicParams(BaseModel):
dataset: str
date: str
config_block: str = "etl-config/dev"
class InternalParams(BaseModel):
dataset: str
date: str
api_key: str
retries: int = 5
def to_internal(p: PublicParams) -> InternalParams:
cfg = MyConfig.load(p.config_block) # Prefect block or your own config
return InternalParams(
dataset=p.dataset,
date=p.date,
api_key=cfg.api_key,
retries=5,
)
@flow
def subflow(params: PublicParams):
internal = to_internal(params)
# use internal...
This keeps the UI clean and makes the data-flow explicit.
References:
- Prefect 3.x form building (UI uses your model’s JSON schema): Form building
- Runtime context (if you need run/deployment info at runtime): Runtime context
If you share which fields you’re hiding and how you compute them, I can tailor the conversion pattern for your case.