---
title: Build the UI for the payment customization function
description: >-
  Create a UI that merchants can use to configure your payment customization
  function.
source_url:
  html: 'https://shopify.dev/docs/apps/build/checkout/payments/build-ui'
  md: 'https://shopify.dev/docs/apps/build/checkout/payments/build-ui.md'
api_type: functions
---

# Build the UI for the payment customization function

Merchants create and manage payment customizations in the Shopify admin. To render the payment customization creation and editing experience for the merchant, Shopify uses [URLs that you configure](https://shopify.dev/docs/apps/build/functions/input-queries/metafields-for-input-queries#creating-your-merchant-interface). You can customize this UI for your function's configuration needs, or to meet other requirements of your app.

***

## What you'll learn

In this tutorial, you'll learn how to do the following tasks:

* Create an App Bridge UI that enables users to create a [function owner](https://shopify.dev/docs/apps/build/functions/input-queries/metafields-for-input-queries#how-it-works).
* Configure the UI paths for your function.

![Screenshot that shows the merchant UI for configuring the payment function](https://shopify.dev/assets/assets/images/apps/checkout/payment-customization-merchant-ui-CsMaywN8.png)

***

## Requirements

* You've completed the [Add configuration to your payment customization](https://shopify.dev/docs/apps/build/checkout/payments/add-configuration) tutorial.
* You created your app with the [React Router app template](https://shopify.dev/docs/api#app-templates).

***

## Step 1: Create the frontend UI for your function

The following example builds a React-based page that enables merchants to create and configure a new payment customization. The code renders a frontend page in your app and uses the GraphQL Admin API to create a payment customization.

1. In `app/routes`, create a new file named `app.payment-customization.$functionId.$id.jsx`.

   The Shopify React Router app template uses file-based routing, so the file name determines the page's URL. The `$` prefix indicates `functionId` and `id` are [dynamic segments](https://reactrouter.com/how-to/file-route-conventions#dynamic-segments). The path for this page is `/app/payment-customization/{functionId}/{id}` .

2. Add the following code in `app.payment-customization.$functionId.$id.jsx`:

   * The [`loader`](https://reactrouter.com/start/framework/data-loading#data-loading) function handles fetching the data to populate the form and is used when this page has an `id` value that is not `new`.
   * The [`action`](https://reactrouter.com/start/framework/actions) function handles submitting the form data to Shopify to create the payment customization.
   * The `PaymentCustomization` function renders the page and form components using [Polaris web components](https://shopify.dev/docs/api/app-home/web-components) and [React Router hooks](https://reactrouter.com/api/hooks/useNavigation#usenavigation).

   ## app/routes/app.payment-customization.$functionId.$id.jsx

   ```javascript
   import { useState, useEffect } from "react";
   import { useActionData, useNavigation, useSubmit, useLoaderData } from "react-router";
   import { boundary } from "@shopify/shopify-app-react-router/server";


   import { authenticate } from "../shopify.server";


   // This is a server-side function that provides data to the component when rendering.
   export const loader = async ({ params, request }) => {
   const { id } = params;


   // If the ID is `new`, then we are creating a new customization and there's no data to load.
   if (id === "new") {
     return {
       paymentMethodName: "",
       cartTotal: "0",
     };
   }


   const { admin } = await authenticate.admin(request);
   const response = await admin.graphql(
     `#graphql
       query getPaymentCustomization($id: ID!) {
         paymentCustomization(id: $id) {
           id
           metafield(namespace: "$app:payment-customization", key: "function-configuration") {
             value
           }
         }
       }`,
     {
       variables: {
         id: `gid://shopify/PaymentCustomization/${id}`,
       },
     }
   );


   const responseJson = await response.json();
   const metafield =
     responseJson.data.paymentCustomization?.metafield?.value &&
     JSON.parse(responseJson.data.paymentCustomization.metafield.value);


   return {
     paymentMethodName: metafield?.paymentMethodName ?? "",
     cartTotal: metafield?.cartTotal ?? "0",
   };
   };


   // This is a server-side action that is invoked when the form is submitted.
   // It makes an admin GraphQL request to create a payment customization.
   export const action = async ({ params, request }) => {
   const { functionId, id } = params;
   const { admin } = await authenticate.admin(request);
   const formData = await request.formData();


   const paymentMethodName = formData.get("paymentMethodName");
   const cartTotal = parseFloat(formData.get("cartTotal"));


   const paymentCustomizationInput = {
     functionId,
     title: `Hide ${paymentMethodName} if cart total is larger than ${cartTotal}`,
     enabled: true,
     metafields: [
       {
         namespace: "$app:payment-customization",
         key: "function-configuration",
         type: "json",
         value: JSON.stringify({
           paymentMethodName,
           cartTotal,
         }),
       },
     ],
   };


   // If the ID is `new`, then we're creating a new customization. Otherwise, we will use the update mutation.
   if (id === "new") {
     const response = await admin.graphql(
       `#graphql
         mutation createPaymentCustomization($input: PaymentCustomizationInput!) {
           paymentCustomizationCreate(paymentCustomization: $input) {
             paymentCustomization {
               id
             }
             userErrors {
               message
             }
           }
         }`,
       {
         variables: {
           input: paymentCustomizationInput,
         },
       }
     );


     const responseJson = await response.json();
     const errors = responseJson.data.paymentCustomizationCreate?.userErrors;


     return { errors };
   } else {
     const response = await admin.graphql(
       `#graphql
         mutation updatePaymentCustomization($id: ID!, $input: PaymentCustomizationInput!) {
           paymentCustomizationUpdate(id: $id, paymentCustomization: $input) {
             paymentCustomization {
               id
             }
             userErrors {
               message
             }
           }
         }`,
       {
         variables: {
           id: `gid://shopify/PaymentCustomization/${id}`,
           input: paymentCustomizationInput,
         },
       }
     );


     const responseJson = await response.json();
     const errors = responseJson.data.paymentCustomizationUpdate?.userErrors;


     return { errors };
   }
   };


   // Required for single fetch compatibility
   export const headers = (headersArgs) => {
   return boundary.headers(headersArgs);
   };


   // This is the client-side component that renders the form.
   export default function PaymentCustomization() {
   const submit = useSubmit();
   const actionData = useActionData();
   const navigation = useNavigation();
   const loaderData = useLoaderData();
   const [paymentMethodName, setPaymentMethodName] = useState(
     loaderData.paymentMethodName
   );
   const [cartTotal, setCartTotal] = useState(loaderData.cartTotal);


   const isLoading = navigation.state === "submitting";


   const errorBanner = actionData?.errors.length ? (
     <s-banner tone="critical" heading="There was an error creating the customization.">
       <ul>
         {actionData?.errors.map((error, index) => {
           return <li key={`${index}`}>{error.message}</li>;
         })}
       </ul>
     </s-banner>
   ) : null;


   const handleSubmit = (event) => {
     event.preventDefault();
     submit({ paymentMethodName, cartTotal }, { method: "post" });
   };


   const handleReset = () => {
     setPaymentMethodName(loaderData.paymentMethodName);
     setCartTotal(loaderData.cartTotal);
   };


   useEffect(() => {
     if (actionData?.errors.length === 0) {
       open("shopify:admin/settings/payments/customizations", "_top");
     }
   }, [actionData?.errors]);


   return (
     <form data-save-bar onSubmit={handleSubmit} onReset={handleReset}>
       <s-page title="Hide payment method">
         <button variant="breadcrumb" href="shopify:admin/settings/payments/customizations">
           Payment customizations
         </button>


         {errorBanner}


         <s-section>
           <s-grid gap="base">
             <s-text-field
               name="paymentMethodName"
               type="text"
               label="Payment method"
               value={paymentMethodName}
               onInput={(e) => setPaymentMethodName(e.target.value)}
               disabled={isLoading}
               autoComplete="on"
               required
             ></s-text-field>


             <s-number-field
               name="cartTotal"
               label="Cart total"
               value={cartTotal}
               onInput={(e) => setCartTotal(e.target.value)}
               disabled={isLoading}
               min="0"
               step="0.01"
               required
             ></s-number-field>
           </s-grid>
         </s-section>
       </s-page>
     </form>
   );
   }
   ```

***

## Step 2: Update your input query to use an app-owned namespace

In the [previous tutorial](https://shopify.dev/docs/apps/checkout/payments/config), you used a metafield namespace that was accessible to any app, so that the metafield namespace could be populated using GraphiQL. To make your function ready for production, you should update the metafield namespace to use a [reserved prefix](https://shopify.dev/docs/apps/build/custom-data/ownership#reserved-prefixes) so that other apps can't use your metafield.

Replace the code in the `extensions/payment-customization/src/run.graphql` file with the following code. The query differs slightly in Rust and JavaScript due to code generation requirements.

## run.graphql

## src/run.graphql

##### Rust input query

```graphql
query Input {
  cart {
    cost {
      totalAmount {
        amount
      }
    }
  }
  paymentMethods {
    id
    name
  }
  paymentCustomization {
    metafield(namespace: "$app:payment-customization", key: "function-configuration") {
      jsonValue
    }
  }
}
```

##### JavaScript input query

```graphql
query RunInput {
  cart {
    cost {
      totalAmount {
        amount
      }
    }
  }
  paymentMethods {
    id
    name
  }
  paymentCustomization {
    metafield(namespace: "$app:payment-customization", key: "function-configuration") {
      jsonValue
    }
  }
}
```

***

## Step 3: Configure the create UI path for your function

Settings in the `shopify.extension.toml` file define the URLs that Shopify uses for merchants to create and edit payment customizations using your function. Shopify automatically fills in any [dynamic tokens](https://shopify.dev/docs/apps/build/functions/input-queries/metafields-for-input-queries#dynamic-id-values) in these URLs.

In `extensions/payment-customization/shopify.extension.toml`, populate the two settings directly under `[ui.paths]`. This change is automatically reflected.

## extensions/payment-customization/shopify.extension.toml

```toml
create = "/app/payment-customization/function-handle/new"
details = "/app/payment-customization/function-handle/:id"
```

***

## Step 4: Update your app access scopes

You must request the `write_payment_customizations` [access scope](https://shopify.dev/docs/api/usage/access-scopes#write_payment_customizations) to invoke payment customization mutations in the Admin API.

1. In `shopify.app.toml` in the root of your app, add the `write_payment_customizations` scope.

   ## shopify.app.toml

   ```toml
   # This file stores configurations for your Shopify app.


   scopes = "write_products, write_payment_customizations"
   ```

2. Save your configuration file. If [`app dev`](https://shopify.dev/docs/api/shopify-cli/app/app-dev) is running, the scope changes will be applied automatically. If not, start the command:

   ## Terminal

   ```terminal
   shopify app dev
   ```

***

## Step 5: Create and test your payment customization

1. From the Shopify admin, go to **Settings** > **Payments**.

2. Under the **Payment customizations** section, click **Manage**.

3. If you have existing customizations from previous tutorials, then click the checkbox next to each of them, and then click **Deactivate**.

4. Click **Add a customization** and then click **payment-customization by {your app}**.

5. Fill in the payment customization form, then click **Save**.

   * For **Payment method**, use **Cash on Delivery**.
   * For **Cart total**, use **200**.

1) Open your development store and build a cart with a total (including shipping and tax) under 200. The **Cash on Delivery** payment method should display in checkout.
2) Add additional items to your cart to raise the total over 200. Your payment function should now hide the **Cash on Delivery** payment option.

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.

***

## Deploy your app

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.

***

## Next steps

* Learn more about how [Shopify Functions](https://shopify.dev/docs/api/functions/current) work and the benefits of using Shopify Functions.
* Consult the [API references for Shopify Functions](https://shopify.dev/docs/api/functions).
* Learn how to use [variables](https://shopify.dev/docs/apps/build/functions/input-queries/use-variables-input-queries) in your input query.

- Review the [UX guidelines](https://shopify.dev/docs/apps/build/checkout/payments/ux-for-payments) to learn how to implement payment customizations in user interfaces.

***
