---
gid: 7bf49050-f172-42ac-95a1-63cf166f200c
title: Build a Shopify app using Remix
description: Learn how to build a Shopify app using Remix, Polaris, App Bridge and Prisma.
---

<Repo framework="remix" href="https://github.com/Shopify/example-app--qr-code--remix" />

<Picker name="framework">
  <PickerOption name="remix" />
</Picker>

<Overview>

  After you scaffold an app, you can add your own functionality to pages inside and outside of the Shopify admin.

  In this tutorial, you'll scaffold an app that makes QR codes for products. When the QR code is scanned, it takes the user to a checkout that's populated with the product, or to the product page. The app logs every time the QR code is scanned, and exposes scan metrics to the app user.

  Follow along with this tutorial to build a sample app, or clone the completed sample app.

  ## What you'll learn

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

  - Update the [Prisma](https://www.prisma.io/) database included in the app template.
  - Use the [@shopify/shopify-app-remix](https://www.npmjs.com/package/@shopify/shopify-app-remix) package to authenticate users and query data.
  - Use [Polaris components](https://polaris.shopify.com/components) to create a UI that adheres to Shopify's [App Design Guidelines](/docs/apps/design-guidelines).
  - Use [Shopify App Bridge](/docs/api/app-bridge) to add interactivity to your app.

  <video autoPlay muted loop controls src="/assets/apps/qr-code-remix.mp4" />

</Overview>

<Requirements>
  <Requirement href="/docs/apps/build/scaffold-app" label="Scaffold an app">
    Scaffold an app that uses the [Remix template](https://github.com/Shopify/shopify-app-template-remix).
  </Requirement>
  <Requirement href="https://www.npmjs.com/package/qrcode" label="Install `qrcode`">
    Enables creation of QR codes.
  </Requirement>
  <Requirement href="https://www.npmjs.com/package/@shopify/polaris-icons" label="Install `@shopify/polaris-icons`">
    Provides placeholder images for the UI.
  </Requirement>
  <Requirement href="https://www.npmjs.com/package/tiny-invariant" label="Install `tiny-invariant`">
    Allows loaders to easily throw errors.
  </Requirement>
</Requirements>

<StepSection>

  <Step>

    ## Add the QR code data model to your database

    To store your QR codes, you need to add a table to the database included in your template.

    <Notice type="info">
      The single table in the template's Prisma schema is the `Session` table. It stores the tokens for each store that installs your app, and is used by the [@shopify/shopify-app-session-storage-prisma](https://www.npmjs.com/package/@shopify/shopify-app-session-storage-prisma) package to manage sessions.
    </Notice>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/prisma/schema.prisma"
        tag="qrcode-model"
      />

      <CodeRef
        framework="nodejs"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/prisma/schema.prisma"
        tag="qrcode-model"
      />

      ### Create the table

      Add a `QRCode` model to your Prisma schema. The model should contain the following fields:

      - `id`: The primary key for the table.
      - `title`: The app user-specified name for the QR code.
      - `shop`: The store that owns the QR code.
      - `productId`: The product that this QR code is for.
      - `productHandle`: Used to create the destination URL for the QR code.
      - `productVariantId`: Used to create the destination URL for the QR code.
      - `destination`: The destination for the QR code.
      - `scans`: The number times that the QR code been scanned.
      - `createdAt`: The date and time when the QR code was created.

      ---

      The `QRCode` model contains the key identifiers that the app uses to retrieve Shopify product and variant data. At runtime, additional product and variant properties are retrieved and used to populate the UI.

    </Substep>

    <Substep>

      ### Migrate the database

      After you add your schema, you need to migrate the database to create the table.

      1. Run the following command to create the table in Prisma:

          <Codeblock terminal>
          ```bash title="npm"
          npm run prisma migrate dev -- --name add-qrcode-table
          ```
          ```bash title="Yarn"
          yarn prisma migrate dev --name add-qrcode-table
          ```
          ```bash title="pnpm"
          pnpm run prisma migrate dev --name add-qrcode-table
          ```
          </Codeblock>

      2. To confirm that your migration worked, open [Prisma Studio](https://www.prisma.io/docs/concepts/components/prisma-studio):

          <Codeblock terminal>
          ```bash title="npm"
          npm run prisma studio
          ```
          ```bash title="Yarn"
          yarn prisma studio
          ```
          ```bash title="pnpm"
          pnpm run prisma studio
          ```
          </Codeblock>

          Prisma Studio opens in your browser.

      4. In Prisma Studio, click the **QRCode** tab to view the table.

      You should see a table with the columns that you created, and no data.

    </Substep>


  </Step>

  <Step>
    ## Get QR code and product data

    After you create your database, add code to retrieve data from the table.

    Supplement the QR code data in the database with product information.

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/models/QRCode.server.js"
      />

      ### Create the model

      Create a model to get and validate QR codes.

      Create an `/app/models` folder. In that folder, create a new file called `QRCode.server.js`.

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/models/QRCode.server.js"
        tag="get-qrcode"
      />

      ### Get QR codes

      Create a function to get a single QR code for your QR code form, and a second function to get multiple QR codes for your app's index page. You'll [create a QR code form](#create-a-qr-code-form) later in this tutorial.

      QR codes stored in the database can be retrieved using the Prisma `FindFirst` and `FindMany` queries.

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/models/QRCode.server.js"
        tag="get-qrcode-image"
      />

      ### Get the QR code image

      A QR code takes the user to `/qrcodes/$id/scan`, where `$id` is the ID of the QR code.  Create a function to construct this URL, and then use the `qrcode` package to return a base 64-encoded QR code image `src`.

      ---

      <Resources>
        [qrcode](https://www.npmjs.com/package/qrcode)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/models/QRCode.server.js"
        tag="get-destination"
      />

      ### Get the destination URL

      Scanning a QR code takes the user to one of two places:

      - The product details page
      - A checkout with the product in the cart

      Create a function to conditionally construct this URL depending on the destination that the merchant selects.

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/models/QRCode.server.js"
        tag="hydrate-qrcode"
      />

      ### Retrieve additional product and variant data

      The QR code from Prisma needs to be supplemented with product data.  It also needs the QR code image and destination URL.

      Create a function that queries the [GraphQL Admin API](/docs/api/admin-graphql) for the product title, and the first featured product image's URL and alt text. It should also return an object with the QR code data and product data, and use the `getDestinationUrl` and `getQRCodeImage` functions that you created to get the destination URL's QR code image.

      ---

      <Resources>
        [GraphQL Admin API](/docs/api/admin-graphql)
      </Resources>


    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/models/QRCode.server.js"
        tag="validate-qrcode"
      />

      ### Validate QR codes

      To create a valid QR code, the app user needs to provide a title, and select a product and destination. Add a function to ensure that, when the user submits the form to create a QR code, values exist for all of the required fields.

      The action for the QR code form will return errors from this function.

    </Substep>

  </Step>

  <Step>
    ## Create a QR code form

    Create a form that allows the app user to manage QR codes.

    To create this form, you'll use a [Remix route](https://remix.run/docs/en/main/guides/routing), [Polaris components](https://polaris.shopify.com/components) and [App Bridge](/docs/api/app-bridge).

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/app.qrcodes.$id.jsx"
      />

      ### Set up the form route

      Create a form that can create, update or delete a QR code.

      In the `app` > `routes` folder, create a new file called `app.qrcodes.$id.jsx`.

      ---

      #### Dynamic segments

      This route uses a [dynamic segment](https://remix.run/docs/en/main/guides/routing#dynamic-segments) to match the URL for creating a new QR code and editing an existing one.

      If the user is creating a QR code, the URL is `/app/qrcodes/new`. If the user is updating a QR code, the URL is `/app/qrcodes/1`, where `1` is the ID of the QR code that the user is updating.

      #### Remix layouts

      The Remix template includes a [Remix layout](https://remix.run/docs/en/main/guides/routing#rendering-route-layout-hierarchies) at `app/routes/app.jsx`. This layout should be used for authenticated routes that render inside the Shopify admin.  It's responsible for configuring App Bridge and Polaris, and authenticating the user using [shopify-app-remix](https://www.npmjs.com/package/@shopify/shopify-app-remix).

      ---

      <Resources>
        [dynamic segment](https://remix.run/docs/en/main/guides/routing#dynamic-segments)
        [Remix layout](https://remix.run/docs/en/main/guides/routing#rendering-route-layout-hierarchies)
        [App Bridge](/docs/api/app-bridge)
        [Polaris](https://polaris.shopify.com/)
        [shopify-app-remix](https://www.npmjs.com/package/@shopify/shopify-app-remix)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/app.qrcodes.$id.jsx"
        tag="authenticate"
      />

      ### Authenticate the user

      Authenticate the route using `shopify-app-remix`.

      ---

      If the user isn't authenticated, `authenticate.admin` handles the necessary redirects. If the user is authenticated, then the method returns an admin object.

      You can use the `authenticate.admin` method for the following purposes:

      - Getting information from the session, such as the `shop`
      - Accessing the [GraphQL Admin API](/docs/api/admin-graphql)
      - Within methods to require and request billing

      ---

      <Resources>
        [Authenticating admin requests](https://www.npmjs.com/package/@shopify/shopify-app-remix#authenticating-admin-requests)
        [GraphQL Admin API](/docs/api/admin-graphql)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/app.qrcodes.$id.jsx"
        tag="data"
      />

      ### Return a JSON Response

      Using the `json` util, return a `Response` that can be used to show QR code data.

      If the `id` parameter is `new`, return JSON with an empty title, and product for the destination.  If the `id` parameter isn't `new`, then return the JSON from `getQRCode` to populate the QR code state.

      ---

      <Resources>
        [json](https://remix.run/docs/en/main/utils/json)
        [GraphQL Admin API](/docs/api/admin-graphql)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/app.qrcodes.$id.jsx"
        tag="state"
      />

      ### Manage the form state

      Maintain the state of the QR code form state using the following variables:

      - `errors`: If the app user doesn't fill all of the QR code form fields, then the action returns errors to display. This is the return value of `validateQRCode`, which is accessed through the Remix `useActionData` hook.
      - `formState`: When the user changes the title, selects a product, or changes the destination, this state is updated. This state is copied from `useLoaderData` into React state.
      - `cleanFormState`: The initial state of the form. This only changes when the user submits the form. This state is copied from `useLoaderData` into React state.
      - `isDirty`: Determines if the form has changed. This is used to enable save buttons when the app user has changed the form contents, or disable them when the form contents haven't changed.
      - `isSaving` and `isDeleting`: Keeps track of the network state using `useNavigation`.  This state is used to disable buttons and show loading states.

      <Resources>
        [useActionData](https://remix.run/docs/en/main/hooks/use-action-data)
        [useLoaderData](https://remix.run/docs/en/main/hooks/use-loader-data)
        [useNavigation](https://remix.run/docs/en/main/hooks/use-navigation)
        [React state](https://react.dev/reference/react/useState)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/app.qrcodes.$id.jsx"
        tag="select-product"
      />

      ### Add a product selector

     Using the App Bridge `ResourcePicker` action, add a modal that allows the user to select a product. Save the selection to form state.

     ![Screenshot showing a App Bridge modal for selecting products](/assets/apps/select-product.png)

      ---

      <Resources>
        [ResourcePicker](/docs/api/app-bridge-library/apis/resource-picker)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/app.qrcodes.$id.jsx"
        tag="save"
      />

      ### Save form data

      Use the `useSubmit` Remix hook to save the form data.

      Copy the data that Prisma needs from `formState` and set the `cleanFormState` to the current `formState`.

      ---

      <Resources>
        [useSubmit](https://remix.run/docs/en/main/hooks/use-submit)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/app.qrcodes.$id.jsx"
        tag="polaris"
      />

      ### Lay out the form

      Using Polaris components, build the layout for the form. Use  `Page`, `Layout`, and `BlockStack` to structure the page.  The page should have two columns.

      ---

      Polaris is the design system for the Shopify admin. Using Polaris components ensures that your UI is accessible, responsive, and displays consistently with the Shopify Admin.

      <Resources>
        [Polaris](https://polaris.shopify.com/components/layout-and-structure/page)
        [Page](https://polaris.shopify.com/components/layout-and-structure/page)
        [Layout](https://polaris.shopify.com/design/layout)
        [BlockStack](https://polaris.shopify.com/components/layout-and-structure/block-stack)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/app.qrcodes.$id.jsx"
        tag="breadcrumbs"
      />

      ### Add breadcrumbs

      Use an App Bridge `ui-title-bar` action to display a title that indicates to the user whether they're creating or editing a QR code. Include a breadcrumb link to go back to the [QR code list](#list-qr-codes).

      ---

      <Resources>
        [ui-title-bar](/docs/api/app-bridge-library/web-components/ui-title-bar)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/app.qrcodes.$id.jsx"
        tag="title"
      />

      ### Add a title field

      Use `TextField` for updating the title.  It should `setFormState`, have some `helpText` and show title errors from `useActionData`.

      Wrap the area in a `Card`, and use `BlockStack` to space the content correctly.

      ---

      <Resources>
        [Card](https://polaris.shopify.com/components/layout-and-structure/card)
        [BlockStack](https://polaris.shopify.com/components/layout-and-structure/block-stack)
        [Text](https://polaris.shopify.com/components/typography/text)
        [TextField](https://polaris.shopify.com/components/selection-and-input/text-field)
        [useActionData](https://remix.run/docs/en/main/hooks/use-action-data)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/app.qrcodes.$id.jsx"
        tag="product"
      />

      ### Add a way to select the product

      If the user hasn't selected a product, then display a `Button` that triggers `selectProduct`.

      If the user has selected a product, use `Thumbnail` to display the product image. Make sure to handle the case where a product has no image.

      Use `inlineError` to display an error from `useActionData` if the user submits the form without selecting a product.

      ---

      <Resources>
        [InlineStack](https://polaris.shopify.com/components/layout-and-structure/inline-stack)
        [Text](https://polaris.shopify.com/components/typography/text)
        [Button](https://polaris.shopify.com/components/actions/button)
        [Thumbnail](https://polaris.shopify.com/components/images-and-icons/thumbnail)
        [polaris-icons](https://polaris.shopify.com/icons)
        [BlockStack](https://polaris.shopify.com/components/layout-and-structure/block-stack)
        [inlineError](https://polaris.shopify.com/components/selection-and-input/inline-error)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/app.qrcodes.$id.jsx"
        tag="destination"
      />

      ### Add destination options

      Use `ChoiceList` to render different destinations. It should `setFormState` when the selection changes.

      If the user is editing a QR code, use a `Button` to link to the destination URL in a new tab.

      ---

      <Resources>
        [InlineStack](https://polaris.shopify.com/components/layout-and-structure/inline-stack)
        [ChoiceList](https://polaris.shopify.com/components/selection-and-input/choice-list)
        [Button](https://polaris.shopify.com/components/selection-and-input/choice-list)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/app.qrcodes.$id.jsx"
        tag="preview"
      />

      ### Display a preview of the QR code

      After saving a QR code, or when editing an existing QR code, provide ways to preview the QR code that the app user created.

      If a QR code is available, then use `EmptyState` to render the QR code.  If no QR code is available, then use an `EmptyState` component with a different configuration.

      Add buttons to download the QR code, and to preview the public URL.

      ---

      <Resources>
        [Card](https://polaris.shopify.com/components/layout-and-structure/card)
        [Text](https://polaris.shopify.com/components/typography/text)
        [EmptyState](https://polaris.shopify.com/components/layout-and-structure/empty-state)
        [BlockStack](https://polaris.shopify.com/components/layout-and-structure/block-stack)
        [Button](https://polaris.shopify.com/components/actions/button)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/app.qrcodes.$id.jsx"
        tag="actions"
      />

      ### Add save and delete buttons

      Use `PageActions` to render **Save** and **Delete** buttons.

      Add a `primaryAction` to save the QR code and a `secondaryAction`. Make sure to handle `loading` and disabled states.

      ---

      <Resources>
        [PageActions](https://polaris.shopify.com/components/actions/page-actions)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/app.qrcodes.$id.jsx"
        tag="action"
      />

      ### Create, update, or delete a QR code

      Create an `action` to create, update, or delete a QR code.

      The `action` should use the store from the session. This ensures that the app user can only create, update, or delete QR codes for their own store.

      The action should return errors for incomplete data using your `validateQRCode` function.

      If the action deletes a QR code, redirect the app user to the index page. If the action creates a QR code, redirect to `app/qrcodes/$id`, where `$id` is the ID of the newly created QR code.

      ---

      <Resources>
        [action](https://remix.run/docs/en/main/route/action)
      </Resources>

    </Substep>

  </Step>

  <Step>
    ## List QR codes

    To allow app users to navigate to QR codes, list the QR codes in the app home.

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/app._index.jsx"
        tag="loader"
      />

      ### Load QR codes

      In the app's index route, load the QR codes using a Remix `loader`.

      The `loader` should load QR codes using the `qrcodes` function from [`app/models/QRCode.server.js`](#get-qr-code-and-product-data), and return them in a JSON `Response`.

      ---

      <Resources>
        [loader](https://remix.run/docs/en/main/route/loader)
        [json](https://remix.run/docs/en/main/utils/json)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/app._index.jsx"
        tag="empty"
      />

      ### Create an empty state

      If there are no QR codes, use `EmptyState` to present a call to action to create QR codes.

      ![Screenshot showing a Polaris EmptyState](/assets/apps/empty-state.png)


      <Resources>
        [EmptyState](https://polaris.shopify.com/components/layout-and-structure/empty-state)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/app._index.jsx"
        tag="table"
      />

      ### Create an index table

      If there are QR codes present, then use the Polaris `IndexTable` component to list them.

      The table should have columns for the product image, QR code title, product name, the date the QR code was created, and the number of times the QR code was scanned.

      ![Screenshot showing a Polaris IndexTable](/assets/apps/index-table.png)

      ---

      <Resources>
        [IndexTable](https://polaris.shopify.com/components/tables/index-table)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/app._index.jsx"
        tag="row"
      />

      ### Create index table rows

      Map over each QR code and render an `IndexTable.Row` that uses Polaris components to structure the row and render QR code information.

      ---

      <Resources>
        [IndexTable.Row](https://polaris.shopify.com/components/tables/index-table#index-table-row)
        [IndexTable.Cell](https://polaris.shopify.com/components/tables/index-table#index-table-cell)
        [Thumbnail](https://polaris.shopify.com/components/images-and-icons/thumbnail)
        [InlineStack](https://polaris.shopify.com/components/layout-and-structure/inline-stack)
        [Text](https://polaris.shopify.com/components/typography/text)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/app._index.jsx"
        tag="deleted"
      />

      ### Warn if a product is deleted

      A merchant can delete a product after creating a QR code for it.  The data returned from `supplementQRCode` includes an `isDeleted` property.  `isDeleted` is true if the product title returned from the [GraphQL Admin API](/docs/api/admin-graphql) is an empty string.

      Render a warning to the user if a product is deleted.

      ---

      For a full list of the icons included in the [@shopify/polaris-icons](https://www.npmjs.com/package/@shopify/polaris-icons) package, refer to the [Icons reference](https://polaris.shopify.com/icons).

      <Resources>
        [Icon](https://polaris.shopify.com/components/images-and-icons/icon)
        [@shopify/polaris-icons](https://www.npmjs.com/package/@shopify/polaris-icons)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/app._index.jsx"
        tag="page"
      />

      ### Lay out the page

      After you create your empty state and index table, adjust the layout of the index page to return the markup that you created.

      Create a layout using Polaris components. Render the empty state and table inside a Polaris `Card`.

      Use the App Bridge `ui-title-bar` to render the title bar with a title. Add a primary button to navigate to the QR code creation form.

      ---

      <Resources>
        [Page](https://polaris.shopify.com/components/layout-and-structure/page)
        [Layout](https://polaris.shopify.com/components/layout-and-structure/layout)
        [Card](https://polaris.shopify.com/components/layout-and-structure/card)
        [ui-title-bar](/docs/api/app-bridge-library/web-components/ui-title-bar)
      </Resources>

    </Substep>

  </Step>

  <Step>
    ## Add a public QR code route

    Make QR codes scannable by customers by exposing them using a public URL. When a customer scans a QR code, the scan count should increment, and the customer should be redirected to the destination URL.

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/qrcodes.$id.jsx"
      />

      ### Create a public QR code route

      Create a public page to render a QR code.

      In the `app` > `routes` folder, create a new file called `qrcodes.$id.jsx`.

      ---

      Because the `qrcodes.$id.jsx` doesn't require authentication or need to be rendered inside of the Shopify admin, it doesn't use the [app layout](#set-up-the-form-route).

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/qrcodes.$id.jsx"
        tag="loader"
      />

      ### Load the QR code

      Create a `loader` to load the QR code on the external route.

      In the function, check that there's an ID in the URL. If there isn't, throw an error using `tiny-invariant`.

      If there's an ID in the URL, load the QR code with that ID using Prisma:

      - If there is no matching QR code ID in the table, throw an error using `tiny-invariant`.
      - If there is a matching ID, return the QR code using a Remix `json` function.

      ---

      <Resources>
        [loader](https://remix.run/docs/en/main/route/loader)
        [tinyinvariant`](https://www.npmjs.com/package/tiny-invariant)
        [json](https://remix.run/docs/en/main/utils/json)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/qrcodes.$id.jsx"
        tag="component"
      />

      ### Render a public QR code image

      In the route's default `export`, render an `img` tag for the QR code image.  Scanning this image takes the user to the destination URL. This is the next route to implement.

    </Substep>

  </Step>

  <Step>

    ## Redirect the customer to the destination URL

    When a QR code is scanned, redirect the customer to the destination URL. You can also increment the QR code scan count to reflect the number of times the QR code has been used.

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/qrcodes.$id.scan.jsx"
      />

      ### Create the scan route

      Create a public route that handles QR code scans.

      In the `app` > `routes` folder, create a new file called `qrcodes.$id.scan.jsx`.

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/qrcodes.$id.scan.jsx"
        tag="validate"
      />

      ### Validate the QR code ID

      Create a `loader` function to load the QR code from the database.

      In this function, check there is an ID in the URL. If the ID isn't present, then throw an error using `tiny-invariant`.

      Load the QR code from the Prisma database. If a QR code with the specified ID doesn't exist, then throw an error using `tiny-invariant`.

      ---

      <Resources>
        [loader](https://remix.run/docs/en/main/route/loader)
        [tinyinvariant`](https://www.npmjs.com/package/tiny-invariant)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/qrcodes.$id.scan.jsx"
        tag="increment"
      />

      ### Increment the scan count

      If the `loader` returns a QR code, then increment the scan count in the database.

      Redirect to the destination URL for the QR code using `getDestinationUrl` and the Remix `redirect` utility.

      ---

      <Resources>
        [redirect](https://remix.run/docs/en/main/utils/redirect)
      </Resources>

    </Substep>

    <Substep>

      <CodeRef
        framework="remix"
        href="https://github.com/Shopify/example-app--qr-code--remix/blob/main/app/routes/qrcodes.$id.scan.jsx"
        tag="redirect"
      />

      ### Redirect

      The `loader` should return the destination URL for the QR code it incremented.  Use `getDestinationUrl` from [`appmodels/QRCode.server.js`](#get-qr-code-and-product-data) to get that URL.  Use `redirect` from Remix to redirect the user to that URL.

      ---

      <Resources>
        [loader](https://remix.run/docs/en/main/route/loader)
        [redirect](https://remix.run/docs/en/main/utils/redirect)
      </Resources>

    </Substep>

  </Step>

  <Step>
    ## Preview and test your app

    Use the CLI to preview your app. If you make changes, you'll see those changes hot reload in the browser.

    <Substep>

      ### Start your server

      Run the Shopify CLI `dev` command to build your app and preview it on your development store.

      1. In a terminal, navigate to your app directory.

      2. Either start or restart your server to build and preview your app:

          <Codeblock terminal>
          ```bash
          shopify app dev
          ```
          </Codeblock>

      3. Press `p` to open the developer console. In the developer console page, click on the preview link for your app home.

      4. If you're prompted to install the app, then click **Install**.

    </Substep>

    <Substep>

      ### Test the QR code index and form

      Follow these steps to test the routes that are exposed to the app user in the Shopify admin. These routes include the app index and the QR code form.

      1. In the index page for your app home, click **Create QR code** to go to the QR code form.

          The QR code form opens at `/app/qrcode/new`. The title of the page is **Create QR code**.

      2. Try to submit the QR code form with an empty title, or without selecting a product.

          An error is returned.

      3. Create a few QR codes for different products and destinations.

      5. Click the **QR codes** breadcrumb to return to the index page.

          The QR code list is populated with the QR codes that you created:

          ![Screenshot showing the QR code list](/assets/apps/complete-remix-app.png)

      5. Select a QR code from the list.

          The QR code form opens at `/app/qrcode/<id>`. The title of the page is **Edit QR code**:

          ![Screenshot showing the QR code form](/assets/apps/qr-code-form.png)

      6. On the **Edit QR code** page, click **Delete**.

      You're taken back to the index page, and the deleted QR code is removed from the list.

    </Substep>

    <Substep>

      ### Test QR code scanning functionality

      Scan the QR code that you created in the previous step.

      1. From the app index page, click an existing QR code or create a new one.

      2. On the QR code form, click **Go to public URL**.

          A new tab opens for the public URL for the QR code.

      3. Scan the QR code with your phone.

          You're taken the destination URL.

      4. Return to your app index page.

          The scan count for the QR code that just scanned is incremented.

    </Substep>

  </Step>

</StepSection>

<NextSteps>
  ## Tutorial Complete!

  Congratulations! You built a QR code app using Remix, Polaris, App Bridge and Prisma. Keep the momentum going with these related tutorials and resources.

  ### Next steps

  <CardGrid>
    <LinkCard href="/docs/apps/webhooks">
      #### Use webhooks
      You can use webhooks to stay in sync with Shopify, or execute code after a specific event occurs in the store.

      For example, if a merchant updates a product's handle, you can use the `products/update` webhook to trigger an update to the handle in your database.
    </LinkCard>

    <LinkCard href="/docs/api/admin-graphql">
      #### Explore the GraphQL Admin API
      The GraphQL Admin API lets you read and write Shopify data, including products, customers, orders, inventory, fulfillment, and more.

      Explore the GraphQL Admin API to learn about the available types and operations.
    </LinkCard>

    <LinkCard href="/docs/apps/getting-started/app-surfaces">
      #### Learn more about extending Shopify
      Learn about the most common places where apps can add functionality to the Shopify platform, and the related APIs and tools available for building.
    </LinkCard>

    <LinkCard href="/docs/apps/distribution">
      #### Select an app distribution method
      Decide how you want to share your app with users. For example, you might make your app available in the Shopify App Store, and bill customers for usage.
    </LinkCard>

    <LinkCard href="/docs/apps/deployment/web">
      #### Deploy your app
      Follow our guide to deploy your Remix app to a testing or production environment.
    </LinkCard>
  </CardGrid>

</NextSteps>