Shopify App Frontends

Gadget provides rich tools for building frontends for Shopify applications, including apps embedded in the Shopify admin using the Shopify App Bridge. Gadget's @gadgetinc/react-shopify-app-bridge implements Shopify's requirements for OAuth flows, secure iframe embedding, and Session Token authentication out of the box so you can focus on building your app.

Frontends for Shopify Apps communicate with Gadget backends using your Gadget app's GraphQL API and the associated JS client for your application. There are a few different npm packages necessary for working with Gadget from a Shopify app frontend:

PackageDescriptionAvailable from
@shopify/app-bridgeShopify's React package for embedding React applications within the Shopify Adminnpm
@gadget-client/example-appThe JS client for your specific Gadget applicationGadget NPM registry
@gadgetinc/reactThe Gadget React bindings library, providing React hooks for making API callsnpm
@gadgetinc/react-shopify-app-bridgeThe Gadget Shopify wrapper library for Shopify Embedded App setup and authenticationnpm

Installation

To build a Shopify App frontend with your Gadget backend, you must first set up a React application. There are a few options for setting up a React app:

Gadget recommends the next.js approach as it is the easiest to get started with and includes a lot of great functionality out of the box.

After creating a React app, you must install your Gadget application's JS client package.

To install the Example App node package, register the Gadget NPM registry for the @gadget-client package scope:

npm config set @gadget-client:registry https://registry.gadget.dev/npm

Then, install the Example App client package and the @gadgetinc/react package using your package manager:

npm install @gadget-client/example-app @gadgetinc/react
yarn add @gadget-client/example-app @gadgetinc/react

These instructions are examples for an example app. When you create your own Gadget app, they'll update to reflect the right name and commands for your specific application.

Create your own app at gadget.new

As you make changes to Example App, Gadget will publish new versions of your API client to its NPM registry. See Client Regeneration for more details on when updates are necessary.

Next, you must install Shopify's App Bridge package (@shopify/app-bridge), and Gadget's Shopify bindings package (@gadgetinc/react-shopify-app-bridge).

npm install --save @gadgetinc/react-shopify-app-bridge @gadgetinc/react @shopify/app-bridge-react
yarn add @gadgetinc/react-shopify-app-bridge @gadgetinc/react @shopify/app-bridge-react

If you are using the Shopify CLI 3.0 to create your app, you need to install your Gadget dependencies in the /web/frontend directory!

The Provider

The @gadgetinc/react-shopify-app-bridge library handles authentication of your embedded app via the Provider component. This provider has two main benefits - it handles authentication and the series of redirects required to complete an embedded app OAuth flow in Shopify, and it handles retrieving a Shopify session token from the App Bridge and passing it along to Gadget for authenticated calls.

The Provider handles these key tasks automatically:

  • automatically starts the OAuth process with new users of the application using Gadget, escaping Shopify's iframe if necessary
  • establishes an iframe-safe secure session with the Gadget backend using Shopify's Session Token authentication scheme
  • sets up the correct React context for making backend calls to Gadget using @gadgetinc/react

The Provider has the following required props:

Provider Interface
TypeScript
export interface ProviderProps {
type: AppType; // 'AppType.Embedded' or 'AppType.Standalone'
shopifyApiKey: string; // the API key from your Shopify app in the partner dashboard that is used with the Shopify App Bridge
api: string; // the API client created using your Gadget application
}

The Gadget provider will handle detecting if your app is being rendered in an embedded context and redirect the user through Shopify's OAuth flow if necessary.

Provider example

Start by setting up your API client, setting the storageType of the browserSession to BrowserSessionStorageType.Temporary:

src/api.ts
TypeScript
1import {
2 BrowserSessionStorageType,
3 Client,
4} from "@gadget-client/my-gadget-app";
5
6export const api = new Client({
7 authenticationMode: {
8 browserSession: {
9 storageType:
10 BrowserSessionStorageType.Temporary,
11 },
12 },
13});

Now we need to import and set up the Provider in the App component:

Once the Provider is set up properly, you can start to build out components in your embedded application.

src/app.tsx
TypeScript
1import {
2 AppType,
3 Provider as GadgetProvider,
4} from "@gadgetinc/react-shopify-app-bridge";
5import { api } from "./api";
6import { ProductManager } from "./productManager";
7import React from "react";
8
9export function MyApp() {
10 return (
11 // type can be omitted. Defaults to AppType.Embedded
12 <GadgetProvider
13 type={AppType.Embedded}
14 shopifyApiKey={apiKey}
15 api={api}
16 >
17 <ProductManager />
18 </GadgetProvider>
19 );
20}

Shopify App Setup

When building embedded apps for Shopify, the App URL field in the Shopify App setup (found in your Partners Dashboard) should be set to the URL where your frontend application is hosted. For example, if you are developing locally this would likely be set to https://localhost. Additionally, the App URL in your Gadget app's Shopify Connection should be set to the same URL.

Note: Shopify expects a Content-Security-Policy header to be set from your application for it to be embedded in the Shopify Admin. The header value should be set to frame-ancestors https://example-shop.myshopify.com/ https://admin.shopify.com;

The useGadget React hook

The Provider handles initializing the App Bridge for us. Now we can build our application component and use the initialized instance of App Bridge via the appBridge key returned from the embedded React hook useGadget.

useGadget provides the following properties:

useGadget React Hook
TypeScript
1export interface useGadget {
2 isAuthenticated: boolean; // 'true' if the user has completed a successful OAuth flow
3 isEmbedded: boolean; // 'true' if the app is running in an embedded context
4 isRootFrameRequest: boolean; // 'true' if a user is viewing a "type: AppType.Embedded" app in a non-embedded context, for example, accessing the app at a hosted Vercel domain
5 loading: boolean; // 'true' if the OAuth flow is in process
6 appBridge: AppBridge; // a ready-to-use app bridge from Shopify, you can also use the traditional useAppBridge hook in your components to retrieve it.
7}

useGadget example

The following example renders a ProductManager component that makes use of Shopify App Bridge components and is ready to be embedded in a Shopify Admin page.

productManager.tsx
TypeScript
1import { useAction, useFindMany } from "@gadgetinc/react";
2import { useGadget } from "@gadgetinc/react-shopify-app-bridge";
3import { Button, Redirect, TitleBar } from "@shopify/app-bridge/actions";
4import { api } from "./api.ts";
5
6function ProductManager() {
7 const { loading, appBridge, isRootFrameRequest } = useGadget();
8 const [_, deleteProduct] = useAction(api.shopifyProduct.delete);
9 const [{ data, fetching, error }, refresh] = useFindMany(api.shopifyProduct);
10
11 if (error) return <>Error: {error.toString()}</>;
12 if (fetching) return <>Fetching...</>;
13 if (!data) return <>No widgets found</>;
14
15 // Set up a title bar for the embedded app
16 const breadcrumb = Button.create(appBridge, { label: "My breadcrumb" });
17 breadcrumb.subscribe(Button.Action.CLICK, () => {
18 appBridge.dispatch(Redirect.toApp({ path: "/breadcrumb-link" }));
19 });
20
21 const titleBarOptions = {
22 title: "My page title",
23 breadcrumbs: breadcrumb,
24 };
25 TitleBar.create(appBridge, titleBarOptions);
26
27 return (
28 <>
29 {loading && <span>Loading...</span>}
30 {isRootFrameRequest && (
31 <span>App can only be viewed in the Shopify Admin!</span>
32 )}
33 {!loading &&
34 !isRootFrameRequest &&
35 data.map((widget, i) => (
36 <button
37 key={i}
38 onClick={(event) => {
39 event.preventDefault();
40 void deleteProduct({ id: widget.id }).then(() => refresh());
41 }}
42 >
43 Delete {widget.title}
44 </button>
45 ))}
46 </>
47 );
48}

GraphQL Queries

When building embedded Shopify apps, there may be instances where a Shop's installed scopes have not been updated to match the required scopes in your Gadget app's Connection. In these situations, it is necessary to re-authenticate with Shopify so that the app can acquire the updated scopes. The following GraphQL query can be run using the app client (passed to the Provider) and provides information related to missing scopes and whether a re-authentication is necessary.

productManager.tsx
GraphQL
1query {
2 shopifyConnection {
3 requiresReauthentication
4 missingScopes
5 }
6}

Embedded app examples

Want to see an example of an embedded Shopify app built using Gadget?

Check out some of our example apps on Github, including: