> ## Documentation Index
> Fetch the complete documentation index at: https://docs.firmhouse.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Integrate data from Shopify

> Use Shopify Storefront API data and selected Shopify customer metafields inside Customer Portal v2 templates.

Customer Portal v2 templates can use Shopify data in two ways:

* Use Shopify's Storefront API from custom template JavaScript to fetch storefront product and collection data.
* Read and update selected Shopify customer metafields through the Firmhouse Shopify API proxy.

Use these features when a portal page needs a richer storefront experience, personalized account fields, loyalty data, preferences, or other Shopify customer data that is not part of the standard Firmhouse subscription data.

## Before you start

These features are available when the Shopify subscriptions app is configured for the Firmhouse project.

You also need access to **Customer Portal** in the Firmhouse sidebar so you can manage Customer Portal v2 settings and edit templates.

## Use the Shopify Storefront API

Customer Portal v2 includes Liquid tags that output the Shopify store domain and the Storefront API public access token for the project.

Use these tags in a template when you want to make client-side requests to Shopify's Storefront GraphQL API:

```liquid theme={null}
<script>
  const shopifyDomain = "{% shopify_store_domain %}";
  const storefrontToken = "{% shopify_storefront_access_token %}";
  const storefrontEndpoint = `https://${shopifyDomain}/api/2026-01/graphql.json`;
</script>
```

You can then fetch products, collections, filters, or other Storefront API data from your custom template JavaScript.

```liquid theme={null}
<script>
  const shopifyDomain = "{% shopify_store_domain %}";
  const storefrontToken = "{% shopify_storefront_access_token %}";
  const storefrontEndpoint = `https://${shopifyDomain}/api/2026-01/graphql.json`;

  fetch(storefrontEndpoint, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Shopify-Storefront-Access-Token": storefrontToken
    },
    body: JSON.stringify({
      query: `{
        products(first: 10) {
          nodes {
            id
            title
            handle
          }
        }
      }`
    })
  })
    .then((response) => response.json())
    .then((data) => console.log(data));
</script>
```

The Storefront API token is a public Shopify storefront token. Do not use it for Shopify Admin API operations or for secret server-side credentials.

## Rotate the storefront token

Firmhouse caches the Storefront API token and exposes it to Customer Portal v2 templates with `{% shopify_storefront_access_token %}`.

To rotate the token:

1. Open **Customer Portal** in the Firmhouse sidebar.
2. Stay on the **Overview** page.
3. Find **Shopify storefront token**.
4. Click **Rotate token**.

Rotating the token invalidates the previous token. Review custom template JavaScript if you have copied the token outside the Customer Portal template system.

## Allow customer metafields

The Shopify API proxy only allows customer metafields that are explicitly listed in the Customer Portal settings.

To allow metafields:

1. Open **Customer Portal** in the Firmhouse sidebar.
2. Stay on the **Overview** page.
3. Find **Shopify API proxy**.
4. Enter allowed keys as a space-separated list in `namespace.key` format.
5. Save the settings.

For example:

```text theme={null}
custom.preferences custom.loyalty_tier
```

Only the listed keys can be read or updated from Customer Portal v2.

## Read a customer metafield

Customer Portal v2 templates include a `shopify_customer_metafields_url` Liquid variable. Use it as the same-origin endpoint for reading and updating allowed Shopify customer metafields.

This endpoint is scoped to the Shopify customer linked to the current subscription.

```liquid theme={null}
<script>
  const metafieldsUrl = "{{ shopify_customer_metafields_url }}";

  fetch(`${metafieldsUrl}?namespace=custom&key=loyalty_tier`)
    .then((response) => response.json())
    .then((metafield) => {
      console.log(metafield.value);
    });
</script>
```

When the metafield exists, the response includes:

```json theme={null}
{
  "namespace": "custom",
  "key": "loyalty_tier",
  "value": "gold",
  "type": "single_line_text_field"
}
```

When the metafield does not exist yet, the response returns the requested namespace and key with `null` values.

## Update a customer metafield

Use a `PATCH` request to update an allowed customer metafield.

```liquid theme={null}
<script>
  const metafieldsUrl = "{{ shopify_customer_metafields_url }}";
  const csrfToken = document.querySelector("meta[name='csrf-token']").content;

  fetch(metafieldsUrl, {
    method: "PATCH",
    headers: {
      "Content-Type": "application/json",
      "X-CSRF-Token": csrfToken
    },
    body: JSON.stringify({
      namespace: "custom",
      key: "preferences",
      value: "weekly_updates",
      type: "single_line_text_field"
    })
  })
    .then((response) => response.json())
    .then((metafield) => {
      console.log(metafield.value);
    });
</script>
```

If you omit `type`, Firmhouse uses `single_line_text_field`.

## Troubleshooting

**The Storefront API token is empty**

Check that the Shopify subscriptions app is configured for the project.

**The metafield request returns forbidden**

Check that the metafield is listed under **Customer Portal > Overview > Shopify API proxy**. The allowed key must match the requested `namespace.key` exactly.

**The metafield request says no Shopify customer is linked**

The current Firmhouse subscription must be linked to a Shopify customer before Customer Portal v2 can read or update Shopify customer metafields.

**The update request fails**

Check that the request includes `namespace`, `key`, and `value`. For `PATCH` requests from custom JavaScript, also include the `X-CSRF-Token` header from the page's CSRF meta tag.

## Related articles

* [Building templates](/customer-portal-v2/components/overview)
* [Common components](/customer-portal-v2/components/common)
