You can build your Shopify app extensions inside your Gadget app using the Shopify CLI and ggt, Gadget's CLI.
In your local terminal, run the ggt dev command replacing <YOUR APP DOMAIN> to pull down your app to your local machine:
terminal
ggt dev ./<YOUR APP DOMAIN> --app=<YOUR APP DOMAIN> --env=development
You can also click the cloud icon next to your environment selector in the Gadget editor to get your app's ggt dev command. See the
ggt guide for more info on working locally.
cd into your project, and open it in an editor.
Add the following workspaces definition to your package.json:
package.json
json
{
"workspaces": ["extensions/*"]
}
Installing dependencies
Once you add the workspaces definition to your package.json, you will need to use the -W flag to add new packages to your core Gadget app:
terminal
yarn add -W <package>
This is required by Yarn workspaces to ensure that all packages are installed in the correct location.
Add a .ignore file to the root of your project.
Add the following to both .ignore (and .gitignore if you are using source control):
add to .ignore and .gitignore
extensions/*/dist
extensions/*/node_modules
If your Gadget app does not have a shopify.app.toml file, you need to manually add one to the root of your project. New Gadget apps will
come with a TOML file. Learn more about working with Shopify TOML files in
Gadget.
Use the Shopify CLI to generate your checkout UI extension:
terminal
yarn shopify app generate extension
The following steps are for admin, checkout, or customer account extensions. For theme app extensions, see the theme app extensions section.
Select the same Shopify app and development store you used to connect to Shopify when prompted by Shopify's CLI.
Select an extension type.
This command will create an extensions folder at your project root, and your extension will be generated by the Shopify CLI.
Start your extension development server by running this command in your Gadget editor terminal:
terminal
yarn shopify:dev
Shopify scripts added to package.json
The Shopify connection, adds the following scripts to your package.json:
// Default Shopify CLI command
"shopify": "shopify"
// Use the current development environment's Shopify app config
"shopify:config:use:development": "shopify app config use shopify.app.development.toml"
// Use the production environment's Shopify app config
"shopify:config:use:production": "shopify app config use shopify.app.toml"
// Start the Shopify CLI dev server using the current development environment's Shopify app config
"shopify:dev": "yarn shopify:deploy:development --force && yarn shopify:config:use:development && shopify app dev --no-update"
// Deploy the development environment; this should be used when developing app extensions to sync the extension to Shopify
"shopify:deploy:development": "yarn shopify:config:use:development && shopify app deploy"
// Deploy the production environment; this can be used for CI setups to deploy the app to Shopify after the Gadget app is deployed to production using ggt deploy
"shopify:deploy:production": "yarn shopify:config:use:production && shopify app deploy"
// An alias for shopify:deploy:production
"shopify:deploy": "yarn shopify:deploy:production"
// Get info about the Shopify app
"shopify:info": "shopify app info"
Bringing an existing extension into Gadget?
If you are porting over an existing extension-only app and you are copying over your root-level app configuration shopify.app.toml, you
need to make sure use_legacy_install_flow = true is set in the [access_scopes]
section so Gadget can manage scope registration.
Using Shopify metafields as input
You can use Shopify metafields to store and retrieve custom data. This has the added benefit of being stored on Shopify's infrastructure, so you don't need to manage stored values in your Gadget database.
You do have the option to store metafield data in your Gadget database if it is required for your app. If you need access to metafield data in Gadget, you can add metadata fields to your Shopify data models.
Metafields are the only way to use custom data as input in some extensions, for example, most Shopify Functions.
Make a network request to your Gadget API
In some extensions, you can make external network requests to read or write data, or run custom logic.
To make external requests, you need to set network_access = true in your extension's shopify.extension.toml file. Some extensions, such as Admin UI extensions, already allow you to make requests to your app backend, and don't require this setting.
Some extension types do not allow external network requests. Check Shopify's documentation for the extension type you're working with to see if network access is allowed.
Initialize your API client
To use your Gadget API client in an extension you can import your Client and initialize it with your current Gadget app environment:
extensions/your-extension-name/src/Extension.jsx
JavaScript
import { MyShopifyAppClient } from "@gadget-client/my-shopify-app";
export const api = new MyShopifyAppClient({ environment: process.env["NODE_ENV"] });
If you are managing your extensions outside of your Gadget project, for example, in a Shopify CLI app, you need to
install your API client
.
Environment selection in extension clients
Shopify extensions are sandboxed, so there is not a simple way to get the current Gadget environment when starting an extension. If you only have a single development environment, using the extension's environment variable process.env.NODE_ENV could work for you.
If you have multiple development environments you will need a way to manually update the environment used to initialize the Client in extensions.
One option: add a small script file to your project that accepts an environment name and does string replacement for the environment used to init Client. Run this with a package.json script command that also starts the Shopify extension dev server. This approach can also work when deploying to production with a CI/CD pipeline.
Using @gadgetinc/shopify-extensions and hooks
The @gadgetinc/shopify-extensions package provides utilities to help you make authenticated requests to your Gadget app's API from within your Shopify extensions. You can use it in combination with Gadget-provided Preact or React hooks to call your app's API.
Install @gadgetinc/shopify-extensions and @gadgetinc/preact (or @gadgetinc/react) in your extension:
install in Preact extensions (Shopify API version 2025-10 and later)
Set up the Provider in your extension by wrapping the exported extension component or app with the Provider component and passing in your API client instance:
extensions/your-extension-name/src/Extension.jsx
import { Provider } from "@gadgetinc/shopify-extensions/preact";
import { api } from "./api";
export default async () => {
// 1. Export the extension
render(<GadgetUIExtension />, document.body);
};
function GadgetUIExtension() {
// 2. Wrap extension in Gadget Provider
const { sessionToken } = shopify;
return (
<Provider api={apiClient} sessionToken={sessionToken}>
<Extension />
</Provider>
);
}
import { Provider } from "@gadgetinc/shopify-extensions/react";
import { api } from "./api";
export default reactExtension(TARGET, () => (
<Provider api={api}>
<Extension />
</Provider>
));
import { Provider } from "@gadgetinc/shopify-extensions/react";
import { api } from "./api";
export default reactExtension(TARGET, () => (
<Provider api={api}>
<Extension />
</Provider>
));
Now you can use the @gadgetinc hooks to interact with your app's API.
Checkout extensions
Checkout extensions are making network requests from an unauthenticated context, the Shopify checkout. This means that requests made to your app's API will be granted the unauthenticated role. Make sure any data passed into the checkout extensions is safe to be seen by any buyer!
The @gadgetinc/shopify-extensions package will include the session token on requests made using your API client. You can use it in combination with Gadget-provided Preact or React hooks to call your app's API.
Then you can make use of the exported Provider and useGadget hook to automatically add the session token to requests made using your API client:
extensions/your-extension-name/src/Checkout.jsx
import "@shopify/ui-extensions/preact";
import { render } from "preact";
import { Provider, useGadget } from "@gadgetinc/shopify-extensions/react";
import { useFindMany } from "@gadgetinc/react";
import { ExampleAppClient } from "@gadget-client/example-app";
// 1. init your API client
const apiClient = new ExampleAppClient();
// 2. Export the extension
export default async () => {
render(<GadgetUIExtension />, document.body);
};
// 3. Wrap extension in Gadget Provider
function GadgetUIExtension() {
const { sessionToken } = shopify;
return (
<Provider api={apiClient} sessionToken={sessionToken}>
<Extension />
</Provider>
);
}
function Extension() {
// @type {{ ready: boolean, api: typeof apiClient }}
const { api, ready } = useGadget();
const [{ data, fetching, error }] = useFindMany(api.customModel, {
// use 'ready' to pause hooks until the API client is ready to make authenticated requests
pause: !ready,
});
// ... rest of extension component
// 4. Render a UI
return <s-banner heading="checkout-ui">Hello, world</s-banner>;
}
import { reactExtension, useApi, Banner } from "@shopify/ui-extensions-react/checkout"";
import { Provider, useGadget } from "@gadgetinc/shopify-extensions/react";
import { useFindMany } from "@gadgetinc/react";
import { ExampleAppClient } from "@gadget-client/example-app";
// initialize a new Client for your Gadget API
const apiClient = new ExampleAppClient();
// the Provider is set up in the reactExtension() initialization function
export default reactExtension("your.extension.target", () => <GadgetUIExtension />);
// component to set up the Provider with the sessionToken from Shopify
function GadgetUIExtension() {
const { sessionToken } = useApi();
return (
<Provider api={apiClient} sessionToken={sessionToken}>
<Extension />
</Provider>
);
}
function Extension() {
// get the 'api' client and a 'ready' boolean from the useGadget hook
const { api, ready } = useGadget<ExampleAppClient>();
const [{ data, fetching, error }] = useFindMany(api.customModel, {
// use 'ready' to pause hooks until the API client is ready to make authenticated requests
pause: !ready,
});
// the rest of your extension component...
return <Banner>Hello, world</Banner>;
}
import { reactExtension, useApi, Banner } from "@shopify/ui-extensions-react/checkout"";
import { Provider, useGadget } from "@gadgetinc/shopify-extensions/react";
import { useFindMany } from "@gadgetinc/react";
import { ExampleAppClient } from "@gadget-client/example-app";
// initialize a new Client for your Gadget API
const apiClient = new ExampleAppClient();
// the Provider is set up in the reactExtension() initialization function
export default reactExtension("your.extension.target", () => <GadgetUIExtension />);
// component to set up the Provider with the sessionToken from Shopify
function GadgetUIExtension() {
const { sessionToken } = useApi();
return (
<Provider api={apiClient} sessionToken={sessionToken}>
<Extension />
</Provider>
);
}
function Extension() {
// get the 'api' client and a 'ready' boolean from the useGadget hook
const { api, ready } = useGadget<ExampleAppClient>();
const [{ data, fetching, error }] = useFindMany(api.customModel, {
// use 'ready' to pause hooks until the API client is ready to make authenticated requests
pause: !ready,
});
// the rest of your extension component...
return <Banner>Hello, world</Banner>;
}
Post-purchase extensions
Post-purchase extensions are a type of checkout extension that requires a JSON Web Token (JWT) to be signed and passed to the extension. This signing can be done in your app backend by passing the JWT from the extension to Gadget as an Authorization: Bearer header.
Unlike other UI extensions, post-purchase extensions are still built with React.
For example, in your post-purchase extension, you can make a request to get offers and determine if you should render the extension:
extensions/your-extension-name/src/index.jsx
React
/**
* Extend Shopify Checkout with a custom Post Purchase user experience.
* This template provides two extension points:
*
* 1. ShouldRender - Called first, during the checkout process, when the
* payment page loads.
* 2. Render - If requested by `ShouldRender`, will be rendered after checkout
* completes
*/
// other imports such as React state hooks and extension components are omitted for brevity
import React from "react";
import { extend, render } from "@shopify/post-purchase-ui-extensions-react";
// your app API client
import { api } from "./api";
/**
* Entry point for the `ShouldRender` Extension Point.
*
* Returns a value indicating whether or not to render a PostPurchase step, and
* optionally allows data to be stored on the client for use in the `Render`
* extension point.
*/
extend("Checkout::PostPurchase::ShouldRender", async ({ inputData, storage }) => {
// get the variant ids of the products in the initial purchase
const productVariantIds = inputData.initialPurchase.lineItems.map((lineItem) => lineItem.product.variant.id);
// make request against POST-offer route in Gadget
const response = await api.fetch("/offer", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${inputData.token}`,
},
body: JSON.stringify({
referenceId: inputData.initialPurchase.referenceId,
productVariantIds,
}),
});
// get response body from route
const jsonResp = await response.json();
// save offers to extension storage
await storage.update({ offers: jsonResp.offers });
// For local development, always show the post-purchase page
return { render: true };
});
render("Checkout::PostPurchase::Render", () => <App />);
export function App() {
// the rest of the post-purchase extension component
// determine what is actually rendered in this component
}
/**
* Extend Shopify Checkout with a custom Post Purchase user experience.
* This template provides two extension points:
*
* 1. ShouldRender - Called first, during the checkout process, when the
* payment page loads.
* 2. Render - If requested by `ShouldRender`, will be rendered after checkout
* completes
*/
// other imports such as React state hooks and extension components are omitted for brevity
import React from "react";
import { extend, render } from "@shopify/post-purchase-ui-extensions-react";
// your app API client
import { api } from "./api";
/**
* Entry point for the `ShouldRender` Extension Point.
*
* Returns a value indicating whether or not to render a PostPurchase step, and
* optionally allows data to be stored on the client for use in the `Render`
* extension point.
*/
extend("Checkout::PostPurchase::ShouldRender", async ({ inputData, storage }) => {
// get the variant ids of the products in the initial purchase
const productVariantIds = inputData.initialPurchase.lineItems.map((lineItem) => lineItem.product.variant.id);
// make request against POST-offer route in Gadget
const response = await api.fetch("/offer", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${inputData.token}`,
},
body: JSON.stringify({
referenceId: inputData.initialPurchase.referenceId,
productVariantIds,
}),
});
// get response body from route
const jsonResp = await response.json();
// save offers to extension storage
await storage.update({ offers: jsonResp.offers });
// For local development, always show the post-purchase page
return { render: true };
});
render("Checkout::PostPurchase::Render", () => <App />);
export function App() {
// the rest of the post-purchase extension component
// determine what is actually rendered in this component
}
And your Gadget POST-offer HTTP route could look like:
api/routes/POST-offer.js
JavaScript
import { RouteHandler } from "gadget-server";
import jwt from "jsonwebtoken";
import { getOffers } from "../utils/offerUtils";
const route: RouteHandler<{
Body: {
referenceId: string;
productVariantIds: string[];
};
}> = async ({ request, reply, api, logger, connections }) => {
let token = request.headers?.Authorization as string;
if (token?.startsWith("Bearer ")) {
token = token.slice(7);
} else {
// if no bearer token is present, return 401 error
await reply.code(401).send();
}
// add SHOPIFY_CLIENT_SECRET (from Shopify app) as an environment variable to decode the token
const decodedToken = jwt.verify(token, process.env["SHOPIFY_CLIENT_SECRET"]);
// get the referenceId from the decoded token
const decodedReferenceId = decodedToken.input_data.initialPurchase.referenceId;
const { referenceId, productVariantIds } = request.body;
if (decodedReferenceId !== referenceId) {
// return error if incoming jwt is not valid
await reply.code(401).send();
}
// fetch custom offers
const offers = await getOffers({ api, logger, connections, productVariantIds });
// reply with the offers
await reply.headers({ "Content-type": "application/json" }).send({ offers });
};
export default route;
import { RouteHandler } from "gadget-server";
import jwt from "jsonwebtoken";
import { getOffers } from "../utils/offerUtils";
const route: RouteHandler<{
Body: {
referenceId: string;
productVariantIds: string[];
};
}> = async ({ request, reply, api, logger, connections }) => {
let token = request.headers?.Authorization as string;
if (token?.startsWith("Bearer ")) {
token = token.slice(7);
} else {
// if no bearer token is present, return 401 error
await reply.code(401).send();
}
// add SHOPIFY_CLIENT_SECRET (from Shopify app) as an environment variable to decode the token
const decodedToken = jwt.verify(token, process.env["SHOPIFY_CLIENT_SECRET"]);
// get the referenceId from the decoded token
const decodedReferenceId = decodedToken.input_data.initialPurchase.referenceId;
const { referenceId, productVariantIds } = request.body;
if (decodedReferenceId !== referenceId) {
// return error if incoming jwt is not valid
await reply.code(401).send();
}
// fetch custom offers
const offers = await getOffers({ api, logger, connections, productVariantIds });
// reply with the offers
await reply.headers({ "Content-type": "application/json" }).send({ offers });
};
export default route;
You will also need a POST-sign-changeset HTTP route in your Gadget app to apply the order changes if a buyer accepts the offer:
api/routes/POST-sign-changeset.js
JavaScript
import { RouteHandler } from "gadget-server";
import { v4 as uuidv4 } from "uuid";
import jwt from "jsonwebtoken";
const route: RouteHandler<{
Body: {
referenceId: string;
changes: string;
};
}> = async ({ request, reply, api, logger, connections }) => {
// get token from headers
let token = request.headers?.authorization as string;
if (token?.startsWith("Bearer ")) {
token = token.slice(7);
} else {
// if no bearer token is present, return 401 error
await reply.code(401).send();
}
// add SHOPIFY_CLIENT_SECRET (from Shopify app) as an environment variable to decode the token
const decodedToken = jwt.verify(token, process.env["SHOPIFY_CLIENT_SECRET"]);
const decodedReferenceId = decodedToken.input_data.initialPurchase.referenceId;
const { referenceId, changes } = request.body;
// compare passed in referenceId with decoded referenceId
if (decodedReferenceId !== referenceId) {
// return error if incoming jwt is not valid
await reply.code(401).send();
}
// create the payload for updating the order
const payload = {
iss: process.env["SHOPIFY_CLIENT_KEY"],
jti: uuidv4(),
iat: Date.now(),
sub: referenceId,
changes,
};
// sign the token and return back to the extension
const responseToken = jwt.sign(payload, process.env["SHOPIFY_CLIENT_SECRET"]);
await reply.send({ token: responseToken });
};
export default route;
import { RouteHandler } from "gadget-server";
import { v4 as uuidv4 } from "uuid";
import jwt from "jsonwebtoken";
const route: RouteHandler<{
Body: {
referenceId: string;
changes: string;
};
}> = async ({ request, reply, api, logger, connections }) => {
// get token from headers
let token = request.headers?.authorization as string;
if (token?.startsWith("Bearer ")) {
token = token.slice(7);
} else {
// if no bearer token is present, return 401 error
await reply.code(401).send();
}
// add SHOPIFY_CLIENT_SECRET (from Shopify app) as an environment variable to decode the token
const decodedToken = jwt.verify(token, process.env["SHOPIFY_CLIENT_SECRET"]);
const decodedReferenceId = decodedToken.input_data.initialPurchase.referenceId;
const { referenceId, changes } = request.body;
// compare passed in referenceId with decoded referenceId
if (decodedReferenceId !== referenceId) {
// return error if incoming jwt is not valid
await reply.code(401).send();
}
// create the payload for updating the order
const payload = {
iss: process.env["SHOPIFY_CLIENT_KEY"],
jti: uuidv4(),
iat: Date.now(),
sub: referenceId,
changes,
};
// sign the token and return back to the extension
const responseToken = jwt.sign(payload, process.env["SHOPIFY_CLIENT_SECRET"]);
await reply.send({ token: responseToken });
};
export default route;
Post-purchase extensions require the Shopify app's API key and secret to be stored as environment variables in your Gadget app.
Customer account UI extensions
Gadget's support for customer account UI extensions is in beta. See the guide for more information.
Admin extensions
By default, Shopify's Admin extensions will add an Authentication header to requests made by the extension.
Your Gadget app will automatically handle these incoming requests, and grant them the shopify-app-users role. This means you can use your api client like you would in an embedded admin frontend, with or without the @gadgetinc/react or @gadgetinc/preact hooks.
Here is how you can use hooks to make authenticated requests in admin extensions:
import { render } from "preact";
import { Provider, useFindFirst } from "@gadgetinc/preact";
import { ExampleAppClient } from "@gadget-client/example-app";
// 1. init your API client
const api = new ExampleAppClient();
// 2. Export the extension
export default async () => {
render(<GadgetUIExtension />, document.body);
};
// 3. Wrap extension in Gadget Provider
function GadgetUIExtension() {
return (
<Provider api={api}>
<Extension />
</Provider>
);
}
function Extension() {
// 4. Read data from a model
const [{ data, fetching, error }] = useFindFirst(api.customModel);
return (
<s-admin-block heading="My Block Extension">
<s-stack direction="block">
<s-text type="strong">
{fetching ? "Loading..." : data ? data.title : "No record found"}
</s-text>
</s-stack>
</s-admin-block>
);
}
import { reactExtension, useApi, AdminBlock, BlockStack, Text } from "@shopify/ui-extensions-react/admin";
import { Provider, useAction } from "@gadgetinc/react";
import { ExampleAppClient } from "@gadget-client/example-app";
const TARGET = "admin.product-details.action.render";
// 1. init your API client
const api = new ExampleAppClient();
// 2. Wrap extension in Provider
export default reactExtension(TARGET, () => (
<Provider api={api}>
<Extension />
</Provider>
));
function Extension() {
// 3. Use hooks to call your app's API
const [{data: product, fetching, error}] = useFindFirst(api.customModel);
return (
<AdminBlock title="My Block Extension">
<BlockStack gap>
<Text fontWeight="bold">
{fetching ? "Loading..." : data ? data.title : "No record found"}
</Text>
</BlockStack>
</AdminBlock>
);
}
import { reactExtension, useApi, AdminBlock, BlockStack, Text } from "@shopify/ui-extensions-react/admin";
import { Provider, useAction } from "@gadgetinc/react";
import { ExampleAppClient } from "@gadget-client/example-app";
const TARGET = "admin.product-details.action.render";
// 1. init your API client
const api = new ExampleAppClient();
// 2. Wrap extension in Provider
export default reactExtension(TARGET, () => (
<Provider api={api}>
<Extension />
</Provider>
));
function Extension() {
// 3. Use hooks to call your app's API
const [{data: product, fetching, error}] = useFindFirst(api.customModel);
return (
<AdminBlock title="My Block Extension">
<BlockStack gap>
<Text fontWeight="bold">
{fetching ? "Loading..." : data ? data.title : "No record found"}
</Text>
</BlockStack>
</AdminBlock>
);
}
Theme app extensions
Theme app extensions are different from other types of extensions because they are built using Liquid and JavaScript. They are not Node projects, so there is no package.json where a Gadget API client can be installed.
Instead, you need to:
Include your app's direct script tag to use the API client in a theme app extension .liquid block:
When you add your script tag, make sure the domain has the correct environment name!
For example, if you are working in the development environment, your script tag src should look like https://YOUR-GADGET-DOMAIN--development.gadget.app/api/client/web.min.js
Create a file in extensions/your-extension-name/assets, and initialize the API client:
document.addEventListener("DOMContentLoaded", function () {
// initialize an API client object
const myExtensionAPI = new Gadget();
const myButton = document.getElementById("my-button");
myButton.addEventListener("click", async () => {
// make a request to your app's API
const response = await myExtensionAPI.myDataModel.findOne("1");
console.log(response);
});
});
document.addEventListener("DOMContentLoaded", function () {
// initialize an API client object
const myExtensionAPI = new Gadget();
const myButton = document.getElementById("my-button");
myButton.addEventListener("click", async () => {
// make a request to your app's API
const response = await myExtensionAPI.myDataModel.findOne("1");
console.log(response);
});
});
Authenticated requests with Shopify app proxies
When making requests from a theme app extension to your Gadget app, you can use a Shopify app proxy to ensure that requests are made securely. The Gadget platform will handle validating the HMAC signature sent in the request query parameters for you. This is useful for ensuring that requests are coming from a valid Shopify store.
Note that a valid HMAC signature doesn't set the session to an authenticated role. This means that making a POST request to your GraphQL
API would be using the unauthenticated role. You can find out more information in our route common use
cases docs.
Determining the theme version of a store
Shopify Theme app extensions can only integrate with Online Store 2.0 themes.
This means you may need to provide instructions for manually adding your app's theme extensions to stores running vintage themes.
The determineShopThemeVersion helper can be used to make sure you are providing the correct installation instructions.
Using determineShopThemeVersion requires the read_themes API scope to be selected in your Shopify connection so Gadget can send a
GraphQL request to Shopify to read theme metadata.
determineShopThemeVersion can be imported from gadget-server/shopify and used in your actions:
api/actions/getShopifyThemeVersion.js
JavaScript
import { determineShopThemeVersion } from "gadget-server/shopify";
export const run: ActionRun = async ({ params, logger, api, connections }) => {
// Get the current Shopify client that belongs to the shop that the action is running in
const shopify = connections.shopify.current;
if (!shopify) {
throw new Error("Must run in a Shopify context");
}
// Pass the client to the helper function to get the theme versions of each page type
return await determineShopThemeVersion(shopify);
};
import { determineShopThemeVersion } from "gadget-server/shopify";
export const run: ActionRun = async ({ params, logger, api, connections }) => {
// Get the current Shopify client that belongs to the shop that the action is running in
const shopify = connections.shopify.current;
if (!shopify) {
throw new Error("Must run in a Shopify context");
}
// Pass the client to the helper function to get the theme versions of each page type
return await determineShopThemeVersion(shopify);
};
You can also use determineShopThemeVersion in a Remix loader:
web/routes/_app._index.jsx
React
import { useLoaderData } from "@remix-run/react";
import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { determineShopThemeVersion } from "gadget-server/shopify";
export const loader = async ({ context }: LoaderFunctionArgs) => {
const shopify = context.connections.shopify.current;
if (!shopify) {
throw new Error("Must run in a Shopify context");
}
return json({
result: await determineShopThemeVersion(shopify),
});
};
export default function Page() {
const { result } = useLoaderData<typeof loader>();
return result.map((item) => (
<div key={item.pageType}>
<h3>Page type: {item.pageType}</h3>
<p>Filename: {item.filename}</p>
<p>Version: {item.version}</p>
</div>
));
}
import { useLoaderData } from "@remix-run/react";
import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { determineShopThemeVersion } from "gadget-server/shopify";
export const loader = async ({ context }: LoaderFunctionArgs) => {
const shopify = context.connections.shopify.current;
if (!shopify) {
throw new Error("Must run in a Shopify context");
}
return json({
result: await determineShopThemeVersion(shopify),
});
};
export default function Page() {
const { result } = useLoaderData<typeof loader>();
return result.map((item) => (
<div key={item.pageType}>
<h3>Page type: {item.pageType}</h3>
<p>Filename: {item.filename}</p>
<p>Version: {item.version}</p>
</div>
));
}
If your extension can only be added to certain page types, you can specify a list of page types to reduce the number of files that need to be fetched: