Using declarative custom data definitions with the new 'app dev'
In this workshop you'll learn about how we've improved the app dev command, and how to use it with declarative metaobject and metafield definitions to power Shopify app functionality in extensions.
Falling behind or just looking for the code? You can refer to the completed workshop or individual exercises.
Anchor to Setup / prerequisitesSetup / prerequisites
Please complete these steps ahead of time.
Anchor to Create a dev storeCreate a dev store
Create a new Plus development store with demo data using the Dev Dashboard:
- Open the Dev Dashboard.
- Navigate to Dev stores.
- Click Add dev store.
- Fill out the details for your dev store.
- Enter a name, such as
masterclass-declarative-data-<your initials>. - IMPORTANT: For Shopify plan, select Plus.
- Build version can remain as Current release.
- IMPORTANT: Check the box to Generate test data for store.
- Click Create store.
- Enter a name, such as
Anchor to Get the latest Shopify CLIGet the latest Shopify CLI
IMPORTANT: You'll need version 3.80 or higher of Shopify CLI for this workshop.
Open a terminal and run the appropriate command to update to the latest version:
Terminal
Anchor to Create an app on the Dev DashboardCreate an app on the Dev Dashboard
Create a new app with Shopify CLI:
-
Run the following command:
Terminal
shopify app init --template none -
IMPORTANT: Select your organization which has a (Dev Dashboard) label. For example:
Terminal
? Which organization is this work for? Type to search...My Organization (Partner Dashboard)> My Organization (Dev Dashboard) # Choose this one! -
Select Yes, create it as a new app.
-
Give your app a name.
That's it! You're ready for the rest of the workshop.
Anchor to Module 1: Using declarative metaobject definitionsModule 1: Using declarative metaobject definitions
In this module, you will create a metaobject definition in the shopify.app.toml, and use a UI extension to render metaobjects in Checkout.
Anchor to Exercise A. Creating the metaobject definition (5 min)Exercise A. Creating the metaobject definition (5 min)
-
Open the app you created for this workshop in your chosen IDE.
-
In a terminal window,
cdto the app's path. -
Run
shopify app dev --use-localhost- When prompted, select the store you created for this workshop.
-
Create a
payment_messagesmetaobject definition inshopify.app.toml:shopify.app.toml
[metaobjects.app.payment_messages]name = "Payment Messages"access.storefront = "public_read"[metaobjects.app.payment_messages.fields.payment_type]name = "Payment Type"type = "single_line_text_field"required = true[metaobjects.app.payment_messages.fields.message]name = "Message"type = "single_line_text_field"required = true -
Check your terminal to see what's happened when we added these.
- Notice error in the terminal. That's right - we now flag which scopes are missing!
-
Add
write_metaobject_definitionsto thescopesfield inshopify.app.toml. Success! -
Confirm your metaobject definition has been created.
- Open GraphiQL from Shopify CLI (
g) and copy/paste the query below. - Or click here to open it in GraphiQL
GraphQL query
query getMetaobjectDefinitions {metaobjectDefinitions(first: 10) {nodes {nameid}}}You should see the
Payment Messagesdefinition in the query results. - Open GraphiQL from Shopify CLI (
-
Next let's add a metaobject via GraphiQL using this definition.
- Add
write_metaobjectsto thescopesfield inshopify.app.toml. - Open GraphiQL from Shopify CLI (
g) and copy/paste the mutation below. - Or click here to open it in GraphiQL
GraphQL mutation
mutation createMetaobject {metaobjectCreate(metaobject: {type: "$app:payment_messages",fields: [{key: "payment_type", value: "creditCard"},{key: "message", value: "Lorem ipsum credit card dolor sit amet, consectetur."}]}) {userErrors {message}metaobject {idtypefields {keyvalue}}}} - Add
-
Finally, let's query the metaobject you just created.
- Open GraphiQL from Shopify CLI (
g) and copy/paste the mutation below. - Or click here to open it in GraphiQL
GraphQL query
query getMetaobjects {metaobjects(first: 10, type: "$app:payment_messages") {nodes {idfields {keyvalue}}}} - Open GraphiQL from Shopify CLI (
Next you will create a UI extension which renders messages from this metaobject in Checkout, depending on the chosen payment method. You will use the new Polaris UI components to do so.
-
In a separate terminal (keep
app devrunning), run the following command:Terminal
shopify app generate extension --template checkout_ui_preact --name payment-banner -
In the generated
extensions/payment-banner/shopify.extension.toml, update the extension target to render above the Checkout payment method list.shopify.extension.toml
[[extensions.targeting]]module = "./src/Checkout.jsx"target = "purchase.checkout.payment-method-list.render-before" # Update this line -
Test that the extension renders where we want it:
- Open Developer Console in Shopify CLI (
p). - Click the extension handle in the Dev Console.
- The first time you do this, you'll need to enter your store password, and click the link again.
- Confirm the banner displays above the payment method list in Checkout.
- Open Developer Console in Shopify CLI (
-
Update the code in
extensions/payment-banner/src/Checkout.jsxto fetch the metaobject you created via the Storefront API and use it as a banner message. Display the banner if the payment type is selected.Checkout.jsx
import {render} from "preact";import {useEffect, useState} from 'preact/hooks';import {useApi, useSelectedPaymentOptions} from "@shopify/ui-extensions/checkout/preact"export default function() {render(<Extension />, document.body)}function Extension() {const [data, setData] = useState();const {query} = useApi();const paymentMethods = useSelectedPaymentOptions();useEffect(() => {query(`query {metaobjects(first: 10, type: "$app:payment_messages") {nodes {idfields {keyvalue}}}}`,{version: "2025-07"}).then(({data, errors}) => setData(data)).catch(console.error);}, [query]);const paymentMessages = data?.metaobjects?.nodes?.map(node => {const message = {id: node.id}node.fields.forEach(field => {message[field.key] = field.value;});return message;});const displayMessages = paymentMessages?.filter(message =>paymentMethods.findIndex(paymentMethod => message.payment_type === paymentMethod.type) !== -1);return displayMessages?.map(message => <s-banner heading={message.message} id={message.id}></s-banner>) || null;} -
BONUS: Add more payment methods via admin and metaobjects via GraphiQL.
- Set the
payment_typefield based on values inPaymentOptiondocumentation - Suggestion: Add
Bank Depositvia payment settings in your store admin (Settings > Payments > Manual payment methods), and add a metaobject for themanualPaymentpayment type.
- Set the
Anchor to Exercise C. Allow merchants to add their own values (5 min)Exercise C. Allow merchants to add their own values (5 min)
- Find your existing metaobjects in the store admin.
- Navigate to Content > Metaobjects.
- Click the View read only button.
- Click on the metaobject entry.
Notice the payment_messages content is here, but not editable. Let's make them editable and add some validation.
-
In the
shopify.app.toml, set the metaobject definition'saccess.adminproperty tomerchant_read_write.shopify.app.toml
[metaobjects.app.payment_messages]name = "Payment Messages"access.storefront = "public_read"access.admin = "merchant_read_write" # Add this -
Also in the
shopify.app.toml, add achoicesvalidation topayment_typeshopify.app.toml
[metaobjects.app.payment_messages.fields.payment_type]name = "Payment Type"type = "single_line_text_field"required = truevalidations.choices = [ # Add this"creditCard", "deferred", "local", "manualPayment", "offsite", "other", "paymentOnDelivery", "redeemable", "wallet", "customOnsite"] -
Add a new payment method for Cash on Delivery.
- From your store admin, navigate to Settings > Payments and scroll to Manual payment methods.
- Click the
+to add and select Cash on Delivery (COD). - Click Activate Cash on Delivery (COD).
-
Add a metaobject in your store admin:
- Navigate to Content > Metaobjects.
- Click on Payment Messages.
- Click Add entry.
- Select
paymentOnDelivery, enter a message, and click Save.
-
Test your new message in Checkout.
Anchor to Module 2: Using declarative metafield definitionsModule 2: Using declarative metafield definitions
Anchor to Exercise A. Customize product limits with a metafield (15 min)Exercise A. Customize product limits with a metafield (15 min)
-
If needed, ensure
app devis still running for your app.- In a terminal window,
cdto the app's path. - Run
shopify app dev --use-localhost
When prompted, select the store you created for this workshop.
- In a terminal window,
-
In
shopify.app.toml, add product metafields for defining a max purchase quantity and messaging on products.shopify.app.toml
[product.metafields.app.max_purchase_quantity]type = "number_integer"validations.min = 0access.admin = "merchant_read_write"[product.metafields.app.max_quantity_message]type = "single_line_text_field"access.admin = "merchant_read_write" -
Check your terminal to see what's happened when we added these.
- A missing scope again! Let's fix it.
-
Add
write_productsto thescopesfield inshopify.app.toml. -
Pick a product in admin and open its metafields.
- Open your store admin.
- Click on Products in the main navigation.
- Select The Collection Snowboard: Liquid.
- Scroll down to Metafields and click View all.
-
These new metafields don't look great! Let's add a
nameinshopify.app.toml.shopify.app.toml
# in [product.metafields.app.max_purchase_quantity]name = "Max Purchase Quantity"# in [product.metafields.app.max_quantity_message]name = "Max Quantity Message" -
Refresh your store admin and fill in values for both metafields.
Max Purchase Quantityshould have a value of 2 or more.
-
Next let's create a Function which validates the cart based on these metafields. In a separate terminal (keep
app devrunning), run the following command:Terminal
shopify app generate extension --template cart_checkout_validation --name max-quantity --flavor vanilla-js -
Activate the function via Checkout Settings:
- Open your store admin and navigate to Settings > Checkout.
- Scroll down to Checkout rules and click Add rule.
- Select max-quantity.
- Click Save.
- Click Turn on.
-
The default template code for Checkout Validations should prevent product quantities greater than 1. To test this on your store:
-
Open the storefront of your dev store.
-
Under Featured products, click on The Collection Snowboard: Liquid.
-
Click Add to cart.
-
Attempt to increase the quantity of the product you just added.
You should see a validation error.
-
Open your terminal where
app devis running and review the execution logs for your function.
-
-
Now let's update the function to use the metafield we created.
-
Update the functions's input query in
extensions/max-quantity/src/run.graphqlto fetch the metafield:run.graphql
query RunInput {cart {lines {quantitymerchandise {__typename... on ProductVariant {product {maxQuantity: metafield(namespace: "$app", key: "max_purchase_quantity") {value}maxQuantityMessage: metafield(namespace: "$app", key: "max_quantity_message") {value}}}}}}} -
Update the function code in
extensions/max-quantity/src/run.jsto use the value and message:run.js
// @ts-check/*** @typedef {import("../generated/api").RunInput} RunInput* @typedef {import("../generated/api").FunctionRunResult} FunctionRunResult*//*** @param {RunInput} input* @returns {FunctionRunResult}*/export function run(input) {const errors = input.cart.lines.flatMap(({ quantity, merchandise }) => {if (merchandise.__typename !== "ProductVariant") {return [];}const maxQuantityValue = merchandise.product?.maxQuantity?.value;if (!maxQuantityValue) {return [];}if (quantity <= parseInt(maxQuantityValue)) {return [];}const localizedMessage =merchandise.product?.maxQuantityMessage?.value ??"Not possible to order more than one of each";return [{localizedMessage,target: "$.cart",}];});return {errors}};
-
-
Test your new function logic:
-
Open your storefront again and navigate to the cart.
-
Adjust the quantity of The Collection Snowboard: Liquid.
You should see the validation message that you entered in the metafield.
-
Open your terminal where
app devis running and review the execution logs for your function.
-
If you complete Exercise A in time, you can move on to Exercise B.
Anchor to Exercise B. BONUS: Add customer override (10 min)Exercise B. BONUS: Add customer override (10 min)
If time allows, you can also add a metafield which allows a customer to exceed the configured purchase limit.
-
Add a customer metafield allowing purchase over the max:
shopify.app.toml
[customer.metafields.app.allow_purchase_over_max]name = "Allow Purchase Over Max"type = "boolean"access.admin = "merchant_read_write"access.customer_account = "read" -
Add
write_customersto thescopesfield inshopify.app.toml. -
Create a customer for yourself in admin and set the metafield value:
- Open your store admin.
- Click on Customers in the main navigation.
- Click Add customer.
- Enter details for a customer, using your own email address, and click Save.
- Under Metafields, click View all.
- For Allow Purchase Over Max, select True and click Save.
-
Update your function to use this metafield:
-
Update the functions's input query in
extensions/max-quantity/src/run.graphqlto fetch the metafield:run.graphql
# Add directly under `cart`buyerIdentity {customer {allowPurchaseOverMax: metafield(namespace: "$app", key: "allow_purchase_over_max") {value}}} -
Update the function code in
extensions/max-quantity/src/run.jsto early return based on the metafield value:run.js
// Add to the start of the `run` functionconst allowPurchaseOverMax = Boolean(input.cart.buyerIdentity?.customer?.allowPurchaseOverMax?.value) ?? false;if (allowPurchaseOverMax) {return {errors: [],}}
-
-
Test your new function logic:
- Open your storefront again and click on the account icon in the header.
- Enter your email address and the emailed login code.
- Return to the Shop and open your cart.
- Confirm your user can add more of The Collection Snowboard: Liquid than is configured on the product.
- Open your terminal where
app devis running and review the execution logs for your function.
🎉 CONGRATULATIONS! You made it to the end! Great work!