Build a Shopify app using Remix
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.
Anchor to What you'll learnWhat you'll learn
In this tutorial, you'll learn how to do the following tasks:
- Update the Prisma database included in the app template.
- Use the @shopify/shopify-app-remix package to authenticate users and query data.
- Use Polaris components to create a UI that adheres to Shopify's App Design Guidelines.
- Use Shopify App Bridge to add interactivity to your app.
Requirements
Scaffold an app that uses the Remix template.
qrcode
Enables creation of QR codes.
@shopify/polaris-icons
Provides placeholder images for the UI.
tiny-invariant
Allows loaders to easily throw errors.
Project
Anchor to Add the QR code data model to your databaseAdd 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.
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 package to manage sessions.
Anchor to Create the tableCreate 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.
Anchor to Migrate the databaseMigrate the database
After you add your schema, you need to migrate the database to create the table.
-
Run the following command to create the table in Prisma:
Terminal
npm run prisma migrate dev -- --name add-qrcode-tableyarn prisma migrate dev --name add-qrcode-tablepnpm run prisma migrate dev --name add-qrcode-tablenpm run prisma migrate dev -- --name add-qrcode-table
yarn prisma migrate dev --name add-qrcode-table
pnpm run prisma migrate dev --name add-qrcode-table
-
To confirm that your migration worked, open Prisma Studio:
Terminal
npm run prisma studioyarn prisma studiopnpm run prisma studionpm run prisma studio
yarn prisma studio
pnpm run prisma studio
Prisma Studio opens in your browser.
-
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.
Anchor to Get QR code and product dataGet 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.
Anchor to Create the modelCreate 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
.
Anchor to Get QR codesGet 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 later in this tutorial.
QR codes stored in the database can be retrieved using the Prisma FindFirst
and FindMany
queries.
Anchor to Get the QR code imageGet 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
.
Anchor to Get the destination URLGet 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.
Anchor to Retrieve additional product and variant dataRetrieve 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 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.
Anchor to Validate QR codesValidate 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.
Anchor to Create a QR code formCreate 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, Polaris components and App Bridge.
Anchor to Set up the form routeSet 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
.
Anchor to Dynamic segmentsDynamic segments
This route uses a dynamic segment 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.
Anchor to Remix layoutsRemix layouts
The Remix template includes a Remix layout 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.
Anchor to Authenticate the userAuthenticate 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
- Within methods to require and request billing
Anchor to Return a JSON ResponseReturn 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.
Anchor to Manage the form stateManage 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 ofvalidateQRCode
, which is accessed through the RemixuseActionData
hook.formState
: When the user changes the title, selects a product, or changes the destination, this state is updated. This state is copied fromuseLoaderData
into React state.cleanFormState
: The initial state of the form. This only changes when the user submits the form. This state is copied fromuseLoaderData
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
andisDeleting
: Keeps track of the network state usinguseNavigation
. This state is used to disable buttons and show loading states.
Anchor to Add a product selectorAdd 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.

Anchor to Save form dataSave 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
.
Anchor to Lay out the formLay 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.
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.
Anchor to Add a title fieldAdd 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.
Anchor to Add a way to select the productAdd 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.
Anchor to Add destination optionsAdd 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.
Anchor to Display a preview of the QR codeDisplay 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.
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.
Anchor to Create, update, or delete a QR codeCreate, 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.
Anchor to List QR codesList QR codes
To allow app users to navigate to QR codes, list the QR codes in the app home.
Anchor to Load QR codesLoad 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
, and return them in a JSON Response
.
Anchor to Create an empty stateCreate an empty state
If there are no QR codes, use EmptyState
to present a call to action to create QR codes.

Anchor to Create an index tableCreate 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.

Anchor to Create index table rowsCreate 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.
Anchor to Warn if a product is deletedWarn 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 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 package, refer to the Icons reference.
Anchor to Lay out the pageLay 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.
Anchor to Add a public QR code routeAdd 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.
Anchor to Create a public QR code routeCreate 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.
Anchor to Load the QR codeLoad 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.
Anchor to Render a public QR code imageRender 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.
Anchor to Redirect the customer to the destination URLRedirect 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.
Anchor to Create the scan routeCreate 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
.
Anchor to Validate the QR code IDValidate 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
.
Anchor to Increment the scan countIncrement 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.
Anchor to RedirectRedirect
The loader
should return the destination URL for the QR code it incremented. Use getDestinationUrl
from appmodels/QRCode.server.js
to get that URL. Use redirect
from Remix to redirect the user to that URL.
Anchor to Preview and test your appPreview 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.
Anchor to Start your serverStart your server
Run the Shopify CLI dev
command to build your app and preview it on your development store.
-
In a terminal, navigate to your app directory.
-
Either start or restart your server to build and preview your app:
Terminal
shopify app dev -
Press
p
to open the developer console. In the developer console page, click on the preview link for your app home. -
If you're prompted to install the app, then click Install.
Anchor to Test the QR code index and formTest 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.
-
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. -
Try to submit the QR code form with an empty title, or without selecting a product.
An error is returned.
-
Create a few QR codes for different products and destinations.
-
Click the QR codes breadcrumb to return to the index page.
The QR code list is populated with the QR codes that you created:
-
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: -
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.
Anchor to Test QR code scanning functionalityTest QR code scanning functionality
Scan the QR code that you created in the previous step.
-
From the app index page, click an existing QR code or create a new one.
-
On the QR code form, click Go to public URL.
A new tab opens for the public URL for the QR code.
-
Scan the QR code with your phone.
You're taken the destination URL.
-
Return to your app index page.
The scan count for the QR code that just scanned is incremented.
Anchor to Tutorial Complete!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.
Anchor to Next stepsNext steps
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.
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.
Learn about the most common places where apps can add functionality to the Shopify platform, and the related APIs and tools available for building.
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.
Follow our guide to deploy your Remix app to a testing or production environment.