Skip to main content
A forma can be parameterized with properties. Properties make a forma reusable across environments and let you hide infrastructure detail behind a small, typed interface, useful when a platform team owns the forma and other teams (or a CI/CD job) only need to supply a few values.

Declaring properties

A forma that extends "@formae/forma.pkl" declares its properties as a plain typed class:
Each member of the class becomes a property:
  • The member name is the CLI flag. team becomes --team, size becomes --size. Override this with @formae.Flag when the two should differ.
  • A member with a default is optional; a member without one is required. Here size defaults to "xs", and team must be supplied.
  • The member’s type validates the input. A value that does not fit the declared type (or its constraints, below) is rejected before anything is applied.
Declaring properties as a typed class requires extends "@formae/forma.pkl", not amends. extends opens the forma module so property values can be injected into it; amends keeps working for the legacy block form below.

Reading properties

Read a property directly by its member name, with the right static type:
properties.team is the String; there is no .value to unwrap. Because the shape is typed, editors resolve properties.team instead of flagging it as an unresolved reference, so the property reads have full completion and type checking.

Override the flag name

By default the member name is the flag, so a camelCase member like certArn would be exposed as --certArn. Annotate the member with @formae.Flag to decouple the two, keeping the typed member name while choosing the flag a consumer passes:
The CLI flag is now --cert-arn (and -p cert-arn=...), while the forma still reads the member as properties.certArn, with full editor resolution:
--help and the property manifest show the overridden flag (cert-arn); the member name (certArn) is unchanged.

Constraining the properties

Constrain a member with a Pkl type so a bad input fails before anything is applied rather than mid-deploy:
team is a closed enumeration: only "team-a" or "team-b" type-check. size takes a pattern constraint, useful when you want to validate shape rather than enumerate every legal value. port constrains a number: --port -5 fails the Int(this > 0) constraint at evaluation time. Either way, an input that doesn’t satisfy the constraint stops the apply before any cloud call.

Setting the properties

A consumer, whether a developer or a CI/CD job, only needs to supply properties when running apply. They don’t need to understand the forma itself. To see the properties a forma exposes, pass it to --help:
Then supply them as flags:
See Write your first forma for a worked example of turning a hardcoded value into a property end to end.

Legacy properties block

A forma that amends "@formae/forma.pkl" declares properties as a properties {} block of formae.Prop objects, read through .value. This form still works and needs no migration:
Each property’s flag becomes a --flag option, and a property without a default is required, exactly as in the typed form. The difference is ergonomics: the block form reads values with .value and is not statically typed, so editors cannot resolve properties.team and constraints live in separate typealias/function definitions rather than on the member itself. Prefer the typed extends form for new formae.

See also