Build a Product Quiz app with Gadget, the Shopify CLI, and Liquid
Topics covered: Embedded Shopify apps, Extend Shopify theme, Access control, Storing files
Time to build: ~1 hour
Product recommendation quizzes help build engaging sales experiences for online shoppers by allowing them to map their problems or concerns to a product that best meets their needs. For Shopify merchants, this can be an appealing proposition! Merchants can build custom quizzes that present shoppers with a tailored experience, resulting in higher sales conversions and greater satisfaction by matching shoppers with the right products.
To get the most out of this tutorial, you will need:
- A Shopify Partner account
- A development store
- A recently-installed Shopify-developed theme (for example, the default Dawn theme)
Introduction
In under an hour, we can create a lightweight, customizable product recommendation quiz app using Gadget, connect the results to products in a Shopify merchant's store, build an admin app used for quiz creation, and finally embed the quiz into a storefront.

Let's start building!
You can fork this Gadget project and try it out yourself.
You will still need to set up your Shopify Connection to a Shopify store. You can also jump to App frontends to hook up your Gadget backend with two different frontends: an embedded Shopify admin app and a Liquid template-based solution.
Getting Started with Gadget
Starting your app
Head over to app.gadget.dev and log in to your Gadget account. Don't have an account? Create a new one by authenticating with Google or Github, or enter your email and a password. Next, Gadget will prompt you to create a new application. Click “Create App” and Gadget will bring you to your new application.
To recommend products to shoppers, we'll need product data in our app that we can map to the results of a product recommendation quiz. Using Gadget's Connections feature, we can connect our app to a Shopify store and pull product data right from the shop.
Connecting to Shopify
The Shopify connection provides us with access to any of the models surfaced in Shopify's Admin API, as well as an authenticated client and webhook consumption. This connection also allows us to sync data between Shopify and Gadget, both scheduled and on-demand.
Connect to Shopify through the Partners dashboard
To complete this connection, you will need a Shopify Partners account as well as a store or development store
Our first step is going to be setting up a custom Shopify application in the Partners dashboard.
- Go to the Shopify Partners dashboard
- Click on the link to the Apps page
Both the Shopify store Admin and the Shopify Partner Dashboard have an Apps section. Ensure that you are on the Shopify Partner Dashboard before continuing.

- Click the Create App button

- Click the Create app manually button and enter a name for your Shopify app

- Go to the Connections page in your Gadget app

- Copy the Client ID and Client secret from your newly created Shopify app and paste the values into the Gadget Connections page
- Click Connect on the Gadget Connections page to move to scope and model selection

Now we get to select what Shopify scopes we give our application access to, while also picking what Shopify data models we want to import into our Gadget app.
- Enable the read and write scopes for the Shopify Product, and select the Product model we want to import into Gadget

- Click Confirm at the bottom of the page
We have successfully created our connection!
Now we want to connect our Gadget app to our custom app in the Partners dashboard.
- In your Shopify app in the Partners dashboard, click on App setup in the side nav bar so you can edit the App URL and Allowed redirection URL(s) fields
- Copy the App URL and Allowed redirection URL from the Gadget Connections page and paste them into your custom Shopify App

Now we need to install our Shopify app on a store, and sync data.
- Go back to the Shopify Partners dashboard
- Click on Apps to go to the Apps page again
- Click on your custom app
- Click on Select store
- Click on the store we want to use to develop our app
- You may be prompted about Store transfer being disabled. This is okay, click Install anyway
- Click Install app to install your Gadget app on your Shopify store


We will see a success message if our app was successfully installed.

Set up is complete! We are now ready to build our Gadget application.
If we have created at least one product in our store we can test out the connection:
- Go back to our Gadget Connections page and click on the Shop Installs button for the added app in the Shopify Apps section
- Click the Sync button for the installed store
- We should see that the Sync action was a success



That's it! We have successfully set up a store and custom app in Shopify, connected our Gadget app to this store, and synced our store data with our Gadget app.
Building our Quiz models
Proposed solution design
We need a way to create, serve, and record quiz responses in our app. The recording of responses enables us to track the conversion state we discussed above, effectively making a response to a quiz a snapshot of a session that a shopper has with our quiz.
We need some models to capture the structure of our product quiz. A Quiz has many Questions, and each Question can have multiple possible Answers. Each Answer has a Recommended Product that relates to a product in a Shopify store. This means that each question answered will be linked to a product to offer shoppers.
We also need a model to capture user responses to our quiz, Quiz Result. This will capture shopper information, such as an email address, and a list of recommended products so follow-up emails can be sent once a quiz is completed. The products recommended to shoppers are stored in a separate model, Shopper Suggestion. Keeping these recommendations in a separate model means you can update quiz questions and answers while maintaining recommendations for shoppers who took previous versions of the quiz.
Here's a diagram to demonstrate what relationships our models will have with each other:

Models outline
We need to create models for our app to represent the components of our quiz - Question, Answer, Recommended Product, Quiz Result, and the Quiz itself. We need to connect these components by their relationships; Gadget's built-in relationship fields make this connection effortless. Let's start with the Quiz model.
Quiz
The Quiz model is the backbone of our application. Our app can have many instances of Quiz, each representing a unique product recommendation quiz created through the admin user interface. Our Quiz model needs a couple of fields to get started: a title, maybe a description or body content, and some identifying information like an ID.
Creating a new model in Gadget will take care of some of these fields for us automatically. Each model in Gadget comes with three fields: ID, Created At, and Updated At.
If we click the + in the Models section of the side nav, we will create a new model that we can rename to Quiz. We can also start adding fields.

Up at the top of this schema view, we've named the model Quiz, and Gadget has created the API Identifier corresponding to the model's name. From here, we can add our first field, Title. Title is a string, and we cannot create an instance of Quiz without it. So, let's select the + button next to FIELDS and create our Title field:

Again, naming the field will automatically generate the API Identifier. We can then select the type of data we're storing in this field, whether or not it has a default value, and any validations we may want to run against this field on object creation. In the case of Title, we want to select the Required validation. We can also add a Uniqueness validation if we want to ensure no two Quizzes have the same title.

Let's now add another field to represent the optional description for the Quiz model. Name the field Body:

For simplicity's sake, we'll set the type to string with no validations.
But what is happening as we create these models and add these fields? Behind the scenes, Gadget automatically generates a CRUD API for each created model and updates this API with any new fields you add, modify, or remove. This means you can quickly test and consume your API immediately after changing your models. Gadget also creates API documentation for your API and a type-safe JavaScript client for you to consume, all in the background as you work.
With that, our Quiz model is done for now, and we can move on to Question.
Question
Let's create another new model, and name it Question. This model will represent a single Question in a given Quiz. We need a string field to hold the question itself.
Let's add Text to Question. Text is a string field with Required validation.

Now that Question is set, we're going to need some Answers. On to the next model!
Answer
By now, you should be getting a feel for how the Gadget schema editor works and how quickly you can build expressive models with exactly the fields and logic you need. Next on our list, our Answer model needs just one additional field: a field named Text. Our Text field will be a string type field with the Required validation, as our Answer needs to have a text body for users to identify which Answer to choose.

Recommended Product
Each Answer in our quiz will offer a different product to shoppers. We use the Recommended Product model as a link between our Answers and Shopify Products. The Result model is also how we'll connect outcomes to product recommendations once we make our relationship connections. We will add these relationships soon. For now, we only need to add a file field called Image so we can upload custom images for our recommended products.

Quiz Result
Our final model for our Quiz app is the Quiz Result model. As discussed at the beginning of this tutorial, the Quiz Result model represents an instance of taking the Quiz and allows us to track the recommended product IDs of any given user who has completed the quiz.
We're going to add an Email field of type email to log the email of the shopper that is completing the quiz, so the shopper can be contacted at a later time. The email field type has built-in validation that makes sure records are in the correct format.

We are going to use a relationship to capture the products that are presented to shoppers so that will be handled in the next section.
That's all for model building!
Now we need to link our models together to represent the conceptual connections they share. This brings us to our next step:
Bringing it all together: Relationships
Now that our models are created we can connect them using Relationship fields.
Quiz relationship fields
Navigate back to the Quiz model, then add a Questions field to represent the connection of instances of the Question model to an instance of Quiz.
Adding a Relationship field is similar to adding a type-based field. Near the bottom of the selection list for the field type, we see Relationships listed. If you want to dive deeper into how Relationships work in Gadget, you can read our Relationship and Relationship Fields documentation. For now, we can move forward with the understanding that we can declare a relationship, and Gadget takes care of linking the models together for us without us needing to create and manage foreign keys.
In the case of Questions, we know already that one Quiz has many Questions. So, we can model this relationship using the has many Relationship field. Selecting this relationship type allows us to then select the child model, Question.
Once we select Question as the child of Quiz, the schema editor allows us to model what the inverse of the relationship looks like, giving us finer control of the API identifier for this relationship in the generated schema. We'll just refer to the inverse of the relationship as Quiz, so the relationship is then Quiz has many Questions, and Question belongs to Quiz.

The other relationship to add to Quiz is to the Quiz Result model. Exactly like Question, a Quiz has many Quiz Result objects through a Results field. You can call the inverse field for both of these relationships Quiz.

Question relationship fields
If we move over to the Question model now, we'll see that Gadget has created a Quiz field on Question for us, linking a Question to one Quiz. In addition to being a child of Quiz, Question is a parent to the Answer model. A Question has many Answers (it wouldn't be a very interesting quiz otherwise!), so we can add an Answers field to our Question model that represents this relationship. Go ahead and add this field now:

Recommended Product relationship fields
Our Recommended Product model will be used to map Answers to Shopify Products that can be recommended to shoppers. Recommended Product is the child of two relationships. It belongs to both Answer and Shopify Product models. We can add both of these relationships to the Recommended Product model.
First, we can add the belongs to relationship to the Answer model with a new field called Answer. When adding a belongs to we need to decide whether the inverse relationship is has one or a has many relationship. In this case, each Answer has one Recommended Product.

The other relationship to add is a field called Product Suggestion. This field represents the link to the Shopify Product we're recommending. This is also a belongs to relationship, but in this case, the inverse relationship needs to be a has many because each Shopify Product can be recommended by multiple answers across multiple quizzes.

This means we've now extended the Shopify Product model beyond what Shopify provides. These additional fields on this connected model are only visible on the Gadget side of the connection and do not sync back to Shopify. Instead, these fields allow us to decorate connection-based models with whatever additional information or logic we may need for our apps, such as relationships. For more on how you can extend Shopify-provided models with Gadget, check out our guide on the Shopify connection.
Quiz Result relationship
We need one more relationship on our Quiz Result model. We want our results to keep track of the product ids that have been recommended to shoppers so that follow-up emails and communication can present the same products.
We're going to use a has-many-through relationship so that our Quiz Result will have many Shopify Products through a new model, Shopper Suggestion.
Create a new field on the Quiz Result model called Shopper Suggestion and make it a has-many-through relationship.
Next, select Shopify Product for the has-many part of the relationship and then type Shopper Suggestion for the through model. This will create a new Shopper Suggestion model in your application.
Finally, we need to define the field name for the relationship. We can call the field on Shopper Suggestion that relates to Quiz Result the same name as the model, Quiz Result. Then we can name the field on Shopper Suggestion that relates to the Shopify Product model Product. Finally, the new field on the Shopify Product model can be called Shopper Suggestion.

That's all of our relationships!
With our models all connected, the schema of our app is complete. We have all the fields and relationships needed to hook up our admin app's UI.
If you're building a public app, you need to make sure quizzes are only accessible to the stores for which they were created. You'll need to add a relationship from the Quiz model to the Shopify Shop model and a Gelly snippet to the Quiz model on the Roles & Permissions page. This will allow you to enforce tenancy on your product quizzes.
Attach code to Actions
Soon we're going to add an admin app that allows us to create and edit our quizzes. It will also allow us to delete quizzes.
When a quiz is deleted, we want to clean up all the related questions, answers, and recommended products. We can add some code to our delete actions on these models to handle this cleanup.
Start with the Quiz model. On the Quiz model's Structure page click on the delete action in the actions list. Then click on the Run Code File button near the bottom of the action panel. A new code file will be generated for you. This code file will be run every time the Quiz model's delete action is called.
Add the following code to this file:
quiz/delete/ondelete.jsJavaScript1/**2 * Effect code for delete on Quiz3 * @param { import("gadget-server").DeleteQuizActionContext } context - Everything for running this effect, like the api client, current record, params, etc. More on effect context: https://docs.gadget.dev/guides/extending-with-code#effect-context4 */5module.exports = async ({ api, record, params, logger }) => {6 // clean up quiz questions7 // on an update, remove old questions and answers and refresh quiz with new options8 const quizQuestions = await api.question.findMany({9 filter: {10 quiz: {11 equals: record.id,12 },13 },14 select: {15 id: true,16 },17 });1819 const ids = quizQuestions.map((question) => question.id);20 await api.question.bulkDelete(ids);21};
This snippet uses the api
to get all the ids of this quiz's questions. The api.question.bulkDelete
is called with this list of ids and all the questions are deleted.
We can add similar snippets to our Question model to delete Answers, and our Answers model to delete Recommended Products.
When our Quiz model's code file is run, question.bulkDelete
will call the delete action for our Question model. So we can attach the following snippet to the delete action on the Question model to remove all associated Answers.
question/delete/ondelete.jsJavaScript1/**2 * Effect code for delete on Question3 * @param { import("gadget-server").DeleteQuestionActionContext } context - Everything for running this effect, like the api client, current record, params, etc. More on effect context: https://docs.gadget.dev/guides/extending-with-code#effect-context4 */5module.exports = async ({ api, record, params }) => {6 // clean up quiz answers7 const answers = await api.answer.findMany({8 filter: {9 question: {10 equals: record.id,11 },12 },13 select: {14 id: true,15 },16 });1718 const ids = answers.map((answer) => answer.id);19 await api.answer.bulkDelete(ids);20};
Similar to the snippet used for the Quiz delete action, this collects the ids of Answers for a single Question and then calls api.answer.bulkDelete
.
Finally, let's add one more code effect to our Answer model's delete action.
answer/delete/ondelete.jsJavaScript1/**2 * Effect code for delete on Answer3 * @param { import("gadget-server").DeleteAnswerActionContext } context - Everything for running this effect, like the api client, current record, params, etc. More on effect context: https://docs.gadget.dev/guides/extending-with-code#effect-context4 */5module.exports = async ({ api, record, params }) => {6 // clean up recommended products7 const recommendations = await api.recommendedProduct.findMany({8 filter: {9 answer: {10 equals: record.id,11 },12 },13 select: {14 id: true,15 },16 });1718 const id = recommendations[0].id;19 await api.recommendedProduct.delete(id);20};
Each Answer only has a single Recommended Product, so we can call api.recommendedProduct.delete
on the single Recommended Product id instead of calling bulkDelete
.
That's all the code we need to add to our quiz app! Let's move on to adding user permissions.
Roles and permissions
The quiz app has two different sets of users. The merchant who is creating the quiz, and shoppers who take the quiz. Merchants will create quizzes in their store admin and will have the Shopify App User role in Gadget. Shoppers will be Unauthenticated, so all quiz-related data needs to be readable by anyone.
Gadget helps manage these different roles using the Roles & Permissions page, which you can access through the Settings option in the left nav.
First, grant permissions to our Shopify App Users role. We need these users, the merchants, to have full access to Quiz, Question, Answer, and Recommended Product models. Click the checkboxes for read, create, update, and delete for all of these models.
Next, Unauthenticated users need to be able to read Quiz, Question, Answer, Recommended Product, and Shopify Product models. Select the read checkboxes for the Unauthenticated role for that list of models.
Our Unauthenticated users also need to be able to save their responses. We can give them create access to the Quiz Result and Shopper Suggestion models.

Now your quiz users have the access they need to be able to edit and read quiz data. Our Gadget app is ready to connect to our frontends!
App frontends
We need two UIs for our product quiz. We need an embedded admin UI to create and edit our quizzes, and we need some liquid code to embed the quiz as a page in our storefront.
Embedded Shopify admin app
Part of what makes Gadget so powerful is how it automatically generates API client packages for you in both JavaScript and TypeScript, making the job of consuming and interacting with your app's backend nearly effortless. We can consume our API in two ways for our app: an embedded app run locally or hosted on Vercel with a merchant-facing view and a customer-facing UI in the Shopify store's theme.
Getting started with the UI
There is a sample quiz admin app that can be used to create new quizzes. To get a copy of this project locally, run:
npx [email protected] --example https://github.com/gadget-inc/examples --example-path packages/product-recommendations-quiz-app/product-quiz-admin
The quiz admin app is a Shopify CLI 3.0 app using Shopify Polaris components and the provided Gadget React tooling.
Before you run the admin app, you need to make changes so that your Gadget app is used as the backend.
Update admin app
You need to update the admin app so that you are using your project's Gadget client.
- Register the Gadget NPM registry with your local environment
npm config set @gadget-client:registry https://registry.gadget.dev/npm
- Remove the sample client
@gadget-client/product-quiz-v2
from theweb/frontend
directory. You may need to runnpm install
oryarn
first!
npm uninstall @gadget-client/product-quiz-v2
yarn remove @gadget-client/product-quiz-v2
- Install your client in the
web/frontend
directory
npm install @gadget-client/example-app
yarn add @gadget-client/example-app
- In
web/frontend/api/gadget.ts
, update the import statement at the top to use your Gadget client
import { Client } from "@gadget-client/example-app";
Now you can start your admin app by running the following npm or yarn command from your application's root directory:
npm run dev
yarn dev
Shopify will prompt you to either create a new Partners app or connect to an existing app. You want to connect to the Partners app you created earlier in the tutorial.
Before we take a look at our app in our shop admin, we need to change our App URLs in Gadget and the Partners dashboard.
Update your App URL
To use your local application as your admin app, you need to update the App URL for your connection. Your current App URL should look like https://<your-app-slug>.gadget.app/shopify/install
.
- Go to the Connections page in Gadget
- Click on the Edit button for your connected app
- Change the App URL to https://localhost

You need to make a similar change to the App URL on the Partners dashboard.
- Navigate to your app in the Partners dashboard
- Click on App setup
- Change the App URL field to https://localhost
Your app is already installed on a store, and you already synced data. If you view your app in your shop, you should be able to see the quiz admin app running!
We use the local-ssl-proxy
package to create an https proxy so that your local app can be embedded in the Shopify admin. This package
uses a self-signed certificate that Chrome and other browsers may block.
If you're having an issue viewing your embedded app locally, try logging in at https://localhost
. If you see a
NET::ERR_CERT_AUTHORITY_INVALID
message for localhost you will need to click Advanced and then Proceed to localhost.

Making our first Quiz
Now for the fun part, making a quiz!
After installing the app you will be redirected to the main page for the embedded quiz app. Go ahead and create a new quiz - add some questions and answers, and link answers to recommended products.
We can look at records in Gadget and see how our front-end app connects with Gadget through the client and makes API calls against it. If we look at the Quiz data by selecting the Data icon on the Quiz model in the left-hand sidebar, we should see at least one instance of Quiz, including its ID, title, and body. We can also inspect our other records to see how our pieces work together to create our quiz experience.
When you've got a quiz that you're happy with, note the ID of the quiz.
Shopify Storefront: Liquid changes
Installing in a Shopify theme
While we used an NPM package to install our client into our freestanding app, we'll need another method of calling the client in our Shopify shop's theme. Gadget allows us to call our API client directly with a script tag.
We only need the client to run to serve the desired product recommendation quiz. In this case, we'll make a new template for the Page resource and then use it on a page we'll create to hold the quiz.
In your Shopify admin for your shop, head to Online Store > Themes and select Edit Code under the Actions menu for the theme you wish to edit.

Under Sections, create a new section called quiz-page.liquid.
We're going to replace this page with the following code:
html1<link rel="stylesheet" href="{{ 'section-main-page.css' | asset_url }}" media="print" onload="this.media='all'">2<link rel="stylesheet" href="{{ 'component-rte.css' | asset_url }}" media="print" onload="this.media='all'">3<!-- Find your direct script tag in the Installing section in your Gadget project's API Reference -->4<script src="YOUR DIRECT SCRIPT TAG HERE"></script>5<script src="{{ 'product-quiz.js' | asset_url }}" defer="defer"></script>6<noscript>{{ 'section-main-page.css' | asset_url | stylesheet_tag }}</noscript>7<noscript>{{ 'component-rte.css' | asset_url | stylesheet_tag }}</noscript>89<div class="page-width page-width--narrow">10 <product-quiz class="quiz">11 <h1 class="main-page-title page-title h0 product-quiz__title">12 </h1>13 <div class="product-quiz__body">14 <span>15 </span>16 </div>17 <div>18 <form class="form">19 <div class="product-quiz__questions" id="questions">20 <div class="product-quiz__question">21 <span class="product-quiz__question-answer">22 </span>23 </div>24 </div>25 <hr class="product-quiz__submit-hr" />26 <div class="product-quiz__email-container">27 <label for="email">Enter your email to complete quiz</label><br>28 <input type="email" id="product-quiz__email" name="email" required="required" style="font-size: 16px; height: 32px"><br><br>29 </div>30 <button31 name="quiz-submit"32 type="submit"33 class="product-quiz__submit button button--secondary"34 >35 Get my results36 </button>37 </form>38 </product-quiz>39</div>4041{% schema %}42{43"name": "t:sections.quiz-page.name",44"tag": "section",45"class": "spaced-section"46}47{% endschema %}
We just need to replace the "YOUR DIRECT SCRIPT TAG URL HERE" with your Gadget app's script tag so we can use the client. Your app's script tag URL can be found in the Installing section of the API Reference docs.
Your direct script tag should be for your development environment! Your script tag needs --development added to your app-specific subdomain.
If your app url looks like quiz.gadget.app, your script tag src should read
<script src="https://quiz--development.gadget.app/api/client/web.min.js"></script>
Now under Templates, select “Add a new template” and add a template called page.quiz.json. This requires you to select the page template type.
Replace the generated file with the following JSON:
1{2 "sections": {3 "main": {4 "type": "quiz-page",5 "settings": {}6 }7 },8 "order": ["main"]9}
Using our client with JavaScript
Under the Assets section in the sidebar, select Add a new asset and create a new JavaScript file called product-quiz.js. You can then add the following to that file:
JavaScript1// initialize an API client object2const api = new Gadget();3const QUIZ_ID = 0; // <- UPDATE ME WITH QUIZ ID FROM GADGET45// query Gadget for the recommended products based on quiz answers6async function fetchRecommendedProducts(answerIds) {7 const queryIdFilter = answerIds.map((answerId) => {8 return { id: { equals: answerId } };9 });1011 const recommendedProducts = await api.answer.findMany({12 filter: {13 OR: queryIdFilter,14 },15 select: {16 recommendedProduct: {17 id: true,18 image: {19 url: true,20 },21 productSuggestion: {22 id: true,23 title: true,24 body: true,25 handle: true,26 },27 },28 },29 });3031 return recommendedProducts;32}3334// fetch the quiz questions and answers to be presented to shoppers35async function fetchQuiz(quizId) {36 const quiz = await api.quiz.findOne(quizId, {37 select: {38 id: true,39 title: true,40 body: true,41 questions: {42 edges: {43 node: {44 id: true,45 text: true,46 answers: {47 edges: {48 node: {49 id: true,50 text: true,51 },52 },53 },54 },55 },56 },57 },58 });5960 return quiz;61}6263// save the shopper's email and recommended productions to Gadget (for follow-up emails!)64async function saveSelections(email, recommendedProducts) {65 const productsQuery = recommendedProducts.map((rp) => {66 return {67 create: {68 product: {69 _link: rp.recommendedProduct.productSuggestion.id,70 },71 },72 };73 });74 await api.quizResult.create({75 quizResult: {76 quiz: {77 _link: QUIZ_ID,78 },79 email: email,80 shopperSuggestions: [...productsQuery],81 },82 });83}8485async function onSubmitHandler(evt) {86 evt.preventDefault();8788 const email = document.getElementById("product-quiz__email").value;8990 const submitButton = this.querySelector(".product-quiz__submit");91 submitButton.classList.add("disabled");9293 const recommendedProducts = await fetchRecommendedProducts(selectedAnswers);9495 // save email and recommendations to Gadget for follow-up emails96 await saveSelections(email, recommendedProducts);9798 // display recommendations99 let recommendedProductHTML =100 "<div><h2>Based on your selections, we recommend the following products</h2><div style='display: flex; overflow: auto'>";101102 recommendedProducts.forEach((result) => {103 const { recommendedProduct } = result;104 const imgUrl = recommendedProduct.image.url;105 const productLink = recommendedProduct.productSuggestion.handle;106 recommendedProductHTML +=107 `<span style="padding: 8px 16px; margin-left: 10px; border: black 1px solid; align-items: center; display: flex; flex-direction: column"><h3>${recommendedProduct.productSuggestion.title}</h3><a class="button" href="/products/${productLink}">Check it out</a>` +108 recommendedProduct.productSuggestion.body +109 `<br/><img src=${imgUrl} width="200px" /><br /></span>`;110 });111112 recommendedProductHTML += "</div></div>";113 document.getElementById("questions").innerHTML = recommendedProductHTML;114115 submitButton.classList.add("hidden");116 this.querySelector(".product-quiz__submit-hr").classList.add("hidden");117 this.querySelector(".product-quiz__email-container").classList.add("hidden");118}119120let selectedAnswers = [];121function selectAnswer(answerId, answerText) {122 selectedAnswers.push(answerId);123 let elId = event.srcElement.id;124 let parent = document.getElementById(elId).parentNode;125 parent.innerHTML = "<h3><b>" + decodeURI(answerText) + "</b> selected</h3>";126}127128fetchQuiz(QUIZ_ID).then(async (quiz) => {129 const questions = quiz.questions.edges;130131 if (!customElements.get("product-quiz")) {132 customElements.define(133 "product-quiz",134 class ProductQuiz extends HTMLElement {135 constructor() {136 super();137 this.form = this.querySelector("form");138 this.heading = this.querySelector(".product-quiz__title");139 this.heading.innerHTML = quiz.title;140 this.body = this.querySelector(".product-quiz__body span");141 this.body.innerHTML = quiz.body;142 this.questions = this.querySelector(".product-quiz__questions");143144 const questionContainer = this.querySelector(".product-quiz__question");145 const answerContainer = this.querySelector(146 ".product-quiz__question-answer"147 );148149 const renderedQuestions = questions.forEach((question, i) => {150 const clonedDiv = questionContainer.cloneNode(true);151 clonedDiv.id = "question_" + i;152 clonedDiv.insertAdjacentHTML(153 "beforeend",154 "<hr /><div><h3>" +155 question.node.text +156 `</h3></div><div class='product-quiz__answers_${i}'></div>`157 );158 this.questions.appendChild(clonedDiv);159160 const answers = question.node.answers.edges;161 answers.forEach((answer, j) => {162 const clonedSpan = answerContainer.cloneNode(true);163 clonedSpan.id = "answer_" + i + "_" + j;164 clonedSpan.insertAdjacentHTML(165 "beforeend",166 `<span><button class="button answer" id="${167 clonedSpan.id168 }" onClick=(selectAnswer(${answer.node.id},"${encodeURIComponent(169 answer.node.text170 )}"))>${answer.node.text}</button></span>`171 );172 this.querySelector(`.product-quiz__answers_${i}`).appendChild(173 clonedSpan174 );175 });176 });177178 this.form.addEventListener("submit", onSubmitHandler);179 }180 }181 );182 }183});
You'll need to make one adjustment here: replace the QUIZ_ID = 0; definition with the ID of the quiz you want to serve. Save your changes, and we're ready to go! Head over to the Pages section of the Shopify admin, and create a new page for your quiz. You can set the template to use your new quiz template.
Once you save this page, you're all done! View the page to see your quiz right in your Shopify store, ready to recommend products to your shoppers.

Conclusion
Today, you've learned how Gadget and Shopify can work together to create engaging buying experiences for your shoppers while providing an approachable platform to build your app in a fraction of the time it takes to do so from scratch. Feel free to expand on this app; since we have the Product ID of the recommended product, we can construct a cart for the shopper on the frontend using Javascript, enabling a faster buying experience.
Want to know more about building effortless, expressive apps with Gadget? Check out our Guides and get building today!
Need support? Join our Discord, or book office hours with our Solutions team!