Skip to main content

Store storefront settings where only your app can write them

Store every value that your extension renders in an app-owned or app-data metafield, so that only your app can change it.


A metafield in a public namespace, such as custom or any other namespace that doesn't start with $app, can be changed by other apps installed on the store that have access to the resource, by staff and collaborator accounts that can edit the resource in the Shopify admin, and by translations. For translatable metafield types, a translation registered for a locale through the GraphQL Admin API or a translation app replaces the value that Liquid renders for buyers browsing in that locale. If your extension renders a public metafield as markup, CSS, or JavaScript, then anyone in that list controls what runs on the storefront.


Anchor to Store the values in an app-owned or app-data metafieldStore the values in an app-owned or app-data metafield

  • App-owned metafields use the reserved $app namespace (app in shopify.app.toml). Their admin access defaults to MERCHANT_READ, so merchants can view the values but not edit them. Translations apply only to publicly accessible metafields, so no other app can translate them.
  • App-data metafields are stored on your app's installation, hidden from the Shopify admin, and accessible only by your app. Liquid reads them through app.metafields.

Keep the default MERCHANT_READ access for values that render as markup, CSS, or JavaScript. MERCHANT_READ_WRITE lets staff and collaborator accounts that can edit the resource change the value in the Shopify admin.

App-owned metafields aren't secret. Liquid can read any metafield regardless of its storefront access setting, so don't store sensitive values in them.

Anchor to Read the values in your extensionRead the values in your extension

In your theme app extension, reference an app-owned metafield through the $app reserved prefix and a sub-namespace, which is the form the theme app extension configuration documents:

{{ product.metafields["$app:settings"].badge_text.value | escape }}

The prefix resolves only inside your extension. The store's own theme code uses the full namespace, app--{your-app-id}--settings.

Anchor to Migrate from a public namespaceMigrate from a public namespace

If your extension reads from a public namespace today:

  1. Declare app-owned definitions for the values in your shopify.app.toml file, and deploy them.

  2. Copy each store's existing values into the $app namespace with the metafieldsSet mutation.

  3. Update your extension to read from $app, with a temporary fallback for stores you haven't copied yet:

    {%- liquid
    assign badge = product.metafields["$app:settings"].badge_text.value
    if badge == blank
    assign badge = product.metafields.my_app.badge_text.value
    endif
    -%}
    <span class="badge">{{ badge | escape }}</span>
  4. After every store's values are copied, remove the fallback and delete the public metafields. Until then, anyone who can write the public metafield controls the value on stores where the $app value is blank.


The app security checks cover this practice: they report shared-namespace metafield values that reach HTML, JavaScript, or URL output. Run them on every release.


Was this page helpful?