# Build a full stack Shopify app  Time to build: ~20 minutes In this tutorial, you'll build a full stack Shopify app that automatically tags products based on keywords found in the product description. This is a great introduction to Gadget and Shopify, and will give you a solid foundation for building more complex apps. ## Prerequisites  Before starting, you will need: * A [Shopify Partner account](https://partners.shopify.com/). * A [Shopify development store](https://help.shopify.com/en/partners/dashboard/managing-stores/development-stores). * At least one product in your store that has a product description. ## Step 1: Create a Gadget app and connect to Shopify  1. Go to [gadget.new](https://gadget.new) and create a new Shopify app. 2. If you haven't set up a Shopify connection before, follow the [Shopify quickstart](https://docs.gadget-canary.xyz/guides/plugins/shopify/quickstart) guide to set up the Shopify connection. **For this tutorial, you need the `write_products` Shopify API scope and the `product` model.** 3. Confirm that your Shopify connection has the `write_products` scope and the `product` model selected in the connection settings: ## Step 2: Add a data model to store keywords  The next step is to create a model that will store your list of vetted keywords that you can use to power your tagging script. 1. Click on **Files** in the left nav to go to the file explorer. 2. Click **+** next to the `api/models` folder in the nav to add a model, and call it `allowedTag`. 3. Click **+** in the FIELDS section of the `api/models/allowedTag/schema` page to add a field, and name it `keyword`. 4. It doesn't make sense to create a record without a `keyword`, so add the **Required** validation to the field. ### Test your `allowedTag.create` action  Gadget instantly creates a new table and column in the underlying database and generates a GraphQL CRUD API and API client for this model. Test your `allowedTag.create` action in the API Playground: 5. Click on `api/models/allowedTag/actions/create.js` to open the `allowedTag` model's `create` action. 6. Click the **Run action** button in the **TRIGGERS** panel on the right of the editor to open up the create action in the API Playground. 7. Enter a `keyword` to store in your database and run the action. Examples of keywords might include different product categories or brands. Make sure that the entered keyword is present in the product description of one of your store's products! 8. Navigate to `api/models/allowedTag/data` to go to this model's data page. You can see your `allowedTag` record: This tutorial walks through building a custom app for a single merchant. Gadget also supports multi-tenant public Shopify apps listed in the Shopify App Store. [To learn more about multi-tenancy in models, check out the guide](https://docs.gadget-canary.xyz/guides/plugins/shopify/advanced-topics/data-security#adding-multi-tenancy-to-custom-models). ## Step 3: Add backend logic to apply tags  Your backend needs to determine the tags to write to a product when a product is created or updated, then write those tags back to Shopify. ### Update your product create and update actions  The `create`, `update`, and `delete` actions for Shopify models are triggered by webhooks or a Shopify data sync. Code in the `run` function of these actions creates, updates, or deletes the record in the database, and validates that the request is coming from the current shop. 1. Paste the following in `api/models/shopifyProduct/actions/create.js` to call an `applyTags` function after a product is created: ```typescript import { applyParams, save, ActionOptions } from "gadget-server"; import { preventCrossShopDataAccess } from "gadget-server/shopify"; import { applyTags } from "../utils"; export const run: ActionRun = async ({ params, record }) => { applyParams(params, record); await preventCrossShopDataAccess(params, record); await save(record); }; export const onSuccess: ActionOnSuccess = async ({ record }) => { // Checks if the 'body' field has changed and applies tags using the applyTags function. if (record.changed("body")) { await applyTags({ id: record.id, body: record.body, tags: record.tags as string[], }); } }; export const options: ActionOptions = { actionType: "create", }; ``` The `record.changed` helper is a special field that Gadget has included to help prevent an infinite loop when updating Shopify records. Updating the tags on a product will fire Shopify's `products/update` webhook. If you are using this webhook as a trigger for running custom code that updates a product, you will be stuck in an endless loop of updating products and running the action. You can use `record.changed` to determine if changes have been made to the key on this record and only run code if changes have occurred. For more info on change tracking in Gadget, refer to the [documentation](https://docs.gadget-canary.xyz/api/example-app/development/gadget-record#change-tracking-dirty-tracking). 2. Copy the `onSuccess` function from `api/models/shopifyProduct/actions/create.js` into `api/models/shopifyProduct/actions/update.js` so that tags are applied when a product is updated as well. Don't forget to import the `applyTags` function at the top of the file! ```typescript import { applyTags } from "../utils"; // ... other imports // ... run function export const onSuccess: ActionOnSuccess = async ({ record }) => { if (record.changed("body")) { await applyTags({ id: record.id, body: record.body, tags: record.tags as string[], }); } }; // ... options ``` ### Add a shared utility function  Now you need to implement the `applyTags` function containing the logic for determining which tags to apply, then writing those tags back to Shopify. A util file is used so you can share this code between the `create` and `update` actions. 3. Create a `utils.js` file at `api/models/shopifyProduct/utils.js`. 4. Paste the following code into `utils.js`: ```typescript import { logger, api, connections } from "gadget-server"; /** * Applies tags to a Shopify product using the Shopify API. */ export const applyTags = async ({ tags, body, id, }: { tags: string[]; body: string | null; id: string; }) => { // get the shopify client for the current shop const shopify = connections.shopify.current; if (id && body && shopify) { // get a unique list of words used in the record's description let wordsInProductDescription = new Set(body.match(/\w+(?:'\w+)*/g)); // filter down to only those words which are allowed const savedKeywords = ( await api.allowedTag.findMany({ // 250 is the max page size in Gadget AND max number of tags in Shopify first: 250, }) ).map((tag) => tag.keyword); // define tags as a set for quick reads const tagsSet = new Set(tags); // get list of non-unique keywords to apply as tags // make sure they aren't already tags on the product const keywordsToApply = savedKeywords.filter( (keyword) => wordsInProductDescription.has(keyword) && !tagsSet.has(keyword) ); // use the built-in logger for backend debugging logger.info( { wordsInProductDescription: [...wordsInProductDescription], savedKeywords, keywordsToApply, }, "words from product description, stored keywords, and the overlap to be applied" ); if (keywordsToApply.length > 0) { // merge with existing tags const finalTags = Array.from(new Set([...keywordsToApply, ...tags])); // log the tags you are applying logger.info({ finalTags }, `applying finalTags to product ${id}`); // enqueue a background action to write the tags to Shopify await api.enqueue(shopify.graphql, { query: `mutation ($id: ID!, $tags: [String!]) { productUpdate(product: {id: $id, tags: $tags}) { product { id } userErrors { message } } }`, variables: { id: `gid://shopify/Product/${id}`, tags: finalTags, }, }); } } }; ``` The `applyTags` function determines which tags to apply and then uses `api.enqueue` update the product in Shopify using background actions. Gadget gives us a **connections** object as an argument to the action, which has an authenticated Shopify API client ready to go. The `shopify.graphql` mutation is run with a [background action](https://docs.gadget-canary.xyz/guides/actions/background) using `api.enqueue` to handle Shopify's rate limits. Under the hood, Gadget [manages Shopify's API rate limits](https://docs.gadget-canary.xyz/guides/plugins/shopify/building-shopify-apps#managing-shopify-api-rate-limits) for you, and using background actions allows Gadget to use the adaptive rate limiter to manage the requests made to Shopify for that shop. Your backend is done. The product actions will use the `applyTags` function to determine the tags to apply, then updates the product in Shopify. ## Step 4: Access control  Gadget has built-in access control permissions to handle _authorization_ for your app's API. This allows you to restrict access based on the user's role. By default, Shopify merchants will _not_ have access to your custom model APIs, for example, the `allowedTag` actions. You can grant permissions to the `shopify-app-users` role to allow merchants to successfully call these APIs. 1. Navigate to the `accessControl/permissions` page. 2. Grant the `shopify-app-users` role access to the `allowedTag/` model's `read`, `create`, and `delete` actions. Now merchants will be able to manage `allowedTag` records from the embedded frontend in their Shopify store admin. ## Step 5: Build a Shopify admin frontend  Your app frontend is in the `web` folder, and includes: * an API client for your Gadget app (`web/api.js`). * reusable React components (`web/components`). * file-based routing (`web/routes`, where the index route is `web/routes/_app._index.jsx`). The entire tagger frontend code snippet is below. Additional details on some of Gadget's provided tooling are below the snippet. 1. Paste the following code into `web/routes/_app._index.jsx` ```tsx import { AutoForm, AutoTable } from "@gadgetinc/react/auto/polaris"; import { Card, Layout, Page, Text } from "@shopify/polaris"; import { api } from "../api"; export default function Index() { return ( {/* This form allows users to add new keywords */} Keywords {/* This table displays the allowed keywords for the Shopify product */} ); } ``` The [`@gadgetinc/react/auto` library](https://docs.gadget-canary.xyz/reference/react/auto) provides autocomponents for your frontend. Autocomponents are pre-built configurable forms and tables that are wired up to your model actions. Read the [autocomponent guide](https://docs.gadget-canary.xyz/guides/frontend/autocomponents) for more information on autocomponent customization. You don't need to use autocomponents in your frontends. Check out the [Shopify frontends guide](https://docs.gadget-canary.xyz/guides/plugins/shopify/frontends) to learn how to manually read and write data. 2. Click on the preview button () in the Gadget editor to go back to your app in the development store admin and preview your frontend. You are done building, time to test it out! ## Step 6: Test your app  1. Add some keywords to your product tagger. You want to make sure to use words that are in your product descriptions. 2. Navigate to **Installs** using the left-nav in the Gadget editor, then click **Sync** on the connected store. Gadget will fetch each of the existing products in Shopify and run them through your `shopifyProduct/create` and `shopifyProduct/update` actions, applying tags as needed. Because those actions are also triggered by webhooks, adding or updating a product will also run your tagging logic. Congratulations! You have built a full stack, custom app that automatically updates tags in Shopify. ## Next steps  Have questions about the tutorial? Ask the Gadget team in our developer [Discord](https://ggt.link/discord). Want to build apps that use Shopify extensions? Try the UI extension tutorial: #### Building with Shopify UI extensions Learn how to use Gadget and metafields to build Shopify customer account UI extensions. ##### Start building → [Start building →](https://docs.gadget-canary.xyz/guides/tutorials/shopify/ui-extension)