Schema Annotations
This page documents all PKL annotations used in formae resource schemas.
Overview
Annotations provide metadata about resources and their fields. The SDK reads these annotations to:
- Generate resource descriptors
- Validate user input
- Control CRUD behavior
- Enable discovery and extraction
ResourceHint
Applied to resource classes to define type metadata.
@formae.ResourceHint {
type = "MYCLOUD::Compute::Instance"
identifier = "$.InstanceId"
}
class Instance extends formae.Resource {
// ...
}
Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
type |
String |
Yes | — | Full resource type name |
identifier |
String |
Yes | — | JSONPath to native ID in API response |
parent |
String |
No | null |
Parent resource type for nested resources |
listParam |
ListProperty |
No | null |
Parent property required for List operations |
discoverable |
Boolean |
No | true |
If false, excluded from discovery |
type
Full resource type name following the pattern NAMESPACE::SERVICE::RESOURCE:
type = "AWS::EC2::Instance"
type = "AZURE::Compute::VirtualMachine"
type = "MYCLOUD::Storage::Bucket"
Naming conventions:
NAMESPACE- Your plugin's namespace (uppercase)SERVICE- Logical service grouping (e.g.,EC2,S3,IAM)RESOURCE- Resource name (PascalCase)
identifier
JSONPath expression to extract the native ID from your cloud API's response:
identifier = "$.InstanceId" // Top-level field
identifier = "$.Resource.Id" // Nested field
identifier = "$.Arn" // ARN as identifier
identifier = "$.Metadata.Guid" // Deep nested
The native ID must:
- Be unique within the resource type
- Be stable (not change over the resource's lifetime)
- Be returned by your Create operation
parent and listParam
For child resources, use parent to declare the parent resource type and listParam to specify which parent properties are needed for the List operation:
@formae.ResourceHint {
type = "MYCLOUD::Storage::Object"
identifier = "$.ObjectKey"
parent = "MYCLOUD::Storage::Bucket"
listParam = new formae.ListProperty {
parentProperty = "BucketName" // Property on the parent resource
listParameter = "Bucket" // Parameter name the List API expects
}
}
class Object extends formae.Resource {
// ...
}
This enables:
- Scoped discovery (list objects within a specific bucket)
- Correct ordering (discover parents before children)
- Automatic resolvable injection from child to parent
For resources requiring multiple parent properties:
listParam = List(
new formae.ListProperty {
parentProperty = "TableName"
listParameter = "TableName"
},
new formae.ListProperty {
parentProperty = "IndexName"
listParameter = "IndexName"
}
)
During discovery, formae reads the parent's properties and passes them to your List operation via ListRequest.ListParameters.
discoverable
Set to false to exclude from automatic discovery:
@formae.ResourceHint {
type = "MYCLOUD::Internal::TempResource"
identifier = "$.Id"
discoverable = false // Don't show in discovery
}
Use for:
- Internal/system resources users shouldn't manage
- Resources that would flood discovery results
- Temporary or ephemeral resources
FieldHint
Applied to resource properties to define field metadata.
@formae.FieldHint {
createOnly = true
description = "The image ID to launch from"
}
imageId: String
Fields
| Field | Type | Default | Description |
|---|---|---|---|
createOnly |
Boolean |
false |
Cannot be changed after creation |
writeOnly |
Boolean |
false |
Can be written but never returned by Read |
readOnly |
Boolean |
false |
Computed by the provider |
description |
String |
null |
Human-readable description |
sensitive |
Boolean |
false |
Contains sensitive data |
immutable |
Boolean |
false |
Synonym for createOnly |
createOnly
Fields that cannot be modified after the resource is created:
@formae.FieldHint { createOnly = true }
region: String
@formae.FieldHint { createOnly = true }
encryptionKeyId: String
Attempting to change a createOnly field triggers a replace operation (delete + create).
writeOnly
Fields that can be written but are never returned by the cloud provider's Read operation:
@formae.FieldHint { writeOnly = true }
password: String
@formae.FieldHint { writeOnly = true }
secretKey: String
WriteOnly fields:
- Are stored by formae but never returned by the provider
- Are always included in update patches (even if unchanged)
- Common for passwords, secrets, and sensitive configuration
This is essential for cloud APIs (like AWS CloudControl) that require certain fields in every update but never return them on read.
readOnly
Fields computed by the provider that users cannot set:
@formae.FieldHint { readOnly = true }
createdAt: String
@formae.FieldHint { readOnly = true }
arn: String
@formae.FieldHint { readOnly = true }
status: String
Read-only fields:
- Are ignored in Create/Update requests
- Are populated from Read responses
- Are included in inventory output
description
Human-readable documentation for the field:
@formae.FieldHint {
description = "The display name of the instance. Must be unique within the region."
}
name: String
Descriptions appear in:
- Generated documentation
- IDE hover tooltips
- Error messages
sensitive
Fields containing secrets or sensitive data:
@formae.FieldHint { sensitive = true }
password: String
@formae.FieldHint { sensitive = true }
apiKey: String
Sensitive fields:
- Are masked in logs and output
- Are excluded from diffs by default
- Trigger warnings if committed to version control
Type Examples
Required vs Optional
// Required - must be provided
@formae.FieldHint {}
name: String
// Optional - can be omitted
@formae.FieldHint {}
description: String?
With Defaults
@formae.FieldHint {}
instanceType: String = "small"
@formae.FieldHint {}
enabled: Boolean = true
@formae.FieldHint {}
maxRetries: Int = 3
Collections
// Map of strings
@formae.FieldHint {}
tags: Mapping<String, String>
// List of strings
@formae.FieldHint {}
securityGroups: Listing<String>
// Optional list
@formae.FieldHint {}
additionalIps: Listing<String>?
Nested Types
@formae.FieldHint {}
networkConfig: NetworkConfig?
class NetworkConfig extends formae.SubResource {
@formae.FieldHint {}
vpcId: String
@formae.FieldHint {}
subnetIds: Listing<String>
@formae.FieldHint {}
assignPublicIp: Boolean = false
}
Complete Example
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"
/// Display name for the instance.
@formae.FieldHint {
description = "Human-readable name for the instance"
}
name: String
/// Machine type determining CPU and memory.
@formae.FieldHint {
description = "Instance size: small, medium, or large"
}
instanceType: String = "small"
/// Image to launch from. Cannot be changed after creation.
@formae.FieldHint {
createOnly = true
description = "AMI or image ID"
}
imageId: String
/// Region where the instance runs.
@formae.FieldHint {
createOnly = true
description = "Cloud region (e.g., us-east-1)"
}
region: String
/// Instance ARN. Computed by the provider.
@formae.FieldHint { readOnly = true }
arn: String?
/// Current instance state.
@formae.FieldHint { readOnly = true }
status: String?
/// Tags for organization.
@formae.FieldHint {}
tags: Mapping<String, String>?
/// SSH key for access.
@formae.FieldHint { sensitive = true }
sshPrivateKey: String?
}
See Also
- Pkl cheatsheet - PKL language basics
- Tutorial: Schema - Step-by-step schema creation