UnitWise

2023

Check it out here or use the password “password” and view the demo store

A screenshot of the features UnitWise adds to your store

A screenshot of the elements you can add to your store using UnitWise.
The buyer-facing surface of this app is basically just the elements outlined with red rectangles.

UnitWise is a relatively simple app for displaying unit pricing. If you’re not familiar with the concept of a unit price, here are some examples:

  • $0.23 / 100mL
  • $2.83 / kg
  • $20 / ea
  • $320 / case

You’ve likely seen these in very small font at the grocery store, typically rendered in 100mL or 100g sizes.

It’s a fairly simple concept, so in some regards, it’s a bit surprising that there’s no built-in support for unit prices in Shopify already! However, we have to remember that Shopify are developing for the 90% usecase, so that’s where apps like UnitWise come in.

Main Features

  • Ability to use existing volume/weight metafield types provided by Shopify
  • Ability to define a custom unit (e.g., “case”, “pallet”, “box”) when neither volume/weight is applicable
  • Render a percent savings depending on the difference in unit price between each variant. If there’s no savings between variants, don’t display a useless (Saves 0%)
  • If two variants have similar (but actually different) unit prices, the app attempts to scale up all unit prices until the differences are visible. For example, in a naive rendering, $0.02/g and $0.02/g look identical, but their true unit prices are $1.95/100g and $2.03/100g, so we pick that.
  • The user chooses where to place the custom elements in their theme
  • User can choose whether to prefer “per 100” units (because this is a popular way to render unit prices)
  • Configuration at the product or variant level, with support for variant-level overrides

Technical considerations

Since the app is so visually simple, I imposed some constraints I wanted to hit for simplicity and robustness:

  • No additional network connections in storefront: I don’t want to hit some API for every pageview, slowing things down
  • No database: I don’t want to be storing merchant data in a database I control (funny enough, I still have to support GDPR even though I capture no merchant data)
  • No server: This one was almost possible! (see below)

So how do we do this?

Metafields!

The post-install flow in the embedded app guides the user through:

  1. install the metafield definitions required for the app to work
  2. configure some unit prices on products/variants using the newly installed metafields
  3. install the theme extension block to a desired spot in their theme

The theme extension blocks will first pre-render (using liquid calculations, very painful) the initial unit price for page load. This ensures that we have SEO support for the unit prices and reduces FOUC. After the page loads, JS takes over to handle further rendering when the variant is changed.

The JS is all self-contained as an inline script tag that’s part of the theme extension block. It’s optimized to only compute the unit prices once, even if you should happen to include the block 2+ times on your page.

Since the app just uses metafields, both liquid and JS can read this. Liquid has native support (but this is poorly documented for private app namespace metafields), and JS can get access after having Liquid dump it into a script tag. I can avoid needing to store any user data outside of Shopify. Even better, Shopify provides the CRUD interface via their admin pages and bulk editor pages. The user benefits because all their product configuration lives in the Shopify admin.

As a result, we have unit prices that render where users want them without any performance impact on their store. If massively popular shops were to install my app, it’d be 100% reliable – if Shopify is up, so is UnitWise.

A product imagined by an AI

For the demo store, I used Midjourney to generate pleasant fake-product photography. It looks amazing! My wife wanted to eat this thing, but was disappointed to learn that it doesn’t even exist.

Since the app is so simple and the only need for a server is during install, I can use a free fly.io backend.

I think there’s a potential untapped area of development for Shopify here. Imagine: little packets of metafield definitions + theme extension + javascript that can be added to your store without a conventional install. My app only needed a server because otherwise I would have no method to install the metafield definitions. Also, Shopify tends to encourage embedded app support. My embedded app also provides some additional instructions that can’t be listed on the app listing page. If we imagine a world where Shopify adds this kind of theme block app, you could deploy a truly serverless app. Imagine just buying the “Unit Price” theme extension and just using it. Or, “T-shirt size guide” – there are tons of things like these that only need some metadata, some Liquid/JS, and some CSS to work. Call them “Shopify Theme Snacks” or some such.

Further work and thoughts

  • Internationalization: The app is i18n’d, but only in English right now. I should translate it to the languages spoken by countries that require unit price by law. Probably my largest leverage, and I can probably even use ChatGPT to translate the entire en.json, just a matter of doing it and creating more app store listings.
  • Better embedded app: The example TypeScript embedded repo Shopify provides is pretty rough. It’d be nice to have a remix/next.js version. I’d opt for Qwik or SolidStart, but it’s probably smart to just use React to get Polaris

Overall, I’m glad that I now have this app running. Since there’s no infrastructure, there’s no maintenance, and it continues to just work. I haven’t advertised it at all, but I have a few shops using it now.

The Shopify app development experience is very painful, and this comes from a place of love and ownership as somebody who worked at Shopify for 7.5 years. If I do it again, I’d strongly consider Gadget. The whole auth thing is frustrating and sometimes has terrible error messages. Support for both a staging/prod version of an app is basically nonexistent. Perhaps most surprisingly, actually charging for your app isn’t a first-class concept in the SDK, and there isn’t a lot of great documentation out there. If you give your app a stupid handle (a codename, or an old name) in Shopify Partners, they cannot rename it for you and you must make a new one, lest your official URL look wonky. This app used to be called QUnitPrice, so it was a huge pain to actually rename it and get the slug /unitwise. The shopify-cli is confusing, and at times it was just hitting HTTP 500s when I tried to do a deploy action that’d work the day before. Some tasks are only achievable through that, and if it’s bugged out, “RIP, I guess I’m not working today after all”. I ended up compiling a 3.5 page document about metafield/metaobject issues while building the app. I encountered a lot of other bugs with Shopify themes. The Partner dashboard makes it really hard to see how many shops are actually paying for my app at any given time, and there’s so much junk like “app-security store installed” and “app-security store uninstalled” constantly (this is a Shopify internal security process testing the app, not a real user store). Filter those out or put them in another list! 😡 There’s not even a list anywhere of “Shops currently paying for your app”, so I wouldn’t know how to reach out to my loyal customers. Perhaps most people have databases and so maintain such a list by virtue of that, but my app doesn’t have a database. Ultimately, I had nowhere to report any of these bugs to! I tried to report some issues to Shopify Partner support and was told to file an issue on a random unrelated repo.