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

# PKL schema reference

> The full formae PKL schema a plugin author works with: resource and field annotations, relationship and ordering types, the value types you emit, and the schema module shape.

This page documents the complete formae PKL schema module (`@formae/formae.pkl`) as a plugin author uses it. It covers the annotations you apply to resource classes and fields, the types that describe relationships and update ordering, the value types your resources emit, and the overall shape of a schema module.

For a hands-on introduction, start with [02 - Resource schema](/plugin-development/tutorial/02-schema). For PKL language basics, see the [PKL cheatsheet](/documentation/reference/pkl-cheatsheet).

## How a schema is structured

A plugin's schema module amends nothing; it imports `@formae/formae.pkl` and defines resource classes. Each resource class extends `formae.Resource`, carries a `@formae.ResourceHint` annotation, and annotates each user-settable property with `@formae.FieldHint`:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
module mycloud

import "@formae/formae.pkl"

/// A compute instance in MyCloud.
@formae.ResourceHint {
    type = "MYCLOUD::Compute::Instance"
    identifier = "$.InstanceId"
}
class Instance extends formae.Resource {
    fixed hidden type: String = "MYCLOUD::Compute::Instance"

    @formae.FieldHint { required = true }
    name: String

    @formae.FieldHint { createOnly = true }
    imageId: String
}
```

The `Resource` base class supplies the shared attributes every managed resource carries: `label`, `group`, `target`, `stack`, and `alias` (a previous label used to rename a managed row in place). It also exposes reflection helpers (`fields()`, `hints()`, `schema()`, `type()`, `props()`) that formae calls at evaluation time to derive the resource descriptor from your annotations. You do not call these yourself.

***

## Resource annotations

### @formae.ResourceHint

Applied to a resource class to declare its type metadata. `type` and `identifier` are the only required fields; everything else has a default.

| Field          | Type                                    | Default  | Description                                                             |
| -------------- | --------------------------------------- | -------- | ----------------------------------------------------------------------- |
| `type`         | `String`                                | required | Full resource type, must match `NAMESPACE::SERVICE::RESOURCE`           |
| `identifier`   | `String`                                | required | Property (JSONPath) to store as the native ID after create              |
| `parent`       | `String?`                               | `null`   | Type of the parent resource, for nested resources                       |
| `parentRefs`   | `(ParentRef \| List<ParentRef>)?`       | `null`   | Maps parent identity properties to the child properties that carry them |
| `listParam`    | `(ListProperty \| List<ListProperty>)?` | `null`   | Parent property (or properties) required for List operations            |
| `discoverable` | `Boolean`                               | `true`   | When `false`, the resource is excluded from discovery                   |
| `extractable`  | `Boolean`                               | `true`   | When `false`, the resource is excluded from CLI extraction              |
| `portable`     | `Boolean`                               | `false`  | When `true`, the resource can be moved across targets                   |
| `docComment`   | `String?`                               | `null`   | Documentation comment carried through into the descriptor               |

There is also a `hidden outputKeyTransformation: (String) -> String` (identity by default). It lets a schema rewrite every PKL property name into the wire key your provider expects (for example, capitalizing keys). It is `hidden`, so you set it in a subclass rather than in the annotation body.

#### type

The full resource type, validated against the pattern `NAMESPACE::SERVICE::RESOURCE` (three segments separated by `::`):

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
type = "AWS::EC2::Instance"
type = "MYCLOUD::Storage::Bucket"
```

* `NAMESPACE`: your plugin's namespace (uppercase), matching the `namespace` in your [plugin manifest](/plugin-development/reference/manifest).
* `SERVICE`: a logical service grouping, for example `EC2` or `Storage`.
* `RESOURCE`: the resource name in PascalCase.

#### identifier

The property that formae stores as the resource's native ID after create. It is a JSONPath expression evaluated against the properties your plugin returns:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
identifier = "$.InstanceId"   // top-level field
identifier = "$.Resource.Id"  // nested field
identifier = "$.Arn"          // ARN as identifier
```

The native ID must be unique within the type, stable across the resource's lifetime, and present in the output of your Create operation. formae passes it to your Read, Update, and Delete operations.

#### parent, parentRefs, and listParam

These three fields describe a child resource's relationship to its parent. See [Parent and child resources](/plugin-development/concepts/parent-child-resources) for the full model.

`parent` names the parent resource type:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
parent = "MYCLOUD::Storage::Bucket"
```

`parentRefs` is the explicit way to map a parent-side identity property to the child-side property that carries it. The two names need not match. Supply one `ParentRef` or a `List` of them:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
@formae.ResourceHint {
    type = "AWS::ECS::TaskSet"
    identifier = "$.Id"
    parent = "AWS::ECS::Service"
    parentRefs = new formae.ParentRef {
        parentProperty = "ServiceName"  // property on the parent
        childProperty = "Service"       // property on this child
    }
}
```

`listParam` is the legacy way to express the same relationship for List operations, using [`ListProperty`](#listproperty). When both are present, `parentRefs` wins; when only `listParam` is present, formae derives the parent mapping from it (the child-side name comes from `listParameter`). Prefer `parentRefs` for new schemas.

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
listParam = new formae.ListProperty {
    parentProperty = "BucketName"  // property on the parent
    listParameter = "Bucket"        // parameter the List API expects
}
```

For resources that need multiple parent properties, pass a `List(...)` of either type.

#### discoverable, extractable, portable

Each is a boolean gate:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
@formae.ResourceHint {
    type = "MYCLOUD::Internal::TempResource"
    identifier = "$.Id"
    discoverable = false   // hidden from discovery
    extractable = false    // excluded from CLI extraction
    portable = true        // may be recreated on a different target
}
```

* `discoverable = false` keeps internal or high-volume resources out of discovery results.
* `extractable = false` keeps a resource out of CLI extraction output (useful for auto-generated or internal state).
* `portable = true` marks a resource as safe to recreate on a different target. When a target replace is triggered, formae proceeds only if every resource on the target is portable. Non-portable resources (the default) block a target replace so that region-bound data and region-specific configuration are never silently moved.

### @formae.SubResourceHint

Applied to a `formae.SubResource` class (a nested structured field). It carries only the `hidden outputKeyTransformation: (String) -> String` (identity by default), which rewrites nested property names on the wire the same way `ResourceHint.outputKeyTransformation` does for top-level properties. A sub-resource without this annotation renders its property names unchanged.

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
class NetworkConfig extends formae.SubResource {
    @formae.FieldHint {}
    vpcId: String

    @formae.FieldHint {}
    subnetIds: Listing<String>
}
```

Field hints on a sub-resource's properties are propagated to the descriptor under a dotted key (for example `networkConfig.vpcId`), including through `Listing<SubResource>` fields and nested sub-resources. Self-referencing sub-resource types are handled without infinite recursion.

***

## Field annotations

### @formae.FieldHint

Applied to a resource or sub-resource property. Every user-settable property needs a `@formae.FieldHint`; formae only tracks annotated properties. An empty `@formae.FieldHint {}` marks a normal, mutable, in-place-updatable field.

| Field                | Type                 | Default     | Description                                                   |
| -------------------- | -------------------- | ----------- | ------------------------------------------------------------- |
| `createOnly`         | `Boolean`            | `false`     | Changing the field forces a replace (delete + create)         |
| `writeOnly`          | `Boolean`            | `false`     | Written but never read back; stripped before drift comparison |
| `required`           | `Boolean`            | `false`     | Must be provided on every create and update                   |
| `requiredOnCreate`   | `Boolean`            | `false`     | Must be provided on create, optional on update                |
| `requiredOnUpdate`   | `Boolean`            | `false`     | Re-sent on every update even when unchanged                   |
| `hasProviderDefault` | `Boolean`            | `false`     | Provider assigns a default when the field is omitted          |
| `edgeKind`           | `EdgeKind`           | `"default"` | Dependency edge classification for ordering (see below)       |
| `attachesTo`         | `Boolean`            | `false`     | Deprecated. Superseded by `edgeKind = "attachesTo"`           |
| `indexField`         | `String?`            | `null`      | Key field for `EntitySet` collections                         |
| `updateMethod`       | `FieldUpdateMethod?` | `null`      | Collection comparison strategy                                |
| `format`             | `String`             | `""`        | Serialized-content format so content is compared, not bytes   |

There are also three output-shaping fields, all `hidden`, used when the value you render differs from what the user declares:

| Field                  | Type                 | Default       | Description                                              |
| ---------------------- | -------------------- | ------------- | -------------------------------------------------------- |
| `outputField`          | `String?`            | `null`        | Override the rendered key for this property              |
| `outputTransformation` | `((Any) -> Any)?`    | `null`        | Transform the value before it is rendered                |
| `outputCondition`      | `((Any) -> Boolean)` | `(_) -> true` | Render the transformed value only when this returns true |

<Note>
  `required` is also inferred from the property's type. A non-nullable property is treated as required even without `required = true`; a nullable property (`String?`) is optional unless you set `required = true`.
</Note>

#### createOnly

The field cannot be changed after create. Changing it forces a replace (delete + create):

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
@formae.FieldHint { createOnly = true }
region: String
```

#### writeOnly

The field can be written but is never returned by Read. It is stored by formae, stripped from read-back state before comparison, and so never produces drift. Common for passwords and secrets:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
@formae.FieldHint { writeOnly = true }
password: String
```

`writeOnly` controls read-back only. It does not, on its own, re-send the value on every update. If the provider drops a write-only value unless it is present in each update request, pair it with `requiredOnUpdate`.

#### opaque

Marks the field as a secret so the agent stores it hashed at rest. You don't set `opaque` directly; formae derives it from the field type. Type the field `(String | formae.SecretValue)` to make it opaque. `opaque` is independent of `writeOnly`, which controls read-back and drift. See [SecretValue](#secretvalue).

#### required and requiredOnCreate

`required` fields are validated before the command reaches your plugin; a missing required field fails with a validation error. `requiredOnCreate` fields must be present on create but may be omitted on update:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
@formae.FieldHint { required = true }
name: String

@formae.FieldHint { requiredOnCreate = true }
initialPassword: String?
```

#### requiredOnUpdate

The provider drops the field unless it is re-sent on every update. formae force-resends the field in each update patch even when its value has not changed. Typical for password-class fields and write-only configuration blocks that a provider replaces wholesale:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
@formae.FieldHint { writeOnly = true; requiredOnUpdate = true }
clientSecret: String?
```

#### hasProviderDefault

The provider assigns a value when the user omits the field. formae then accepts that provider value rather than generating a "remove" operation, which prevents oscillation on every reconcile:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
@formae.FieldHint { hasProviderDefault = true }
bucketEncryption: BucketEncryption?
```

When the user omits the field, formae accepts the provider's default (no patch). When the user sets it, formae applies the user's value (a patch if it differs).

#### edgeKind and attachesTo

`edgeKind` classifies the dependency edge that a cross-resource reference in this field creates, which controls create and destroy ordering. Its allowed values come from the [`EdgeKind`](#edgekind-typealias) type alias. Set it on the field that references another resource:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
@formae.FieldHint { edgeKind = "attachesTo" }
targetGroupArn: (String|formae.Resolvable)?
```

`attachesTo` is the older boolean form of the same idea (`attachesTo = true` is equivalent to `edgeKind = "attachesTo"`). It is deprecated in favor of `edgeKind`; prefer `edgeKind` in new schemas.

#### updateMethod and indexField

`updateMethod` controls how a collection field is compared between desired and actual state. Its allowed values come from the [`FieldUpdateMethod`](#fieldupdatemethod-typealias) type alias. `indexField` names the key property for `EntitySet` collections. See [Collection semantics](/plugin-development/concepts/collection-semantics) for the full model.

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
@formae.FieldHint { updateMethod = "EntitySet"; indexField = "Key" }
tags: Listing<Tag>?

@formae.FieldHint { updateMethod = "Atomic" }
policyDocument: Dynamic?
```

| Value         | Behavior                                               |
| ------------- | ------------------------------------------------------ |
| (none)        | Unordered set comparison                               |
| `"Array"`     | Position-based comparison                              |
| `"Set"`       | Explicit unordered set                                 |
| `"EntitySet"` | Key-based comparison, requires `indexField`            |
| `"Atomic"`    | Opaque value, single replace with no sub-field diffing |

#### format

Declares that a String field holds serialized structured content, so formae compares its canonical content rather than raw bytes:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
@formae.FieldHint { format = "json" }
dashboard: String
```

| Value         | Behavior                                                                                 |
| ------------- | ---------------------------------------------------------------------------------------- |
| (none / `""`) | Opaque String, compared byte for byte (default)                                          |
| `"json"`      | Parsed and canonicalized as JSON before comparison; key order and whitespace are ignored |

`"json"` is the only recognized format today. Use it when a provider re-serializes a document into an equivalent but differently formatted form, which would otherwise surface as drift on every sync.

#### Output shaping

When the key or value you render differs from what the user declares, use `outputField`, `outputTransformation`, and `outputCondition`. `outputField` overrides the rendered key; `outputTransformation` maps the value; `outputCondition` gates whether the transformed value is emitted at all:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
@formae.FieldHint {
    outputField = "Tags"
    outputTransformation = (it) -> formae.transform.capitalizeMemberKeys.apply(it)
}
tags: Listing<Tag>?
```

The `formae.transform` helper carries ready-made transforms: `capitalizeMemberKeys`, `capitalizeKeys`, and `toString`.

### @formae.ConfigFieldHint

Applied to a target Config field to declare whether it can change in place or requires a target replace. Fields without this annotation are treated as immutable.

| Field        | Type      | Default | Description                                                                                  |
| ------------ | --------- | ------- | -------------------------------------------------------------------------------------------- |
| `createOnly` | `Boolean` | `true`  | When `true`, changing the field triggers a target replace; when `false`, it updates in place |

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
open class Config {
    hidden fixed type: String = "MYCLOUD"

    /// Credential profile. Can change without recreating resources.
    @formae.ConfigFieldHint { createOnly = false }
    hidden profile: String?

    /// Deployment region. Changing it requires a full target replace.
    @formae.ConfigFieldHint { createOnly = true }
    hidden region: String

    fixed Type: String = type
    fixed Profile: String? = profile
    fixed Region: String = region
}
```

Note that `ConfigFieldHint.createOnly` defaults to `true` (the safe, immutable default), which is the opposite of `FieldHint.createOnly`. When a target Config carries `ConfigFieldHint` annotations, formae auto-generates a [`ConfigSchema`](#configschema) on the target output describing each field's mutability. See [Replacing a target](/documentation/concepts/target#replacing-a-target) for the user-facing behavior.

***

## Relationships and ordering

### EdgeKind (typealias)

`EdgeKind` classifies the dependency edge a cross-resource reference creates, which drives create and destroy ordering. Its three allowed values:

| Value                 | Meaning                                                                                                                                    | Ordering                                                                                                               |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| `"default"`           | Construction data flow. The default for unannotated reference fields.                                                                      | On destroy: consumer first, producer second.                                                                           |
| `"attachesTo"`        | Reachability. The consumer is reachable through the producer at runtime (for example an ECS Service through an ALB Listener).              | On destroy: producer first, consumer second.                                                                           |
| `"runtimeDependency"` | Containment. The consumer uses the producer and its containment children at runtime, even when those children are not directly referenced. | On create: producer first, then each child, then consumer. On destroy: consumer first, then each child, then producer. |

Set it through `@formae.FieldHint { edgeKind = "..." }` on the referencing field.

### ParentRef

Maps a parent-side identity property to the child-side property that carries its value. The two names need not match. Used in `ResourceHint.parentRefs`.

| Field            | Type     | Description                                             |
| ---------------- | -------- | ------------------------------------------------------- |
| `parentProperty` | `String` | Property on the parent resource this mapping references |
| `childProperty`  | `String` | Property on this child that holds the parent's value    |

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
new formae.ParentRef {
    parentProperty = "ServiceName"
    childProperty = "Service"
}
```

### ListProperty

The legacy relationship type used in `ResourceHint.listParam`. It names the parent property and the List API parameter that carries the parent's value on the child.

| Field            | Type     | Description                                                          |
| ---------------- | -------- | -------------------------------------------------------------------- |
| `parentProperty` | `String` | Name of the property on the parent resource                          |
| `listParameter`  | `String` | Name of the parameter the List API accepts to query nested resources |

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
new formae.ListProperty {
    parentProperty = "BucketName"
    listParameter = "Bucket"
}
```

When formae derives a parent mapping from `listParam`, the child-side property name is taken from `listParameter`.

### FieldUpdateMethod (typealias)

The set of collection comparison strategies accepted by `FieldHint.updateMethod`:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
typealias FieldUpdateMethod = "Array" | "Atomic" | "EntitySet" | "Set"
```

See [updateMethod](#updatemethod-and-indexfield) above and [Collection semantics](/plugin-development/concepts/collection-semantics).

***

## Value types you emit

These are the types a resource's properties can carry beyond plain scalars, lists, and maps.

### Value

`Value` wraps a String property with visibility and update-strategy metadata. It has two dimensions:

| Field        | Type                    | Default    | Description                                               |
| ------------ | ----------------------- | ---------- | --------------------------------------------------------- |
| `value`      | `String`                | required   | The underlying string value                               |
| `visibility` | `"Clear" \| "Opaque"`   | `"Clear"`  | `Opaque` marks the value as secret                        |
| `strategy`   | `"Update" \| "SetOnce"` | `"Update"` | `SetOnce` writes the value on create and never updates it |

Construct one with the `formae.value(...)` function, then optionally chain the `.opaque` or `.setOnce` helpers:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
// A clear, updatable value
someField = formae.value("hello")

// A secret that is set once and never updated
apiKey = formae.value("s3cr3t").opaque.setOnce
```

<Note>
  You do not handle `Value` specially in your plugin code. formae core strips the value, visibility, and strategy metadata before your plugin sees properties, and adds it back on return. Your plugin receives and returns plain JSON. You would only see `Value` metadata when debugging raw properties.
</Note>

See the user-facing [Values](/documentation/concepts/values) concept for how end users work with these.

### SecretValue

`SecretValue` is an opaque variant of `Value` for secret fields. Type a property `(String | formae.SecretValue)` (nullable and union variants included), and formae derives the field's `opaque` hint from the type: the agent stores the value as a one-way hash wherever it persists state (apply, sync, discovery, drift, and extract) and keeps it out of logs. The user still supplies a plain string; the field is opaque whatever value they set.

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
@formae.FieldHint {}
masterPassword: (String|formae.SecretValue)?
```

This is the schema-side equivalent of `formae.value(...).opaque`. Chain `.setOnce` for a secret that is written on create and never updated. Being opaque is independent of `writeOnly`, which controls read-back and drift; a secret the provider never returns is typically both.

### Prop

`Prop` reads a value from a CLI flag (`prop:<flag>`), falling back to a default. It infers its type from the default's type (`String`, `Boolean`, `Int`, or `Float`).

| Field     | Type                             | Default | Description                        |
| --------- | -------------------------------- | ------- | ---------------------------------- |
| `flag`    | `String?`                        | `null`  | The `prop:` flag name to read      |
| `default` | `(String\|Boolean\|Int\|Float)?` | `null`  | Value used when the flag is absent |

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
hidden replicas = new formae.Prop {
    flag = "replicas"
    default = 3
}
```

The related module-level helpers are `formae.flag(key)` (reads a `prop:` value or returns `null`), `formae.cmd()`, and `formae.mode()`.

### Tag

<Warning>
  `formae.Tag` is deprecated and will be removed in a future version. AWS resources should use `aws.Tag` from `@aws/aws.pkl`. It is documented here only because it still exists in the schema.
</Warning>

A simple key/value pair with fields `key: String` and `value: Any`, rendered as `Key` and `Value`.

### Resolvables and embedded values

Reference fields that point at another resource's output use the `Resolvable` family (`Resolvable`, `MappingResolvable`, `ListingResolvable`, and the `StackResolvable` / `TargetResolvable` / `PolicyResolvable` variants). A `Resolvable` carries `label`, `type`, `stack`, `property`, and a `visibility` of `"Clear"` or `"Opaque"` (with an `.opaque` helper). `MappingResolvable.at(key)` and `ListingResolvable.at(index)` produce a resolvable that points at a nested element of the resolved value.

A String field that embeds a resolvable inside literal text uses `formae.embed(...)`, which produces an `Embedded` value the agent substitutes at resolve time before shipping a plain String to your plugin.

For how to declare a resolvable class, expose properties through `hidden res`, and accept references on your fields, see the plugin [Resolvables](/plugin-development/concepts/resolvables) and [Embedded types](/plugin-development/concepts/embedded-types) pages. The user-facing [Resolvable](/documentation/concepts/resolvable) concept covers how end users reference them in a forma.

***

## Schema module shape

You rarely reference these output classes directly; formae derives them from your annotations at evaluation time. They are documented so you can read evaluated output and understand what your annotations produce.

### Schema

`Schema` is the resource descriptor formae builds for each resource class from its `ResourceHint` and the `FieldHint`s on its properties.

| Field            | Type                          | Default          | Source                                                   |
| ---------------- | ----------------------------- | ---------------- | -------------------------------------------------------- |
| `Identifier`     | `String`                      | required         | `ResourceHint.identifier`                                |
| `Fields`         | `Listing<String>`             | required         | All non-hidden annotated property names                  |
| `Hints`          | `Mapping<String, FieldHint>?` | `null`           | Per-field hints, including dotted keys for sub-resources |
| `Discoverable`   | `Boolean`                     | `true`           | `ResourceHint.discoverable`                              |
| `Extractable`    | `Boolean`                     | `true`           | `ResourceHint.extractable`                               |
| `Portable`       | `Boolean`                     | `false`          | `ResourceHint.portable`                                  |
| `Parent`         | `String`                      | `""`             | `ResourceHint.parent` (empty when none)                  |
| `ParentMappings` | `Listing<ParentRef>`          | `new Listing {}` | Derived from `parentRefs`, falling back to `listParam`   |

`Schema` also exposes `query(prop)`, which returns the field names whose `FieldHint` has a given boolean property set (for example the createOnly fields).

### ConfigSchema

`ConfigSchema` describes the per-field mutability of a target's Config. It holds a single `Hints: Mapping<String, ConfigFieldHint>?`. formae generates it automatically from the `@formae.ConfigFieldHint` annotations on a target's Config class; you do not construct it by hand.

***

## See also

* [02 - Resource schema](/plugin-development/tutorial/02-schema) - hands-on schema walkthrough
* [Parent and child resources](/plugin-development/concepts/parent-child-resources) - `parent`, `parentRefs`, `listParam`
* [Collection semantics](/plugin-development/concepts/collection-semantics) - `updateMethod` and `indexField`
* [Resolvables](/plugin-development/concepts/resolvables) and [Embedded types](/plugin-development/concepts/embedded-types)
* [Plugin manifest reference](/plugin-development/reference/manifest) - `namespace` and plugin metadata
* [Plugin interface reference](/plugin-development/reference/plugin-interface) - the CRUD operations your schema drives
* [PKL cheatsheet](/documentation/reference/pkl-cheatsheet) - PKL language basics
* [Values](/documentation/concepts/values) and [Resolvable](/documentation/concepts/resolvable) - user-facing concepts
