* feat: guide the AI when no resource type fits a service Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: report a failed resource type search and model a synced instance in evals Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: keep named resource types first and list only related names on search failure Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: point the sync note at the hub sync button only Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor: list the instance's resource types when the search finds nothing Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: let the user create a resource type instead of syncing the hub Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: make the linked-variable resource the default, not the only option Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: add a cli eval for a service without a hub resource type Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: keep the resource type search fresh and serve it without embeddings Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: let the global chat create a resource type after the user confirms it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: simulate create_resource_type in global evals and fix the in-house API checklists Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: reuse unchanged custom resource type vectors and match names case-insensitively Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: add the unsynced note whatever the resource type search returns Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: carry the no-type rule into flow inputs and script parameters, and ask the chat to relay the sync Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: tell script chat to hand the user the schema it cannot create Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: skip the resource type fingerprint query when no index is loaded Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: index resource types locally when the hub's embeddings cannot be fetched Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: match short service names in the resource type text fallback Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: name custom resource types with the c_ prefix the Resources page uses Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: require the c_ prefix in in-house API evals and a real sync mention Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: cover workspace scoping of the resource type text search Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: say the form adds the c_ prefix unless an admin turns it off Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: refresh resource type names and the resources tab after the chat creates a type Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf: fingerprint resource types without sorting every schema Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: tell the global chat in each message when the hub's types are not synced Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: send users who cannot deploy to an admin for a resource type, and have the cli list types first Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015YR13nJWziUSWktZwhC7dx * refactor: move the resource type search index changes to their own pull request Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015YR13nJWziUSWktZwhC7dx --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
7.9 KiB
Windmill Resources
Resources store credentials and configuration for external services.
File Format
Resource files use the pattern: {path}.resource.json
Example: f/databases/postgres_prod.resource.json
Resource Structure
{
"value": {
"host": "db.example.com",
"port": 5432,
"user": "admin",
"password": "$var:g/all/db_password",
"dbname": "production"
},
"description": "Production PostgreSQL database",
"resource_type": "postgresql"
}
Required Fields
value- Object containing the resource configurationresource_type- Name of the resource type (e.g., "postgresql", "slack")
Variable References
Reference variables in resource values:
{
"value": {
"api_key": "$var:g/all/api_key",
"secret": "$var:u/admin/secret"
}
}
Reference formats:
$var:g/all/name- Global variable$var:u/username/name- User variable$var:f/folder/name- Folder variable
Secrets
Never put a secret (password, API key, token) inline in a resource value. Store it in a secret variable and reference that variable as $var:<path>.
$var:<path>is a reference, not a value: it resolves to the variable's value at run time. Never invent a value for a variable.- A resource that references a variable needs the variable to exist first, so create or deploy the variable before the resource.
- A secret's plaintext never goes in a file of the repo: a
.variable.yamlholding it would be committed. Ask the user to create the secret on the workspace instead, e.g.wmill variable add '<value>' <path>(a secret by default), then reference it by path.
Resource References
Reference other resources:
{
"value": {
"database": "$res:f/databases/postgres"
}
}
Passing a Resource or Variable as a Run Argument
A script or flow argument typed as a resource (schema format: resource-<type>) is passed as
the bare string $res:<path> — the whole argument value. Same for a variable, with
$var:<path>. This applies everywhere job arguments are supplied: wmill script run/preview,
wmill flow run/preview, the runScriptByPath / runFlowByPath API, a schedule's args, a
trigger's configured static args.
{
"db": "$res:f/databases/postgres_prod",
"api_token": "$var:g/all/api_token"
}
The reference is resolved when the job runs, under the job's run-as identity — the caller for an ordinary run, but the configured principal for a schedule, a trigger, or a runnable set to run on behalf of someone else. The run fails if that identity cannot read the referenced resource or variable.
Never wrap it in an object. The resolver only rewrites a JSON value that is a string
starting with $res: / $var:; keys are never inspected. These are all wrong and are passed
through to the script unchanged:
{ "db": { "$res": "f/databases/postgres_prod" } }
{ "db": { "resource": "f/databases/postgres_prod" } }
{ "db": "f/databases/postgres_prod" }
The string may sit anywhere a string can — a top-level argument, a nested object field
({ "gh_auth": { "token": "$var:g/all/gh_token" } }), or an array element (array elements are
walked only while nested at most two levels deep, and only for arrays of at most 1000 items).
The prefix must be on the string itself.
Common Resource Types
PostgreSQL
{
"resource_type": "postgresql",
"value": {
"host": "localhost",
"port": 5432,
"user": "postgres",
"password": "$var:g/all/pg_password",
"dbname": "windmill",
"sslmode": "prefer"
}
}
MySQL
{
"resource_type": "mysql",
"value": {
"host": "localhost",
"port": 3306,
"user": "root",
"password": "$var:g/all/mysql_password",
"database": "myapp"
}
}
Slack
{
"resource_type": "slack",
"value": {
"token": "$var:g/all/slack_token"
}
}
AWS S3
{
"resource_type": "s3",
"value": {
"bucket": "my-bucket",
"region": "us-east-1",
"accessKeyId": "$var:g/all/aws_access_key",
"secretAccessKey": "$var:g/all/aws_secret_key"
}
}
HTTP/API
{
"resource_type": "http",
"value": {
"baseUrl": "https://api.example.com",
"headers": {
"Authorization": "Bearer $var:g/all/api_token"
}
}
}
Kafka
{
"resource_type": "kafka",
"value": {
"brokers": "broker1:9092,broker2:9092",
"sasl_mechanism": "PLAIN",
"security_protocol": "SASL_SSL",
"username": "$var:g/all/kafka_user",
"password": "$var:g/all/kafka_password"
}
}
NATS
{
"resource_type": "nats",
"value": {
"servers": ["nats://localhost:4222"],
"user": "$var:g/all/nats_user",
"password": "$var:g/all/nats_password"
}
}
MQTT
{
"resource_type": "mqtt",
"value": {
"host": "mqtt.example.com",
"port": 8883,
"username": "$var:g/all/mqtt_user",
"password": "$var:g/all/mqtt_password",
"tls": true
}
}
When No Resource Type Fits
The Windmill Hub publishes resource types for most services, and an instance gets them by syncing with the hub. When none fits the service:
- If the instance is reported as not having synced the hub's types, mention that a superadmin can sync them: the hub most likely has one for the service. That holds for a type a hub script takes too: it is missing here until the sync, so say so before asking for a resource of it. Create one of your own only if the user still wants it, since after a sync the workspace would hold both.
- Otherwise, or when the user prefers it to syncing, propose a custom resource type named
c_<name>holding the service's connection fields (base URL, account or region identifiers, credentials), as in "Custom Resource Types" below, and ask the user before creating it: every resource of that type and every script taking one depends on its fields. - A plain variable instead of a resource is fine when the user asks for it; without that, default to the resource, its secret in a variable it references.
Custom Resource Types
A custom type's name starts with c_: the hub's types reach every instance under their plain names, so the prefix keeps a custom type from clashing with one synced later. Scripts and flows refer to the full name: format: resource-c_custom_api, RT.CCustomApi in TypeScript, a c_custom_api TypedDict in Python.
Create custom resource types with JSON Schema:
{
"name": "c_custom_api",
"schema": {
"type": "object",
"properties": {
"base_url": {"type": "string", "format": "uri"},
"api_key": {"type": "string"},
"timeout": {"type": "integer", "default": 30}
},
"required": ["base_url", "api_key"]
},
"description": "Custom API connection"
}
Save as: c_custom_api.resource-type.json
OAuth Resources
OAuth resources are managed through the Windmill UI and marked:
{
"is_oauth": true,
"account": 123
}
OAuth tokens are automatically refreshed by Windmill.
Using Resources in Scripts
TypeScript (Bun/Deno)
export async function main(db: RT.Postgresql) {
// db contains the resource values
const { host, port, user, password, dbname } = db;
}
Python
class postgresql(TypedDict):
host: str
port: int
user: str
password: str
dbname: str
def main(db: postgresql):
# db contains the resource values
pass
CLI Commands
# List resources
wmill resource list
# List resource types with schemas
wmill resource-type list --schema
# Get specific resource type schema
wmill resource-type get postgresql
# Deploy resources to the workspace — destructive to remote state, so only run when
# the user explicitly asks to deploy/publish/push. Depending on how the repo is wired,
# deploy via `git push` or `wmill sync push` (see the Deploying section in AGENTS.wmill.md).
wmill sync push