Shopify app metafields and metaobjects
Metafields
Shopify metafields provide a means to expand Shopify's data model by storing additional information on various resources such as orders, products, customers, and the shop itself. They are commonly utilized in custom and public app development to enhance the functionality and user experience of Shopify apps.
While metafields are valuable for app development, working with them can be challenging for developers. The data is stored in a separate resource known as the "metafield object" on Shopify's end, whereas developers typically prefer the data to be directly associated with the resource they are extending. Moreover, Shopify's API rate limits can add complexity to the process of navigating across these additional resources.
To simplify the developer experience, Gadget offers a streamlined solution that enables fast synchronization, querying, and mutation of Shopify metafields within the backend of the app. This feature provides developers with efficient tools to manage metafields, reducing the complexities associated with working with metafields and improving the overall development workflow.
Storing metafield data in Gadget
Gadget allows you to store metafield data directly on any of the following Shopify resources:
- App Installation
- Collection
- Company
- Company Location
- Customer
- Draft Order
- Location
- Market
- Order
- Product
- Product Image
- Product Variant
- Shop
To do this, you must first copy the desired resource to your Gadget backend. Go to the Connections screen and pick the desired scopes and models (e.g. the read_products scope and the Product model), and connect to a store.
Once you have the desired model in your Gadget backend, click on Add Field to decorate the model with the additional metafield values. Name your field and select an appropriate type for the incoming data. To start the metafield import, you also need to click the Store data from Shopify Metafield box.
You now need to register your metafield's Namespace. Enter the Namespace that is used for your metafield in the Shopify Store, then click Register Namespace. Gadget will then register for webhooks on the entered Namespace.
Once you get a message stating that the Namespace registrations are complete, you can enter the key for your metafield and select the correct metafield type.
Here's an example of what the form looks like when we try to teach Gadget to store a metafield capturing each product variant's weight under the Namespace "gadget_app" and key "spiciness":
Your Gadget app is now set up to store the data directly on this model. You can go back to your Connections page and sync to pull in metafield data. Once the metafield data is synced to your Gadget app, you can query or mutate metafield values using Gadget Actions.
Storing reference metafield types as relationships
You can store reference metafields in as relationship fields in Gadget. The reference id stored in the metafield will be automatically parsed by Gadget and, if the related record exists in your Gadget database, the relationship to the existing record will be created.
For example, if you want to store a page_reference
metafield on your shopifyProduct
model, you can:
- set up the field on the
shopifyProduct
model using the above instructions - set the field type in Gadget to a belongs to relationship
- set the inverse of the relationship as either a has one or has many so that, for example,
shopifyPage
has manyshopifyProduct
.
Gadget will parse any incoming gid
values (sent via the reference metafield types) from Shopify and create the relationship. You can then access related record information as you would with any other relationship in Gadget.
Creating a relationship to a custom model
You can store a reference to a custom model record in a metafield. The metafield value in Shopify can be stored as a number_integer
or single_line_text_field
metafield type. The value stored in the metafield should be the related custom model record's id
in your Gadget database.
For example, if you have a custom model called bike
and you want to store a reference to a bike
record on a shopifyProduct
model as a metafield, you can:
- set up the field on the
shopifyProduct
model using the above instructions - set the field type in Gadget to a belongs to relationship
- set the inverse of the relationship as either a has one or has many so that, for example,
bike
has oneshopifyProduct
You can then store id
values for a bike
record as a metafield on products in Shopify. Gadget will automatically create the relationship on the incoming webhook, and you can access bike
record information from your shopifyProduct
records like you would any other relationship in Gadget.
Querying metafields
Gadget's approach to metafields offers two key advantages:
Fetching data: You can retrieve metafield values alongside the records they are associated with in a single query. This simplifies the data retrieval process and reduces the number of API requests needed.
Range queries: Gadget allows you to perform range queries directly on the metafield values. This enables you to apply filters, comparisons, or other conditions on the metafield values, providing more flexibility in data retrieval operations.
Gadget's metafield implementation facilitates efficient querying of metafield data alongside corresponding records and supports range queries for more advanced filtering and comparison operations.
Metafield query examples
Here's an example of a range query being run on “spiciness”, a metafield stored on the Shopify Product, in order to fetch products that have a spiciness rating of greater than 5:
1query {2 shopifyProducts(3 filter: { spiciness: { greaterThan: 5 } }4 sort: { spiciness: Ascending }5 ) {6 edges {7 node {8 title9 id10 spiciness11 handle12 }13 }14 }15}
Writing to metafields
To mutate and sync metafield values in Gadget, you need to define the mutation logic in code. This is done by writing code within your action functions that Gadget executes when the API is triggered.
Metafield writing examples
Here's an example of a Run
function added to the update
action of a Shopify Product model that sends the metafield information to Shopify:
1export const run: ActionRun = async ({ api, record, params, connections }) => {2 if (record.changed("title")) {3 await connections.shopify.current?.graphql(4 `mutation ($metafields: [MetafieldsSetInput!]!) {5 metafieldsSet(metafields: $metafields) {6 metafields {7 key8 namespace9 value10 }11 userErrors {12 message13 }14 }15 }`,16 {17 metafields: [18 {19 key: "a_metafield",20 namespace: "test321",21 ownerId: `gid://shopify/Product/${record.id}`,22 type: "single_line_text_field",23 value: record.title + " as a metafield!",24 },25 ],26 }27 );28 }29};
1export const run: ActionRun = async ({ api, record, params, connections }) => {2 if (record.changed("title")) {3 await connections.shopify.current?.graphql(4 `mutation ($metafields: [MetafieldsSetInput!]!) {5 metafieldsSet(metafields: $metafields) {6 metafields {7 key8 namespace9 value10 }11 userErrors {12 message13 }14 }15 }`,16 {17 metafields: [18 {19 key: "a_metafield",20 namespace: "test321",21 ownerId: `gid://shopify/Product/${record.id}`,22 type: "single_line_text_field",23 value: record.title + " as a metafield!",24 },25 ],26 }27 );28 }29};
This will only update the metafield value in Shopify! If you have registered the metafield on a Gadget model, the webhook that fires after the metafield is updated in Shopify will update the metafield value in Gadget.
We have an example of a more dynamic metafield update available as a gist on Github.
Migrating private metafields to app-owned metafields
As Shopify will be deprecating the use of private metafields soon, we recommend Gadget apps currently relying on them make the necessary changes to migrate to app-owned metafields.
To sync or register webhooks for app-owned metafield namespaces you may use either prefix app--{your-app-id}--{namespace}
or the shorthand $app:{namespace}
reserved namespaces when setting up metafields.
For more information on the deprecation of private metafields check out Shopify's guide here.
Adding app-owned metafields
Let's walk through an example of migrating your private metafield in Gadget to an app-owned metafield.
We recommend using the shorthand prefix ($app:{namespace}
) as this works best when you deploy to production and will not require you to find and input the app ID.
If you do choose to use the longer namespace prefix: the app ID to input within the prefix can be found within the URL for your Shopify partners app.
- Head to the Shopify Partners dashboard
- Click on apps and select the app you wish to use with the metafield from the list
- Once selected - grab the URL from the browser.
- Within the URL (example:
partners.shopify.com/2862902/apps/69412093953/overview
) your app ID will be the second number, so in this case6941209353
.
- Head to the set metafield on your Shopify model
- Unselect the private metafield option on your Shopify model's metafield
- Within the namespace input you will now add the prefix before your original namespace - so now instead of the namespace being
colors
it will be$app:colors
- Now click on Register Namespace and once successfully registered you're all done
Metaobjects
Metaobjects are similar to metafields, but they don't need to be tied to a Shopify resource. A metaobject definition can take any shape - metaobject fields are made up of groups of metafield types and can include references to Shopify resources, such as Orders or Products, or even references to other metaobjects!
Metaobjects can be created in the Shopify admin or using the metaobject API. Created metaobjects can be accessed directly in Liquid, using the Storefront API, or in the Admin API.
Metaobjects in Gadget
To read or write metaobjects you need to add the Metaobject and Metaobject Definitions scopes when setting up or editing your Shopify connection.
It's important to note that:
- The metaobjects API is available in API version
2023-01
and above - There are no models to import for metaobjects
- Gadget apps do not automatically subscribe to metaobject webhooks, but they can be subscribed to using global action webhook triggers
If you want to sync metaobject data from Shopify to Gadget, you will need to do so manually:
- Create custom models in Gadget that match your metaobject definitions used to store the metaobject data
- An action that can be triggered manually, on a schedule, or as part of some other action (ie. store install) that manually fetches metaobjects stored in Shopify. Note that you will need to deal with Shopify rate limits and add retry logic yourself.
Metaobject definitions
You can use Shopify's GraphQL API to read, create, update, and delete metaobject definitions.
You can use your Gadget app's current Shopify connection to call these APIs to add or make changes to metaobject definitions on a merchant's store. For example, if you wanted to store some information about bikes (their style, model name, year, and a reference to the product record they pertain to), you would simply run the following code snippet:
1// define a metaobject that can be used to store information for bicycles2const createMetaobjectDefinition = await connections.shopify.current.graphql(3 `mutation ($definition: MetaobjectDefinitionCreateInput!) {4 metaobjectDefinitionCreate(definition: $definition) {5 metaobjectDefinition {6 id7 }8 userErrors {9 message10 }11 }12 }`,13 {14 definition: {15 access: {16 storefront: "PUBLIC_READ",17 },18 capabilities: {19 publishable: {20 enabled: true,21 },22 },23 description: "Model information for bikes",24 displayNameKey: "model",25 fieldDefinitions: [26 {27 description: "Style of bike",28 key: "style",29 name: "Style",30 type: "single_line_text_field",31 required: true,32 },33 {34 description: "Bike model name",35 key: "model",36 name: "Model",37 type: "single_line_text_field",38 required: true,39 },40 {41 description: "Model year",42 key: "year",43 name: "Year",44 type: "number_integer",45 validations: {46 name: "min",47 value: "1950",48 },49 required: true,50 },51 {52 description: "Bike product reference",53 key: "bike_ref",54 name: "Product reference",55 type: "product_reference",56 required: true,57 },58 ],59 name: "Bike",60 type: "bike",61 },62 }63);
1// define a metaobject that can be used to store information for bicycles2const createMetaobjectDefinition = await connections.shopify.current.graphql(3 `mutation ($definition: MetaobjectDefinitionCreateInput!) {4 metaobjectDefinitionCreate(definition: $definition) {5 metaobjectDefinition {6 id7 }8 userErrors {9 message10 }11 }12 }`,13 {14 definition: {15 access: {16 storefront: "PUBLIC_READ",17 },18 capabilities: {19 publishable: {20 enabled: true,21 },22 },23 description: "Model information for bikes",24 displayNameKey: "model",25 fieldDefinitions: [26 {27 description: "Style of bike",28 key: "style",29 name: "Style",30 type: "single_line_text_field",31 required: true,32 },33 {34 description: "Bike model name",35 key: "model",36 name: "Model",37 type: "single_line_text_field",38 required: true,39 },40 {41 description: "Model year",42 key: "year",43 name: "Year",44 type: "number_integer",45 validations: {46 name: "min",47 value: "1950",48 },49 required: true,50 },51 {52 description: "Bike product reference",53 key: "bike_ref",54 name: "Product reference",55 type: "product_reference",56 required: true,57 },58 ],59 name: "Bike",60 type: "bike",61 },62 }63);
Querying metaobjects
You can utilize the metaobject API in Gadget to query actual instances of metaobjects. By using the Shopify connection established in Gadget, you can create instances of a specific metaobject, such as a bike metaobject. It's important to note that you need to have a corresponding metaobject definition in place before creating instances of the metaobject.
1// add a new instance of my "bike" metaobject2const createMetaobjectSeedData = await connections.shopify.current.graphql(3 `mutation metaobjectCreate($metaobject: MetaobjectCreateInput!) {4 metaobjectCreate(metaobject: $metaobject) {5 metaobject {6 handle7 }8 userErrors {9 message10 }11 }12 }`,13 {14 metaobject: {15 type: "bike",16 handle: "kona-dew-plus-2015",17 capabilities: {18 publishable: {19 status: "ACTIVE",20 },21 },22 fields: [23 {24 key: "style",25 value: "hybrid",26 },27 {28 key: "model",29 value: "Dew Plus",30 },31 {32 key: "year",33 value: "2015",34 },35 {36 key: "bike_ref",37 value: "gid://shopify/Product/7965217390872",38 },39 ],40 },41 }42);
1// add a new instance of my "bike" metaobject2const createMetaobjectSeedData = await connections.shopify.current.graphql(3 `mutation metaobjectCreate($metaobject: MetaobjectCreateInput!) {4 metaobjectCreate(metaobject: $metaobject) {5 metaobject {6 handle7 }8 userErrors {9 message10 }11 }12 }`,13 {14 metaobject: {15 type: "bike",16 handle: "kona-dew-plus-2015",17 capabilities: {18 publishable: {19 status: "ACTIVE",20 },21 },22 fields: [23 {24 key: "style",25 value: "hybrid",26 },27 {28 key: "model",29 value: "Dew Plus",30 },31 {32 key: "year",33 value: "2015",34 },35 {36 key: "bike_ref",37 value: "gid://shopify/Product/7965217390872",38 },39 ],40 },41 }42);
You can also use the metaobjects query to read metaobject data. The following example uses the metaobject type
field to fetch instances of a "bike" metaobject:
1await connections.shopify.current.graphql(2 `query GetMetaobjects($type: String!) {3 metaobjects(type: $type) {4 fields {5 key6 reference7 type8 value9 }10 id11 type12 displayName13 }14 }`,15 { type: "bike" }16);
1await connections.shopify.current.graphql(2 `query GetMetaobjects($type: String!) {3 metaobjects(type: $type) {4 fields {5 key6 reference7 type8 value9 }10 id11 type12 displayName13 }14 }`,15 { type: "bike" }16);
References to metaobjects
Even though metaobjects are stored independently of Shopify resources, Shopify also allows you to make associations between a metaobject and existing resources, such as products or orders, through metaobject references.
Metaobject references are stored on records as metafields and contain a reference to the metaobject. To capture this relationship in Gadget, you need to add a new metafield to a Shopify model in Gadget and select the "Metaobject reference" type. This field will then contain a reference to the metaobject in Shopify.