ShopifyQL is a Data Querying Language built specifically to give insights into shop data. This workshop will walk you through the basics of how you can use ShopifyQL and the ShopifyQL API to query data. You will then use that data to display it in the sample app provided by the Shopify CLI using Polaris Viz.
After you've finished this tutorial, you will have:
Create a new app using the Shopify CLI.
Navigate to the directory where you want to create your app and run the following command:
npm init @shopify/app@latest
When prompted, give a name for your app, for example, shopifyql-workshop
. This is the name of the folder the app will be installed into. After it's created, navigate into this folder.
cd shopifyql-workshop
Finally, run the following command:
npm run dev
When prompted, create the project as a new app and provide a name. The value provided here is how your app will show up in your Partner Dashboard. If you have more than one Partner organization or development store, you will also be prompted to select them as well.
Open up your code editor and navigate to the folder where you've created the app through the Shopify CLI.
The first thing we need to do is make sure the app has the right access scope. Open up shopify.app.toml
and add read_reports
as a scope. You will now have the following line in that file:
# This file stores configurations for your Shopify app.
scopes = "write_products,read_reports"
Because the ShopifyQL API is still in the unstable
version, we'll need to make sure that the app is set up to access that version of the Admin API. Open up web/index.js
and start by updating this import to include ApiVersion
.
import { Shopify, LATEST_API_VERSION, ApiVersion } from "@shopify/shopify-api";
Next, find where the API_VERSION
context attribute is being set. Update the value to ApiVersion.Unstable
. This section of your code will now look like this:
Shopify.Context.initialize({
API_KEY: process.env.SHOPIFY_API_KEY,
API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
SCOPES: process.env.SCOPES.split(","),
HOST_NAME: process.env.HOST.replace(/https?:\/\//, ""),
HOST_SCHEME: process.env.HOST.split("://")[0],
API_VERSION: ApiVersion.Unstable,
IS_EMBEDDED_APP: true,
// This should be replaced with your preferred storage strategy
SESSION_STORAGE: new Shopify.Session.SQLiteSessionStorage(DB_PATH),
});
Lastly, we won't be needing the product-related endpoints, so delete this section of code found in index.js
:
app.get("/api/products/count", async (req, res) => {
const session = await Shopify.Utils.loadCurrentSession(
req,
res,
app.get("use-online-tokens")
);
const { Product } = await import(
`@shopify/shopify-api/dist/rest-resources/${Shopify.Context.API_VERSION}/index.js`
);
const countData = await Product.count({ session });
res.status(200).send(countData);
});
app.get("/api/products/create", async (req, res) => {
const session = await Shopify.Utils.loadCurrentSession(
req,
res,
app.get("use-online-tokens")
);
let status = 200;
let error = null;
try {
await productCreator(session);
} catch (e) {
console.log(`Failed to process products/create: ${e.message}`);
status = 500;
error = e.message;
}
res.status(status).send({ success: status === 200, error });
});
Open the following file: web/frontend/pages/index.jsx
.
We'll be starting from scratch here, so delete all of the contents in this file.
Create a blank page as a placeholder until we've built out the component to show the ShopifyQL data. Copy over the following code snippet:
import { Page } from "@shopify/polaris";
import { TitleBar } from "@shopify/app-bridge-react";
export default function HomePage() {
return (
<Page>
<TitleBar title="ShopifyQL Report" primaryAction={null} />
</Page>
);
}
Run the server by entering the following command in your project's root folder. If your server is already running, restart it by running CTRL + C
and re-running this command:
npm run dev
Copy over the link provided to you and open it in a browser window. If you had previously installed the app on your store, you will be prompted to update it, because we changed the access scopes of the app. If you haven't installed the app on your store, install it now. Once completed, you will see your app load in the admin with an empty page.
For the sake of cleanliness, let's define a place to store the query we'll use in this workshop, as well as any future queries. Start by creating a new folder in the web
folder named queries
.
In that new folder, create a new file named sales.js
. This is where we'll create and save our first ShopifyQL query.
We need to import the gql
library, and then set up the skeleton for our query. Copy in the following piece of code:
import { gql } from "graphql-request";
export const SALES_QUERY = gql`
{
}`;
Now that we have the skeleton for our GraphQL call, let's add a ShopifyQL query. The first one we'll create is an example of how we can get all of the sales from the store. We'll start by setting up the GraphQL query to the ShopifyQL API with a basic response. Insert the following code into the SALES_QUERY
constant we created in the previous step:
shopifyqlQuery(query: "") {
__typename
... on TableResponse {
tableData {
rowData
columns {
name
}
}
}
}
You'll notice that we're passing in an empty query string into shopifyqlQuery
. This is where we will define the actual data we want to pull from the shop. Let's query the shop's sales for the quantity of each product for the past year by using the following ShopifyQL query and inserting it as the parameter value in our GraphQL query above:
"FROM sales SHOW ordered_item_quantity GROUP BY product_title SINCE -1y UNTIL today ORDER BY ordered_item_quantity DESC"
We also need to build out the response to get a few more fields, so inside of the columns
object, add the dataType
and displayName
fields.
Lastly, it's good practice to include errors in our response, so add the following below the ... on TableResponse
section:
parseErrors {
code
message
range {
start {
line
character
}
end {
line
character
}
}
}
Your completed file will look like this:
import { gql } from "graphql-request";
export const SALES_QUERY = gql`
{
shopifyqlQuery(
query: "FROM sales SHOW ordered_item_quantity GROUP BY product_title SINCE -1y UNTIL today ORDER BY ordered_item_quantity DESC"
) {
__typename
... on TableResponse {
tableData {
rowData
columns {
name
dataType
displayName
}
}
}
parseErrors {
code
message
range {
start {
line
character
}
end {
line
character
}
}
}
}
}
`;
Back in web/index.js
, create a new route that will call the Shopify GraphQL API. We will be calling this route from our frontend code later. Find the section already in the code for endpoints and add the following:
app.get("/api/shopifyql/sales", async (req, res) => {
});
We will use this endpoint to call the Shopify Admin API.
In order to make calls to the API, we need an active session, which contains information like the shop and our API access token. Inside of the callback function, load the session and add a simple error check:
const session = await Shopify.Utils.loadCurrentSession(
req,
res,
app.get("use-online-tokens")
);
if (!session) {
res.status(401).send("Could not find a Shopify session");
return;
}
The code generated from the CLI includes a GraphQL client, so we will use that here, using the session to pass in the shop and our API access token.
const client = new Shopify.Clients.Graphql(
session.shop,
session.accessToken
);
Call the API using the query we built in the previous step and send it as the response data.
const salesData = await client.query({
data: {
query: SALES_QUERY
},
});
res.send(salesData);
Remember to import SALES_QUERY
from the file we created in the previous step. Add the following line below the imports at the top of the file.
import { SALES_QUERY } from "./queries/sales.js";
Your completed route will look like this:
app.get("/api/shopifyql/sales", async (req, res) => {
const session = await Shopify.Utils.loadCurrentSession(
req,
res,
app.get("use-online-tokens")
);
if (!session) {
res.status(401).send("Could not find a Shopify session");
return;
}
const client = new Shopify.Clients.Graphql(
session.shop,
session.accessToken
);
const salesData = await client.query({
data: {
query: SALES_QUERY
},
});
res.send(salesData);
});
Now that we've built the backend, we will build a new custom component that will eventually call the route to load and display the data. We will start by navigating to web/frontend/components
where we will create a new file called ShopifyQLDataChart.jsx
.
Inside this new file, start by importing the Card
component from Polaris:
import { Card } from "@shopify/polaris";
Now, complete the export, including a parameter for the title, which gets passed into the Card
component.
export function ShopifyQLDataChart({ title }) {
return (
<Card title={title}></Card>
);
}
Using the provided useAppQuery
function, call the route we created in the previous section in order to get the data we'll use to feed the component, including some error checking. We'll also log the response for now to check what we're getting from the server.
Add the import statement at the top of the file.
import { useAppQuery } from "../hooks";
Then call the route and check for errors.
const {
data: salesData,
isLoading: isLoadingSalesData,
isError: salesDataError,
} = useAppQuery({
url: "/api/shopifyql/sales",
});
console.log(salesData);
if (isLoadingSalesData) {
return <div>Loading...</div>;
}
if (salesDataError) {
return <div>Error</div>;
}
Your complete file will look like this:
import { Card } from "@shopify/polaris";
import { useAppQuery } from "../hooks";
export function ShopifyQLDataChart({ title }) {
const {
data: salesData,
isLoading: isLoadingSalesData,
isError: salesDataError,
} = useAppQuery({
url: "/api/shopifyql/sales",
});
console.log(salesData);
if (isLoadingSalesData) {
return <div>Loading...</div>;
}
if (salesDataError) {
return <div>Error</div>;
}
return (
<Card title={title}></Card>
);
}
Now that we're making the call to the backend, it's a good time to check to make sure things are working as expected. We will do that by navigating back to web/frontend/pages/index.jsx
. Here, create a Layout
component, including a Section
. Start by importing the Layout
component.
import { Page, Layout } from "@shopify/polaris";
Add the Layout
and Layout.Section
elements inside of the Page
element, after the TitleBar
element.
<Layout>
<Layout.Section>
</Layout.Section>
</Layout>
Now, import the ShopifyQLDataChart
component that we created in the previous section.
import { ShopifyQLDataChart } from "../components/ShopifyQLDataChart";
Finally, add the component to the Layout.Section
and provide a title.
<ShopifyQLDataChart title="Products ordered over the past year" />
Onces completed, your web/frontend/pages/index.jsx
file will look like this:
import { Page, Layout } from "@shopify/polaris";
import { TitleBar } from "@shopify/app-bridge-react";
import { ShopifyQLDataChart } from "../components/ShopifyQLDataChart";
export default function HomePage() {
return (
<Page>
<TitleBar title="ShopifyQL Report" primaryAction={null} />
<Layout>
<Layout.Section>
<ShopifyQLDataChart title="Products ordered over the past year" />
</Layout.Section>
</Layout>
</Page>
);
}
Your app will have now loaded in the admin. It will render with the component we created, looking like the following:
Now check the console to see the response from the query we made. You will see an object with the following structure in the console, though the data itself might be different:
Take some time now to explore the object and see how the data is organized. You can also update and change the query to see how the response changes as well.
We have verified the query is executing as expected, so let's use the response data to populate a DonutChart
component from Polaris Viz. Before we build our own chart, we need to bring Polaris Viz into our project.
In a new terminal window, navigate to the web/
directory and install polaris-viz
by running the following commands:
cd web
npm install @shopify/polaris-viz -s
Next, open web/frontend/App.jsx
. Import the PolarisVizProvider
and the bundled styles provided by the package.
import { PolarisVizProvider } from "@shopify/polaris-viz";
import "@shopify/polaris-viz/build/esm/styles.css";
Inside your PolarisProvider
component, wrap all children in the PolarisVizProvider
component.
Your App.jsx
file will now look like this.
import { BrowserRouter } from "react-router-dom";
import { NavigationMenu } from "@shopify/app-bridge-react";
import { PolarisVizProvider } from "@shopify/polaris-viz";
import Routes from "./Routes";
import "@shopify/polaris-viz/build/esm/styles.css";
import {
AppBridgeProvider,
QueryProvider,
PolarisProvider,
} from "./components";
export default function App() {
// Any .tsx or .jsx files in /pages will become a route
// See documentation for <Routes /> for more info
const pages = import.meta.globEager("./pages/**/!(*.test.[jt]sx)*.([jt]sx)");
return (
<PolarisProvider>
<PolarisVizProvider>
<BrowserRouter>
<AppBridgeProvider>
<QueryProvider>
<NavigationMenu
navigationLinks={[
{
label: "Page name",
destination: "/pagename",
},
]}
/>
<Routes pages={pages} />
</QueryProvider>
</AppBridgeProvider>
</BrowserRouter>
</PolarisVizProvider>
</PolarisProvider>
);
}
Now that we have verified our query and setup the chart visualizer component, we can begin building our own instance of a DonutChart
.
Navigate to web/frontend/components/ShopifyQLDataChart.jsx
. Start by importing DonutChart
.
import { DonutChart } from "@shopify/polaris-viz";
Inside of our existing Card
, add the DonutChart
component with some wrapping divs to help style our content to fit inside the Card.
<div
style={{
padding: "var(--p-space-5) var(--p-space-5)",
}}
>
<div
style={{
height: 600,
borderRadius: "var(--p-border-radius-2)",
overflow: "hidden",
}}
>
<DonutChart data={chartData} theme="Light"></DonutChart>
</div>
</div>
Now we need to build thechartData
object by transforming our ShopifyQL response to fit the expected data structure for the DonutChart
.
Each section of a DonutChart is an object with a name
and data
object. The data object is an array of objects with key
and value
properties.
We can iterate over our ShopifyQL rowData
to build this structure. Add this block of code before our return()
render.
let tableData = salesData.body.data.shopifyqlQuery.tableData;
let chartData = tableData.rowData.map((row) => {
return {
name: row[0],
data: [
{
key: "last year",
value: parseInt(row[1], 10),
},
],
};
});
Your complete ShopifyQLDataChart.jsx
file will now look like this:
import { Card } from "@shopify/polaris";
import { DonutChart } from "@shopify/polaris-viz";
import React from "react";
import { useAppQuery } from "../hooks";
export function ShopifyQLDataChart({ title }) {
const {
data: salesData,
isLoading: isLoadingSalesData,
isError: salesDataError,
} = useAppQuery({
url: "/api/shopifyql/sales",
});
if (isLoadingSalesData) {
return <div>Loading...</div>;
}
if (salesDataError) {
return <div>Error</div>;
}
let tableData = salesData.body.data.shopifyqlQuery.tableData;
let chartData = tableData.rowData.map((row) => {
return {
name: row[0],
data: [
{
key: "last year",
value: parseInt(row[1], 10),
},
],
};
});
return (
<Card title={title}>
<div
style={{
padding: "var(--p-space-5) var(--p-space-5)",
}}
>
<div
style={{
height: 600,
borderRadius: "var(--p-border-radius-2)",
overflow: "hidden",
}}
>
<DonutChart data={chartData} theme="Light"></DonutChart>
</div>
</div>
</Card>
);
}
Refresh the app inside of the Shopify Admin. You will see the data chart is now populated with the sales data for your shop. Each slice of the pie represents a proportion of a single product's sales quantity relative to total sales.
Congratulations! You have completed the tutorial for ShopifyQL. Start building visualizations for your clients!
For more information on ShopifyQL, check out these helpful resources:
You can also get involved with the Shopify Developer Community by joining our Discord Server.