# Customer account authentication & customer account UI extensions  ## What are customer account UI extensions?  Shopify customer account extensions allow developers to build custom apps and UIs in Shopify's order status portal. Details about the new customer extensibility features can be found in the [Shopify docs](https://shopify.dev/docs/apps/customer-accounts). 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, and that the customer can only access their own data. Shopify's customer account UI extensions and customer authentication in Gadget are currently only available to stores using Shopify's new customer accounts. Classic customer accounts are not supported. ## Enable customer account authentication  To set up your Gadget API to handle requests from the customer account UI extension: 1. Go to **Settings** -> **Plugins** -> **Shopify** in the Gadget editor 2. **Edit** an existing connection or [set up a new Shopify connection](https://docs.gadget.dev/guides/plugins/shopify/quickstart) 3. Include the `read_customers` access scope in your connection 4. Enable **Customer account authentication** and **Confirm** your changes The `shopifyCustomer` data model will be added to your connection configuration automatically if it was not already selected. Customer data requires you to fill out Shopify's [protected customer data access](https://docs.gadget.dev/guides/plugins/shopify/building-shopify-apps#protected-customer-data-access) form for webhooks to be registered. ### What does enabling customer account authentication do?  Enabling customer account authentication on a Gadget app will make the following changes to your app: * Add a new `shopify-storefront-customers` [access role](https://docs.gadget.dev/guides/access-control#roles) to `accessControl/permissions` that allows you to manage authorization for customer requests * This role is granted **read access** to any customer data that is stored in the app * A is automatically created and enforced so that customers can only read their own data * Add a relationship from the `session` model to `shopifyCustomer` so `session` records can be created for customers making requests * A [customer account login trigger](https://docs.gadget.dev/guides/actions/triggers#shopify-customer-account-login-trigger) is added to the `shopifyCustomer` model's `create` action so a `shopifyCustomer` record can be automatically retrieved from Shopify if the record does not already exist in your app's database * This role is granted **read access** to any customer data that is stored in the app * A is automatically created and enforced so that customers can only read their own data Customer account authentication is also supported by Gadget's source control system. Enabling it will set `customerAuthenticationEnabled: true` in a project's `settings.gadget.ts` file. Note that this file is only available when using [`ggt`, the Gadget CLI](https://docs.gadget.dev/guides/development-tools/cli), to pull your project down to your local machine. ## Customer data tenancy  In most cases, customers should only access their own data. To ensure this, Gadget automatically sets up customer data tenancy for the following Shopify models when you enable customer account authentication: * `shopifyCheckout` * `shopifyCompanyContact` * `shopifyCustomerAddress` * `shopifyCustomerMergeable` * `shopifyCustomerPaymentMethod` * `shopifyDraftOrder` * `shopifyGiftCard` * `shopifyOrder` * `shopifySubscriptionContract` For any other models, including custom models, you will need to manually add customer data tenancy. ### Set up customer multi-tenancy manually  You must do three things to enable multi-tenancy for customers on your custom models: 1. Add a relationship between your custom model and the `shopifyShop` model so that your custom model belongs to `shopifyShop` 2. Add a relationship between your custom model and the `shopifyCustomer` model so that your custom model belongs to `shopifyCustomer` 3. Add the `preventCrossShopDataAccess` function to the `run` function of your custom model's actions 4. Add a tenancy filter to your custom model ### Using `preventCrossShopDataAccess`  You can use the [`preventCrossShopDataAccess` function](https://docs.gadget.dev/reference/gadget-server#preventcrossshopdataaccess-params-record-options), imported from the `gadget-server` package, to ensure that customers can only access their data in your custom model's actions. This function will automatically be added to any custom actions created on Shopify data models. An example of how to use this function in a custom `blog` model's `create` action: ```typescript import { applyParams, save, ActionOptions } from "gadget-server"; // add `preventCrossShopDataAccess` to the import statement import { preventCrossShopDataAccess } from "gadget-server/shopify"; export const run: ActionRun = async ({ params, record, logger, api, connections, }) => { applyParams(params, record); // add this line to prevent customers from accessing other customers' data await preventCrossShopDataAccess(params, record); await save(record); }; export const options: ActionOptions = { actionType: "create", }; ``` ### Adding a tenancy filter  Adding a tenancy filter to the access control configuration for your custom model will make sure that customers can only access their own data. These filtered model permissions are written in Gelly, Gadget's data access language. For example, this Gelly snippet could be used to filter a `customerOffer` model data by customer. The `customerOffer` model has a `customer` relationship field that relates to the `shopifyCustomer` model. ```gelly // in Example of a tenancy filter for a custom model filter ($session: Session) on CustomerOffer [ where customerId == $session.shopifyCustomerId ] ``` For more information on tenancy filters and filtered model permissions, see the [access control guide](https://docs.gadget.dev/guides/access-control#filtered-model-permissions). ### Disabling customer multi-tenancy for a Shopify model  You may want to disable customer multi-tenancy for a Shopify model but still enforce shop multi-tenancy with `preventCrossShopDataAccess`. To do this, set the `enforceCustomerTenancy` option to `false` in the `preventCrossShopDataAccess` function call: ```typescript await preventCrossShopDataAccess(params, record, { enforceCustomerTenancy: false }); ``` For more information on the `preventCrossShopDataAccess` function, see the [Gadget API reference](https://docs.gadget.dev/reference/gadget-server#preventcrossshopdataaccess-params-record-options). ## 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 your Gadget app to work with Shopify extensions](https://docs.gadget.dev/guides/plugins/shopify/advanced-topics/extensions#working-with-extensions) and generate a new customer account UI extension 2. Install the `@gadgetinc/shopify-extensions` package at your Gadget project root OR at your extension root: ```bash yarn add @gadgetinc/shopify-extensions ``` 3. Install the `@gadgetinc/preact` package (only on Shopify API version `2025-10` or later): ```bash yarn add @gadgetinc/preact ``` 4. Initialize a new client, and wrap the extension component with the `Provider` component from `@gadgetinc/shopify-extensions` and pass it the `client` and `sessionToken`: ```preact import "@shopify/ui-extensions/preact"; import { render } from "preact"; import { Provider, useGadget } from "@gadgetinc/shopify-extensions/preact"; import { useMaybeFindFirst } from "@gadgetinc/preact"; import { ExampleAppClient } from "@gadget-client/example-app"; // 1. init your API client const apiClient = new ExampleAppClient(); // 2. Export the extension export default async () => { render(, document.body); }; // 3. Wrap extension in Gadget Provider function GadgetUIExtension() { return ( ); } function MenuActionItemButtonExtension() { // 3. Use ready to ensure session is initialized before making API calls // @type {{ ready: boolean, api: typeof apiClient }} const { api, ready } = useGadget(); // 4. Read data from custom data model const [{ data: exists, fetching, error }] = useMaybeFindFirst(api.customModel, { pause: !ready, }); // only show button if no existing note return Open modal; } ``` ```tsx import { ExampleAppClient } from "@gadget-client/example-app"; import { useFindOne } from "@gadgetinc/react"; import { Provider, useGadget } from "@gadgetinc/shopify-extensions/react"; import { Button, reactExtension, useApi, } from "@shopify/ui-extensions-react/customer-account"; const client = new ExampleAppClient(); export default reactExtension("customer-account.order.action.menu-item.render", () => ); function GadgetUIExtension() { const { sessionToken } = useApi(); return ( ); } function MenuActionExtension() { // get the 'api' client and a 'ready' boolean from the useGadget hook const { api: gadgetApi, ready } = useGadget(); // get an instance of the Shopify API object const api = useApi(); // the useFindOne hook gets the issueId (a custom field on order) // of the current order from the Gadget API const [{ data: order, fetching, error }] = useFindOne( gadgetApi.shopifyOrder, api.orderId.split("/").pop(), { select: { issueId: true, }, // use ready to pause hooks until the API client is ready to make authenticated requests pause: !ready, } ); // do not display anything if the data is still being fetched or there is an error if (fetching) return null; if (error) { console.error(error); return null; } // disable the button if an issue has already been reported return ( ); } ``` 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. It reads a custom `issueId` field on the `shopifyOrder` model. The `Provider` component will automatically pass the `sessionToken` to the `api` client. This will allow customers to make authenticated requests to your Gadget API, and grants them the `shopify-storefront-customers` role. A `ready` boolean is used to pause hook calls until the `api` client is ready to make authenticated requests. If you are not using React or Preact to build your extensions, you can still use the `@gadgetinc/shopify-extensions` package to handle authentication and make requests to your Gadget API. Check out the [package docs](https://www.npmjs.com/package/@gadgetinc/shopify-extensions) for more information. ## Disabling customer account authentication  To disable customer account authentication you can edit your existing Shopify connection in **Settings** in the Gadget editor and disable the **Customer account authentication** setting. If you are using [`ggt`, the Gadget CLI](https://docs.gadget.dev/guides/development-tools/cli), to pull your Gadget project down to your local machine, you can also disable customer account authentication by setting `customerAuthenticationEnabled: false` in your project's `settings.gadget.ts` file. Removing customer account authentication will automatically remove the customer account login trigger from `api/models/shopifyCustomer/actions/create.js`.