Template compositions
A template composition is a full page in Studio, addressable at a URL, one of the two composition types (the other is a section composition). It comes in two kinds:
- Linked: bound to a content type (connected_content_type set), so one template renders every entry of that type at its own URL.
- Freeform: bound to no content type (connected_content_type: ""). A single standalone page.
This page is the API reference for creating template composition entries. For the concepts and the Studio UI walkthrough, read Templates and Freeform templates. The create call, authentication, and zlib encoding are shared by all compositions (see Create compositions via the API) and the ui tree is built from the building blocks.
How to read this page
Two Template shapes, one page. Read in this order:
- Fields: the extra keys a Template carries beyond the shared composition envelope in index.md, 4.
- Data sources: which of the six value sources are available on each Template kind.
- URL metadata: every Template binds to a URL. This section shows how the URL pattern and its metadata line up.
- Example: linked template, hello-world for a Template bound to a content type. Copy, swap the CT / field names.
- Example: freeform template, hello-world for a Template with no content type. Content comes from pinned entries or queries.
If you've already read building-blocks.md once, the JSON in the examples below reads directly. If any node or binding looks unfamiliar, go back to the shared vocabulary there.
Fields
Beyond the fields common to every composition (index.md, 4), a template sets:
| Field | Linked | Freeform |
|---|---|---|
| place_composition_as | "page" | "page" |
| connected_content_type | content-type uid | "" |
| url | the resolved pattern (e.g. /blog/{{entry.url}}) | /<composition-ct-uid>/<composable_uid> |
| url_metadata | { url_source, url_queries } | { url_source, url_queries } |
A template has no linked_schemas (that field is section-only).
Data sources
All six value sources are available, with one exception: a freeform template has no template source (there is no connected entry), so its content comes from pinned entries (contentstack), queries (contentstack_queries), static values, or component defaults. See the availability matrix.
URL metadata
url is the pattern. url_metadata describes how it resolves. url_queries is a JSON string, not an object:
"url_metadata": {
"url_source": "user_specified_pattern",
"url_queries": "{\"include\":[],\"only\":{},\"where\":{}}"
}url_source is one of:
| Value | Meaning |
|---|---|
| custom_preview_url | from the content type's Custom Preview URL config |
| content_type_url_pattern | from the content type's URL pattern |
| default_url_pattern | the /<ct-uid>/<composable_uid> fallback (freeform's value) |
| user_specified_pattern | a hand-written pattern. Freeform only on the API path: on a linked template it reverts, because url_queries is generated when the UI's Edit-URL panel is saved, and Studio re-derives from the content type's pattern on editor load |
| legacy_url | a pre-variable * wildcard pattern. Never write this deliberately: it's the state a composition falls into when url_metadata is omitted |
url_metadata is required on every template composition: omitting it is not a valid "minimal envelope". The write succeeds and delivery still resolves off the url pattern alone, so the omission looks harmless, but Studio has no derivation to read, treats the composition as legacy_url, and the Edit-URL panel presents the pattern as hand-typed, losing the content-type link the first time an author edits it. Pick the source that matches how the URL is actually derived: content_type_url_pattern for a linked template on a page-type CT, default_url_pattern for a freeform identity URL, user_specified_pattern for a hand-chosen freeform path.
url_queries is a { only, where } JSON-string used by the URL-pattern resolver. Leave include as []. Reference-field resolution for both URL variables (like {{entry.author.slug}}) and template bindings is driven by data_sources[template].resolvedReferences, the composition's built-in reference-toggle metadata. Full pattern grammar: URL variables reference.
URL variables: quick reference
The most common variables in a Template's url pattern:
| Variable | Resolves to | Requires |
|---|---|---|
| {{entry.url}} | Entry's own url field (single-line text) | Nothing: CT ships with url field by default |
| {{entry.slug}} | Entry's slug field | A slug field on the CT |
| {{entry.title}} | Entry's title field | Always available |
| {{entry.<field>}} | Any single-line text field on the entry | The field exists on the CT |
| {{entry.<group>.<field>}} | A field inside a Group | Nested field access |
| {{entry.<ref>.<field>}} | A field on a referenced entry | The reference field name in data_sources[template].resolvedReferences |
| {{entry.publish_details.locale}} | Locale of the current entry publish | Always available |
Runtime-verified URL-pattern gotcha. For Studio's canvas to render, the preview-entry picker must find an entry where every variable in the pattern is populated. If your pattern references entry.url but the entry's url field is empty or set to a value the picker rejects (e.g. it starts with a leading slash and the pattern doesn't expect one), you'll see "No entry matches this URL pattern. Create an entry with these fields populated: url." Practical rules:
- Prefer using {{entry.title}} or {{entry.slug}} in patterns, since those fields are always populated. Studio URL-encodes the substitution ("Audit Test Entry" becomes Audit%20Test%20Entry).
- If you use {{entry.url}}, verify entry.url is set to the full path (e.g. /blog/audit-post) and set the Template pattern to {{entry.url}} (no prefix, no double-slash).
- Avoid combining a prefix + {{entry.url}} when entry.url itself starts with /. You'll get /blog//foo and no match.
Example: a blog Template with the URL keyed off the author's slug (a reference-field variable):
"url": "/blog/{{entry.author.slug}}/{{entry.slug}}",
"url_metadata": {
"url_source": "user_specified_pattern",
"url_queries": "{\"only\":{},\"where\":{}}"
},
// Multi-variable patterns like this one are authored in Studio's Edit-URL modal — that flow
// is what generates url_queries and makes user_specified_pattern stick on a linked template.
// Writing this shape by API alone reverts to the CT's pattern on the next editor load.
"data_sources": [
{ "uid": "template", "resolvedReferences": { "template": ["author"] } }
]data_sources[template].resolvedReferences tells the SDK to expand the author reference so entry.author.slug is available: same mechanism drives reference-field bindings inside the ui tree.
Example: linked template
Connected to blog_post: the entry's title, then its sections modular-blocks field.
{
"title": "Blog Post",
"composable_uid": "blog_post_template",
"place_composition_as": "page",
"connected_content_type": "blog_post",
"url": "/blog/{{entry.url}}",
"url_metadata": {
"url_source": "content_type_url_pattern",
"url_queries": "{\"include\":[],\"only\":{},\"where\":{}}"
},
"data_sources": "[]",
"schema_version": "1.0.0",
"ui": {
"type": "page",
"slots": {
"root": [
{
"type": "header",
"props": {
"text": {
"type": "plaintext",
"binding": { "type": "template", "value": { "path": { "title": {} } } }
}
}
},
{
"uid": "rep1",
"type": "repeater",
"metadata": { "repeaterBindingFieldType": "modular_block" },
"props": {
"items": {
"type": "array",
"binding": { "type": "template", "value": { "path": { "sections": {} } } }
},
"children": { "type": "slot", "slot": "r" }
},
"slots": {
"r": [
{
"type": "condition-block",
"metadata": {
"condition": {
"type": "modular_block",
"operator": "eq",
"value": "hero",
"conditionBinding": {
"type": "repeater",
"value": {
"repeaterUID": "rep1",
"content_type_uid": "blog_post",
"selected_field": "sections",
"path": { "hero": {} }
}
},
"dataBinding": {
"type": "repeater",
"value": { "repeaterUID": "rep1", "path": { "hero": {} } }
}
}
},
"slots": {
"c": [
{
"type": "text",
"props": {
"text": {
"type": "plaintext",
"binding": {
"type": "repeater",
"value": { "repeaterUID": "rep1", "path": { "headline": {} } }
}
}
}
}
]
}
}
]
}
}
]
}
}
}The ui is stored zlib-encoded. The tree is shown expanded for reference. See index.md, 6.
Example: freeform template
No connected entry: a pinned hero entry, a static tagline, and a query of featured posts.
{
"title": "Spring 2026 Landing",
"composable_uid": "spring_2026_landing",
"place_composition_as": "page",
"connected_content_type": "",
"url": "/compositions/spring_2026_landing",
"url_metadata": {
"url_source": "default_url_pattern",
"url_queries": "{\"include\":[],\"only\":{},\"where\":{}}"
},
"schema_version": "1.0.0",
"data_sources": [
{
"uid": "contentstack",
"data": [{ "uid": "blt_hero…", "_content_type_uid": "hero" }]
},
{
"uid": "contentstack_queries",
"data": [
{
"uid": "featured-posts",
"name": "Featured Posts",
"spec": {
"contentType": "blog_post",
"sourceType": "content_type",
"query": { "featured": true },
"params": { "limit": 3, "skip": 0, "desc": "created_at" }
}
}
]
}
],
"ui": {
"type": "page",
"slots": {
"root": [
{
"type": "header",
"props": {
"text": {
"type": "plaintext",
"binding": {
"type": "contentstack",
"value": {
"uid": "blt_hero…",
"_content_type_uid": "hero",
"path": { "headline": {} }
}
}
}
}
},
{
"type": "text",
"props": {
"text": {
"type": "plaintext",
"binding": { "type": "static_value", "value": "Ship your spring campaign in a day." }
}
}
},
{
"type": "box",
"metadata": { "repeaterWrapper": true },
"props": { "children": { "type": "slot", "slot": "b" } },
"slots": {
"b": [
{
"uid": "rep1",
"type": "repeater",
"props": {
"items": {
"type": "array",
"binding": {
"type": "contentstack_queries",
"value": { "queryUID": "featured-posts", "path": { "entries": {} } }
}
},
"children": { "type": "slot", "slot": "r" }
},
"slots": {
"r": [
{
"type": "text",
"props": {
"text": {
"type": "plaintext",
"binding": {
"type": "repeater",
"value": { "repeaterUID": "rep1", "path": { "title": {} } }
}
}
}
}
]
}
}
]
}
}
]
}
}
}For a template that embeds a section, see the Section compositions reference.