URL variables reference

View as Markdown
Last updated September 14, 2026

Every variable Studio accepts inside a composition URL pattern, grouped by category. Studio's URL editor validates patterns against this set: typos and unknown variables are rejected before save.

Variable categories

CategoryFormExample
Entry field{{entry.<field>}}{{entry.title}}
Entry reference field{{entry.<reference>.<field>}}{{entry.author.name}}
Entry system field{{entry.<system_field>}}{{entry.uid}}
Taxonomy{{taxonomy:<taxonomy_uid>}}{{taxonomy:brand}}
Context (metadata){{<metadata_field>}}{{environment}}
Date pseudo{{entry_created_date:<part>}}{{entry_created_date:year}}
Legacy wildcard*/blogs/*

Every category is available on Connected templates. Sections don't render at a visitor URL, so URL variables don't apply to them.

Entry field

{{entry.<field>}}: any field declared on the content type connected to the template.

URL pattern: /blogs/{{entry.title}}
Resolves to: /blogs/AI%20101                (entry.title = "AI 101")

Spaces are URL-encoded as %20. Studio's URL editor validates that <field> exists on the connected content type's schema: typos produce a field_not_exists validation error.

Entry reference field

{{entry.<reference>.<field>}}: a field on an entry referenced by the current entry.

URL pattern: /authors/{{entry.primary_author.handle}}/posts/{{entry.title}}

primary_author must be a Reference field on the connected content type, and handle must exist on the referenced content type's schema.

For multi-reference fields (reference_to.length > 1), the sub-field is accepted if it exists on at least one of the referenced content types. The first match wins at resolve time.

References are auto-included in the SDK's useCompositionData fetch. You don't need to declare extendQuery.includeReferences for fields used in URL patterns.

Entry system fields

Properties present on every Content Management API (CMA) entry but not declared in the content type's schema. Allowed in patterns without a schema lookup:

System fieldWhat it is
{{entry.uid}}The entry's unique identifier (UID)
{{entry.created_at}}ISO timestamp when the entry was created
{{entry.updated_at}}ISO timestamp of the most recent update
{{entry.created_by}}UID of the user who created the entry
{{entry.updated_by}}UID of the user who most recently updated the entry
{{entry.locale}}The locale code the entry was fetched in
{{entry._version}}Numeric version of the entry

Taxonomy

{{taxonomy:<taxonomy_uid>}}: the value of a taxonomy assigned to the entry.

URL pattern: /{{taxonomy:industry}}/{{entry.title}}
Resolves to: /finance/quarterly-report       (entry has taxonomy industry = "finance")

Studio collects taxonomies referenced in patterns but doesn't validate the <taxonomy_uid> against the schema: taxonomy terms aren't part of the content type schema.

Context (metadata) variables

Five built-in metadata variables resolved from the request / project context, not from the entry.

VariableValue at resolve time
{{environment}}Inert: accepted by the pattern engine but resolves to nothing and repoints no fetch. Environment is set once at SDK init and applies uniformly to the whole page.
{{locale}}The selected locale code (e.g. en-us, fr-fr). Not recommended in URL patterns: carry locale via your routing layer + the SDK's locale query option instead. See Multi-locale at scale.
{{branch}}Inert: accepted by the pattern engine but resolves to nothing. Branch is set once at SDK init.
{{composition_uid}}The composition's UID
{{content_type_uid}}The connected content type's UID

In the Edit URL modal, Connected templates show Insert chips for {{environment}}, {{entry.title}}, {{entry.uid}}, {{taxonomy:brand}}, {{locale}} (the chip exists, but prefer routing-layer locale + the SDK locale query option instead of inserting it).

The chips are quick-insert shortcuts. The full set above is typeable manually: {{content_type_uid}} and other entry / taxonomy variants work the same. ({{environment}} and {{branch}} can be typed too, but are inert, see above.)

Date pseudo variables (auto-generated only)

When you configure a content type's URL pattern (/blogs/:year/:month/:title), Studio auto-generates these pseudo variables from the matching pattern:

Pattern partAuto-generated to
:year{{entry_created_date:year}}
:year_short{{entry_created_date:year_short}}
:month{{entry_created_date:month}}
:monthname{{entry_created_date:monthname}}
:monthname_short{{entry_created_date:monthname_short}}
:day{{entry_created_date:day}}

You can't type these manually in the URL editor: pasting {{entry_created_date:year}} produces a pseudo_variable_not_allowed validation error. They're an internal marker the URL engine uses. The content type's :year-style pattern is the user-facing surface.

If you want a year in your URL without using the content type's URL pattern, store the year as an entry field and use {{entry.published_year}} instead.

Legacy wildcard

*: matches exactly one path segment, captures it for routing alignment, carries no value.

URL pattern: /blogs/*
Matches:     /blogs/ai-101, /blogs/llm-economics, /blogs/agent-design

Used by legacy linked patterns from before variable-based URLs landed. The Edit URL modal warns when it detects a legacy pattern with a "This composition has a legacy URL pattern. Once changed, you cannot revert it." banner.

One-way migration. Confirming the change in the Edit URL modal upgrades the composition to a variable-based URL permanently: there is no rollback. Be sure the new pattern resolves correctly against your CT's fields before saving.

For new templates, prefer variable-based patterns (/blogs/{{entry.title}}): they carry data into the resolved URL instead of routing it.

Pick a SLUG field, not the title. {{entry.title}} renders the display-name verbatim, so "AI 101" becomes /blogs/AI%20101, which is fragile (capitals, spaces, %20-encoding, breaks on title rename). Bind the URL to your CT's slug field (commonly entry.url or entry.slug). Studio gives you /blogs/ai-101. The CT's url field is the canonical Contentstack URL slug, use it for the URL pattern, use {{entry.title}} for display only.

Where URLs come from (recap)

For a new linked template, Studio derives the URL pattern from the first available source:

  1. Custom Preview URL for the content type: strongly recommended. Configure this once per CT. tryCustomPreviewUrl() runs first.
  2. Content type URL pattern (e.g. /blogs/:title): tryContentTypeUrlPattern() runs if #1 returned null.
  3. Default fallback (/<content_type_uid>/<composition_uid>/{{entry.title}}): createDefaultLinkedUrl() runs if #2 also returned null.

For Sections (always render in canvas, no visitor URL) the derivation skips #1+#2 and uses the default pattern directly.

Two extra URL-source values exist but apply OUTSIDE this derivation flow:

  • USER_SPECIFIED_PATTERN: the author hand-edited the URL via the URL pattern editor AFTER creation
  • LEGACY_URL: the composition pre-dates the URL-source metadata and runs on the legacy URL path

Dev configures once. Authors never touch URLs. Set the Custom Preview URL on each content type when the CT is created (in Contentstack, open Content Models, select the CT, go to Settings, and set Custom Preview URL, for example /blogs/{{entry.url}} for blogs or /products/{{entry.slug}} for products). After that, every new linked template against that CT auto-derives the correct URL pattern: authors never edit URLs in Studio. Hand-editing a template's URL pattern is a smell that something upstream wasn't configured.

See Templates: Connected content type for the derivation flow.

Validation rules

Studio's URL editor blocks save on any of these:

ErrorTrigger
invalid_syntaxMalformed {{ }} braces, unclosed pairs
field_not_exists{{entry.foo}} where foo isn't on the schema
unknown_variable{{some_random_thing}} that matches no category
pseudo_variable_not_allowed{{entry_created_date:*}} typed manually
empty_patternThe pattern field is empty

Resolved-value behavior

  • Spaces in variable values are URL-encoded as %20
  • Reference variables trigger an includeReferences query addition automatically: you don't need to declare it via extendQuery
  • Taxonomy variables resolve from the entry's taxonomies.<uid> array. First value wins for multi-value taxonomies

See also