Skip to main content

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.

Create a new Plus development store with demo data using the Dev Dashboard:

  1. Open the Dev Dashboard.
  2. Navigate to Dev stores.
  3. Click Add dev store.
  4. 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.

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

# Depending on your OS and package manager
npm install -g @shopify/cli@latest
yarn global add @shopify/cli@latest
pnpm install -g @shopify/cli@latest

# Only for macOS
brew tap shopify/shopify
brew install shopify-cli

Anchor to Create an app on the Dev DashboardCreate an app on the Dev Dashboard

Create a new app with Shopify CLI:

  1. Run the following command:

    Terminal

    shopify app init --template none
  2. 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!
  3. Select Yes, create it as a new app.

  4. 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)

  1. Open the app you created for this workshop in your chosen IDE.

  2. In a terminal window, cd to the app's path.

  3. Run shopify app dev --use-localhost

    • When prompted, select the store you created for this workshop.
  4. Create a payment_messages metaobject definition in shopify.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
  5. 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!
  6. Add write_metaobject_definitions to the scopes field in shopify.app.toml. Success!

  7. Confirm your metaobject definition has been created.

    GraphQL query

    query getMetaobjectDefinitions {
    metaobjectDefinitions(first: 10) {
    nodes {
    name
    id
    }
    }
    }

    You should see the Payment Messages definition in the query results.

  8. Next let's add a metaobject via GraphiQL using this definition.

    • Add write_metaobjects to the scopes field in shopify.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 {
    id
    type
    fields {
    key
    value
    }
    }
    }
    }
  9. Finally, let's query the metaobject you just created.

    GraphQL query

    query getMetaobjects {
    metaobjects(first: 10, type: "$app:payment_messages") {
    nodes {
    id
    fields {
    key
    value
    }
    }
    }
    }

Anchor to Exercise B. Use the metaobject to create a payment banner in Checkout (15 min)Exercise B. Use the metaobject to create a payment banner in Checkout (15 min)

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.

  1. In a separate terminal (keep app dev running), run the following command:

    Terminal

    shopify app generate extension --template checkout_ui_preact --name payment-banner
  2. 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
  3. 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.
  4. Update the code in extensions/payment-banner/src/Checkout.jsx to 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 {
    id
    fields {
    key
    value
    }
    }
    }
    }`,
    {
    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;
    }
  5. BONUS: Add more payment methods via admin and metaobjects via GraphiQL.

    • Set the payment_type field based on values in PaymentOption documentation
    • Suggestion: Add Bank Deposit via payment settings in your store admin (Settings > Payments > Manual payment methods), and add a metaobject for the manualPayment payment type.

Anchor to Exercise C. Allow merchants to add their own values (5 min)Exercise C. Allow merchants to add their own values (5 min)

  1. Find your existing metaobjects in the store admin.
    1. Navigate to Content > Metaobjects.
    2. Click the View read only button.
    3. Click on the metaobject entry.

Notice the payment_messages content is here, but not editable. Let's make them editable and add some validation.

  1. In the shopify.app.toml, set the metaobject definition's access.admin property to merchant_read_write.

    shopify.app.toml

    [metaobjects.app.payment_messages]
    name = "Payment Messages"
    access.storefront = "public_read"
    access.admin = "merchant_read_write" # Add this
  2. Also in the shopify.app.toml, add a choices validation to payment_type

    shopify.app.toml

    [metaobjects.app.payment_messages.fields.payment_type]
    name = "Payment Type"
    type = "single_line_text_field"
    required = true
    validations.choices = [ # Add this
    "creditCard", "deferred", "local", "manualPayment", "offsite", "other", "paymentOnDelivery", "redeemable", "wallet", "customOnsite"
    ]
  3. Add a new payment method for Cash on Delivery.

    1. From your store admin, navigate to Settings > Payments and scroll to Manual payment methods.
    2. Click the + to add and select Cash on Delivery (COD).
    3. Click Activate Cash on Delivery (COD).
  4. Add a metaobject in your store admin:

    1. Navigate to Content > Metaobjects.
    2. Click on Payment Messages.
    3. Click Add entry.
    4. Select paymentOnDelivery, enter a message, and click Save.
  5. 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)

  1. If needed, ensure app dev is still running for your app.

    1. In a terminal window, cd to the app's path.
    2. Run shopify app dev --use-localhost

    When prompted, select the store you created for this workshop.

  2. 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 = 0
    access.admin = "merchant_read_write"

    [product.metafields.app.max_quantity_message]
    type = "single_line_text_field"
    access.admin = "merchant_read_write"
  3. Check your terminal to see what's happened when we added these.

    • A missing scope again! Let's fix it.
  4. Add write_products to the scopes field in shopify.app.toml.

  5. Pick a product in admin and open its metafields.

    1. Open your store admin.
    2. Click on Products in the main navigation.
    3. Select The Collection Snowboard: Liquid.
    4. Scroll down to Metafields and click View all.
  6. These new metafields don't look great! Let's add a name in shopify.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"
  7. Refresh your store admin and fill in values for both metafields.

    • Max Purchase Quantity should have a value of 2 or more.
  8. Next let's create a Function which validates the cart based on these metafields. In a separate terminal (keep app dev running), run the following command:

    Terminal

    shopify app generate extension --template cart_checkout_validation --name max-quantity --flavor vanilla-js
  9. Activate the function via Checkout Settings:

    1. Open your store admin and navigate to Settings > Checkout.
    2. Scroll down to Checkout rules and click Add rule.
    3. Select max-quantity.
    4. Click Save.
    5. Click Turn on.
  10. The default template code for Checkout Validations should prevent product quantities greater than 1. To test this on your store:

    1. Open the storefront of your dev store.

    2. Under Featured products, click on The Collection Snowboard: Liquid.

    3. Click Add to cart.

    4. Attempt to increase the quantity of the product you just added.

      You should see a validation error.

    5. Open your terminal where app dev is running and review the execution logs for your function.

  11. Now let's update the function to use the metafield we created.

    1. Update the functions's input query in extensions/max-quantity/src/run.graphql to fetch the metafield:

      run.graphql

      query RunInput {
      cart {
      lines {
      quantity
      merchandise {
      __typename
      ... on ProductVariant {
      product {
      maxQuantity: metafield(namespace: "$app", key: "max_purchase_quantity") {
      value
      }
      maxQuantityMessage: metafield(namespace: "$app", key: "max_quantity_message") {
      value
      }
      }
      }
      }
      }
      }
      }
    2. Update the function code in extensions/max-quantity/src/run.js to 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
      }
      };
  12. Test your new function logic:

    1. Open your storefront again and navigate to the cart.

    2. Adjust the quantity of The Collection Snowboard: Liquid.

      You should see the validation message that you entered in the metafield.

    3. Open your terminal where app dev is 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.

  1. 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"
  2. Add write_customers to the scopes field in shopify.app.toml.

  3. Create a customer for yourself in admin and set the metafield value:

    1. Open your store admin.
    2. Click on Customers in the main navigation.
    3. Click Add customer.
    4. Enter details for a customer, using your own email address, and click Save.
    5. Under Metafields, click View all.
    6. For Allow Purchase Over Max, select True and click Save.
  4. Update your function to use this metafield:

    1. Update the functions's input query in extensions/max-quantity/src/run.graphql to fetch the metafield:

      run.graphql

      # Add directly under `cart`
      buyerIdentity {
      customer {
      allowPurchaseOverMax: metafield(namespace: "$app", key: "allow_purchase_over_max") {
      value
      }
      }
      }
    2. Update the function code in extensions/max-quantity/src/run.js to early return based on the metafield value:

      run.js

      // Add to the start of the `run` function
      const allowPurchaseOverMax = Boolean(input.cart.buyerIdentity?.customer?.allowPurchaseOverMax?.value) ?? false;
      if (allowPurchaseOverMax) {
      return {
      errors: [],
      }
      }
  5. Test your new function logic:

    1. Open your storefront again and click on the account icon in the header.
    2. Enter your email address and the emailed login code.
    3. Return to the Shop and open your cart.
    4. Confirm your user can add more of The Collection Snowboard: Liquid than is configured on the product.
    5. Open your terminal where app dev is running and review the execution logs for your function.

🎉 CONGRATULATIONS! You made it to the end! Great work!


Was this page helpful?