How to: import the Gadget API into a Shopify storefront (product quiz) 

When building storefront apps using Liquid, you may need to make requests to a Gadget backend. This guide will show you how to use your Gadget project's direct script tag in the Shopify storefront to embed a product quiz in a store.

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.

A gif of the completed quiz embedded in a Shopify storefront. There are two questions about dogs, the shopper selects an answer for each question and enters their email, and two product are recommended based on the selected answers.
Requirements

Before starting this guide you need the following:

Fork product quiz app 

Good news, everyone! Instead of building an app from scratch, you are going to fork an existing product quiz app. This app already contains:

  • a Shopify connection with the required scopes and data models
  • data models for managing quiz data, including questions, answers, and recommended products
  • backend logic for cleaning up data when a quiz is deleted
  • an embedded frontend for creating and managing quizzes

Click here to fork the app:

Fork on Gadget

Here is a diagram showing an overview of the app's tech stack:

A diagram of the quiz tech stack, including Gadget's DB and API, and the Shopify storefront and admin set up

The next step will be installing your quiz app on a Shopify store, but first, let's take a look at the data models and permissions that have been added for you.

Quiz data models 

You need a way to create, serve, and record quiz responses in your app. The recording of responses enables you to track the conversion state mentioned in the intro, effectively making a response to a quiz a snapshot of a session that a shopper has with your quiz.

You need some models to capture the structure of the product quiz. A quiz has many questions, and each question can have multiple possible answers. Each answer has a recommendedProduct that relates to a product in a Shopify store. This means that each question answered will be linked to a product to offer shoppers.

You also need a model to capture user responses to the quiz, quizResult. 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, shopperSuggestion. 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.

A screenshot of the data models list in Gadget, will all the quiz models

Here's a diagram to demonstrate what relationships our models will have with each other:

The Model Relationship diagram for our app

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.

Permissions have already been granted to this app. The shopify-app-users role, which is assigned to merchants when building a quiz in the store admin, has been granted read and write access to the quiz, question, answer, and recommendedProduct models. unauthenticated users, which are shoppers, can read data from the quiz, question, answer, recommendedProduct, and shopifyProduct models. unauthenticated users also need to be able to save their responses. They have been granted permission to create new records of the quizResult and shopperSuggestion models.

You can view these permissions by clicking on Settings and then Roles & Permissions in the left nav.

A screenshot of some of the permissions set for unauthenticated and shopify-app-users roles, including the Recommended Product model, Shopper Suggestion, and Quiz Result. The later two are create-only for unauthenticated users, and the Recommended Product model is read/write access for shopify-app-users and read-only for unauthenticated users

Connecting to Shopify 

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.

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.

Because the quiz app was forked, the Shopify connection has already been created for you. The read_products API scope has been selected, and the shopifyProduct data model imported into your app.

All that needs to be done to complete the connection is to connect to a Shopify Partners app and install it on a development store. After installing, Shopify product data will automatically be synced to your Gadget app's database via a code effect added to the Shopify Shop install action. You can find this code snippet in shopifyShop/install.js.

  • Go to the Shopify Partners dashboard and create a new app
  • Click Create app manually and name your app
  • Copy the Client ID and Client secret back to Gadget. You can + Add App Credentials to the Development environment in Gadget, then click Confirm
A gif of the Add App Credentials button for the Development environment being clicked
  • Copy the App URL and Redirection URL back to the Shopify Partners dashboard. These can be pasted on the App setup page of your Partners app
  • Go back to the Overview page of your Partners app and click Select store to install on a development store

Your app should successfully install on the development store, and a sync of shopifyProduct data should start automatically.

A screenshot of a completed connection in Gadget, where the app has been installed on one store

To view the status of your sync:

  • On the Connections page, click Shop Installs

Once your sync is complete, you can build your first quiz in the shop admin.

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 in the Shopify admin. 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 frontend app connects with Gadget through the client and makes API calls against it. If we look at the quiz data by selecting the Data label 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.

Embed your quiz in a Shopify storefront 

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.

The Shopify Theme Editor

Under Sections, create a new section called quiz-page.liquid.

We're going to replace this page with the following code:

html
1<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
4<!-- Find your direct script tag in the Installing section in your Gadget project's API Reference -->
5<script src="YOUR DIRECT SCRIPT TAG HERE"></script>
6<script>
7 // create a new instance of the Gadget client at the `api` global connected to the Development environment
8 window.api = new Gadget({
9 environment: "Development"
10 })
11</script>
12
13<script src="{{ 'product-quiz.js' | asset_url }}" defer="defer"></script>
14<noscript>{{ 'section-main-page.css' | asset_url | stylesheet_tag }}</noscript>
15<noscript>{{ 'component-rte.css' | asset_url | stylesheet_tag }}</noscript>
16
17<div class="page-width page-width--narrow">
18 <product-quiz class="quiz">
19 <h1 class="main-page-title page-title h0 product-quiz__title">
20 </h1>
21 <div class="product-quiz__body">
22 <span>
23 </span>
24 </div>
25 <form class="form">
26 <div class="product-quiz__questions" id="questions">
27 <div class="product-quiz__question">
28 <span class="product-quiz__question-answer">
29 </span>
30 </div>
31 </div>
32 <hr class="product-quiz__submit-hr" />
33 <div class="product-quiz__email-container">
34 <label for="email">Enter your email to complete quiz</label><br>
35 <input type="email" id="product-quiz__email" name="email" required="required" style="font-size: 16px; height: 32px"><br><br>
36 </div>
37 <button
38 name="quiz-submit"
39 type="submit"
40 class="product-quiz__submit button button--secondary"
41 >
42 Get my results
43 </button>
44 </form>
45 </product-quiz>
46</div>
47
48{% schema %}
49{
50"name": "t:sections.quiz-page.name",
51"tag": "section",
52"class": "spaced-section"
53}
54{% 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 your quiz app's API Reference docs.

Your direct script tag should be for your Development environment! Your script tag needs --development added to your app-specific subdomain.

html
<script src="https://example-app--development.gadget.appapi/client/web.min.js"></script>

When you deploy your Gadget app to Production and set up a quiz on a shopper-facing store, make sure to use the production URL in your script tag.

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:

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:

JavaScript
1// initialize an API client object
2const api = new Gadget();
3const QUIZ_ID = 1; // <- UPDATE ME WITH QUIZ ID FROM GADGET
4
5// query Gadget for the recommended products based on quiz answers, using a JS query
6async function fetchRecommendedProducts(answerIds) {
7 const queryIdFilter = answerIds.map((answerId) => {
8 return { id: { equals: answerId } };
9 });
10
11 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 });
30
31 return recommendedProducts;
32}
33
34// fetch the quiz questions and answers to be presented to shoppers, using a GraphQL query
35async function fetchQuiz(quizId) {
36 const quiz = await api.query(`
37 query {
38 quiz(id: ${QUIZ_ID}) {
39 id
40 title
41 body
42 questions {
43 edges {
44 node {
45 id
46 text
47 answers {
48 edges {
49 node {
50 id
51 text
52 }
53 }
54 }
55 }
56 }
57 }
58 }
59 }`);
60
61 return quiz.quiz;
62}
63
64// save the shopper's email and recommended productions to Gadget (for follow-up emails!)
65async function saveSelections(email, recommendedProducts) {
66 const productsQuery = recommendedProducts.map((rp) => {
67 return {
68 create: {
69 product: {
70 _link: rp.recommendedProduct.productSuggestion.id,
71 },
72 },
73 };
74 });
75 await api.quizResult.create({
76 quiz: {
77 _link: QUIZ_ID,
78 },
79 email: email,
80 shopperSuggestions: [...productsQuery],
81 });
82}
83
84async function onSubmitHandler(evt) {
85 evt.preventDefault();
86
87 const email = document.getElementById("product-quiz__email").value;
88
89 const submitButton = this.querySelector(".product-quiz__submit");
90 submitButton.classList.add("disabled");
91
92 const recommendedProducts = await fetchRecommendedProducts(selectedAnswers);
93
94 // save email and recommendations to Gadget for follow-up emails
95 await saveSelections(email, recommendedProducts);
96
97 // display recommendations
98 let recommendedProductHTML =
99 "<div><h2>Based on your selections, we recommend the following products</h2><div style='display: flex; overflow: auto'>";
100
101 recommendedProducts.forEach((result) => {
102 const { recommendedProduct } = result;
103 const imgUrl = recommendedProduct.image?.url;
104 const productLink = recommendedProduct.productSuggestion.handle;
105 recommendedProductHTML +=
106 `<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>` +
107 recommendedProduct.productSuggestion.body +
108 `<br/><img src=${imgUrl} width="200px" /><br /></span>`;
109 });
110
111 recommendedProductHTML += "</div></div>";
112 document.getElementById("questions").innerHTML = recommendedProductHTML;
113
114 submitButton.classList.add("hidden");
115 this.querySelector(".product-quiz__submit-hr").classList.add("hidden");
116 this.querySelector(".product-quiz__email-container").classList.add("hidden");
117}
118
119let selectedAnswers = [];
120function selectAnswer(answerId, answerText) {
121 selectedAnswers.push(answerId);
122 let elId = event.srcElement.id;
123 let parent = document.getElementById(elId).parentNode;
124 parent.innerHTML = "<h3><b>" + decodeURI(answerText) + "</b> selected</h3>";
125}
126
127fetchQuiz(QUIZ_ID).then(async (quiz) => {
128 const questions = quiz.questions.edges;
129
130 if (!customElements.get("product-quiz")) {
131 customElements.define(
132 "product-quiz",
133 class ProductQuiz extends HTMLElement {
134 constructor() {
135 super();
136 this.form = this.querySelector("form");
137 this.heading = this.querySelector(".product-quiz__title");
138 this.heading.innerHTML = quiz.title;
139 this.body = this.querySelector(".product-quiz__body span");
140 this.body.innerHTML = quiz.body;
141 this.questions = this.querySelector(".product-quiz__questions");
142
143 const questionContainer = this.querySelector(".product-quiz__question");
144 const answerContainer = this.querySelector(
145 ".product-quiz__question-answer"
146 );
147
148 const renderedQuestions = questions.forEach((question, i) => {
149 const clonedDiv = questionContainer.cloneNode(true);
150 clonedDiv.id = "question_" + i;
151 clonedDiv.insertAdjacentHTML(
152 "beforeend",
153 "<hr /><div><h3>" +
154 question.node.text +
155 `</h3></div><div class='product-quiz__answers_${i}'></div>`
156 );
157 this.questions.appendChild(clonedDiv);
158
159 const answers = question.node.answers.edges;
160 answers.forEach((answer, j) => {
161 const clonedSpan = answerContainer.cloneNode(true);
162 clonedSpan.id = "answer_" + i + "_" + j;
163 clonedSpan.insertAdjacentHTML(
164 "beforeend",
165 `<span><button class="button answer" id="${
166 clonedSpan.id
167 }" onClick=(selectAnswer(${answer.node.id},"${encodeURIComponent(
168 answer.node.text
169 )}"))>${answer.node.text}</button></span>`
170 );
171 this.querySelector(`.product-quiz__answers_${i}`).appendChild(
172 clonedSpan
173 );
174 });
175 });
176
177 this.form.addEventListener("submit", onSubmitHandler);
178 }
179 }
180 );
181 }
182});

You'll need to make one adjustment here: replace the QUIZ_ID = 0; definition with the ID of the quiz you want to serve. Your Quiz ID can be grabbed from the quiz model's Data page in Gadget. This JavaScript file includes examples of using the GraphQL API (to fetch the quiz) and the JS API (to fetch recommended products) to make requests to your Gadget backend.

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. View the page to see your quiz right in your Shopify store, ready to recommend products to your shoppers.

Conclusion 

Today, we learned how Gadget and Shopify can work together to create engaging buying experiences for shoppers. This collaboration also offers an easy-to-use platform for building your app in less time. You can expand your app by using the product id to quickly create a shopping cart on the frontend using Javascript, resulting in a faster buying experience. When deploying to Production, remember to update the script tag in your liquid file to match the Production Gadget environment.

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!