# Remote Fetch
Explains the remote_fetch Liquid tag: what it is, how to use it, all supported options, and how errors are handled.
What is remote_fetch?
remote_fetch
is a custom Liquid inline tag that performs an HTTP request at render time and exposes the parsed result under an alias of your choice. It’s built for templated content that needs fresh, per‑user data (e.g., weather, inventory, personalization).
Highlights:
Simple: fetch a URL and use its result in the template.
Safe: on error, it soft‑fails by default.
Composable: you can chain multiple fetches (e.g., use output from the first in the second).
Flexible: supports headers, timeouts, response size caps.
Basic syntax
{% remote_fetch 'https://api.example.com/users/{{profile.user_id}}' alias:'data' %}
Hello {{ data.first_name }}
Key parts:
URL (required): quoted string; can include Liquid interpolation from the user's profile.
alias: the variable name under which the parsed response is available. defaults to
response
Inside the tag, render whatever you want; the alias is available in scope.
Supported options
alias
The variable name bound to the parsed value.
Default:
response
timeout_ms
:Per‑request read/connect timeout.
Default: 750ms
max_bytes
:Hard cap on response size in bytes. Protects from unexpectedly large payloads.
Default: 1MB
headers
:Quoted JSON object. Header values may include Liquid interpolation.
Header names are handled in a case‑insensitive manner.
Example:
'{ "Header-Name": "value", "X-Foo": "bar-{{macro}}" }'
URL interpolation
You can embed Liquid expressions in the URL:
{% remote_fetch 'https://api.example.com/users/{{profile.user_id}}?lang={{profile.locale}}' alias:'user' %}
These are resolved before the request is made.
Request Chaining
The recommended, easy‑to‑reason‑about approach:
Fetch the first URL and set alias A.
If A exists, fetch the second URL and set alias B.
At the end, if A and B exist, render success; otherwise render a single fallback.
Example:
For a given article URL, show a “Read now” CTA if allowed; otherwise “Subscribe to read”.
{% remote_fetch 'https://cms.example.com/resolve?path={{landing_url}}' alias:'doc' %}
{% if doc %}
{% remote_fetch 'https://paywall.example.com/entitlements?user_id={{profile.user_id}}&doc_id={{landing_url}}' alias:'ent' %}
{% endif %}
{% if doc and ent and ent.allowed %}
Read now
{% else %}
Subscribe to read
{% endif %}
Headers
Add custom headers via the headers option as a quoted JSON object:
{% remote_fetch 'https://api.example.com/secure'
headers:'{"Authorization":"Bearer {{secrets.api_key.token}}"}',
alias:'data' %}
Values are interpolated first, then sent.
Keep plain-text secrets out of templates where possible; prefer managed secrets embedded within the template as outlined in the Secrets documentation.
Secrets
Summary
Secrets let you reference sensitive values (like API keys or tokens) in templates without hardcoding them.
They are injected into the template context under the
secrets
object.You can reference simple and structured secrets, use them in remote_fetch URLs/headers, and combine them with Liquid filters.
Usage
Simple secret:
{{ secrets.partner_api_key }}
Structured secret (object with multiple fields):
{{ secrets.basic_auth.username }}
{{ secrets.basic_auth.password }}
{{ secrets.partner.token }}
Using secrets with remote_fetch
In headers (recommended):
Keep credentials out of URLs by using headers when possible.
Example:
{% remote_fetch 'https://api.partner.com/v1/data?city={{profile.city }}' headers:'{ "X-Api-Key": "{{ secrets.secret_name.token }}" }', alias:'data' %}
In URLs:
For providers that require tokens in the query string.
Example:
{% remote_fetch 'https://api.partner.com/v1/data?token={{secrets.partner.token }}&city={{ profile.city }}' alias:'data' %}
Working with structured secrets
Basic auth example:
Username:
{{ secrets.basic_auth.username }}
Password:
{{ secrets.basic_auth.password }}
Combine in a header:
{% remote_fetch 'https://api.example.com/account' headers:'{ "Authorization": "Basic {{ secrets.basic_auth.username }}:{{ secrets.basic_auth.password }}" }', alias:'account' %}
Best practices
Prefer headers for credentials
Use Authorization or a custom header whenever the provider supports it.
Use the
default
filter for resilienceAlways provide a sensible fallback when a secret might be missing or not yet configured.
Avoid printing secrets in content
Don’t include secrets (even partial) in titles, bodies, or output visible to users.
Missing secrets
Liquid resolves missing keys to empty by default; use default to provide fallbacks.
Timeouts and Size Limits
timeout_ms
sets a per‑request timeout (connect + read). Choose values that balance user experience and backend SLAs.max_bytes
caps the response body size. Use a safe ceiling for your endpoint; it protects both servers and renderers.
Caching and Repeated remote_fetch Calls
Many authors place the same remote_fetch URL in multiple template properties (for example, title, body, and landing URL). This section explains how caching works so you don’t make unnecessary network calls.
Summary
Requests are cached in memory by URL + method + headers.
The first time a unique request is made, it hits the network and caches the response for a short time.
Subsequent identical requests (even within the same template render) are served from cache.
What is cached
Response body and status for GET requests
Keyed by:
The fully rendered URL (including query string and any Liquid interpolation)
HTTP method (GET)
Effective headers sent (names are normalized)
If any of those change, it counts as a different cache key and results in a separate request.
When the cache is used
Within the same render: The first call populates the cache. Any identical subsequent calls during that render re-use the cached response.
Across nearby renders: As long as the cache entry hasn’t expired and hasn’t been evicted due to memory pressure, identical requests will be served from cache.
Scope and lifetime
Scope: In‑memory cache local to the running process. It’s not shared across servers or regions.
TTL: Each entry has a short time‑to‑live suitable for “fresh enough” personalization. After TTL, the next identical request will refresh it.
Capacity: The cache is bounded. When full, expired entries are cleared first; if still full, older entries are evicted.
Frequently asked questions
How do I handle a single fallback when chaining multiple calls?
Use the sequential pattern: guard the second fetch with
{% if firstAlias %}
, then a single final{% if firstAlias and secondAlias %} … {% else %} fallback {% endif %}
.
Can I show custom content on fetch errors?
Yes. With the default soft‑fail, the alias is unset. Use a conditional to show your message.
Can I pass headers that use Liquid values?
Yes, via
headers:'{...}'
. Values may include{{ ... }}
.
Last updated