<Repo extension="rust" href="https://github.com/Shopify/example-discount-function-settings-extension" />
<Repo extension="javascript" href="https://github.com/Shopify/example-discount-function-settings-extension" />
<Picker name="extension">
<PickerOption name="javascript" />
<PickerOption name="rust" />
</Picker>
<Overview>
You can create custom UI elements that allow merchants to configure your discount function metafield values from the **Discount details** page.
![An image showing the discount details Admin UI extension.](/assets/apps/discounts/discount-ui-extension.png)
## What you'll learn
In this tutorial, you'll learn how to:
* Create an extension-only app hosted by Shopify without ongoing hosting needs
* Build a discount function that applies percentage discounts while excluding specific collections
* Create and connect an Admin UI extension for merchant configuration
* Configure app-scoped metafields to store discount settings
* Set up the complete integration between the Admin UI extension and discount function
The end result will be a discount that offers a percentage off all products except those in specific collections (for example, excluding items already on sale).
</Overview>
<Requirements>
<Requirement
href="https://www.shopify.com/partners"
label="Create a Partner account"
/>
<Requirement
href="/docs/apps/tools/development-stores#create-a-development-store-to-test-your-app"
label="Create a development store"
/>
<Requirement href="/docs/apps/getting-started/create" label="Install Shopify CLI">
Install Shopify CLI using version [3.67 or
higher](/docs/api/shopify-cli#upgrade).
</Requirement>
<Requirement href="https://nodejs.org/en/download" label="Install Node.js">
Install Node.js 16 or higher.
</Requirement>
<If extension="rust">
<Requirement href="https://www.rust-lang.org/tools/install" label="You've installed rust">
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.
</Requirement>
<Requirement
href="https://bytecodealliance.github.io/cargo-wasi/install"
label="You've installed or upgraded to the latest version of cargo-wasi"
>
To ensure compatibility with the latest version of Rust, install or update to
the latest version of cargo-wasi by running `cargo install cargo-wasi`.
</Requirement>
</If>
</Requirements>
<StepSection>
<Step>
## Create an app
<Substep>
<Codeblock terminal>
```bash
shopify app init
```
</Codeblock>
When prompted, select `Build an extension-only app`.
</Substep>
</Step>
<Step>
## Build a discount function
Discount Admin UI extensions allow developers to create a UI for merchants to configure metafields. The discount function that you'll create in this step will use the metafields as input to define discount logic. A discount Admin UI extension requires a discount function extension.
Here you'll create a percentage discount that excludes specific collections from receiving a discount.
To do this, you need to do the following:
- Update your discount function
- Create a discount with a metafield that stores the IDs of the excluded collections
<Substep>
### Scaffold the discount function.
<If extension="javascript">
<Codeblock terminal>
```bash
shopify app generate extension --template product_discounts --name product-discount-js --flavor vanilla-js
```
</Codeblock>
</If>
<If extension="rust">
<Codeblock terminal>
```bash
shopify app generate extension --template product_discounts --name product-discount-rust --flavor rust
```
</Codeblock>
</If>
</Substep>
<Substep>
### Copy and update the `run.graphql` file to query the metafield
<CodeRef extension="rust" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-rust/src/run.graphql" tag="product-discount.configuration" />
<CodeRef extension="javascript" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-js/src/run.graphql" tag="discount-ui-extension.configuration" />
Add `discountNode.metafield` for dynamic function configuration with `collectionIds` and `percentage` fields. Include `merchandise.product.inAnyCollection` to check if products belong to specified collections.
---
Ensure the namespace is `$app:example-discounts--ui-extension`. The query should dynamically fetch the function configuration and include necessary product and collection details.
</Substep>
<If extension="javascript">
<Substep>
From the `extensions/product-discount-js` directory, run the following command to regenerate types based on your input query:
<Codeblock terminal>
```bash
shopify app function typegen
```
</Codeblock>
</Substep>
</If>
<Substep>
<If extension="javascript">
### Copy and update the `run.js` file to return the percentage discount and exclude certain collections
</If>
<If extension="rust">
### Copy and update the `run.rs` file to return the percentage discount and exclude certain collections
</If>
<CodeRef extension="rust" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-rust/src/run.rs" tag="discount-ui-extension.run-configuration" />
<CodeRef extension="javascript" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-js/src/run.js" tag="discount-ui-extension.run-configuration" />
<If extension="javascript">
In the `run.js` file, update the `run` method to calculate the percentage discount while excluding certain collections.
</If>
<If extension="rust">
In the `run.rs` file, update the `run` method to calculate the percentage discount while excluding certain collections.
</If>
</Substep>
<Substep>
### Update the function configuration
<CodeRef extension="rust" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-rust/shopify.extension.toml" tag="discount-ui-extension.configuration" />
<CodeRef extension="javascript" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-js/shopify.extension.toml" tag="discount-ui-extension.configuration" />
<If extension="javascript">
The function's `.toml` file stores its static configuration. To ensure the connection to the Admin UI extension and the metafield definition,
validate that the `extensions.ui` property is set to `handle = "product-discount-js-block"` and make sure that
the `extensions.input.variables` is set to the same namespace and key as the metafield definition. You'll create the Admin UI extension with the correct handle in subsequent steps.
</If>
<If extension="rust">
The function's `.toml` file stores its static configuration. To ensure the connection to the Admin UI extension and the metafield definition,
validate that the `extensions.ui` property is set to `handle = "product-discount-rust-block"` and make sure that
the `extensions.input.variables` is set to the same namespace and key as the metafield definition. You will create the Admin UI extension with the correct handle in subsequent steps.
</If>
</Substep>
<Substep>
### Update your extension-only app's access scopes
<CodeRef extension="rust" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/shopify.app.toml" tag="discount-ui-extension-app.access_scopes" />
<CodeRef extension="javascript" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/shopify.app.toml" tag="discount-ui-extension-app.access_scopes"/>
You must request the `read_products,write_discounts` [access scopes](/docs/api/usage/access-scopes) to give your app the permissions that it needs to read products and write discounts.
In `shopify.app.toml`, located in the root of your app, add the `read_products,write_discounts` scopes to the `access_scopes`.
</Substep>
</Step>
<Step>
## Build the discount function settings Admin UI extension
Now you'll create and configure a discount Admin UI extension to allow merchants to configure their discounts.
The Admin UI extension will allow merchants to select the percentage off that they want to apply, and any collection(s) they want to exclude from the discount.
<Substep>
### Scaffold an Admin UI extension using a template
Create a discount details function settings extension using Shopify CLI. Shopify CLI provides a template that you can use to scaffold a new extension within your app.
<If extension="rust">
<Codeblock terminal>
```bash
shopify app generate extension --template discount_details_function_settings --name product-discount-rust-block --flavor react
```
</Codeblock>
</If>
<If extension="javascript">
<Codeblock terminal>
```bash
shopify app generate extension --template discount_details_function_settings --name product-discount-js-block --flavor react
```
</Codeblock>
</If>
</Substep>
<Substep>
### Copy the `DiscountFunctionSettings.jsx` file
<CodeRef extension="rust" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-rust-block/src/DiscountFunctionSettings.jsx" tag="discount-ui-extension.ui-extension" />
<CodeRef extension="javascript" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-js-block/src/DiscountFunctionSettings.jsx" tag="discount-ui-extension.ui-extension" />
Replace the code in your `DiscountFunctionSettings.jsx` file with the code from the example. Once you've done this, you'll be ready to move on to the next step, review each section of the code to understand how it works in the following steps.
</Substep>
<Substep>
### Review the target configuration in the `DiscountFunctionSettings.jsx` file
<CodeRef extension="rust" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-rust-block/src/DiscountFunctionSettings.jsx" tag="discount-ui-extension.target" />
<CodeRef extension="javascript" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-js-block/src/DiscountFunctionSettings.jsx" tag="discount-ui-extension.target" />
Review the target configuration in the `DiscountFunctionSettings.jsx` file to render the extension on the **Discount details** page in Shopify admin.
</Substep>
<Substep>
### Review the target configuration in your UI extension's `.toml` file
<CodeRef extension="rust" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-rust-block/shopify.extension.toml" tag="discount-ui-extension.target-configuration" />
<CodeRef extension="javascript" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-js-block/shopify.extension.toml" tag="discount-ui-extension.target-configuration" />
<Notice type="info">
The extension target in the component export must match the target defined in your Admin UI extension's `.toml` file.
</Notice>
</Substep>
<Substep>
<CodeRef extension="rust" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-rust-block/src/DiscountFunctionSettings.jsx" tag="discount-ui-extension.ui-components" />
<CodeRef extension="javascript" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-js-block/src/DiscountFunctionSettings.jsx" tag="discount-ui-extension.ui-components" />
### Use UI components
Admin UI extensions are rendered using [Remote UI](https://github.com/Shopify/remote-dom/tree/remote-ui), which is a fast and secure remote-rendering framework. Because Shopify renders the UI remotely, components used in the extensions must comply with a contract in the Shopify host. We provide these components through the Admin UI extensions library.
<Resources>
[Admin UI extensions components](/docs/api/admin-extensions/components)
</Resources>
</Substep>
<Substep>
### Review the `App` component
<CodeRef extension="rust" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-rust-block/src/DiscountFunctionSettings.jsx" tag="discount-ui-extension.app-component" />
<CodeRef extension="javascript" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-js-block/src/DiscountFunctionSettings.jsx" tag="discount-ui-extension.app-component" />
The `App` component is the main component of the extension. It renders a form with a percentage field and a collections section. It uses the `useExtensionData` hook to manage the state of the form and the collections section.
</Substep>
<Substep>
### Review the `CollectionsSection` component
<CodeRef extension="rust" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-rust-block/src/DiscountFunctionSettings.jsx" tag="discount-ui-extension.collections-section" />
<CodeRef extension="javascript" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-js-block/src/DiscountFunctionSettings.jsx" tag="discount-ui-extension.collections-section" />
The `CollectionsSection` component renders the collections section, displaying the selected collections and providing a button to add or remove collections. It uses the [App Bridge resource picker API](https://shopify.dev/docs/api/app-bridge-library/apis/resource-picker) to select collections.
</Substep>
<Substep>
### Review `useExtensionData` hook to manage state
<CodeRef extension="rust" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-rust-block/src/DiscountFunctionSettings.jsx" tag="discount-ui-extension.use-collection" />
<CodeRef extension="javascript" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-js-block/src/DiscountFunctionSettings.jsx" tag="discount-ui-extension.use-collection" />
The `useExtensionData` hook manages the state of the form and collections section. It uses the `useApi` hook to interact with the GraphQL Admin API. The `useEffect` hook is used to set the initial state of the form and collections section.
</Substep>
<Substep>
### To hide the `CollectionsField` component, you can use a `Box` component with the `display="none"` property.
<CodeRef extension="rust" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-rust-block/src/DiscountFunctionSettings.jsx" tag="discount-ui-extension.collections-field" />
<CodeRef extension="javascript" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-js-block/src/DiscountFunctionSettings.jsx" tag="discount-ui-extension.collections-field" />
The `Box` component can be used to hide inputs that you don't want to display. In this case, you're using `CollectionsField` to keep track of the selected collections and trigger the **save bar in discount details** when selections are changed through the resource picker.
</Substep>
<Substep>
### Review app-scoped metafield definition creation
<CodeRef extension="rust" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-rust-block/src/DiscountFunctionSettings.jsx" tag="discount-ui-extension.metafields" />
<CodeRef extension="javascript" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-js-block/src/DiscountFunctionSettings.jsx" tag="discount-ui-extension.metafields" />
For security reasons, it's mandatory to create a metafield definition under a reserved namespace. Reserved metafield definitions ensure that metafield definitions are private to the app and any additional permissions must be set manually. Because this Admin UI extension will work in the Shopify admin, you'll need to request these permissions at app installation time.
<Notice type="info" title="App scoped metafield definition">
Learn more about [metafield definitions under an app-scoped namespace](/docs/apps/build/custom-data/reserved-prefixes#create-a-metafield-definition-under-a-reserved-namespace)
</Notice>
</Substep>
<Substep>
### To apply the extension metafield change, use the `applyExtensionMetafieldChange` function.
<CodeRef extension="rust" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-rust-block/src/DiscountFunctionSettings.jsx" tag="discount-ui-extension.apply-extension-metafield-change" />
<CodeRef extension="javascript" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-js-block/src/DiscountFunctionSettings.jsx" tag="discount-ui-extension.apply-extension-metafield-change" />
This function applies the changes to the discount metafield, and will be saved when merchants create the discount in the Shopify admin.
</Substep>
</Step>
<Step>
### Update the function and app names
![An image showing the discount list DiscountCreationModal with the app name, function name and description.](/assets/apps/discounts/discount-function-name.png)
<Substep>
<CodeRef extension="rust" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-rust/locales/en.default.json" />
<CodeRef extension="javascript" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-js/locales/en.default.json" />
Update the discount function's `locales/en.default.json` file to configure the function name and description which will appear in the `DiscountCreationModal` in the `DiscountList` component.
</Substep>
<Substep>
<CodeRef extension="rust" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/shopify.app.toml" tag="discount-ui-extension-app.app_name" />
<CodeRef extension="javascript" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/shopify.app.toml" tag="discount-ui-extension-app.app_name" />
Update the app name in the `shopify.app.toml` file. This will display in the `DiscountCreationModal` in the `DiscountList` component.
</Substep>
<Substep>
![An image showing the discount details ExtensionCard with the extension name and description.](/assets/apps/discounts/discount-extension-name.png)
<CodeRef extension="rust" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-rust-block/locales/en.default.json" />
<CodeRef extension="javascript" href="https://github.com/Shopify/example-discount-function-settings-extension/blob/main/extensions/product-discount-js-block/locales/en.default.json" />
Update the UI extension's `locales/en.default.json` file to configure the name and description which will appear in the extension block in DiscountDetails.
</Substep>
</Step>
<Step>
## Deploy your app to Shopify
For access scopes to be published to your development store, you'll need to deploy the app and install the app on your development store.
<Substep>
### Register the new function names and scopes with Shopify by deploying the app
<Codeblock terminal>
```bash
shopify app deploy
```
</Codeblock>
</Substep>
<Substep>
### Start your dev server
Shopify CLI creates a tunnel using Cloudflare to expose your local development server to the internet so that you can test your app as you make changes. Learn more about the [Shopify CLI dev command](/docs/api/shopify-cli/app/app-dev).
<Codeblock terminal>
```bash
shopify app dev
```
</Codeblock>
</Substep>
<Substep>
### Install the app
You'll now install the app.
1. In the same terminal window running `shopfiy app dev`, press `p` to open the developer console.
1. In the developer console page, click on the **App home** link to install the app.
</Substep>
</Step>
<Step>
## Create a discount using your app
1. From the terminal where your dev server is running press `p`.
1. Click on the preview link for the `admin-discount-details.function-settings.render` extension target. This opens the discount details page.
1. You should see your extension that allows you to configure the discount.
1. Configure your discount, and click **Save**.
You should see a new discount in the discounts list.
</Step>
<Step>
## Test your discount
1. Checkout with products from the excluded collection.
No **discount** should be applied to the cart.
1. Add a product that is **not** from the excluded collection.
The **discount** should now be applied to the cart.
1. To debug your function, or view its output, you can review its logs in your [Partner Dashboard](https://partners.shopify.com/current/apps):
- Log in to your Partner Dashboard and navigate to **Apps** > "your app" > **Extensions** > **product-discount**.
- Click on any function run to view its input, output, and any logs written to `STDERR`.
</Step>
</StepSection>
<NextSteps>
## Next Steps
<CardGrid>
<LinkCard href="/docs/apps/build/functions">
#### Learn more about Shopify Functions
Learn more about how Shopify Functions work and the benefits of using Shopify Functions.
</LinkCard>
<LinkCard href="/docs/api/functions">
#### Consult the Shopify Functions API references
Consult the API references for Shopify Functions
</LinkCard>
<LinkCard href="/docs/apps/build/discounts/ux-for-discounts">
#### Review the UX guidelines
Review the UX guidelines to learn how to implement discounts in user interfaces.
</LinkCard>
<LinkCard href="/docs/apps/launch/deployment/deploy-app-versions">
#### Learn more about deploying app versions
Learn more about deploying app versions to Shopify
</LinkCard>
</CardGrid>
</NextSteps>