Metafields on locale pages (/ja, /en-es) are cached and return old values

Short description of issue

Metafields on locale pages (/es-es, /ja) are cached and return old values

Link to Shopify Store

imou-store-1018.myshopify.com (/es-es), tkindg-nd.myshopify.com (/ja)

Reproduction steps

We set metafields on the Shop object level to track settings for our app. This is stored across multiple keys. The main ones in this case are the keys ‘settings’ and ‘selling_plans’. We set these keys when settings change so it happens infrequently.

It is not consistent but often times we will see that the metafields load old versions for a locale using path (/ja, /es-es) but we haven’t seen the subdomain version running into issues yet.

It happens frequently to the stores I’ve linked.

To recover from this we are forced to unset the metafield and then set it again. This is the only way for it to recover. Alternatively it does to have in some cases changed after a few days.

Additional info

In the screenshot you can see the updated_at key for the two paths is completely different. Setting the metafield multiple times doesn’t change the locale one.

What type of topic is this

Bug report

This is how we load the keys.

Looks like the screenshots I included in the post are missing but these are the settings on two different paths of the same website.

Hi @kartik-stoq

Is this caching issue only happening on markets/locales? ie: metafields update as expected on the default market?

Yes @Liam-Shopify that appears to be the case in all of them.

We’re noticing this happening across markets as well now. The same metafield will be an older value on some markets but be the current value in other markets.

It eventually resolves itself but we see up to several days of delay.

Hi again @kartik-stoq

Could you share a timestamp of the requests and the x-request-id response header for stale and non-stale cases?

Will have to wait for another one. It is intermittent and merchants don’t always report when they see issues with it.

Also importantly I don’t see how I can get x-request-id and timestamps from Liquid interpolations. Can you tell me what I can look for in these cases.

Still seeing major issues with metafields being cached.

Here is another example from today. Even setting and unsetting the metafield is not fixing them. The value is several months old.

The value is being loaded from metafields in the theme app extension Liquid.

Hi @kartik-stoq

DMing you for details on this.

We found what the issue is. The Liquid templates we use in theme app extensions don’t update regularly or get cached for a while. The initial problem that we mentioned with locale pages is probably because locale pages don’t get enough traffic for the cache to bust.

Conceptually a solution is to know when metafields are out of date using the Shopify is out of date using the ‘now’ date filter and then accordingly handle the issue.

{%- comment -%} This gets baked into the cached Liquid template {%- endcomment -%}
window._RestockRocketConfig.liquidRenderedAt = {{ ‘now’ | date: ‘%s’ }};

// Client-side detection
function isCacheStale() {
const liquidRenderedAt = window._RestockRocketConfig.liquidRenderedAt;
const now = Math.floor(Date.now() / 1000); // Current time in seconds
const cacheAge = now - liquidRenderedAt; // Age in seconds

const MAX_CACHE_AGE = 2 * 60 * 60; // 2 hours

if (cacheAge > MAX_CACHE_AGE) {
  console.debug(`STOQ - Liquid cache is ${Math.round(cacheAge / 60)} minutes old, fetching fresh data`);
  return true; // Stale
}

return false; // Fresh

}

if (isCacheStale()) {
fetchFreshData();
} else {
useCachedMetafields();
}

Update: Root Cause Found - Translation Apps Registering on App Metafields

We’ve identified the actual root cause of this issue, and it’s not simply Liquid template caching or CDN staleness.

The Problem

When app metafield definitions have storefront: PUBLIC_READ access (which is required for theme app extensions to read them in Liquid), those metafields become visible in Shopify’s “Translate and Adapt” app and any third-party translation app installed on the store.

If the merchant (or a translation app) translates these metafields for a specific locale (e.g. Korean, Japanese, Spanish), Shopify starts serving the translated version of the metafield value on locale pages instead of the base value. This is by design for content that should be translated - but for app metafields that store operational JSON data (settings, variant IDs, selling plan configurations), this is bad.

Here’s why:

  1. App writes metafield with fresh data (e.g. selling_plans JSON blob)
  2. Translation app has previously registered a translated version for locale X
  3. On locale X pages, Shopify serves the old translated value instead of the fresh base value
  4. The outdated: true flag is set on the translation, but Shopify still serves it
  5. The app’s metafield updates never reach locale pages until the translation is removed

You can verify this by querying the translatableResource GraphQL API:

{
  translatableResource(resourceId: "gid://shopify/Metafield/YOUR_METAFIELD_ID") {
    translations(locale: "ja") {
      key
      value
      outdated
    }
  }
}

If translations returns results with outdated: true, that stale translated value is what’s being served on your locale pages.

Why There’s No Simple Prevention

  • Metafield definitions do NOT have a translatable capability toggle (unlike metaobject definitions which do)
  • The only storefront access options are PUBLIC_READ or NONE — there’s no “readable but not translatable” middle ground
  • Any metafield with PUBLIC_READ is automatically exposed to translation tools

The Solution

Since you can’t prevent translations from being registered, you need to detect and remove them. There are two approaches:

Approach 1: Delete and Recreate the Metafield (No Extra Scopes Needed)

Deleting a metafield removes its GID and all associated translations. Re-creating it assigns a new GID with no translations attached:

# For each affected metafield key:
shop.unset_metafield(key)  # Deletes metafield + all its translations
shop.set_metafield(key)    # Creates fresh metafield with new GID, no translations

This is the approach we’re using. We run a daily detection job that queries translatableResourcesByIds across all shops to find metafields with translations, then remediate by deleting and re-setting the affected metafields.

Approach 2: Use translationsRemove Mutation (Requires write_translations Scope)

If you have write_translations scope, you can surgically remove translations without recreating the metafield:

mutation {
  translationsRemove(
    resourceId: "gid://shopify/Metafield/YOUR_METAFIELD_ID"
    translationKeys: ["value"]
    locales: ["ja", "ko", "es"]
  ) {
    userErrors { message field }
  }
}

Detection: How to Find Affected Shops

Use translatableResourcesByIds with GraphQL aliases to batch-check all metafields across all locales in a single query per shop:

{
  ko: translatableResourcesByIds(first: 50, resourceIds: ["gid://shopify/Metafield/123", "gid://shopify/Metafield/456"]) {
    nodes { resourceId translations(locale: "ko") { key } }
  }
  ja: translatableResourcesByIds(first: 50, resourceIds: ["gid://shopify/Metafield/123", "gid://shopify/Metafield/456"]) {
    nodes { resourceId translations(locale: "ja") { key } }
  }
}

If any translations array is non-empty, that metafield has a registered translation for that locale.

Feature Request to Shopify

It would be very helpful if Shopify added a translatable capability to metafield definitions (similar to what exists for metaobject definitions) so that app developers can mark operational/config metafields as non-translatable while keeping them publicly readable.

The current behavior — where any PUBLIC_READ metafield is automatically exposed to translation tools — creates a class of bugs that’s difficult to detect and affects any app that stores structured/operational data in shop-level metafields with theme extension access.

@Liam-Shopify Figured this out finally after months. If we could add the translatable property to metafields and make it default disabled that would be great and prevent these kind of issues.