6 min read

The next constraint

Philos Running Web ShopPhilos Running
Engineering Story

Philos is a women’s performance running brand. The clothes are made for running, but the brand around them draws on movement, ritual and Ancient Greece.

They were building the brand from the ground up, and the missing piece was the web shop.

Our job was to turn that identity into a storefront that felt consistent with the product itself, without losing the practical advantages of Shopify underneath it.

The client needed to be able to manage products, content and storefront sections without requiring a developer for every change.

A headless storefront would have given us more freedom on the frontend, but it would also have changed that relationship with Shopify’s Theme Editor. So we stayed inside Shopify’s native theme system.

That was a reasonable trade-off. It was also the first constraint.

One product, several products

A product could exist in several colours and sizes. In Shopify, that naturally meant one Product with multiple Variants. That was also how we wanted to model it.

The interface saw things differently. If a pair of running shorts existed in black, blue and red, the collection page was supposed to show three separate cards. From the customer’s point of view, each colour behaved almost like its own product.

The underlying model was: Product → Color × Size variants

The presentation model was: Product × Color

We looked at other Shopify stores with a similar interface and found that some solved the mismatch by creating a separate Shopify product for every colour. That would have made parts of the storefront simpler, because the data model and the presentation model would have matched more closely.

We kept the colours as variants of the same product. That did not remove the complexity. It moved it into the storefront.

The first place it showed up was the product listing. A collection could no longer assume that one Shopify product meant one visible card. One product might need to appear several times, once for each colour.

Filtering inherited the same constraint. If a customer filtered by colour, size or product type, the filtering logic still had to return Product × Color results, not just Shopify products.

Even product counts stopped being trivial. Shopify could tell us how many products were in a collection, but that was not necessarily the number of cards the customer would see. A collection with ten Shopify products could represent many more colour-level items in the interface.

Constraints travel

The original decision had already travelled further than the product card.

The gallery created a different version of the same pattern.

Changing colour on the product page affected more than the main image. A single product could have several colour-specific galleries, and each gallery could contain more than a conventional slider. It might include slider images, text with an image, or two images with captions.

We had to model the content so it still belonged to one product while allowing each colour to have its own structured gallery.

Metaobjects gave us the right building blocks, but they came with modelling constraints of their own. The content had to fit into what Shopify could represent and connect, while still being flexible enough for several different gallery structures and several galleries on the same product.

Once that model was in place, the next constraint appeared. We had to retrieve the right gallery for the selected colour and make that structured data available where the storefront needed it.

And once the data was available, the interface had to respond to it. Changing colour now meant changing more than a selected variant. The product page had to move to a different presentation of the same underlying product.

The same Product × Color model showed up elsewhere too. Related products had to respect it. Mini-cart recommendations had to respect it too. They also had to avoid products already in the cart and find something that could actually be bought. Navigation counts had to decide whether they represented Shopify products or what the customer actually saw.

None of those requirements was particularly unusual on its own. The complexity came from the fact that they all inherited decisions made earlier.

More than one Shopify world

The storefront initially fit well inside Shopify’s theme model. Liquid handled server-rendered content, while the Ajax API covered the browser-side interactions that needed fresh product or cart data.

The richer product model started to stretch that setup. Metaobjects gave us a way to represent the content we needed, but the browser-side interactions around that data introduced new limits.

Our Product × Color filtering created another pressure point. We needed browser-side filtering that could account for variant-level information, which went beyond what the Ajax API exposed.

The Storefront API gave us that additional access. It could query the product and variant data we needed, work with metafield references and support storefront filtering.

That solved the immediate problem, but it also introduced another Shopify data model into the project.

The same underlying Product or Variant could be represented differently depending on where we accessed it. Liquid exposed one representation, the Ajax API another and Storefront GraphQL another. They did not necessarily expose the same fields, and even something as basic as an identifier could arrive in a different format.

Liquid, the Ajax API, the Storefront API and the Admin API were built for different responsibilities.

Their reasonable decisions became our constraints. Our reasonable decisions created the next ones.

What the constraints were buying us

Commerce systems have to keep a lot of state correct at the same time. A product has to stay connected to the right variants. Inventory has to reflect what can actually be sold. Prices, carts, checkout and orders have to remain consistent while customers browse, change variants, add items and complete purchases.

Shopify was not optimised for giving us unlimited freedom at every layer. It was optimised for keeping those core commerce behaviours predictable.

Liquid runs inside Shopify’s theme environment, where predictable server-side rendering matters more than giving developers a general-purpose programming language.

The Ajax API gives browser-side code enough access for common storefront interactions without exposing administrative capabilities. The Storefront API allows much richer querying, but it still exposes a storefront-specific contract rather than administrative access. The Admin API sits behind stronger authentication and permissions precisely because it can change products, inventory, customers and orders.

Shopify's APIs limit what each part of the system can do, and where privileged changes can be made.

The price of a good decision

What stayed with me was not one particularly clever workaround. It was the pattern. A good decision did not make complexity disappear. It decided where that complexity would live next.

Keeping the native Shopify theme model preserved the client’s ability to manage the storefront, but constrained the frontend. Keeping colours as variants preserved the product model, but pushed complexity into presentation. Using metaobjects gave us a way to model the content, but introduced another set of constraints around how that content was structured and retrieved. Moving beyond the Ajax API gave us richer access to storefront data, but introduced another contract to understand.

The boundaries that made our work harder were often the same boundaries that let us trust the platform we were building on.