Skip to main content

Store metafields on Shop users

After a buyer authenticates through Sign in with Shop and you've exchanged a consent token for an access token, you can store custom data on the user with metafields. This data is available in Shop Pay checkouts and readable from Checkout UI extensions and Shopify Functions.

Developer preview

The Shop platform is in early access. Features and APIs may change before general availability.


You'll create, set, and read custom data on Shop users through a simple membership program:

  • Create a MetafieldDefinition to structure your data using the Shop Partners API.
  • Set a metafield on a user using the Shop Users API.
  • Create a discount Function that reads your metafield to offer members a discount.

  • A Shop app with a client ID and secret.
  • A user who has connected to your app through Sign in with Shop.
  • An app extension that can create Functions.

The Shop User is Shopify's cross-merchant buyer model (where the person it represents is referred to as the Shopper). The data in the Shop User model is owned by the Shopper and controlled by Shopify. As a Partner, you can access data the Shopper has granted you, or data you own and have stored on the Shop User, like metafields.

If you have used metafields on other types this will be familiar to you, but there are a few restrictions because this data lives on the Shopper:

  • All Shop User metafields are structured: they must be backed by a definition.
  • All Shop User metafields are private, by default, to the Partner that created them. Your app-reserved namespace is implicitly used for data privacy.

Anchor to Step 1: Use the Shop Partners API to create a MetafieldDefinitionStep 1: Use the Shop Partners API to create a MetafieldDefinition

For this membership example, you'll create a simple boolean field. You can use any of the supported metafield types.

Use your client credentials and HTTP Basic Authentication with the Shop Partners API. The app that creates the definition is automatically granted the Admin access policy on it; you can grant additional access policies later if you need other apps to read or write the same field.

One-time setup

You only need to do this step once per definition. It declares the shape of the data and establishes access control over every metafield of that definition.

Endpoint: https://shop.app/api/latest/partners/graphql.json

GraphQL Mutation

mutation DefineMetafield($definition: MetafieldDefinitionCreateInput!) {
metafieldDefinitionCreate(definition: $definition) {
createdDefinition {
key
type {
name
category
}
description
}
userErrors {
field
message
}
}
}

GraphQL Variables

{
"definition": {
"key": "my-membership",
"name": "My Membership",
"description": "Presence of this field indicates that the current buyer has a membership to the My Membership Program",
"type": "boolean"
}
}
Protect your client secret

Client credentials must only be used from a trusted server, never from a browser, mobile app, Shopify Function, or Checkout UI extension. Load them from a secret manager or environment variable rather than embedding them in source.

Sharing this definition with another app (via an additional access policy) effectively shares Shopper-owned data with that app. Treat each grant as a privacy decision, not a convenience.


Anchor to Step 2: Get a user access tokenStep 2: Get a user access token

To write a metafield onto a specific Shop User, you need an access token scoped to that Shopper. Use the fetchTokensForUser mutation on the Shop Partners API, authenticating with your client credentials over HTTP Basic Authentication.

This mutation has two modes; you'll typically use the second one after the first exchange.

The first time a Shopper authenticates to your app through Sign in with Shop on a merchant's storefront, that flow gives you a signed consentToken (a short-lived JWT) representing the Shopper's delegated consent to your app. Exchange it for a user access token and a publicId you can store and re-use.

Where the consent token comes from

You only get a consentToken as the result of a Shopper completing a Sign in with Shop flow with your app on a merchant's storefront. Don't construct one yourself, and don't accept one from arbitrary input.

Endpoint: https://shop.app/api/latest/partners/graphql.json

GraphQL Mutation

mutation FetchTokensForUser($consentToken: String, $publicId: String) {
fetchTokensForUser(consentToken: $consentToken, publicId: $publicId) {
publicId
accessToken
refreshToken
expiresIn
tokenType
scope
userErrors {
field
message
}
}
}

GraphQL Variables

{
"consentToken": "<JWT from the Sign in with Shop flow>"
}

Persist the returned publicId against your own record of this Shopper. You won't need the consentToken again; use publicId for every subsequent token exchange.

Anchor to Step 2b: Subsequent exchanges (publicId)Step 2b: Subsequent exchanges (publicId)

Once you have a publicId for a Shopper, use the same mutation with publicId instead of consentToken to mint a fresh access token whenever you need one.

GraphQL Variables

{
"publicId": "<stored publicId for this Shopper>"
}

Exactly one of consentToken or publicId should be provided per call.

Anchor to Handling the returned tokensHandling the returned tokens

Use the accessToken as a Bearer token in Step 3. Treat both tokens like any other user-scoped credential:

  • Keep them server-side. Never expose them to the storefront, browser, Shopify Function, or Checkout UI extension.
  • Never log the access or refresh token (including in error reports or analytics).
  • Store refresh tokens encrypted at rest and scope them per-Shopper.

Anchor to Step 3: Set the Metafield on the UserStep 3: Set the Metafield on the User

With the user access token from Step 2, call metafieldsSet on the Shop Users API to set the value of your metafield on this Shopper. Authenticate with Authorization: Bearer <accessToken>, not your client credentials. You can set multiple metafields in one call by passing more entries in the metafields array.

The identifier.key matches the key you chose in Step 1. The namespace is implicit, your app's reserved namespace is used automatically, so you don't need to specify it.

Endpoint: https://shop.app/api/latest/users/graphql.json

GraphQL Mutation

mutation SetMetafield($metafields: [MetafieldSetInput!]!) {
metafieldsSet(metafields: $metafields) {
metafields {
value
ownerId
definition {
key
type {
name
}
description
}
}
userErrors {
field
message
}
}
}

GraphQL Variables

{
"metafields": [
{
"identifier": {"key": "my-membership"},
"value": "true"
}
]
}

Always check userErrors on the response: if validation fails (invalid value for the definition's type, missing definition, namespace conflict, etc.), the metafields array comes back empty rather than raising. If your value derives from buyer-controlled input, validate it against the definition's type server-side before calling metafieldsSet, as values written here can later flow into Functions and Checkout UI extensions.


Anchor to Step 4: Read the Metafield from a Shopify FunctionStep 4: Read the Metafield from a Shopify Function

Now that the metafield is set on the Shopper, you can read it during checkout from a Shopify Function or a Checkout UI extension. Both must be owned by an app created by a Partner.

Try it on a development store

Local development against Shop User metafields in checkout is not currently supported. To exercise this end-to-end, install your app on a development store and test against it directly.

Anchor to Discount Function input queryDiscount Function input query

For our membership example, we'll write a Function that grants a discount to any Shopper whose my-membership metafield is set. Functions declare the data they need through an input query at extensions/<your-function>/src/run.graphql.

extensions/my-membership-discount/src/run.GraphQL

query Input {
cart {
buyerIdentity {
shopUser {
metafield(key: "my-membership") {
value
}
}
}
}
}

When the Function runs, cart.buyerIdentity.shopUser.metafield.value will be "true" for members and null for everyone else. Use that to decide whether to return a discount in your Function's run handler.

Add any other fields your run handler needs — for example, cart.lines { id quantity } for a per-line discount, or cart.deliveryGroups for a free-shipping discount. The available selections depend on the Function target you're building against; see the Shopify Functions reference for the full input schema for each target.

Anchor to Reading the same metafield from a Checkout UI extensionReading the same metafield from a Checkout UI extension

Checkout UI extensions declare the metafields they want to load in their shopify.extension.toml. Add a [[extensions.metafields]] block for each metafield you need. The $app namespace resolves to your app's reserved namespace, the same one your definition was created under.

extensions/checkout-ui/shopify.extension.toml

# Loads metafields on checkout resources, including the cart,
# products, customers, and more. Learn more:
# https://shopify.dev/docs/api/checkout-ui-extensions/unstable/configuration#metafields

[[extensions.metafields]]
namespace = "$app"
key = "my-membership"

The metafield is then available to the extension through the standard checkout extension APIs.



Was this page helpful?