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.

An image showing a donut chart of products sold.

Learning objectives

After you've finished this tutorial, you will have:

What you'll need (Account Requirements)

What you'll need Libraries and Tools

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.

An image showing the empty app loaded in the Shopify admin.

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:

Admin page with blank card

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:

ShopifyQL Response data

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.

Donut Chart with Light Theme

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.