Config Basics
Config classes inherit fromConfig, which extends Pydantic’s BaseModel with extra="forbid" to catch typos. Every field is declared with one of the SDK’s field types, most often Field[T]:
- Unknown fields raise
ValidationError(catches typos in YAML) - Type coercion is automatic (string
"10"becomes int10) - Validation constraints go inside the field type; see Validation Patterns
Outputs Basics
Outputs classes inherit fromOutputs with the same extra="forbid" setting. Output fields use plain types:
on_observe, on_create, and on_update. on_observe returns the same outputs the write path returned for the object’s current state, so every field is derived from identity or config, or read back from the external object. Values that change without a platform write belong in health(), not in outputs. Other resources can reference these fields through the dependency system.
Field Types
Required Fields
Fields without defaults are required:Optional Fields with Defaults
Provide sensible defaults for common cases, and name each default in the docstring:Optional Fields That May Be Absent
Use aNone default for truly optional fields:
FieldReference for Dynamic Values
Field[T] accepts either a direct value or a reference to another resource’s output:
How FieldReference Works
When the runtime processes a resource, it resolves allFieldReference values before calling your lifecycle methods. By the time on_create runs, self.config.database_url contains the actual string value, not the reference. A reference also orders the work: the platform runs the resource only once the referenced resource is ready.
You don’t need to handle
FieldReference resolution in your code. The runtime resolves all references to their actual values before invoking lifecycle methods.Dependency for Whole-Resource Access
When you need access to an entire resource (its config, outputs, and methods) rather than just a single field, useDependency[T]:
Resolving Dependencies
In your lifecycle methods, callresolve() to get the typed resource instance:
The platform runs the handler only once the dependency is ready, and resolves it first.
resolve() returns that pre-resolved instance, and raises RuntimeError when the dependency was not resolved. Never wait for a dependency inside a handler.YAML Syntax
Users specify whole-resource dependencies without afield key:
Dependency vs FieldReference
A config can hold both: a
Field[str] for the one connection string, and a Dependency[T] for the whole database:
field key and yields a single value:
Validation Patterns
AField[T] also accepts a field reference, so a constraint applied to the whole field would also run against the reference. Put constraints inside the type parameter instead, where they apply only to direct values. Before any handler runs, the runtime validates the resolved values against the same class, so a referenced value is checked too.
Field Constraints
Wrap the value type inAnnotated with Pydantic’s Field or StringConstraints:
Field Validators
Validate individual values withAfterValidator inside the type parameter, so references pass through untouched:
Model Validators
Validate relationships between fields. Skip the check while either value is still a reference; it runs again when the runtime validates the resolved values:Type Coercion
Pydantic automatically coerces compatible types:size_gb=100 and enabled=True. For strict typing without coercion, use a strict type inside the parameter; count below rejects the string "10":
Output Design Best Practices
Expose What Dependents Need
Think about what downstream resources will need: connection details for applications, the identifier for management operations, and the resource path for cloud operations:Use Consistent Naming
Follow these conventions:Keep Outputs Stable
Output field names are part of your API. Renaming one, sayresource_name to secret_name, breaks every resource that references it. Expose only what dependents use, never internal state such as a raw API response, because a field you remove later breaks its references too.
Include Sufficient Context
Provide enough information for dependents to work without additional API calls. A baresecret_id makes every dependent rebuild the full path; full paths let them use the secret directly:
Complete Example
Here’s a well-designed Config and Outputs pair:What’s Next
Lifecycle Methods
Implement on_observe, on_create, on_update, and on_delete handlers.
Building Providers Overview
Full guide to creating and deploying providers.