Code others can pick up
Split sections, documented snippets, conventions shared across the team: another developer can find their way without me.
Online Store 2.0 themes, metaobjects, Admin GraphQL API, webhooks: I've been building on Shopify since 2019. Here's how I work, with concrete code.
Split sections, documented snippets, conventions shared across the team: another developer can find their way without me.
Liquid for rendering, metaobjects for editable content, APIs for business data: every need has its place, without unnecessary apps.
Server-side rendering, light JavaScript, partial updates through the Section Rendering API instead of full reloads.
An Online Store 2.0 section: the collection and the number of products are set in the theme editor, with an empty state.
{% liquid
assign collection = section.settings.collection
assign limit = section.settings.limit | default: 4
%}
<div class="product-grid">
{% for product in collection.products limit: limit %}
{% render 'product-card', product: product %}
{% else %}
<p>{{ 'sections.featured_collection.empty' | t }}</p>
{% endfor %}
</div>
{% schema %}
{
"name": "Featured collection",
"settings": [
{ "type": "collection", "id": "collection", "label": "Collection" },
{ "type": "range", "id": "limit", "label": "Products", "min": 2, "max": 12, "step": 1, "default": 4 }
],
"presets": [{ "name": "Featured collection" }]
}
{% endschema %}Content is managed in the Shopify admin; the theme simply displays it.
{% assign faqs = shop.metaobjects.faq.values %}
{% for faq in faqs %}
<details>
<summary>{{ faq.question.value }}</summary>
{{ faq.answer | metafield_tag }}
</details>
{% endfor %}Adding to cart returns the updated cart drawer HTML directly, in a single request.
// Bundled section rendering: the cart drawer HTML comes back with the add-to-cart response
const response = await fetch(`${window.Shopify.routes.root}cart/add.js`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
items: [{ id: variantId, quantity: 1 }],
sections: "cart-drawer",
}),
});
const { sections } = await response.json();
document.querySelector("#shopify-section-cart-drawer").outerHTML = sections["cart-drawer"];Products changed since the last sync, page by page, to feed an ERP or a PIM.
query ProductsUpdatedSince($cursor: String, $since: String!) {
products(first: 100, after: $cursor, query: $since) {
pageInfo {
hasNextPage
endCursor
}
nodes {
id
handle
updatedAt
variants(first: 50) {
nodes {
sku
inventoryQuantity
}
}
}
}
}Each webhook is checked against its HMAC signature before processing, with a constant-time comparison.
import crypto from "node:crypto";
// rawBody must be the untouched request body, before any JSON parsing
export function isValidWebhook(rawBody, hmacHeader, secret) {
const digest = crypto.createHmac("sha256", secret).update(rawBody, "utf8").digest("base64");
const expected = Buffer.from(digest);
const received = Buffer.from(hmacHeader ?? "");
return expected.length === received.length && crypto.timingSafeEqual(expected, received);
}01
Theme, apps, data: we start from what's in place before writing a line.
02
Branches, code reviews and shared conventions, on a development theme separate from the live store.
03
A checked launch, and what you need to know to keep evolving the code.
Yes, that's the most common case: marketplace or custom theme, we start from what exists and evolve it cleanly.
Yes, through the Admin GraphQL API and webhooks: catalogue, stock, orders and customers synced one way or both ways.
No. Many needs can be handled in the theme with Liquid and metaobjects, without extra subscriptions or third-party scripts slowing the store down.
That's the goal: shared conventions, documented components and Git history, so another developer can take over.
Theme, integration, API or app: describe what you need and I'll get back to you.