> ## Documentation Index
> Fetch the complete documentation index at: https://docs.formae.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Properties

> Parameters that turn a forma into a reusable, type-safe template exposed as CLI flags.

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:

```pkl theme={"languages":{"custom":["/languages/pkl.json"]}}
extends "@formae/forma.pkl"
import "@formae/formae.pkl"

properties: Props

class Props {
  /// The team that owns this service
  team: String

  /// Database size, as a t-shirt size
  size: String = "xs"
}
```

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`](#override-the-flag-name) 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.

<Note>
  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](#legacy-properties-block)
  below.
</Note>

### Reading properties

Read a property directly by its member name, with the right static type:

```pkl theme={"languages":{"custom":["/languages/pkl.json"]}}
local _vpc = new vpc_resources.VpcResources {
  team = properties.team
}

local _database = new database_resources.DatabaseResources {
  team = properties.team
  size = properties.size
  vpc = _vpc.vpc
}
```

`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:

```pkl theme={"languages":{"custom":["/languages/pkl.json"]}}
class Props {
  /// ARN of the ACM certificate
  @formae.Flag { name = "cert-arn" }
  certArn: String = ""
}
```

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:

```pkl theme={"languages":{"custom":["/languages/pkl.json"]}}
new listener.Listener {
  certificateArn = properties.certArn
}
```

`--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:

```pkl theme={"languages":{"custom":["/languages/pkl.json"]}}
class Props {
  /// Owning team
  team: "team-a" | "team-b"

  /// Database size
  size: String(matches(Regex(#"(?i)^(XS|S)$"#))) = "xs"

  /// Port to listen on
  port: Int(this > 0) = 8080
}
```

`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`:

```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
formae apply --help database.pkl
```

```
...

Properties:
      --size        property: size [default: "xs"]
      --team        property: team [required]
```

Then supply them as flags:

```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
formae apply --mode reconcile --team team-a --size s database.pkl
```

See [Write your first forma](/documentation/get-started/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:

```pkl theme={"languages":{"custom":["/languages/pkl.json"]}}
amends "@formae/forma.pkl"
import "@formae/formae.pkl"

properties {
  team = new formae.Prop {
    flag = "team"
  }
  size = new formae.Prop {
    flag = "size"
    default = "xs"
  }
}

local _database = new database_resources.DatabaseResources {
  team = properties.team.value
  size = properties.size.value
}
```

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

* [Write your first forma](/documentation/get-started/write-your-first-forma): a hands-on walkthrough that adds a property to a real bucket.
* [Build self-service infrastructure](/documentation/guides/build-self-service-infrastructure): expose a parameterized forma as a developer-facing interface.
* [Apply modes](/documentation/concepts/apply-modes): reconcile vs. patch, and how properties interact with each.
