---
title: Create the UI for a pre-order and Try Before You Buy (TBYB) app
description: >-
  Learn how to add functionality to your app that can help merchants create
  pre-orders.
source_url:
  html: >-
    https://shopify.dev/docs/apps/build/purchase-options/deferred/create-deferred-purchase-app/create-ui
  md: >-
    https://shopify.dev/docs/apps/build/purchase-options/deferred/create-deferred-purchase-app/create-ui.md
---

# Create the UI for a pre-order and Try Before You Buy (TBYB) app

Previously, you set up the foundation of your app. You're now ready to add app components that can help merchants create their pre-orders.

On the app page, you want to add functionality that enables merchants to do the following tasks:

* Set the pre-order name
* Configure the delivery, inventory, and billing policies for the pre-order
* Select products that will have the pre-order

You'll use [Polaris](https://shopify.dev/docs/api/polaris) and [App Bridge](https://shopify.dev/docs/api/app-home/web-components) to build the user interface. You'll use GraphQL Admin API mutations and queries to create and retrieve pre-orders.

At the end of this tutorial, you'll have an understanding on how to create pre-orders. This app will be simple, but you'll learn where to find resources to build more complex features on your own.

**Note:**

The sample app in this tutorial is assigned with a `PRE_ORDER` category. When you create your own app, you can assign a category based on the app's purpose. For more information, refer to [Selling plans category](https://shopify.dev/docs/apps/build/purchase-options/deferred#selling-plan-categories).

***

## What you'll learn

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

* Add new routes to your app's server that call the Shopify Admin API
* Add UI to create a pre-order
* Test your app on a dev store

This tutorial is not intended to give you a full example on how to build a pre-order application, but more a reference starter.

***

## Requirements

* Complete the [Getting started](https://shopify.dev/docs/apps/build/purchase-options/deferred/create-deferred-purchase-app/start-building) tutorial to set up the foundation of your app.
* Familiarize yourself with [selling plans](https://shopify.dev/docs/apps/build/purchase-options) and [pre-orders and try before you buy](https://shopify.dev/docs/apps/build/purchase-options/deferred).

***

## Step 1: Create the pre-order creation route

Create a file `app.create.jsx` under `app/routes`. This file will create the route `/app/create` that will let you create a pre-order.

## app/routes/app.create.jsx

## Create pre-order route

```jsx
import { Form, useActionData, useNavigation } from "react-router";
import { useState } from "react";
import { authenticate } from "../shopify.server";
import db from "../db.server";


export const action = async ({ request }) => {
  const { admin } = await authenticate.admin(request);
  const formData = await request.formData();


  const title = formData.get("title");
  const productId = formData.get("productId");
  const variantId = formData.get("variantId");
  const startDate = formData.get("startDate");
  const endDate = formData.get("endDate");


  if (!title || !productId || !variantId || !startDate || !endDate) {
    return json(
      { error: "All fields are required" },
      { status: 400 }
    );
  }


  try {
    await db.preOrder.create({
      data: {
        title,
        productId,
        variantId,
        startDate: new Date(startDate),
        endDate: new Date(endDate),
        shopDomain: admin.shop.domain,
      },
    });


    return { success: true };
  } catch (error) {
    return { error: error.message }, { status: 500 };
  }
};


export default function CreatePreOrder() {
  const nav = useNavigation();
  const actionData = useActionData();
  const [selectedProduct, setSelectedProduct] = useState(null);
  const [selectedVariant, setSelectedVariant] = useState(null);


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


  return (
    <div className="p-4 md:p-8">
      <h1 className="text-2xl font-bold mb-4">Create Pre-order</h1>
      <Form method="post">
        <div className="space-y-4">
          <div>
            <label htmlFor="title" className="block mb-2">
              Pre-order Title
            </label>
            <input
              type="text"
              id="title"
              name="title"
              className="w-full p-2 border rounded"
              required
            />
          </div>


          <div>
            <label htmlFor="startDate" className="block mb-2">
              Start Date
            </label>
            <input
              type="datetime-local"
              id="startDate"
              name="startDate"
              className="w-full p-2 border rounded"
              required
            />
          </div>


          <div>
            <label htmlFor="endDate" className="block mb-2">
              End Date
            </label>
            <input
              type="datetime-local"
              id="endDate"
              name="endDate"
              className="w-full p-2 border rounded"
              required
            />
          </div>


          <div>
            <label htmlFor="productId" className="block mb-2">
              Product
            </label>
            <input
              type="text"
              id="productId"
              name="productId"
              className="w-full p-2 border rounded"
              required
            />
          </div>


          <div>
            <label htmlFor="variantId" className="block mb-2">
              Variant
            </label>
            <input
              type="text"
              id="variantId"
              name="variantId"
              className="w-full p-2 border rounded"
              required
            />
          </div>


          {actionData?.error && (
            <div className="text-red-500">{actionData.error}</div>
          )}


          <button
            type="submit"
            className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 disabled:bg-blue-300"
            disabled={isLoading}
          >
            {isLoading ? "Creating..." : "Create Pre-order"}
          </button>
        </div>
      </Form>
    </div>
  );
}
```

This code creates a basic form to create pre-orders. The form includes fields for:

* Pre-order title
* Start date
* End date
* Product ID
* Variant ID

When submitted, it will:

1. Validate that all required fields are present
2. Create a new pre-order record in the database
3. Show success/error messages accordingly

**Info:**

For now, we're using simple text inputs for product and variant IDs. In a later step, we'll add proper product selection using the Shopify API.

***

## Step 2: Create app components

Create the following app components:

* [name](#create-the-name-component)
* [checkout charge](#create-the-checkout-charge-component)
* [product picker](#create-the-product-picker-component)

**Tip:**

We recommend keeping your app component files in the `app/components` folder.

### Create the name component

Create a component that enables merchants to set the pre-order name. The name component includes the following Polaris components:

* [Section](https://shopify.dev/docs/api/app-home/web-components/layout-and-structure/section)
* [Text field](https://shopify.dev/docs/api/app-home/web-components/forms/text-field)

![A screen capture showing the name component](https://shopify.dev/assets/assets/images/api/purchase-options/app-name-DY0s2FZy.png)

In your `app/routes/app.create.jsx` file, add the following code:

## app/routes/app.create.jsx

```jsx
import { useState } from "react";


export default function Index() {
  const [sellingPlanName, setSellingPlanName] = useState("");


  const handleSellingPlanNameChange = (newValue) => {
    setSellingPlanName(newValue);
  };


  return (
    <s-page>
      <ui-title-bar title="Create a pre-order" />
      <s-section heading="Pre-order details">
        <s-text-field
          label="Pre-order name"
          name="sellingPlanName"
          value={sellingPlanName}
          onChange={(e) => handleSellingPlanNameChange(e.target.value)}
          required
        />
      </s-section>
    </s-page>
  );
}
```

### Create the checkout charge component

When merchants create a pre-order, they can decide the initial charge when customers check out. The type of charge can be set with a `PERCENTAGE` or `PRICE` type. Learn more about [selling plan checkout charge types](https://shopify.dev/docs/api/admin-graphql/latest/enums/SellingPlanCheckoutChargeType) and [selling plan checkout charge values](https://shopify.dev/docs/api/admin-graphql/2024-01/unions/SellingPlanCheckoutChargeValue).

The checkout charge component includes the following Polaris components:

* [Section](https://shopify.dev/docs/api/app-home/web-components/layout-and-structure/section)
* [Text field](https://shopify.dev/docs/api/app-home/web-components/forms/text-field)
* [Date picker](https://shopify.dev/docs/api/app-home/web-components/forms/date-picker)

![A screen capture showing the deposit component](https://shopify.dev/assets/assets/images/api/purchase-options/app-deposit-BURUfLU_.png)

In the `app/components` folder, create the file `checkoutCharge.jsx`.

## app/components/CheckoutCharge.jsx

```jsx
export default function CheckoutCharge({
  selectedDates,
  setSelectedDates,
  initialCheckoutCharge,
  setInitialCheckoutCharge,
  isLoading,
}) {
  return (
    <s-stack direction="vertical" gap="base">
      <s-number-field
        label="Initial deposit"
        name="checkoutCharge"
        suffix="%"
        value={initialCheckoutCharge}
        onChange={(e) => setInitialCheckoutCharge(e.target.value)}
        min="0"
        max="100"
        disabled={isLoading}
        required
      />
      {initialCheckoutCharge < 100 && (
        <s-stack direction="vertical" gap="base">
          <s-text as="label">Remaining balance charge date</s-text>
          <s-date-picker
            selected={selectedDates}
            onChange={setSelectedDates}
            disabled={isLoading}
          />
        </s-stack>
      )}
    </s-stack>
  );
}
```

### Create the product picker component

Create a component that enables merchants to select products that will have the pre-order. The product selection component includes the [Resource Picker API](https://shopify.dev/docs/api/app-home/apis/resource-picker).

![A screen capture showing the product selection component](https://shopify.dev/assets/assets/images/api/purchase-options/app-product-CWrPwC9s.png)

In the `app/components` folder, create the file `ProductPicker.jsx`.

## app/components/ProductPicker.jsx

```jsx
export default function ProductPicker({
  selectedProducts,
  setSelectedProducts,
  isLoading,
}) {
  async function selectProducts(selectedProductIds, searchQuery) {
    const selectedItems = await window.shopify.resourcePicker({
      selectionIds: selectedProductIds,
      multiple: true,
      query: searchQuery,
      type: "product",
      action: "select",
    });


    if (selectedItems) {
      setSelectedProducts(selectedItems);
    }
  }


  return (
    <s-stack gap="base">
      <s-stack direction="horizontal" gap="base" align="start">
        <s-box flex="1">
          <s-search-field
            id="productSearch"
            name="productSearch"
            placeholder="Search products"
            value=""
            disabled={isLoading}
          />
        </s-box>
        <s-button onClick={() => selectProducts([], "")} disabled={isLoading}>
          Browse
        </s-button>
      </s-stack>


      {selectedProducts && selectedProducts.length > 0 && (
        <s-stack gap="base">
          {selectedProducts.map(
            ({ id, images, variants, title, totalVariants }, index) => {
              const hasImage = images && images.length;


              return (
                <s-stack
                  key={`${id}-${index}`}
                  direction="horizontal"
                  gap="base"
                  align="center"
                  wrap="false"
                >
                  <s-box width="50px">
                    <s-thumbnail
                      source={hasImage ? images[0].originalSrc : ""}
                      alt={
                        hasImage && images[0].altText ? images[0].altText : ""
                      }
                      size="medium"
                      showIcon={!hasImage}
                    />
                  </s-box>
                  <s-box flex="1">
                    <s-stack>
                      <s-text>{title}</s-text>
                      {totalVariants !== undefined ? (
                        <s-text tone="subdued">
                          ({variants?.length || totalVariants} of{" "}
                          {totalVariants} variants selected)
                        </s-text>
                      ) : null}
                    </s-stack>
                  </s-box>
                  <s-link
                    removeUnderline
                    onClick={() => selectProducts(selectedProducts)}
                  >
                    Edit
                  </s-link>
                </s-stack>
              );
            },
          )}
        </s-stack>
      )}
    </s-stack>
  );
}
```

***

## Step 3: Action to create the pre-order

The next step is to create the `action` that will make it possible to create the pre-order with all the information we are gathering with the previous created components. Add the action inside your `app/routes/app.create.jsx` file. The following action is currently showing a working example on how to use the [sellingPlanGroupCreate](https://shopify.dev/docs/api/admin-graphql/latest/mutations/sellingPlanGroupCreate) mutation to create a pre-order, this example can also be updated to create a Try before you buy.

## app/routes/app.create.jsx

```jsx
export const action = async ({ request }) => {
  const { admin } = await authenticate.admin(request);
  const form = await request.formData();
  const sellingPlanName = form.get("sellingPlanName");
  const initialCheckoutCharge = form.get("initialCheckoutCharge");
  const selectedProducts = form.get("selectedProducts");
  const selectedProductsArray = selectedProducts
    ? selectedProducts.split(",")
    : [];
  const selectedDates = form.get("selectedDates");
  const haveRemainingBalance = Number(initialCheckoutCharge) < 100;
  const response = await admin.graphql(
    `#graphql
    mutation sellingPlanGroupCreate($input: SellingPlanGroupInput!, $resources: SellingPlanGroupResourceInput!) {
      sellingPlanGroupCreate(input: $input, resources: $resources) {
        sellingPlanGroup {
          id
        }
        userErrors {
          field
          message
        }
      }
    }`,
    {
      variables: {
        input: {
          name: sellingPlanName,
          merchantCode: "Pre-order",
          options: ["pre-order"],
          position: 1,
          sellingPlansToCreate: [
            {
              name: "Pre-order with deposit",
              options: "Pre-order with deposit",
              category: "PRE_ORDER",
              billingPolicy: {
                fixed: {
                  checkoutCharge: {
                    type: "PERCENTAGE",
                    value: {
                      percentage: Number(initialCheckoutCharge),
                    },
                  },
                  remainingBalanceChargeTrigger: haveRemainingBalance
                    ? "EXACT_TIME"
                    : "NO_REMAINING_BALANCE",
                  remainingBalanceChargeExactTime: haveRemainingBalance
                    ? new Date(selectedDates).toISOString()
                    : null,
                },
              },
              deliveryPolicy: {
                fixed: {
                  fulfillmentTrigger: "UNKNOWN",
                },
              },
              inventoryPolicy: {
                reserve: "ON_FULFILLMENT",
              },
            },
          ],
        },
        resources: {
          productIds: selectedProductsArray,
        },
      },
    },
  );
  const responseJson = await response.json();


  const userErrors = responseJson.data?.sellingPlanGroupCreate?.userErrors;
  if (userErrors && userErrors.length > 0) {
    return {
      errors: userErrors.map(error => error.message).join(', ')
    };
  }


  return {
    sellingPlanGroup:
      responseJson.data?.sellingPlanGroupCreate?.sellingPlanGroup?.id,
  };
```

***

## Step 4: Complete create page

The following code illustrate the complete code for the `app/routes/app.create.jsx` file. We use the [Toast API](https://shopify.dev/docs/api/app-home/apis/toast) to add feedback when a merchant is creating a new pre-order.

## app/routes/app.create.jsx

```jsx
import { useState, useEffect } from "react";
import { useActionData, useNavigation, useSubmit, useNavigate } from "react-router";
import { authenticate } from "../shopify.server";
import ProductPicker from "../components/ProductPicker";
import CheckoutCharge from "../components/CheckoutCharge";
import { boundary } from "@shopify/shopify-app-react-router/server";


export const action = async ({ request }) => {
  const { admin } = await authenticate.admin(request);
  const form = await request.formData();
  const sellingPlanName = form.get("sellingPlanName");
  const initialCheckoutCharge = form.get("initialCheckoutCharge");
  const selectedProducts = form.get("selectedProducts");
  const selectedProductsArray = selectedProducts
  ? selectedProducts.split(",")
  : [];
  const selectedDates = form.get("selectedDates");
  const haveRemainingBalance = Number(initialCheckoutCharge) < 100;
  const response = await admin.graphql(
    `#graphql
    mutation sellingPlanGroupCreate($input: SellingPlanGroupInput!, $resources: SellingPlanGroupResourceInput!) {
      sellingPlanGroupCreate(input: $input, resources: $resources) {
        sellingPlanGroup {
          id
        }
        userErrors {
          field
          message
        }
      }
    }`,
    {
      variables: {
        input: {
          name: sellingPlanName,
          merchantCode: "Pre-order",
          options: ["pre-order"],
          position: 1,
          sellingPlansToCreate: [
            {
              name: "Pre-order with deposit",
              options: "Pre-order with deposit",
              category: "PRE_ORDER",
              billingPolicy: {
                fixed: {
                  checkoutCharge: {
                    type: "PERCENTAGE",
                    value: {
                      percentage: Number(initialCheckoutCharge),
                    },
                  },
                  remainingBalanceChargeTrigger: haveRemainingBalance
                    ? "EXACT_TIME"
                    : "NO_REMAINING_BALANCE",
                  remainingBalanceChargeExactTime: haveRemainingBalance
                    ? new Date(selectedDates).toISOString()
                    : null,
                },
              },
              deliveryPolicy: {
                fixed: {
                  fulfillmentTrigger: "UNKNOWN",
                },
              },
              inventoryPolicy: {
                reserve: "ON_FULFILLMENT",
              },
            },
          ],
        },
        resources: {
          productIds: selectedProductsArray,
        },
      },
    },
  );
  const responseJson = await response.json();


  const userErrors = responseJson.data?.sellingPlanGroupCreate?.userErrors;
  if (userErrors && userErrors.length > 0) {
    return {
      errors: userErrors.map(error => error.message).join(', ')
    };
  }


  return {
    sellingPlanGroup:
      responseJson.data?.sellingPlanGroupCreate?.sellingPlanGroup?.id,
  };
};


export default function Index() {
  const nav = useNavigation();
  const actionData = useActionData();
  const submit = useSubmit();
  const navigate = useNavigate();
  const [selectedProducts, setSelectedProducts] = useState([]);
  const [sellingPlanName, setSellingPlanName] = useState("");
  const [initialCheckoutCharge, setInitialCheckoutCharge] = useState(0);
  const [errors, setErrors] = useState(null);


  const handleSellingPlanNameChange = (newValue) => {
    setSellingPlanName(newValue);
  };


  const isLoading =
    ["loading", "submitting"].includes(nav.state) && nav.formMethod === "POST";


  useEffect(() => {
    if (actionData?.sellingPlanGroup) {
      shopify.toast.show("Pre-order created", {
        isError: false,
      });
    } else if (actionData?.errors) {
      setErrors(actionData.errors);
    }
  }, [actionData]);


  const today = new Date();
  const [selectedDates, setSelectedDates] = useState({
    start: today,
    end: today,
  });


  const createPreorder = () =>
    submit(
      {
        selectedProducts: selectedProducts.map((product) => product.id),
        sellingPlanName,
        initialCheckoutCharge,
        selectedDates: initialCheckoutCharge < 100 ? selectedDates.start : null,
      },
      { replace: true, method: "POST" },
    );


  const handleSubmit = (event) => {
    event.preventDefault();


    // Clear any existing errors
    setErrors(null);


    // Validate required fields
    if (!sellingPlanName.trim()) {
      shopify.toast.show("Please enter a pre-order name", {
        isError: true,
      });
      return;
    }


    if (selectedProducts.length === 0) {
      shopify.toast.show("Please select at least one product", {
        isError: true,
      });
      return;
    }


    if (initialCheckoutCharge < 0 || initialCheckoutCharge > 100) {
      shopify.toast.show("Initial deposit must be between 0% and 100%", {
        isError: true,
      });
      return;
    }


    if (initialCheckoutCharge < 100 && !selectedDates.start) {
      shopify.toast.show("Please select a remaining balance charge date", {
        isError: true,
      });
      return;
    }


    createPreorder();
  };


  const handleReset = () => {
    setSelectedProducts([]);
    setSellingPlanName("");
    setInitialCheckoutCharge(0);
    setSelectedDates({
      start: today,
      end: today,
    });
    setErrors(null);
  };


  return (
    <form data-save-bar onSubmit={handleSubmit} onReset={handleReset}>
      <s-page>
        <ui-title-bar title="Create a pre-order">
          <button variant="breadcrumb" type="button" onClick={() => navigate("/app")}>
            Pre-orders
          </button>
        </ui-title-bar>
        {errors && (
          <s-banner tone="critical" heading="There was an error creating the pre-order">
            <s-text>{errors}</s-text>
          </s-banner>
        )}
        <s-stack gap="base">
          <s-section heading="Pre-order details">
            <s-text-field
              label="Pre-order name"
              name="sellingPlanName"
              value={sellingPlanName}
              onChange={(e) => handleSellingPlanNameChange(e.target.value)}
              disabled={isLoading}
              required
            />
          </s-section>


          <s-section heading="Payment settings">
            <CheckoutCharge
              selectedDates={selectedDates}
              setSelectedDates={setSelectedDates}
              initialCheckoutCharge={initialCheckoutCharge}
              setInitialCheckoutCharge={setInitialCheckoutCharge}
              isLoading={isLoading}
            />
          </s-section>


          <s-section heading="Products">
            <ProductPicker
              selectedProducts={selectedProducts}
              setSelectedProducts={setSelectedProducts}
              isLoading={isLoading}
            />
          </s-section>
        </s-stack>
      </s-page>
    </form>
  );
}


export const headers = (headersArgs) => {
    return boundary.headers(headersArgs);
  };
```

***

## Next steps

The code that you just created is only a first step to create a complete pre-order or Try before you buy app. You can use the following API object to improve your application:

* [SellingPlanCheckoutCharge](https://shopify.dev/docs/api/admin-graphql/2024-01/objects/SellingPlanCheckoutCharge) that will let you decide how customers are charged with pre-order and TBYB.
* [SellingPlanFixedBillingPolicy](https://shopify.dev/docs/api/admin-graphql/2024-01/objects/SellingPlanFixedBillingPolicy) that will let you decide how customers will handle the remain balance customer will need to pay for a pre-order and TBYB
* [SellingPlanFixedDeliveryPolicy](https://shopify.dev/docs/api/admin-graphql/2024-01/objects/SellingPlanFixedDeliveryPolicy) that will let you decide how fulfillment will work with you pre-order and TBYB.
* [SellingPlanInventoryPolicy](https://shopify.dev/docs/api/admin-graphql/2024-01/objects/SellingPlanInventoryPolicy) that will let you decide how the inventory will update with you pre-order and TBYB.

***
