Sign up to Gadget

Building with Shopify customer account extensions



Learn how to build a fullstack Shopify app that allows customers to report problems with orders using customer account extensions.




Building with Shopify customer account extensions

Emma Hyde
February 7, 2024

One of the most exciting features to come out of Shopify’s Winter 24 Editions is more insight into customer account extensibility. With this new Shopify extension type, developers are able to create apps that support a merchant’s customers’ experience. With a link from the merchant, customers will have access to the order index, order status, and profile pages without requiring any sign-in. Any changes or updates they make to their customer profile will be reflected in the Shopify admin without any action from the merchant.

In this tutorial, we’ll build a customer account extension that allows customers to report problems with orders directly from the order status page. It’s important to note that this is only available on new customer accounts

- Basic knowledge of JavaScript and React
- A Shopify Partner account
- Access to Gadget (some functionalities are in beta as of this writing)
- Familiarity with Node.js and React frontend development
- Access to Gadget’s customer account extension beta (ask to join in our developer Discord)

Prefer a video?

Step 1: Set up your project and connect to Shopify

The first thing you’ll want to do is set up a new project in Gadget and connect it to Shopify. 

  1. Create a new project by going to This will set up a Gadget app with a hosted and auto-scaled PostgreSQL database, Node.js backend, and a React frontend, powered by Vite.
  2. Choose a domain name for your project (we went with customer-order-problem-reporter).
  3. Connect your project to Shopify. If you’re unsure of the best way to do this, you can read our guide on connecting Gadget and Shopify here (Spoiler: in just 2 minutes you’ll have all the necessary data synced with your Gadget app). For this build, we’re going to need read access to the Order and Customer API scopes, and the underlying Order and Customer data models.

Step 2: Request protected customer data and install on a store

For this build, it’s important that you request access to protected customer data, indicating that you require it for app functionality.

After this, you’ll want to install your app on a store. Once installed, to make sure everything is running correctly, we recommend creating a test order to confirm your customer and order data syncs to your Gadget database as expected.

Note: If you want to sync order data older than 60 days, you’ll need special permission from Shopify. There's a unique access scope you’ll need and a checkbox on the partner's dashboard that will allow you to do so.

Step 3: Build your custom data models

In order to provide buyers with different options that they can report as a problem, we need to create a custom model. To allow customers to quickly submit issues and help merchants easily resolve them, we’re going to provide a list of options instead of allowing for freeform input.

  1. Create a custom data model in Gadget named <inline-code>issue<inline-code>.
  2. Because issues need descriptions, we’re going to add a string field called <inline-code>description<inline-code> and make this a required field using the included validation option. Merchants won’t be able to create an issue without a description.
  3. For B2B customers, we want to take advantage of that extra B2B layer that is baked right into Shopify's customer account extensions. To do this, we’ll create a boolean field called <inline-code>isB2B<inline-code>, and when true, we’ll offer a separate subset of issues unique to B2B customers.
  4. Finally, we want to relate reported issues back to the order a customer is having problems with. To do that, we’ll create one more field for <inline-code>orders<inline-code> as a <inline-code>hasMany<inline-code> relationship with <inline-code>shopifyOrder<inline-code>, then set the relationship under <inline-code>belongs to<inline-code> to be <inline-code>issue<inline-code>. You should now be able to see it on the <inline-code>shopifyOrder<inline-code> model. 

Step 4: Set up your backend logic

Next, we’re going to build some backend logic so that when a customer selects an issue with one of our orders, our extension will go ahead and relate the issue to the actual order in our database. 

Gadget automatically generates a CRUD API for your app, and by default gives you create, update, and delete actions, as well as a read API, detailed in your app’s API docs. Each action comes with a code file, so you can edit and change them as needed. Actions have two main functions: <inline-code>run<inline-code> and <inline-code>onSuccess<inline-code>. The <inline-code>run<inline-code> function is typically useful for database operations, and <inline-code>onSuccess<inline-code> is great for longer-running actions or side effects. For this extension, we’re going to create a <inline-code>custom<inline-code> action. 

This action will allow us to run a custom operation on an individual order. Add a new action called <inline-code>addIssue<inline-code> and set the action type to <inline-code>custom<inline-code>. We’ll add the following code to connect the issue the customer selects with an individual order.

In this code, all the <inline-code>run<inline-code> function is doing is saving the record to my database, preventing cross-shop data access, which means merchants and customers won't be able to see each other's data. So if you are building a public app, data tenancy is handled for Shopify models automatically by Gadget.

The <inline-code>onSuccess<inline-code> function, on the other hand, has changed. We’ve also defined a custom parameter for this custom action called <inline-code>issueId<inline-code>. This will allow us to pass the selected issue from the customer, and it’s being pulled from our parameters object.

We’ve included some comments within the code, which provide an example of what you can do when an issue is submitted, if you’d like to explore more.

Step 5: Modify access controls

Next, we’ll configure the necessary access control and roles for our extension, as we need to define them for both merchants and customers.

  1. Navigate to the Access control part of your Gadget app.
  2. We want to give merchants the ability to manage any submitted issues from an embedded UI, so we’re going to grant <inline-code>shopify-app-users<inline-code> access to the <inline-code>issue<inline-code> data model’s <inline-code>read<inline-code>, <inline-code>create<inline-code>, and <inline-code>delete<inline-code> actions.
  3. Add a new role, which we’ll call <inline-code>shopify-customers<inline-code> and grant this new role <inline-code>read<inline-code> access on the <inline-code>issues<inline-code> model. We’ll also grant them permission to <inline-code>addIssue<inline-code> and <inline-code>read<inline-code> on the <inline-code>shopifyOrder<inline-code> model, so they can report problems with their orders, while being able to see if an issue has already been filed for a particular order.
  1. To avoid having customers report issues for other customers, we’re going to add a Gelly filter called <inline-code>orderIssue.gelly<inline-code>. This will enforce tenancy for our extension by confirming that the customer ID on the Shopify order matches the Shopify customer ID for the current session.
  1. Paste the following snippet into your new Gelly file:

Step 6: Create your embedded frontend

To allow merchants to manage these issues, we’ll need an embedded frontend.

To set up your frontend, copy and paste the following into <inline-code>frontend/ShopPage.jsx<inline-code>.

With this, we’ve included an API client that will be unique to your app, and we are importing this into a couple sample pages, like <inline-code>ShopPage.jsx<inline-code>. Because this is a React frontend, we can use state, state hooks, or any React hooks that we desire. We provide some useful React hooks for interacting with your Gadget app's API in the <inline-code>@gadgetinc/react<inline-code> package — you can read more in our docs.

At this point, you’ll want to test that everything works, so go ahead and add a few options to your issues. Make sure to include one that is specific to B2B!

Step 7: Creating the Shopify CLI Extension

Go ahead and run <inline-code>yarn create @shopify/app<inline-code>. Since Gadget gives you all of the infrastructure you need to run your app, we don’t need to use Remix here. Instead, we will select Start by adding your first extension to create an extension-only Shopify CLI app. Finally, run <inline-code>yarn generate extension<inline-code>.

It’s important to note, you do not want to create your extension as a new app. Instead, we’re going to connect to an existing app, specifically, the Partnera app we’ve just spent all this time building.

For extension type, select Customer account UI and select JavaScript React as the language. Now, we should see the extension in our file, which means we can work on it in VSCode or your editor of choice.

In the generated code, you’ll find an extensions folder. You should have a <inline-code>shopify.extension.toml<inline-code> file, which you can use to change your extension settings, a <inline-code>package.json<inline-code> file, and in the <inline-code>src<inline-code> folder, you’ll find your actual extension code. 

Go ahead and start your extension using <inline-code>yarn dev<inline-code> and select the same store you installed your app on earlier. Copy and paste the generated URLs to make sure everything runs in the customer account pages as expected.

We’re going to update our <inline-code>.toml<inline-code> file to include an additional menu action modal extension. This will add the widget, the UI, and the card that appears, when a customer needs to select an issue.

Your <inline-code>shopify.extension.toml<inline-code> file should look like this:

Make sure that the <inline-code>network_access<inline-code> flag is enabled and set to true, this will allow us to call our Gadget app’s API. An additional extension target, <inline-code>customer-account.order.action.render<inline-code> has also been added to the default <inline-code>toml<inline-code> config. 

Lastly, we need to install our Gadget API client. Start by running <inline-code>yarn add @gadgetinc/react<inline-code> and @gadget-client, and enter the name of your Gadget app. If you check out your extension code again, and look at <inline-code>package.json<inline-code>, you should see that your two Gadget packages are installed.

We’ll also set a custom auth token with a passed-in Shopify session token to just allow us to make secure requests from within this extension.

Note: Custom auth tokens are currently in beta. If you’d like to try them out, you can request access in our Discord.

Step 8: Writing extension code

Now that our API client is installed, we can initialize our API client and add the custom code that powers the extension.

First, create a new file <inline-code>src/api.js<inline-code> and paste the following code. Make sure to use your app’s <inline-code>@gadget-client<inline-code> dependency!

This code initializes a version of your Gadget app’s API client and provides a function that can be called to add the session token to the client, so all calls are authenticated and get granted the <inline-code>shopify-customers<inline-code> role we created earlier.

Next, paste the following code into <inline-code>src/MenuActionExtension.jsx<inline-code>:

This code uses the customer account API to check order fulfillment status and renders a button on the orders page if part of an order has been fulfilled.

Finally, create a new file <inline-code>src/MenuActionModelExtension.jsx<inline-code>. This is the modal that will open when the button from <inline-code>MenuActionExtension.jsx<inline-code> is clicked. Paste the following code:

This code renders a form and makes use of Gadget’s <inline-code>useActionForm<inline-code> hook to manage the selected state and call the <inline-code>addIssue<inline-code> action when a customer submits a problem.

You should be done writing code now. Time to test out our extension.

Step 9: Test and deploy

Run your app using <inline-code>yarn dev<inline-code> and follow the link in Shopify’s developer console back to the customer order page. Click on the Report a problem button to open the modal and select a problem for the order. Not seeing the button? Make sure one of your orders is at least partially fulfilled in the store admin.

Once you have reported a problem, you should see an <inline-code>issue<inline-code> stored on the record of the <inline-code>shopifyOrder<inline-code> data model for which you reported a problem.

That’s it! You've now built a Shopify app using Gadget that allows customers to report problems with their orders directly from the order status page. Extend the app by adding a Twilio or SendGrid integration to notify merchants of problems, or add a new page to the admin app that allows merchants to address any issues.

If you have questions or just want to talk about how awesome building Shopify extensions with Gadget is, you can always join our developer Discord.

Keep reading to learn about how it's built

Under the hood

We're on Discord, drop in and say hi!
Join Discord
Bouncing Arrow