External Frontends with the Shopify CLI 

The Shopify CLI can produce a frontend application for your Shopify app. If the built-in Gadget frontend doesn't work for you, you can use the frontend generated by the Shopify CLI as an external frontend for your Gadget backend application. This lets you still take advantage of Gadget's OAuth handling, scalable backend database, and robust webhook processing while using the frontend generated by the Shopify CLI.

Gadget's built-in frontend hosting 

Gadget backend applications come with a built-in hosted frontend for Shopify app development. The built-in frontend automatically handles:

  • hosting and deployment
  • Shopify OAuth
  • Shopify's security requirements for embedded applications
  • installing and managing Shopify Polaris and the @shopify/app-bridge libraries.

Gadget's built-in frontend uses Vite, which is the same as the Shopify CLI, and means that the two are quite similar to work with.

Gadget recommends using the frontend that comes with your Gadget application for building Shopify apps, instead of using the frontend generated by the Shopify CLI. However, using the frontend codebase generated by the Shopify CLI as an external frontend may be necssary if you have special performance or hosting requirements.

Using the Shopify CLI frontend 

To use Shopify's generated frontend as an external frontend, you will need to make a few changes to the code generated by the Shopify CLI. Instead of deleting the web folder from your repository after generating a CLI app, keep it in place, and follow these steps:

  • Update web/index.js to not implement Shopify OAuth, as Gadget handles OAuth and syncing data from the Shopify API. Instead, web/index.js just needs to serve the frontend application with the correct security headers for Shopify.

Replace the contents of web/index.js with the following:

1// @ts-check
2import { join } from "path";
3import * as fs from "fs";
4import express from "express";
5import serveStatic from "serve-static";
7const __dirname = new URL(".", import.meta.url).pathname;
9const PORT = parseInt(process.env["BACKEND_PORT"] || process.env["PORT"], 10);
10const STATIC_PATH =
11 process.env["NODE_ENV"] === "production"
12 ? `${__dirname}/frontend/dist`
13 : `${__dirname}/frontend/`;
15const app = express();
17// return Shopify's required iframe embedding headers for all requests
18app.use((req, res, next) => {
19 const shop = req.query.shop;
20 if (shop) {
21 res.setHeader(
22 "Content-Security-Policy",
23 `frame-ancestors https://${shop} https://admin.shopify.com;`
24 );
25 }
26 next();
29// serve any static assets built by vite in the frontend folder
30app.use(serveStatic(STATIC_PATH, { index: false }));
32// serve the client side app for all routes, allowing it to pick which page to render
33app.use("/*", async (_req, res, _next) => {
34 return res
35 .status(200)
36 .set("Content-Type", "text/html")
37 .send(fs.readFileSync(join(STATIC_PATH, "index.html")));
  • You can then delete the other example code that @shopify/cli created in the web/ directory when it created your app if you like by running the following command in your app's root directory
rm -f web/shopify.js web/product-creator.js web/gdpr.js

Finally, we can set up our Gadget Client and use the Provider to handle OAuth for our embedded app.

You need to install your Gadget dependencies in the web/frontend directory of your Shopify CLI application! Change into this directory before running the following commands:

cd web/frontend
  • Install local-ssl-proxy in the web/frontend directory
npm install local-ssl-proxy
yarn add local-ssl-proxy
  • Update the dev script in web/frontend/package.json to vite & local-ssl-proxy --source 443 --target 3005.
2 // ...
3 "scripts": {
4 "build": "vite build",
5 "dev": "vite & local-ssl-proxy --source 443 --target 3005",
6 "coverage": "vitest run --coverage"
7 }
Working with Windows?

If you are working with Windows, the dev command above will not work. You will need to split it up into two separate commands and run them separately. For example, "dev": "vite" and "dev-proxy": "local-ssl-proxy --source 443 --target 3005".

This allows us to use our local frontend when doing development inside Shopify's admin, which uses HTTPS.

  • Replace your web/frontend/vite.config file with the following code:
1import { defineConfig } from "vite";
2import { dirname } from "path";
3import { fileURLToPath } from "url";
4import react from "@vitejs/plugin-react";
6if (
7 process.env["npm_lifecycle_event"] === "build" &&
8 !process.env["CI"] &&
9 !process.env["SHOPIFY_API_KEY"]
10) {
11 console.warn(
12 "\nBuilding the frontend app without an API key. The frontend build will not run without an API key. Set the SHOPIFY_API_KEY environment variable when running the build command.\n"
13 );
16const host = "localhost";
17const port = 3005;
19export default defineConfig({
20 root: dirname(fileURLToPath(import.meta.url)),
21 plugins: [react()],
22 define: {
23 "process.env": JSON.stringify({
25 }),
26 },
27 resolve: {
28 preserveSymlinks: true,
29 },
30 server: {
31 host: host,
32 port: port,
33 hmr: {
34 protocol: "ws",
35 host: host,
36 port: port,
37 clientPort: port,
38 },
39 },
Changing the port

If you wish to change the port for your local server, make sure to modify both the port variable in web/frontend/vite.config and the target at the end of the dev script in the web/frontend/package.json. Note that the ports must be the same for the proxy to function correctly.

Shopify CLI apps using Gadget don't need to use ngrok and instead run at https://localhost. This vite config keeps vite's hot module reloading functionality working quickly without using ngrok which is faster and more reliable.

  • You need to register the Gadget NPM registry for the @gadget-client package scope:
npm config set @gadget-client:registry https://registry.gadget.dev/npm
  • The following npm modules are required when creating an app that will be embedded in the Shopify Admin:
npm install @gadgetinc/react @gadgetinc/react-shopify-app-bridge @gadget-client/example-app
yarn add @gadgetinc/react @gadgetinc/react-shopify-app-bridge @gadget-client/example-app

Make sure to replace `example-app` with your app's package name!

  • To deploy your frontend using hosting platforms such as Vercel, Heroku or Netlify, you will need to add a new file web/frontend/.npmrc to help point to the Gadget registry.
  • The next step is to set up your Gadget client in the application. You can use this client to make requests to your Gadget application. You can create a new file in your project, and add the following code:
import { Client } from "@gadget-client/example-app";
export const api = new Client();
  • Now you need to set up the Provider in web/frontend/App.jsx. We can also use the useGadget hook to ensure we are authenticated before we make requests using the API. Here is a small snippet as an example:
1import {
2 AppType,
3 Provider as GadgetProvider,
4 useGadget,
5} from "@gadgetinc/react-shopify-app-bridge";
6import { api } from "./api";
8import { PolarisProvider } from "./components";
11 Gadget's Provider takes care of App Bridge authentication, you do not need Shopify's default AppBridgeProvider.
13export default function App() {
14 return (
15 <GadgetProvider
16 type={AppType.Embedded}
17 shopifyApiKey={process.env["SHOPIFY_API_KEY"]}
18 api={api}
19 >
20 <PolarisProvider>
21 <EmbeddedApp />
22 </PolarisProvider>
23 </GadgetProvider>
24 );
27// This is where we make sure we have auth'd with AppBridge
28// Once we have authenticated, we can render our app!
29// Feel free to use the default page navigation that Shopify's CLI sets up for you
30// example here - https://github.com/gadget-inc/examples/blob/main/packages/shopify-cli-embedded/web/frontend/App.jsx
31function EmbeddedApp() {
32 // we use `isAuthenticated` to render pages once the OAuth flow is complete!
33 const { isAuthenticated } = useGadget();
34 return isAuthenticated ? (
35 <span>Hello, world!</span>
36 ) : (
37 <span>Authenticating...</span>
38 );

If you are looking for examples of how to use our API client, visit our examples repository.

To use App Bridge components from Shopify you will need the useGadget React hook. More information about the hook can be found in the useGadget React hook docs.

Next steps 

Once you've updated your Shopify CLI web folder to function as an external frontend, you can start building your app! See the Shopify Connection guide for more information on building apps for Shopify with Gadget.


If using the Shopify CLI's web folder as the frontend for your application, you'll need to deploy it to a hosting platform. See the External Deployment guide for more information on deploying your frontend elsewhere.