--- title: Create an admin UI extension for a cart and checkout validation function description: Use Shopify Functions to limit a cart's item quantity during checkout according to the merchant's preferences. source_url: html: https://shopify.dev/docs/apps/build/checkout/cart-checkout-validation/create-admin-ui-validation?extension=polaris md: https://shopify.dev/docs/apps/build/checkout/cart-checkout-validation/create-admin-ui-validation.md?extension=polaris --- # Create an admin UI extension for a cart and checkout validation function Imagine you're selling limited edition sneakers with only two pairs per customer, or managing inventory for high-demand products where bulk purchases could leave other customers empty-handed. Cart and checkout validation helps merchants enforce these types of business rules automatically, ensuring fair access while protecting their brand reputation. In this tutorial, you'll build a complete validation system using [Shopify Functions](https://shopify.dev/docs/api/functions/current) and [admin UI extensions](https://shopify.dev/docs/api/admin-extensions). Your validation function will enforce product quantity limits during checkout, while your admin UI will give merchants an intuitive interface to configure these limits for each product variant. Follow along with this tutorial to build a sample app, or clone the completed sample app. Note Errors from validation functions are exposed to the Storefront API's [`Cart`](https://shopify.dev/docs/storefronts/headless/building-with-the-storefront-api/cart/manage) object, in themes using the [`cart` template](https://shopify.dev/docs/storefronts/themes/architecture/templates/cart), and during checkout. ![A checkout with an error about a product quantity that is too high](https://cdn.shopify.com/shopifycloud/shopify-dev/production/assets/assets/images/apps/checkout/validation/checkout-error-screenshot-BK_cONmy.png) ## What you'll learn In this tutorial, you'll learn how to do the following: * Create a cart validation function that enforces quantity limits during checkout * Build an admin UI extension that gives merchants an intuitive interface for configuring validation rules * Use metafields to store and retrieve validation configuration data * Link validation functions with admin UI extensions using TOML configuration * Test validation in both the checkout UI and GraphQL Storefront API * Deploy a complete validation system to production stores ## Requirements [Create a Partner account](https://www.shopify.com/partners) [Create a development store](https://shopify.dev/docs/apps/tools/development-stores#create-a-development-store-to-test-your-app) [Create an app](https://shopify.dev/docs/apps/getting-started/create) Create an that has the `read_products` [access scopes](https://shopify.dev/docs/api/usage/access-scopes), using the [latest version of Shopify CLI](https://shopify.dev/docs/api/shopify-cli#upgrade). [Install Node.js](https://nodejs.org/en/download) Install Node.js 22 or higher. [You've installed rust](https://www.rust-lang.org/tools/install) On Windows, Rust requires the [Microsoft C++ Build Tools](https://docs.microsoft.com/en-us/windows/dev-environment/rust/setup). Make sure to select the **Desktop development with C++** workload when installing the tools. [You've installed the `wasm32-unknown-unknown` target](https://doc.rust-lang.org/rustc/platform-support/wasm32-unknown-unknown.html) To ensure compatibility with the latest version of Rust, install the `wasm32-unknown-unknown` target. ## Project Polaris [View on GitHub](https://github.com/Shopify/example-checkout--validation--preact) ## Create the validation function The validation function is the core logic that runs during checkout to enforce quantity limits. It reads the merchant's configuration from metafields and compares cart item quantities against the set limits, returning validation errors when limits are exceeded. In this step, you'll use Shopify CLI to [generate](https://shopify.dev/docs/api/shopify-cli/app/app-generate-extension) a starter function, define the data inputs using an [input query](https://shopify.dev/docs/apps/build/functions/input-queries/metafields-for-input-queries), and implement the validation logic using JavaScript or Rust. 1. Navigate to your app directory: ## Terminal ```terminal cd ``` 2. Run the following command to create a new validation extension: ## Terminal ```terminal shopify app generate extension --template cart_checkout_validation --name cart-checkout-validation ``` Tip Shopify Functions support any language that compiles to WebAssembly (Wasm), such as Rust, AssemblyScript, or TinyGo. You specify the Wasm template option when you're using a language other than Rust and can conform to the Wasm API. [Learn more about the Wasm API](https://shopify.dev/docs/apps/build/functions/programming-languages/webassembly-for-functions). 1. Choose the language that you want to use. For this tutorial, you should select either **Rust** or **JavaScript**. Shopify defaults to Rust as the most performant and recommended language choice to stay within the platform limits. For more information, refer to [language considerations](https://shopify.dev/docs/apps/build/functions/programming-languages). ## Terminal ```terminal ? What would you like to work in? > (1) Rust (2) JavaScript (3) TypeScript (4) Wasm ``` 1) Navigate to the `extensions/cart-checkout-validation` directory: ## Terminal ```terminal cd extensions/cart-checkout-validation ``` ### Define the Graph​QL input query Replace the contents of `src/cart_validations_generate_run.graphql` file with the following code. The `cart_validations_generate_run.graphql` file defines the input for the function. You need to retrieve the quantity and merchandise ID of the current cart lines. Tip [Metafields](https://shopify.dev/docs/apps/build/custom-data) allow your app to store custom data related to the validation function. Using the [`$app` reserved prefix](https://shopify.dev/docs/apps/build/custom-data/ownership#reserved-prefixes) makes the metafield private to your app. ### Generate Type​Script types (Java​Script only) If you're using JavaScript, then run the following command to regenerate types based on your input query: ## Terminal ```terminal shopify app function typegen ``` ### Implement the validation logic Replace the `src/cart_validations_generate_run.rs` file with the following code. The function logic checks that the quantity of each cart line isn't above the quantity set in the configuration metafield. You can configure the quantity limits for each product variant using the admin UI extension that you will create in the next step. Tip You can associate a validation error with a specific checkout UI field, or a global error by specifying the `target` property. The `target` property follows the pattern that's provided in the [Cart and Checkout Validation API reference](https://shopify.dev/docs/api/functions/reference/cart-checkout-validation/graphql#supported-checkout-field-targets). For example, using the global target `$.cart` will result in a global error at the top of checkout. ### Build the function If you're using Rust, then build the function's Wasm module: ## Terminal ```terminal cargo build --target=wasm32-wasip1 --release ``` Tip If you encounter any errors, then ensure that you've [installed Rust and the `wasm32-wasip1` target](#requirements). ## Create the validation user interface in admin Now that you have a validation function that can read configuration data, you need to give merchants a way to set those configurations. The admin UI extension provides an intuitive interface in the Shopify admin where merchants can set quantity limits for each product variant. The following steps show how to build an [admin UI extension](https://shopify.dev/docs/api/admin-extensions) that enables merchants to configure your validation function. The interface will display a table of products and variants, with input fields for setting quantity limits. ![Admin UI extension for configuring the validation function](https://cdn.shopify.com/shopifycloud/shopify-dev/production/assets/assets/images/apps/checkout/validation/admin-ui-extension-screenshot-854NUCCy.png) 1. Navigate to your app directory: ## Terminal ```terminal cd ``` 2. Run the following command to create a new validation rule UI extension: ## Terminal ```terminal shopify app generate extension --template validation_settings_ui --name validation-settings ``` 3. Navigate to the `extensions/validation-settings` directory: ## Terminal ```terminal cd extensions/validation-settings ``` ### Implement the admin UI extension Replace the validation settings UI code with the linked code. ## Link the user interface to the validation function With both the function and extension built, you need to connect them so that the admin UI can configure the validation function. This connection is established through TOML configuration files that specify which UI extension manages which function. ### Reference the admin UI extension's handle name The handle name is in the `shopify.extension.toml` file for the admin UI extension. ## /extensions/product-limits-function-ui/shopify.extension.toml ```toml api_version = "2025-10" [[extensions]] name = "t:name" handle = "product-limits-function-ui" type = "ui_extension" uid = "81136fe0-112a-9eba-b3e3-1e8d47ffaec836dff13a" [[extensions.targeting]] module = "./src/ValidationSettings.jsx" target = "admin.settings.validation.render" ``` ### Configure the validation function extension Update the `shopify.extension.toml` file in the validation function directory to reference the UI extension's handle from the [previous step](#reference-the-admin-ui-extensions-handle-name). ## /extensions/product-limits-function/shopify.extension.toml ```toml api_version = "2025-10" [[extensions]] name = "t:name" handle = "product-limits-function" type = "function" uid = "9beb9559-a34a-eca2-aa94-2cf3baa77d52afcba76e" description = "t:description" [extensions.ui] handle = "product-limits-function-ui" [[extensions.targeting]] target = "cart.validations.generate.run" input_query = "src/cart_validations_generate_run.graphql" export = "cart_validations_generate_run" [extensions.build] command = "cargo build --target=wasm32-wasip1 --release" path = "target/wasm32-wasip1/release/cart-checkout-validation.wasm" watch = ["src/**/*.rs"] [access_scopes] scopes = "read_products" ``` ### Configure app scopes Make sure that the `shopify.app.toml` file in your app root folder has the `read_products` access scope. Note If you're adding new access scopes to an existing app, then you need to redeploy and reinstall the app on the store by running `shopify app deploy`. ## /extensions/product-limits-function/shopify.extension.toml ```toml api_version = "2025-10" [[extensions]] name = "t:name" handle = "product-limits-function" type = "function" uid = "9beb9559-a34a-eca2-aa94-2cf3baa77d52afcba76e" description = "t:description" [extensions.ui] handle = "product-limits-function-ui" [[extensions.targeting]] target = "cart.validations.generate.run" input_query = "src/cart_validations_generate_run.graphql" export = "cart_validations_generate_run" [extensions.build] command = "cargo build --target=wasm32-wasip1 --release" path = "target/wasm32-wasip1/release/cart-checkout-validation.wasm" watch = ["src/**/*.rs"] [access_scopes] scopes = "read_products" ``` ## Test the validation on your development store Testing your validation system involves verifying both the admin UI configuration and the checkout validation logic. You'll test the complete flow: configuring limits in the admin, adding items to cart, and seeing validation errors during checkout. Run your development server and test both components on your development store. You can validate the system through the checkout UI, Storefront API, and by testing edge cases like missing configurations. ### Setup 1. If you're developing a function in a language other than JavaScript or TypeScript, ensure you have configured `build.watch` in your [function extension configuration](https://shopify.dev/docs/api/functions/configuration#properties). 1) Navigate back to your app root: ## Terminal ```terminal cd ../.. ``` 1. Use the Shopify CLI [`dev` command](https://shopify.dev/docs/api/shopify-cli/app/app-dev) to start app preview: ## Terminal ```terminal shopify app dev ``` You can keep the preview running as you work on your function. When you make changes to a watched file, Shopify CLI rebuilds your function and updates the function extension's drafts, so you can immediately test your changes. 2. Follow the CLI prompts to preview your app on your development store. 1) From the Shopify admin, go to **Settings** > **Checkout**. 2) Under **Checkout rules**, click **Add rule**. A new page opens and displays a list of checkout rules. ![Adding a checkout rule via the Checkout settings page in the Shopify admin](https://cdn.shopify.com/shopifycloud/shopify-dev/production/assets/assets/images/apps/checkout/validation/admin-add-a-checkout-rule-CYMtLGpL.png) 3) Find the `cart-checkout-validation` function that you want to test and select it. 4) In the validation configuration, set the limit to five for each product variant. 5) Click **Save**, but don't turn on the validation yet. ### Using checkout 1. Before turning on the validation, create a cart that exceeds the quantity limit you set. For example, in your development store, create a cart with a quantity of 10 products. 2. Go back to the checkout rules page in the Shopify admin and enable this validation by clicking on **Turn on**. ![Configure a checkout rule via the Checkout settings page in the Shopify admin](https://cdn.shopify.com/shopifycloud/shopify-dev/production/assets/assets/images/apps/checkout/validation/admin-configure-a-checkout-rule-D0bFG4zq.png) 3. Optional: Control how checkout behaves when encountering runtime exceptions by selecting the validation under **Checkout rules** and toggling **Allow all customers to complete checkout**. 4. Complete a checkout in your online store and verify that the validation error message displays. ![A checkout with an error about a product quantity that is too high](https://cdn.shopify.com/shopifycloud/shopify-dev/production/assets/assets/images/apps/checkout/validation/checkout-error-screenshot-BK_cONmy.png) 5. Verify that checkout progress is blocked. Clicking the **Continue to shipping** button in 3-page checkout, or the **Pay now** button in 1-page checkout, shouldn't redirect the user. ### Using Graph​QL 1. You can also verify the validation is working properly through the Storefront API. After the validation is turned on, create a cart with the [`cartCreate`](https://shopify.dev/docs/api/storefront/latest/mutations/cartCreate) mutation: ## Create a cart ```graphql mutation cartCreate { cartCreate(input: { lines: [] }) { cart { id } } } ``` 2. Using the Storefront API [`cartLinesAdd`](https://shopify.dev/docs/api/storefront/latest/mutations/cartLinesAdd) mutation, confirm that the mutation's `userErrors` field contains the function's error message, and that executing the mutation was unsuccessful. ## Add line items to a cart ## GraphQL mutation ```graphql mutation cartCreate { cartCreate(input: { lines: [] }) { cart { id } } } ``` ## Output ```json { "data": { "cartLinesAdd": { "cart": null, "userErrors": [ { "code": "VALIDATION_CUSTOM", "field": [ "cartId" ], "message": "Orders are limited to a maximum of 5 of Monstera" } ] } } } ``` ### Testing edge cases To ensure your validation works reliably, test these common scenarios: 1. **No limits configured**: Add products to cart when no quantity limits have been set in the admin UI. The validation should pass without errors. 2. **Invalid limit values**: Try setting negative numbers or non-numeric values in the admin interface. The UI should handle these gracefully. 3. **Products without variants**: Test with products that only have a default variant to ensure the UI displays correctly. 4. **Large quantities**: Test with quantities at and just above the configured limits to verify exact boundary behavior. Troubleshooting ### Debugging using logs 1. Open your terminal where `shopify app dev` is running, and review your function executions. When [testing functions on development stores](https://shopify.dev/docs/apps/build/functions/test-debug-functions#test-your-function-on-a-development-store), the output of `dev` includes executions of your functions, any debug logging you have added to them, and a link to a local file with the full function execution details. 2. In a new terminal window, use the Shopify CLI [`app function replay`](https://shopify.dev/docs/api/shopify-cli/app/app-function-replay) command to [replay a function execution locally](https://shopify.dev/docs/apps/build/functions/test-debug-functions#execute-the-function-locally-using-shopify-cli), and debug your function without the need to re-trigger the function execution on Shopify. ## Terminal ```terminal shopify app function replay ``` 1. Select the function execution from the top of the list. Press `q` to quit when you are finished debugging. ### Common issues and solutions **Admin UI not loading**: Verify that your validation function's TOML file correctly references the UI extension handle, and that the UI extension was generated successfully. **Validation not triggering**: Check that the validation function is properly deployed and activated in your development store's checkout settings. **Configuration not saving**: Ensure your app has the `read_products` scope and that metafield definitions are created correctly. **Function logs not appearing**: Confirm your function is being executed by adding console output or using the debugging steps above. ## Deploy to production When you're ready to release your changes to users, you can create and release an [app version](https://shopify.dev/docs/apps/launch/deployment/app-versions). An app version is a snapshot of your app configuration and all extensions. 1. Navigate to your app directory. 2. Run the following command. Optionally, you can provide a name or message for the version using the `--version` and `--message` flags. ## Terminal ```terminal shopify app deploy ``` Releasing an app version replaces the current active version that's served to stores that have your app installed. It might take several minutes for app users to be upgraded to the new version. Tip If you want to create a version, but avoid releasing it to users, then run the `deploy` command with a `--no-release` flag. You can release the unreleased app version using Shopify CLI's [`release`](https://shopify.dev/docs/api/shopify-cli/app/app-release) command, or through the Dev Dashboard. ## /extensions/product-limits-function/shopify.extension.toml ```toml api_version = "2025-10" [[extensions]] name = "t:name" handle = "product-limits-function" type = "function" uid = "9beb9559-a34a-eca2-aa94-2cf3baa77d52afcba76e" description = "t:description" [extensions.ui] handle = "product-limits-function-ui" [[extensions.targeting]] target = "cart.validations.generate.run" input_query = "src/cart_validations_generate_run.graphql" export = "cart_validations_generate_run" [extensions.build] command = "cargo build --target=wasm32-wasip1 --release" path = "target/wasm32-wasip1/release/cart-checkout-validation.wasm" watch = ["src/**/*.rs"] [access_scopes] scopes = "read_products" ``` ## /extensions/product-limits-function/src/cart\_validations\_generate\_run.graphql ```graphql query Input { cart { lines { quantity merchandise { __typename ... on ProductVariant { id product { title } } } } } validation { metafield(namespace: "$app:product-limits", key: "product-limits-values") { jsonValue } } } ``` ## /extensions/product-limits-function/src/cart\_validations\_generate\_run.rs ```rust use super::schema; use shopify_function::prelude::*; use shopify_function::Result; use std::collections::HashMap; #[derive(Deserialize, Default, PartialEq)] pub struct Configuration { limits: HashMap, } #[shopify_function] fn run(input: schema::run::Input) -> Result { let mut operations = Vec::new(); let mut errors = Vec::new(); let configuration = if let Some(metafield) = input.validation().metafield() { metafield.json_value() } else { return Ok(schema::CartValidationsGenerateRunResult { operations: vec![] }); }; input.cart().lines().iter().for_each(|line| { let quantity = line.quantity(); match &line.merchandise() { schema::run::input::cart::lines::Merchandise::ProductVariant(variant) => { let limit = configuration.limits.get(variant.id()).unwrap_or(&i32::MAX); let product_name = variant.product().title(); // Check item quantity in the cart against the configured limit if quantity > limit { errors.push(schema::ValidationError { message: format!( "Orders are limited to a maximum of {} of {}", limit, product_name ), target: "cart".to_owned(), }); } } _ => {} }; }); let operation = schema::ValidationAddOperation { errors }; operations.push(schema::Operation::ValidationAdd(operation)); Ok(schema::CartValidationsGenerateRunResult { operations }) } ``` ## /extensions/product-limits-function-ui/src/ValidationSettings.jsx ```jsx import "@shopify/ui-extensions/preact"; import { render } from "preact"; import { useState } from "preact/hooks"; export default async () => { const existingDefinition = await getMetafieldDefinition(); if (!existingDefinition) { // Create a metafield definition for persistence if no pre-existing definition exists const metafieldDefinition = await createMetafieldDefinition(); if (!metafieldDefinition) { throw new Error("Failed to create metafield definition"); } } const configuration = JSON.parse( shopify.data.validation?.metafields?.[0]?.value ?? "{}" ); const products = await getProducts(); render( , document.body ); }; function Extension({ configuration, products }) { const [variantLimits, setVariantLimits] = useState(configuration); const [errors, setErrors] = useState([]); if (!products || products.length === 0) { return No products found.; } const shouldShowVariants = (variants) => { ``` ## /extensions/product-limits-function-ui/shopify.extension.toml ```toml api_version = "2025-10" [[extensions]] name = "t:name" handle = "product-limits-function-ui" type = "ui_extension" uid = "81136fe0-112a-9eba-b3e3-1e8d47ffaec836dff13a" [[extensions.targeting]] module = "./src/ValidationSettings.jsx" target = "admin.settings.validation.render" ``` ## Tutorial complete! Nice work - what you just built could be used by Shopify merchants around the world! Your validation system combines the power of Shopify Functions with intuitive admin configuration to help merchants enforce business rules at checkout. ### Next steps Now that you've built a complete validation system, explore these related topics to expand your app's capabilities: [![](https://shopify.dev/images/icons/32/undefined.png)![](https://shopify.dev/images/icons/32/undefined-dark.png)](https://shopify.dev/docs/apps/build/checkout/cart-checkout-validation) [Create other validation functions](https://shopify.dev/docs/apps/build/checkout/cart-checkout-validation) [![](https://shopify.dev/images/icons/32/undefined.png)![](https://shopify.dev/images/icons/32/undefined-dark.png)](https://shopify.dev/docs/apps/build/checkout/start-building) [Build checkout UI extensions](https://shopify.dev/docs/apps/build/checkout/start-building) [![](https://shopify.dev/images/icons/32/undefined.png)![](https://shopify.dev/images/icons/32/undefined-dark.png)](https://shopify.dev/docs/apps/build/custom-data) [Advanced metafield patterns](https://shopify.dev/docs/apps/build/custom-data) [![](https://shopify.dev/images/icons/32/undefined.png)![](https://shopify.dev/images/icons/32/undefined-dark.png)](https://shopify.dev/docs/api/functions) [Explore Shopify Functions](https://shopify.dev/docs/api/functions)