> ## 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.

# Resolvables

> Declare which resource properties other resources can reference, and let formae resolve them at apply time.

Resolvables enable resources to reference properties of other resources that aren't known until apply time. For example, a Subnet can reference a VPC's ID before the VPC exists, and formae resolves the actual value when the VPC is created.

## When to Use Resolvables

Use resolvables when a resource property depends on output from another resource:

* Subnet needs VPC ID (generated when VPC is created)
* Security Group references VPC ID
* IAM Policy references Role ARN
* Any property that uses an identifier generated by cloud infrastructure

## Defining a Resolvable Class

Each resource type that can be referenced needs a Resolvable class:

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

const type = "MyPlugin::Network::VPC"

// Define what properties can be referenced
open class VPCResolvable extends formae.Resolvable {
    hidden type = module.type

    // Each resolvable property creates a self-referencing instance
    hidden id: VPCResolvable = (this) {
        property = "VpcId"
    }
}

@formae.ResourceHint {
    type = module.type
    identifier = "VpcId"
}
open class VPC extends formae.Resource {
    @formae.FieldHint {}
    cidrBlock: String

    local parent = this

    // Expose resolvable for other resources to reference
    hidden res: VPCResolvable = new {
        label = parent.label
        stack = parent.stack?.label
    }
}
```

The `hidden res` property lets other resources reference this resource's properties.

## Accepting Resolvable References

Use a union type to accept either a literal value or a resolvable reference:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
@formae.ResourceHint {
    type = "MyPlugin::Network::Subnet"
    identifier = "SubnetId"
}
open class Subnet extends formae.Resource {
    // Accepts either "vpc-123" or a reference to another resource
    @formae.FieldHint {}
    vpcId: String|formae.Resolvable

    @formae.FieldHint {}
    cidrBlock: String
}
```

## Embedding resolvables in string fields

`String|formae.Resolvable` lets a field be *either* a literal or a reference, where the whole value is one or the other. To let users splice a reference into the *middle* of a string (the user-facing side is covered in [Embedding resolvables in strings](/documentation/concepts/resolvable#embedding-resolvables-in-strings)), widen the field to also accept `formae.Embedded`:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
open class Function extends formae.Resource {
    // Accepts a plain string, or a string with embedded references
    // authored with formae.embed(...)
    @formae.FieldHint {}
    functionCode: String|formae.Embedded

    @formae.FieldHint {}
    functionConfig: FunctionConfig
}
```

A user can then write `functionCode = formae.embed("const id = '\(store.res.id)'; …")`. formae pulls each embedded reference into the same dependency graph as a whole-value resolvable, resolves it at apply time, and hands your plugin the fully assembled plain string. Your plugin never sees an embed marker, exactly as with `formae.Resolvable`. Extraction is handled for you too: formae regenerates the `formae.embed(...)` call rather than the resolved value.

Widen a field to `formae.Embedded` only where embedding genuinely helps, typically free-form text the user authors (code, scripts, templates, user-data) that needs a value from another resource. For a field that is *only ever* a whole reference, `formae.Resolvable` remains the right type.

## Using Resolvables in Forma Files

Users reference resolvables through the resource's `res` property:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
import "@myPlugin/network/vpc.pkl"
import "@myPlugin/network/subnet.pkl"

local myVpc = new vpc.VPC {
    label = "main-vpc"
    cidrBlock = "10.0.0.0/16"
}

new subnet.Subnet {
    label = "public-subnet"
    vpcId = myVpc.res.id        // Reference the VPC's ID
    cidrBlock = "10.0.1.0/24"
}
```

When evaluated, the `vpcId` property contains a resolvable marker that formae resolves at apply time.

## How Resolution Works

formae uses resolvables to build a dependency graph that determines the order of operations. If a Subnet references a VPC's ID, formae knows to create the VPC first and destroy the Subnet first.

During apply, formae creates the VPC, waits for the response containing `{"VpcId": "vpc-12345"}`, then passes that resolved value to the Subnet's Create call. Your plugin never sees resolvable markers, just the final `{"VpcId": "vpc-12345", "CidrBlock": "10.0.1.0/24"}`.

Resources without dependencies on each other can be processed in parallel.

Note: Parent-child relationships (covered separately) are about *discovery* ordering. When formae discovers child resources, it automatically injects resolvables that feed into this same dependency graph.

## Multiple Resolvable Properties

A resource can expose multiple resolvable properties:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
open class BucketResolvable extends formae.Resolvable {
    hidden type = "MyPlugin::Storage::Bucket"

    hidden arn: BucketResolvable = (this) {
        property = "Arn"
    }

    hidden name: BucketResolvable = (this) {
        property = "BucketName"
    }

    hidden domainName: BucketResolvable = (this) {
        property = "DomainName"
    }
}
```

Users can then reference any of these:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
new policy.Policy {
    resource = myBucket.res.arn       // "arn:aws:s3:::my-bucket"
    bucketName = myBucket.res.name    // "my-bucket"
}
```

## Resolvables in Collections

Resolvables work within collections:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
@formae.ResourceHint {
    type = "MyPlugin::Network::SecurityGroup"
    identifier = "SecurityGroupId"
}
open class SecurityGroup extends formae.Resource {
    @formae.FieldHint {}
    vpcId: String|formae.Resolvable

    @formae.FieldHint {
        updateMethod = "EntitySet"
        indexField = "SecurityGroupId"
    }
    ingressRules: Listing<IngressRule>?
}

open class IngressRule {
    // Can reference another security group
    sourceSecurityGroupId: String|formae.Resolvable
    port: Int
}
```

## Collection Resolvables

Some resource properties resolve to collections (Mappings or Listings) rather than scalar values. For example, a Docker Compose stack exposes an `endpoints` Mapping, and an OVH database service exposes an `endpoints` Listing.

To let users reference individual items within these collections, use `MappingResolvable` or `ListingResolvable` instead of the base `Resolvable`:

### Mapping Properties

Use `MappingResolvable` when a property resolves to a key-value map:

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

open class StackResolvable extends formae.Resolvable {
    local _self = this
    hidden endpoints: formae.MappingResolvable = new formae.MappingResolvable {
        label = _self.label
        type = _self.type
        stack = _self.stack
        property = "endpoints"
    }
}
```

Users access individual values with `at(key)`:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
// Resolves to "http://localhost:3000"
url = lgtmStack.res.endpoints.at("lgtm:3000")
```

Special characters in keys (colons, dots) are escaped automatically.

### Listing Properties

Use `ListingResolvable` when a property resolves to an ordered list:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
open class ServiceResolvable extends formae.Resolvable {
    local _self = this
    hidden endpoints: formae.ListingResolvable = new formae.ListingResolvable {
        label = _self.label
        type = _self.type
        stack = _self.stack
        property = "endpoints"
    }
}
```

Users access elements with `at(index)`, which returns a `MappingResolvable` for further field navigation:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
// Access the first endpoint's URI
uri = dbService.res.endpoints.at(0).at("uri")
```

### Typed Element Resolvables

For list properties whose elements have a known structure, you can create a typed resolvable for the element. This gives users field-level access instead of the generic `at(key)`:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
/// Typed resolvable for endpoint objects in the list
open class EndpointResolvable extends formae.Resolvable {
    local _self = this
    hidden uri: formae.Resolvable = new formae.Resolvable {
        label = _self.label; type = _self.type; stack = _self.stack
        property = if (_self.property != null) _self.property + ".uri" else "uri"
    }
    hidden component: formae.Resolvable = new formae.Resolvable {
        label = _self.label; type = _self.type; stack = _self.stack
        property = if (_self.property != null) _self.property + ".component" else "component"
    }
}

/// Override at() to return the typed element
open class EndpointListingResolvable extends formae.ListingResolvable {
    local _self = this
    function at(index: Int): EndpointResolvable = new EndpointResolvable {
        label = _self.label; type = _self.type; stack = _self.stack
        property = if (_self.property != null)
            _self.property + "." + index.toString()
            else index.toString()
    }
}
```

Users get ergonomic field access:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
// With typed element resolvable
uri = dbService.res.endpoints.at(0).uri

// Without (generic fallback)
uri = dbService.res.endpoints.at(0).at("uri")
```

This pattern is opt-in. The generic `MappingResolvable.at(key)` fallback always works. Use typed element resolvables when your users frequently reference specific fields in list items.

### Return Type Guidelines

Use the correct resolvable type for each property to get compile-time safety:

| Property resolves to       | Resolvable type            | `at()` available?  |
| -------------------------- | -------------------------- | ------------------ |
| Scalar (String, Int, etc.) | `formae.Resolvable`        | No (compile error) |
| Mapping                    | `formae.MappingResolvable` | `at(key: String)`  |
| Listing                    | `formae.ListingResolvable` | `at(index: Int)`   |

## What Plugin Authors Need to Know

**You don't handle resolution.** formae's resolver handles all resolution logic. Your plugin:

1. **Defines** which properties can be referenced (via the Resolvable class)
2. **Accepts** union types for properties that might receive references
3. **Returns** property values in Read/Create responses that formae uses to resolve references

The resolution machinery is entirely in formae's core. Plugins just define the schema and implement CRUD operations.

## Summary

| Concept                     | Purpose                                     |
| --------------------------- | ------------------------------------------- |
| `XxxResolvable` class       | Declares which properties can be referenced |
| `String\|formae.Resolvable` | Accepts either literal or reference         |
| `hidden res` property       | Exposes resolvable for users                |

Start by identifying which resource properties other resources might need to reference. Those are your resolvable candidates.
