Merchants create and manage delivery customizations in the Shopify admin. Shopify uses [URLs that you configure](/docs/apps/build/functions/input-output/metafields-for-input-queries#creating-your-merchant-interface) to render the delivery customization creation and editing experience for the merchant. 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](/docs/apps/build/functions/input-output/metafields-for-input-queries#how-it-works).
- Configure the UI paths for your function.

## Requirements
- You've completed the [Add configuration to your delivery customization](/docs/apps/build/checkout/delivery-shipping/delivery-options/add-configuration) tutorial.
- You created your app with the [Remix app template](/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 delivery customization. The code renders a frontend page in your app and uses the GraphQL Admin API to create a delivery customization.
1. In `app/routes`, create a new file named `app.delivery-customization.$functionId.$id.jsx`.
The Shopify Remix 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://remix.run/docs/en/main/guides/routing#dynamic-segments). The path for this page is `/app/delivery-customization/{functionId}/{id}` .
1. Add the following code in `app.delivery-customization.$functionId.$id.jsx`:
- The [`loader`](https://remix.run/docs/en/main/route/loader) 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://remix.run/docs/en/main/route/action) function handles submitting the form data to Shopify to create the delivery customization.
- The `DeliveryCustomization` function renders the page and form components using [Polaris components](https://polaris.shopify.com/components) and [Remix hooks](https://remix.run/docs/en/main/hooks/use-navigation).
```javascript
import { useState, useEffect } from "react";
import {
Banner,
Card,
FormLayout,
Layout,
Page,
TextField,
} from "@shopify/polaris";
import {
Form,
useActionData,
useNavigation,
useSubmit,
useLoaderData,
} from "@remix-run/react";
import { json } from "@remix-run/node";
import { authenticate } from "../shopify.server";
export const loader = async ({ params, request }) => {
const { functionId, id } = params;
const { admin } = await authenticate.admin(request);
if (id != "new") {
const gid = `gid://shopify/DeliveryCustomization/${id}`;
const response = await admin.graphql(
`#graphql
query getDeliveryCustomization($id: ID!) {
deliveryCustomization(id: $id) {
id
title
enabled
metafield(namespace: "$app:delivery-customization", key: "function-configuration") {
id
value
}
}
}`,
{
variables: {
id: gid,
},
}
);
const responseJson = await response.json();
const deliveryCustomization = responseJson.data.deliveryCustomization;
const metafieldValue = JSON.parse(deliveryCustomization.metafield.value);
return {
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
stateProvinceCode: metafieldValue.stateProvinceCode,
message: metafieldValue.message,
}),
};
}
return {
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
stateProvinceCode: "",
message: "",
}),
};
};
export const action = async ({ params, request }) => {
const { functionId, id } = params;
const { admin } = await authenticate.admin(request);
const formData = await request.formData();
const stateProvinceCode = formData.get("stateProvinceCode");
const message = formData.get("message");
const deliveryCustomizationInput = {
functionId,
title: `Change ${stateProvinceCode} delivery message`,
enabled: true,
metafields: [
{
namespace: "$app:delivery-customization",
key: "function-configuration",
type: "json",
value: JSON.stringify({
stateProvinceCode,
message,
}),
},
],
};
if (id != "new") {
const response = await admin.graphql(
`#graphql
mutation updateDeliveryCustomization($id: ID!, $input: DeliveryCustomizationInput!) {
deliveryCustomizationUpdate(id: $id, deliveryCustomization: $input) {
deliveryCustomization {
id
}
userErrors {
message
}
}
}`,
{
variables: {
id: `gid://shopify/DeliveryCustomization/${id}`,
input: deliveryCustomizationInput,
},
}
);
const responseJson = await response.json();
const errors = responseJson.data.deliveryCustomizationUpdate?.userErrors;
return json({ errors });
} else {
const response = await admin.graphql(
`#graphql
mutation createDeliveryCustomization($input: DeliveryCustomizationInput!) {
deliveryCustomizationCreate(deliveryCustomization: $input) {
deliveryCustomization {
id
}
userErrors {
message
}
}
}`,
{
variables: {
input: deliveryCustomizationInput,
},
}
);
const responseJson = await response.json();
const errors = responseJson.data.deliveryCustomizationCreate?.userErrors;
return json({ errors });
}
};
export default function DeliveryCustomization() {
const submit = useSubmit();
const actionData = useActionData();
const navigation = useNavigation();
const loaderData = useLoaderData();
const [stateProvinceCode, setStateProvinceCode] = useState(loaderData.stateProvinceCode);
const [message, setMessage] = useState(loaderData.message);
useEffect(() => {
if (loaderData) {
const parsedData = JSON.parse(loaderData.body);
setStateProvinceCode(parsedData.stateProvinceCode);
setMessage(parsedData.message);
}
}, [loaderData]);
const isLoading = navigation.state === "submitting";
useEffect(() => {
if (actionData?.errors.length === 0) {
open('shopify:admin/settings/shipping/customizations', '_top')
}
}, [actionData?.errors]);
const errorBanner = actionData?.errors.length ? (
{actionData?.errors.map((error, index) => {
return