Building customer account UI extensionsÂ
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:
- Add a
shopifyCustomerId
string field to thesession
model
- Navigate to the Access control page in the Gadget editor
- Add a new role called
shopify-customers
- 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.
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.
- Hover over an action that the
shopify-customers
role has access to and click + Filter - Enter a file name, such as
customerOrder.gelly
, and click on the Create to create the new Gelly file
- Open the new
.gelly
file and add awhere
clause to filter by theshopifyCustomerId
field on thesession
model:
<your-file-name>.gellygellyfilter ($session: Session) on ShopifyOrder [where customerId == $session.shopifyCustomerId]
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:
- Set up a new Shopify CLI extenion-only app with a customer account UI extension
Change directory (
cd
) into your new app and install your Gadget API client and the@gadgetinc/react
package- Create a new file in your extension app
src
folder calledapi.js
and paste the following code:
extensions/customer-account-ui/src/api.jsJavaScript1import { Client } from "@gadget-client/<your-app-domain>";2export const api = new Client();34// used to add the session token to the request headers for the API client5export 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 });23};
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.
- Wrap the extension component with the
Provider
component from@gadgetinc/react
and pass theapi
client to it
extensions/customer-account-ui/src/MenuActionExtension.jsJavaScript1import { 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";910// the Provider is set up in the reactExtension() initialization function11export default reactExtension(12 "customer-account.order.action.menu-item.render",13 () => (14 <Provider api={api}>15 <MenuActionExtension />16 </Provider>17 )18);1920// ... your extension component
The Provider
allows you to use Gadget's React hooks and components in your extension.
- Call
setAuthToken
from in your extension code file, for example:
extensions/customer-account-ui/src/MenuActionExtension.jsJavaScript1import { 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";910export default reactExtension(11 "customer-account.order.action.menu-item.render",12 () => (13 <Provider api={api}>14 <MenuActionExtension />15 </Provider>16 )17);1819function MenuActionExtension() {20 const { orderId, sessionToken } = useApi();2122 useEffect(() => {23 // setup the api client to always query using the custom shopify auth implementation24 if (sessionToken) {25 setAuthToken(api, sessionToken);26 }27 }, [sessionToken]);2829 // the useFindOne hook gets the issueId (a custom field on order)30 // of the current order from the Gadget API31 const [{ data: orderIssue, fetching, error }] = useFindOne(32 api.shopifyOrder,33 orderId.split("/").pop(),34 {35 select: {36 issueId: true,37 },38 }39 );4041 // do not display anything if the data is still being fetched or there is an error42 if (fetching) return null;43 if (error) {44 console.error(error);45 return null;46 }4748 // disable the button if an issue has already been reported49 return (50 <Button disabled={!!orderIssue.issueId}>51 {orderIssue.issueId ? "Report submitted" : "Report a problem"}52 </Button>53 );54}
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.
Follow along with an end-to-end app build that uses customer account UI extensions to allow customers to report problems with fulfilled orders.