Building customer account UI extensions 

Currently in beta

Note: This feature is currently in beta. To use this feature, you need to join our Discord and request access to the customer account UI extension beta.

What are customer account UI extensions? 

Shopify introduced a new extension that allows you to build custom apps and UIs in the order status portal. Details about the new customer extensibility features can be found in the Shopify docs.

To build a customer account UI extension with Gadget, you need to make sure that requests made to your Gadget API from the extension are secure. To do this, we need to make some changes to the default Shopify app setup in Gadget.

Securing customer requests 

Follow these steps to set up your Gadget apps to securely handle requests from the customer account UI extension:

  1. Add a shopifyCustomerId string field to the session model
A screenshot highlighting the session model and the added shopifyCustomerId string field
  1. Navigate to the Access control page in the Gadget editor
  2. Add a new role called shopify-customers
  3. Grant the shopify-customers role access the actions that are required for your apps

We have now granted shopify-customers the ability to call your Gadget API from within an extension. We can also add Gelly files to ensure that multi-tenancy is handled correctly, and customers cannot read or update each other's data.

Relationship to shopifyCustomer required

To properly filter in Gelly against the shopifyCustomerId on the current session, the model you are filtering on needs to have a relationship to the shopifyCustomer model.

  1. Hover over an action that the shopify-customers role has access to and click + Filter
  2. Enter a file name, such as customerOrder.gelly, and click on the Create to create the new Gelly file
  1. Open the new .gelly file and add a where clause to filter by the shopifyCustomerId field on the session model:
filter ($session: Session) on ShopifyOrder [
where customerId == $session.shopifyCustomerId
Correct model required

Note that the model name in the .gelly file should match the model name of the action! This example uses the shopifyOrder model.

Now you are ready to make requests from the customer account UI extension to your Gadget API.

Setting up the Shopify CLI extension 

Now you can set up a Shopify CLI app with a customer account UI extension and make requests to your Gadget API. Follow these steps to set up the CLI app:

  1. Set up a new Shopify CLI extenion-only app with a customer account UI extension
  2. Change directory (cd) into your new app and install your Gadget API client and the @gadgetinc/react package

  3. Create a new file in your extension app src folder called api.js and paste the following code:
1import { Client } from "@gadget-client/<your-app-domain>";
2export const api = new Client();
4// used to add the session token to the request headers for the API client
5export const setAuthToken = (api, sessionToken) => {
6 api.connection.setAuthenticationMode({
7 custom: {
8 async processFetch(_input, init) {
9 const token = await sessionToken.get();
10 const headers = new Headers(init.headers);
11 headers.append("Authorization", `ShopifySessionToken ${token}`);
12 init.headers ??= {};
13 headers.forEach(function (value, key) {
14 init.headers[key] = value;
15 });
16 },
17 async processTransactionConnectionParams(params) {
18 const token = await sessionToken.get();
19 params.auth.shopifySessionToken = token;
20 },
21 },
22 });

Make sure to replace <your-app-domain> with your Gadget app domain!

This code adds a Shopify session token to headers for all requests made to your Gadget API.

  1. Wrap the extension component with the Provider component from @gadgetinc/react and pass the api client to it
1import { useEffect } from "react";
2import {
3 Button,
4 reactExtension,
5 useApi,
6} from "@shopify/ui-extensions-react/customer-account";
7import { api, setAuthToken } from "./api";
8import { Provider, useFindOne } from "@gadgetinc/react";
10// the Provider is set up in the reactExtension() initialization function
11export default reactExtension(
12 "",
13 () => (
14 <Provider api={api}>
15 <MenuActionExtension />
16 </Provider>
17 )
20// ... your extension component

The Provider allows you to use Gadget's React hooks and components in your extension.

  1. Call setAuthToken from in your extension code file, for example:
1import { useEffect, useState } from "react";
2import {
3 Button,
4 reactExtension,
5 useApi,
6} from "@shopify/ui-extensions-react/customer-account";
7import { api, setAuthToken } from "./api";
8import { Provider, useFindOne } from "@gadgetinc/react";
10export default reactExtension(
11 "",
12 () => (
13 <Provider api={api}>
14 <MenuActionExtension />
15 </Provider>
16 )
19function MenuActionExtension() {
20 const { orderId, sessionToken } = useApi();
22 useEffect(() => {
23 // setup the api client to always query using the custom shopify auth implementation
24 if (sessionToken) {
25 setAuthToken(api, sessionToken);
26 }
27 }, [sessionToken]);
29 // the useFindOne hook gets the issueId (a custom field on order)
30 // of the current order from the Gadget API
31 const [{ data: orderIssue, fetching, error }] = useFindOne(
32 api.shopifyOrder,
33 orderId.split("/").pop(),
34 {
35 select: {
36 issueId: true,
37 },
38 }
39 );
41 // do not display anything if the data is still being fetched or there is an error
42 if (fetching) return null;
43 if (error) {
44 console.error(error);
45 return null;
46 }
48 // disable the button if an issue has already been reported
49 return (
50 <Button disabled={!!orderIssue.issueId}>
51 {orderIssue.issueId ? "Report submitted" : "Report a problem"}
52 </Button>
53 );

This is an example of how to use the useFindOne hook to fetch custom data from your Gadget API and use it in your extension.

Fullstack app build + customer account UI extension

Follow along with an end-to-end app build that uses customer account UI extensions to allow customers to report problems with fulfilled orders.