> ## 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.
* Read and update selected Firmhouse subscription metadata keys through a same-origin Firmhouse endpoint.

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.

## Allow Firmhouse subscription metadata

Customer Portal v2 can also read and update selected keys from the Firmhouse subscription's own metadata.

To allow subscription metadata keys:

1. Open **Customer Portal** in the Firmhouse sidebar.
2. Stay on the **Overview** page.
3. Find **Firmhouse subscription metadata**.
4. Enter the allowed top-level keys as a space-separated list.
5. Wrap keys containing spaces in double quotes.
6. Save the settings.

For example:

```text theme={null}
profile_tag "_Delivery Date"
```

Only the listed top-level keys are exposed to Customer Portal v2.

## Read subscription metadata

Customer Portal v2 templates include a `firmhouse_subscription_metadata_url` Liquid variable. Use it as the same-origin endpoint for reading and updating the allowed subscription metadata keys.

```liquid theme={null}
<script>
  const metadataUrl = "{{ firmhouse_subscription_metadata_url }}";

  fetch(metadataUrl)
    .then((response) => response.json())
    .then((payload) => {
      console.log(payload.metadata);
    });
</script>
```

The response includes only the configured keys:

```json theme={null}
{
  "metadata": {
    "profile_tag": "vip"
  }
}
```

If no keys are configured, or if the configuration contains invalid unmatched quotes, the response returns an empty metadata object.

## Update subscription metadata

Use a `PATCH` request with a JSON `metadata` object to update one or more allowed keys. Firmhouse merges the submitted keys into the subscription's existing metadata and leaves other metadata keys unchanged.

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

  fetch(metadataUrl, {
    method: "PATCH",
    headers: {
      "Content-Type": "application/json",
      "X-CSRF-Token": csrfToken
    },
    body: JSON.stringify({
      metadata: {
        profile_tag: "loyal",
        "_Delivery Date": "2026-07-10"
      }
    })
  })
    .then((response) => response.json())
    .then((payload) => {
      console.log(payload.metadata);
    });
</script>
```

Sending an empty `metadata` object is allowed and leaves the stored values unchanged.

## Troubleshooting subscription metadata

**A key is missing from the response**

Only keys listed in **Customer Portal > Overview > Firmhouse subscription metadata** are returned.

**The update request returns forbidden**

One or more submitted keys are not allowed for this project.

**The update request returns unprocessable entity**

Check that the request body includes a `metadata` object. Requests fail when `metadata` is missing, is not a JSON object, or the submitted values fail subscription metadata validation.

## Related articles

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