# 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

Contact your account manager for details on how to set up Secrets for Remote Fetch.

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 resilience

    • Always 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