How to Relate Articles, Features, and Other Content on Shopify — Semantic Recommendations with Embeddings and Metafields
Scroll to the bottom of this column and you will find three posts listed under "Related articles." It may look like an ordinary feature, but Shopify's blog engine actually has no built-in way to display related articles — and the columns on this site carry no topical tags that could link posts together.
The related articles you see are chosen by precomputing the semantic similarity of article bodies with AI embeddings and storing the results in Shopify metafields. This post walks through how we designed and built the feature, including the reasoning behind each decision. The approach applies to relating any content on Shopify — articles, feature pages, products — and, as described below, it works with Liquid themes just as well as with Hydrogen. It should also be useful as a general lesson in deciding where heavy computation should live.
What you'll learn in this article
- → A design pattern for adding a related-articles feature to a Shopify blog
- → How embeddings measure the semantic similarity of posts, and what it costs
- → Why we store the results in article-reference metafields
- → How to run recomputation on a schedule when Shopify offers no blog webhooks
The starting point: no tags, no built-in feature
Let's lay out the constraints.
- The columns on this site are served by Shopify's blog feature (the site itself is built with Hydrogen — the full story is in another column). There are Japanese and English locales.
- The only tags on our articles are the language tags "Japanese" and "English." Nothing indicates what topic an article covers.
- Shopify blogs have no built-in "related articles" feature.
The common implementation — "show articles that share a tag" — was off the table, since there are no topical tags. Designing a tag taxonomy, applying it to every existing article, and maintaining it for every new post would be a real operational burden; so would having an editor hand-pick related posts for each article. The requirement was clear: let a machine pick semantically similar articles, without adding any manual workflow.
Design principle: never compute at display time
If the goal is just "pick semantically similar articles," you could run a search or a similarity computation on every page request. But article pages are among the most-visited pages on the site, and putting heavy work on that path is bad engineering. We chose the opposite: do all the computation in a nightly batch, and have the page simply read the stored result.
Measuring "semantic closeness" — embeddings
Embeddings are a technique that converts text into a numeric vector with hundreds to thousands of dimensions. Texts with similar meanings end up close together in that vector space, so measuring the angle between two articles' vectors (cosine similarity) lets you quantify how similar their content is — without relying on matching tags or keywords. The decisive difference from keyword matching is that two articles can be linked even when they share few words, as long as their context is close.
We use OpenAI's text-embedding-3-small (1,536 dimensions). It is multilingual, so it handles Japanese articles as-is, and it costs roughly $0.02 per million tokens. Re-embedding every article on this site (about 80 posts) from scratch costs a few yen. For this use case, favoring cost and simplicity over raw performance is entirely reasonable.
Similarity is computed within each language group: Japanese-tagged articles are ranked only against other Japanese articles, English-tagged ones only against English. An article tagged with both languages is ranked in both groups. This structurally prevents accidents like an English post showing up in the related list on a Japanese page.
Where the results live — article-reference metafields
The precomputed results are stored in each article's metafields (Shopify's mechanism for attaching arbitrary structured data to its objects). The key choice here is the list-of-article-references type (list.article_reference). Instead of duplicating the related articles' titles and dates into a JSON blob, we store a list of references (IDs) to the articles themselves. Operationally, this difference is decisive.
- Nothing goes stale — the storefront resolves the latest title and publish date from the referenced article at display time, so fixing a title later never leaves an outdated copy in a related-articles list.
- Unpublished articles disappear automatically — the Storefront API only resolves published articles, so a reference to an unpublished post silently drops out of the list. No broken links.
- Order is preserved — reference lists keep their write order, so "most related first" is exactly how they render.
Each article carries three metafields: the Japanese-facing ranking and the English-facing ranking (both readable from the storefront), plus a private cache of the computed embedding vector (used for incremental runs, described next).
Inside the offline pipeline
The computation is a single Node.js script with no dependencies. Four steps:
The key is step 2: incremental execution. A hash of each article's title and body is stored alongside its embedding cache; if the hash hasn't changed since the last run, the script reuses the cached vector instead of calling the embedding API. In steady state, only new or edited articles get embedded. The ranking itself, however, is recomputed across all articles every run — because adding one new article can change the related lists of existing articles too.
When to recompute — no webhook, so cron
Ideally we would recompute the moment an article is published, but (as of this writing) Shopify provides no webhook for blog article creation or updates — the list of subscribable webhook topics simply has no article topics. So recomputation runs on a GitHub Actions schedule (once a day, early morning JST), with a manual trigger for urgent cases — one click re-runs the whole thing.
This means a new article can take up to a day to appear in related lists, which is perfectly fine for recommendation freshness. That trade-off is only acceptable because of what this feature is: a slightly stale related-articles list breaks nothing.
The storefront just reads (Liquid themes included)
The display side (Hydrogen, in our case) ends up almost embarrassingly thin. We add a few lines to the article page's data query to resolve the reference metafields. The Storefront API returns the referenced articles' titles, publish dates, and handles within the same single query, so there is no extra request and no caching layer. The component then picks the Japanese or English list based on the current locale and renders dates and titles.
Note that this is not headless-specific know-how. A regular Liquid theme can read article metafields directly from Liquid, and reference lists resolve as arrays of article objects — so the same related-articles section can be built as a theme section or snippet. The precompute-plus-metafields architecture doesn't care what the frontend is.
For articles whose metafields haven't been written yet, the related-articles section simply doesn't render. Even if the whole pipeline stopped, nothing on the site would break — operationally, that matters.
How it went
The first production run wrote 182 metafields across roughly 80 published articles, and both locales rendered exactly as intended (Japanese-only articles never appear in English related lists). Since then, the daily batch has been processing only the diffs.
Quantitative evaluation is still ahead of us, but the headline result is this: every article got semantically related recommendations in one sweep, with zero added tagging or editorial workload.
Takeaways — a reusable pattern, not just for articles
Generalized, the architecture is: do the heavy computation offline, store the results in metafields, and only resolve references at display time.
- It applies broadly to anything you don't want to compute per request — similarity, rankings, scoring.
- Reference-type metafields let Shopify handle freshness, unpublish cleanup, and ordering for you.
- Events that have no webhook can be covered by an idempotent batch plus cron plus a manual trigger.
Shopify's metafields — especially the reference types — are an excellent home for precomputed data. Reference types can be defined not only on articles but on products, collections, and pages in exactly the same way, so the applications are many: cross-linking feature pages and articles, related products, rankings, personalization seeds. On this site, we are considering extending the same pattern to our other blogs (news and events) and adding an editorial override that lets an editor pin a specific article.
References
- Vector embeddings (OpenAI)
- New embedding models and API updates (OpenAI)
- Metafields — custom data overview (Shopify.dev)
- Metafield data types (Shopify.dev)
- metafieldsSet mutation (Shopify Admin API)
- WebhookSubscriptionTopic enum (Shopify Admin API)
- Events that trigger workflows: schedule (GitHub Docs)
This column is based on the implementation on this site as of July 2026. Shopify's feature set (metafield types, webhook topics, etc.) and OpenAI's pricing may change.