I’ve been developing an app for Shopify for the past few weeks and it has been a very interesting, if quite challenging, experience. I decided to use React/Next.js/etc, as per this tutorial.

Unfortunately, there was a single issue that had me befuddled for a while. You see, my app is a little JavaScript snippet that renders a widget, but only for particular products that can be selected in the options. This can obviously be accomplished by using Shopify’s script tags – pieces of JavaScript that get rendered on every page of your Shopify store. The only problem is that by default there is no way to conditionally render the scripts e.g. only for some specific products.

Also, the documentation for script tags is somewhat lacking, and perhaps my google-fu was failing me, but for the life of me I could not find a way to conditionally run a script tag for a particular product.

I was pretty frustrated at this point. As far as I could tell there were only two options available:

  1. Grab the product’s slug from the URL (This is unique, but changeable)
  2. Try to grab the product’s slug or id from some HTML element

Eugh. Both options seemed really hacky. For example, what would happen if someone decided to change their product slug? Probably nothing good. Nope, nope, nope.

So I decided to do what I usually do when faced with what seems like an insurmountable obstacle – just start poking around a bit.

Exploring the 2nd idea, I had my developer console open, and thought that maybe I’ll find something interesting by clicking open the gazillions of script tags. That turned out to be a good idea when I found this gem hidden in the sea of HTML spaghetti:

<script>
  window.ShopifyAnalytics = window.ShopifyAnalytics || {};
  window.ShopifyAnalytics.meta = window.ShopifyAnalytics.meta || {};
  window.ShopifyAnalytics.meta.currency = 'EUR';
  var meta = {"product":{"id":2060440305757}, /* etc. */};
  for (var attr in meta) {
    window.ShopifyAnalytics.meta[attr] = meta[attr];
  }
</script>

Eureka!

It turned out that the product metadata is right there, rendered in the HTML page in the form a script tag. As far as I could tell, this was not documented anywhere. But it could be that my google-fu had failed me once more.

Now that I had an easy way to grab the product id, the solution was quite straightforward:

  1. Create a template JavaScript file containing some token, e.g. @productIdList@ that gets replaced by the store’s selected product ids
  2. Create an API endpoint that uses the template and does the actual replacing

The first one was rather easy. I simply created a JavaScript file called script-tag.js.template with the following content:

(function() {
  if (!meta || !meta.product) {
    return;
  }

  var productIds = @productIdList@;

  productIds.forEach(function(productId) {
    console.log('Hello from product ' + productId);
  });
})();

The second one was a bit harder since I had never used koa before and was mainly stumbling over how to configure a new endpoint and set the appropriate content type header. It turned out that you can’t set the headers with ctx.headers = {…}, even though you set the body with ctx.body = {…}. Damn it!

After banging my head against the wall for a while, I ended up with the following:

// Assuming we have some database model with 1:M mapping from shop to settings
const Settings = require('./db/models/settings');
const fs = require('fs');

const template = fs.readFileSync('./templates/script-tag.js.template', 'utf8');

// A koa router
router.get('/api/settings', async ctx => {
  const { shop } = ctx.session;
  const settings = await Settings.findOne({ shop });

  const productIds = `[${settings.productIds.join(',')}]`;
  const script = template.replace(/@productIdList@/g, productIds);

  ctx.set('Content-Type', 'application/json');
  ctx.body = script;
});

I then fired up Postman and made a call to my new endpoint. The result was this:

(function() {
  if (!meta || !meta.product) {
    return;
  }

  var productIds = [2060440305757];

  productIds.forEach(function(productId) {
    console.log('Hello from product ' + productId);
  });
})();

Aww yeah, that looked promising.

I then fired up my browser, said a quick prayer to all the Gods of Coding, navigated to my development store and opened up the developer console to find this:

Hello from product 2060440305757

Nice.

So that’s how you can create a conditional script tag for a Shopify app. Turned out to be easier than I thought. A pleasant surprise indeed.