Skip to main content

Using custom data

Shop Minis extensions can fetch custom data from metafields and metaobjects.

caution

Metafields related to products and shops must have storefront access set to PUBLIC_READ. Metafields related to orders must have customerAccount access set to READ. In order to do this, you'll need to create metafield definitions using the default namespace (scoped to your app). Learn more about access controls for metafields here.

Metafields support various different types of content, including references to other Shopify resources:

# A metafield on a product.
query {
product {
relatedProduct: metafield(namespace: "app--12345--foo", key: "related-product") {
reference {
... on Product {
title
}
}
}
}
}
# A metafield on an order.
query {
order {
discountedPrice: metafield(namespace: "app--12345--foo", key: "discounted-price") {
value
}
}
}

Metafields can also reference metaobjects, allowing custom data models that suit a wide range of use-cases:

query {
product {
customItems: metafield(namespace: "app--12345--foo", key: "custom-items") {
references {
... on Metaobject {
configJSON: field(key: "config-json") {
value
}

discountCode: field(key: "discount-code") {
value
}

previewVideo: field(key: "preview-video") {
reference {
... on Video {
sources { url }
}
}
}

taggedProducts: field(key: "tagged-products") {
references {
... on Product {
title
}
}
}
}
}
}
}
}
tip

Try to model custom data carefully and only fetch what is necessary to render the extension component. This will minimize the performance impact of your Mini on merchants' surfaces. For example, don't fetch all product variants if you will only show the first one. And don't fetch all the fields if you only need a handful of them.

Appendix

Example bundle extension

This document outlines how to develop a Shop Minis extension for Shopify that displays a list of products and their variants on the Shop's Product Details Page by utilizing metafields.

Preparing data

To model the data we will use the list.product_reference type. You can read more about it in the Shopify Metafields Types documentation.

TypeDescriptionExample value
list.product_referenceA list of product references.["gid://shopify/Product/1","gid://shopify/Product/2"]

We will create a metafield on the Shopify Product that represents the Bundle. The metafield will contain a list of the Product IDs that are part of the bundle. Then, in the Mini extensions we will be able to reference those products and expand their attributes.

Creating a Metafield definition

To create a metafield definition, execute the mutation below against the Shopify Admin API:

mutation CreateMetafieldDefinition($definition: MetafieldDefinitionInput!) {
metafieldDefinitionCreate(definition: $definition) {
createdDefinition {
id
namespace
key
access {
admin
storefront
}
}
userErrors {
field
message
code
}
}
}

Variables:

{
"definition": {
"name": "Shop Bundles Metafield Definition",
"namespace": "$app:shop-bundles",
"key": "bundle-product-list",
"type": "list.product_reference",
"ownerType": "PRODUCT",
"access": {
"admin": "PRIVATE",
"storefront": "PUBLIC_READ"
}
}
}

Creating a Metafield on a Product Representing a Bundle

Following the metafield definition, you can now create a Metafield on a product that represents the bundle using this GraphQL mutation:

mutation CreateMetafield {
productUpdate(
input: {
id: "gid://shopify/Product/8343768334591",
metafields: [ {
namespace: "$app:shop-bundles",
key: "bundle-product-list",
value: "[\"gid://shopify/Product/7922605621503\",\"gid://shopify/Product/7922591727871\",\"gid://shopify/Product/7922600935679\"]",
type: "list.product_reference"}]}
) {
product {
metafield(namespace: "$app:shop-bundles", key: "bundle-product-list") {
value
namespace
}
}
userErrors {
field
message
}
}
}

Example response:

{
"data": {
"productUpdate": {
"product": {
"metafield": {
"value": "[\"gid://shopify/Product/7922605621503\",\"gid://shopify/Product/7922591727871\",\"gid://shopify/Product/7922600935679\"]",
"namespace": "app--3328713--shop-bundles"
}
},
"userErrors": []
}
}
}
tip

Save the expanded namespace that was part of the output of the mutation, you will need it to run the query inside the Mini.

In our example, we have used the following Product IDs to represent the main bundle product and the different bundled products that it contains. You should replace these Product IDs for the ones you want to offer.

Main Bundle Product(input.id)gid://shopify/Product/8343768334591
Bundled Product #1(metafield.value[0])gid://shopify/Product/7922605621503
Bundled Product #2(metafield.value[1])gid://shopify/Product/7922591727871
Bundled Product #3(metafield.value[2])gid://shopify/Product/7922600935679

Querying the product from Storefront API

To fetch the bundle’s metafield data via the Storefront API, use the following query:

{
product(id: "gid://shopify/Product/8343768334591") {
bundledProducts: metafield(namespace: "app--3328713--shop-bundles", key: "bundle-product-list") {
references(first: 10) {
nodes {
... on Product {
id
title
variants(first: 100) {
edges {
node {
id
title
}
}
}
}
}
}
}
}
}

Example Response:

{
"data": {
"product": {
"bundledProducts": {
"references": {
"nodes": [
{
"id": "gid://shopify/Product/7922605621503",
"title": "The Full Stack Snowboard",
"variants": {
"edges": [
{
"node": {
"id": "gid://shopify/ProductVariant/43698716705023",
"title": "Default Title"
}
}
]
}
},
{
"id": "gid://shopify/Product/7922591727871",
"title": "The Hero Snowboard",
"variants": {
"edges": [
{
"node": {
"id": "gid://shopify/ProductVariant/43698695733503",
"title": "Default Title"
}
}
]
}
},
{
"id": "gid://shopify/Product/7922600935679",
"title": "The H2 Snowboard",
"variants": {
"edges": [
{
"node": {
"id": "gid://shopify/ProductVariant/43698709922047",
"title": "Default Title"
}
}
]
}
}
]
}
}
}
}
}

Querying the data in the Mini extension

The input.graphql file in your Mini extension should contain the following query:

query ProductVariantsRenderBefore {
product {
bundledProducts: metafield(
namespace: "app--116780335105--shop-bundles"
key: "bundle-product-list"
) {
references(first: 10) {
nodes {
... on Product {
id
title
featuredImage {
url
}
variants(first: 100) {
nodes {
id
title
image {
url
}
}
}
}
}
}
}
}
}

Rendering the BundleSelector extension

In the render.tsx we will map the output of the query (extensionData) to the BundleSelectorItems type that our BundleSelector extension is expecting:

import {ComponentProps, useEffect, useState} from 'react'
import {BundleSelector} from '@shopify/shop-minis-ui-extensions'
import {useExtensionShopActions} from '@shopify/shop-minis-platform-sdk/actions'
import {VariantAvailability} from '@shopify/shop-minis-platform-sdk/'


import type {BundlesProductPageAboveVariantsPickerExtensionQueryData as InputData} from './input.graphql'


type BundleSelectorItems = ComponentProps<typeof BundleSelector>['items']


function mapBundleItems(response?: InputData | null): BundleSelectorItems {
if (!response?.product?.bundledProducts?.references?.nodes.length) return []


const bundles: BundleSelectorItems =
response.product.bundledProducts.references.nodes
.filter(product => 'variants' in product)
.map(product => {
const options = product.variants.nodes.map((variant, index) => {
return {
name: product.title,
value: variant.title,
availability: VariantAvailability.Available,
imageUrl: variant.image?.url,
variantId: variant.id,
selected: index === 0,
}
})
return {
title: product.title,
options,
}
})


return bundles
}


export function Render({extensionData}: {extensionData: InputData}) {
const initialBundleSelectorItems: BundleSelectorItems =
mapBundleItems(extensionData)
const [bundleSelectorItems, setBundleSelectorItems] = useState(
initialBundleSelectorItems
)
const {updateLineItemAttributes} = useExtensionShopActions()
useEffect(() => {
if (!bundleSelectorItems) return


const lineItemAttributes = bundleSelectorItems.map(
item => item.options.find(option => option.selected)?.variantId
)


updateLineItemAttributes({
quantityAvailabalie: 4,
lineItemAttributes: [
{
key: '_component_reference',
value: JSON.stringify(lineItemAttributes),
},
],
})
}, [updateLineItemAttributes, bundleSelectorItems, extensionData])


// Don't show the component if we didn't get a response from the query
if (bundleSelectorItems.length === 0) return null


return (
<BundleSelector
items={bundleSelectorItems}
onChange={setBundleSelectorItems}
/>
)
}

Running the extension

After we run the Mini and our extension is loaded, we can see that in our Bundle product the 3 bundled products are displayed in the What's included section:

Caching

The Shop Minis API uses caching to ensure your queries are fast and to provide a great experience for Shop app users. This means changes to products or other resources like metafields may take several minutes to be reflected in your Mini. However, order and customer related data will always be up to date.