Giovanni Furetti
11/10/2025, 9:50 PMMarvin
11/10/2025, 9:50 PMMarvin
11/10/2025, 9:52 PMslack-webhook), name, description, and metadata.
- Cardinality: One Block Type can have many Block Schemas and many Block Documents (indirectly).
- Example: “Slack Webhook” is a Block Type.
- Block Schema
- What it is: The versioned validation schema for a Block Type at a specific point in time (generated from the block class fields).
- Key traits: Includes field definitions and capabilities, tracked by a checksum (not semantic version). A new schema is registered whenever the block class’ fields change.
- Cardinality: One Block Type → many Block Schemas.
- Example: The SlackWebhook fields as of today (just url) vs. a future version that adds channel. Each produces a different schema checksum.
- Block Document
- What it is: A saved, configured instance of a block that stores actual values.
- Key traits: Has a user-given name and data; references exactly one Block Schema (via block_schema_id) for validation and deserialization.
- Cardinality: One Block Schema → many Block Documents.
- Example: A saved Slack Webhook with your org’s webhook URL named prod-alerts.
How they relate
- BlockType → BlockSchema(s) → BlockDocument(s)
- BlockDocument references a specific BlockSchema; BlockSchema references a BlockType.
- Versioning: When a block class changes, a new BlockSchema (new checksum) is created. Existing Block Documents continue to reference their original schema for backwards compatibility.
Quick example
from prefect.blocks.notifications import SlackWebhook
# Create a configured instance (Block Document)
slack = SlackWebhook(url="<https://hooks.slack.com/services/XXX/YYY/ZZZ>")
slack.save("prod-alerts") # creates a Block Document tied to the current Block Schema
# Later, load and use the same document
loaded = SlackWebhook.load("prod-alerts")
await loaded.notify("Flow succeeded!")
Useful links
- Concepts overview: Blocks in Prefect 3
- API refs:
- Block Types: Read Block Type
- Block Schemas: Read Block Schema
- Block Documents: Read Block Document
If you want, I can show how schema changes impact existing documents or how to migrate documents when a block’s fields change.Giovanni Furetti
11/10/2025, 9:55 PMMarvin
11/10/2025, 9:55 PMMarvin
11/10/2025, 10:01 PMimport asyncio
from prefect.client.orchestration import get_client
async def main():
async with get_client() as client:
# 1) Look up the Block Type by slug (e.g., "slack-webhook")
block_type = await client.request(
"GET", "/block-types/{slug}", path_params={"slug": "slack-webhook"}
)
block_type_id = block_type["id"]
# 2) List block schemas for that type
# You can add limit/offset or additional filters (e.g., capabilities) as needed
schemas = await client.request(
"POST",
"/block-schemas",
json={
"block_schemas": {
"block_type_id": {"any_": [block_type_id]},
# optional examples:
# "capabilities": {"all_": ["read-path"]},
# "version": {"equals_": 1},
}
},
params={"limit": 200, "offset": 0},
)
print(f"Found {len(schemas)} schemas")
for s in schemas:
print(s["id"], s["checksum"], s.get("capabilities"))
asyncio.run(main())
2) REST API (cURL)
- 1) Get the Block Type by slug (replace the URL and auth for your environment):
curl -s \
-H "Authorization: Bearer $PREFECT_API_KEY" \
"$PREFECT_API_URL/api/block-types/slack-webhook"
- 2) Use the returned id to list schemas for that type:
curl -s -X POST \
-H "Authorization: Bearer $PREFECT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"block_schemas": {
"block_type_id": { "any_": ["<BLOCK_TYPE_ID>"] }
}
}' \
"$PREFECT_API_URL/api/block-schemas?limit=200&offset=0"
Docs for these endpoints:
- Block Type by slug: Read Block Type by slug
- List Block Schemas (with filters): Read Block Schemas
3) CLI
- To inspect a block type (helpful for the slug and high-level info):
prefect block type inspect <slug>
- To list all block types (to find the slug):
prefect block type ls
Note: The CLI does not currently provide a command to list all schemas for a type; use the API/Python approach above to enumerate schemas.
If you tell me the block type slug you’re targeting, I can paste a ready-to-run snippet with that slug filled in.Giovanni Furetti
11/10/2025, 10:10 PM@Marvin If I want to create a new block document via API, do I necessarily need both a block type and a block schema?
Can’t it automatically take the latest one based on the block type?
Marvin
11/10/2025, 10:10 PMMarvin
11/10/2025, 10:13 PMblock_type_id and a block_schema_id to create a block document. The server does not automatically choose the “latest” schema for a type.
Why: Prefect allows multiple schemas per block type over time. To preserve compatibility, the API requires you to be explicit about which schema a document targets.
Two good ways to proceed
- Preferred (Python SDK): Let the block class handle it
- Using the Python block class automatically registers the type/schema (if needed) and saves the document against the “current” schema for that class:
from prefect.blocks.notifications import SlackWebhook
slack = SlackWebhook(url="<https://hooks.slack.com/services/XXX/YYY/ZZZ>")
slack.save("prod-alerts") # Creates a block document with the current schema
- API-only: Resolve type → pick a schema → create the document
1) Get the block type ID by slug:
curl -s \
-H "Authorization: Bearer $PREFECT_API_KEY" \
"$PREFECT_API_URL/api/block-types/slack-webhook"
2) List schemas for that type and choose one (e.g., the most recently updated):
curl -s -X POST \
-H "Authorization: Bearer $PREFECT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"block_schemas": {
"block_type_id": { "any_": ["<BLOCK_TYPE_ID>"] }
}
}' \
"$PREFECT_API_URL/api/block-schemas?limit=200&offset=0"
3) Create the block document with both IDs:
curl -s -X POST \
-H "Authorization: Bearer $PREFECT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "prod-alerts",
"data": { "url": "<https://hooks.slack.com/services/XXX/YYY/ZZZ>" },
"block_type_id": "<BLOCK_TYPE_ID>",
"block_schema_id": "<BLOCK_SCHEMA_ID>",
"is_anonymous": false
}' \
"$PREFECT_API_URL/api/block-documents"
Python (async) example that picks the most recently updated schema
import asyncio
from prefect.client.orchestration import get_client
async def main():
async with get_client() as client:
bt = await client.request("GET", "/block-types/{slug}", path_params={"slug": "slack-webhook"})
block_type_id = bt["id"]
schemas = await client.request(
"POST",
"/block-schemas",
json={"block_schemas": {"block_type_id": {"any_": [block_type_id]}}},
params={"limit": 200},
)
# Choose the most recently updated (fallback to created)
def ts(s): return s.get("updated") or s.get("created") or ""
latest = max(schemas, key=ts)
created = await client.request(
"POST",
"/block-documents",
json={
"name": "prod-alerts",
"data": {"url": "<https://hooks.slack.com/services/XXX/YYY/ZZZ>"},
"block_type_id": block_type_id,
"block_schema_id": latest["id"],
"is_anonymous": False,
},
)
print("Created:", created["id"])
asyncio.run(main())
Docs
- Create block document: POST /block-documents
- Get block type by slug: GET /block-types/{slug}
- List block schemas: POST /block-schemas
If you share the block type slug you’re using, I can tailor a ready-to-run snippet.Giovanni Furetti
11/10/2025, 10:15 PMMarvin
11/10/2025, 10:15 PMMarvin
11/10/2025, 10:23 PMdata payload — not the child’s raw data.
What to send in the parent’s data
- For any field that is typed as a Block, set that field to a reference object:
- By ID:
{
"block_document_id": "<uuid>"
}
- Or by slug (type/name):
{
"block_document_slug": "<block-type-slug>/<block-name>"
}
End-to-end flow (API-only)
1) Ensure the child block document already exists (create it first if needed).
2) In the parent’s create call (POST /block-documents), include those reference objects in data for the fields that are blocks.
3) The server validates that the referenced document exists and is compatible with the parent’s schema.
Example payload (parent referencing a credentials block)
POST $PREFECT_API_URL/api/block-documents
Authorization: Bearer $PREFECT_API_KEY
Content-Type: application/json
{
"name": "my-parent",
"data": {
"credentials": { "block_document_slug": "aws-credentials/prod" },
"bucket": "my-bucket",
"region": "us-east-1"
},
"block_type_id": "<PARENT_BLOCK_TYPE_ID>",
"block_schema_id": "<PARENT_BLOCK_SCHEMA_ID>",
"is_anonymous": false
}
Alternative: reference by ID
"credentials": { "block_document_id": "00000000-0000-0000-0000-000000000000" }
Retrieving IDs to build references
- Get a block type by slug: GET /block-types/{slug}
- Get a block document by name for a type (returns the document with its ID): GET /block-types/{slug}/documents/{name}
Notes and gotchas
- Create children first: The API does not auto-create nested child documents when you create the parent.
- Schema compatibility: The referenced child must match the field’s expected Block Type; otherwise you’ll get a validation error.
- Depth: You can nest blocks multiple levels; each nested block field uses the same reference-object format.
- Reading back: Responses include data with those reference objects, and the server maintains internal block_document_references metadata.
- Python SDK convenience: If you construct a parent block in Python and pass actual Block instances for nested fields, parent.save(...) will serialize those nested blocks to these reference objects automatically.
Docs
- Create block document: POST /block-documents
- Update block document data: PATCH /block-documents/{id}/data
- Blocks concepts: Blocks in Prefect 3Giovanni Furetti
11/11/2025, 6:34 AMdata payload?Marvin
11/11/2025, 6:34 AMMarvin
11/11/2025, 6:42 AMdata payload. There isn’t a single standalone page that documents the exact JSON shape, but it’s covered in a few places:
- Blocks concepts overview (how blocks are saved/loaded and referenced): Blocks concepts
- REST API for Block Documents (shows the data field and block_document_references): Read Block Document by ID
- Custom Blocks guide (mentions composing/nesting blocks): Custom blocks
What it looks like in practice
- In the parent’s data, any field that holds another Block is stored as a reference object rather than inlined:
- By ID: {"block_document_id": "<uuid>"}
- Or by slug: {"block_document_slug": "<block-type-slug>/<block-name>"}
- The API response also includes block_document_references which tracks referenced child documents.
Example (illustrative):
{
"id": "a6d2…",
"name": "parent",
"block_type_id": "…",
"block_schema_id": "…",
"data": {
"child": {
"block_document_id": "9b1d…"
},
"other_child": {
"block_document_slug": "s3-bucket/my-bucket-creds"
}
},
"block_document_references": {
"child": "9b1d…",
"other_child": "…"
}
}
If you want, I can pull a live example from your workspace (e.g., given a parent block slug) to show the exact payload you’ll see from the REST API.