Schema fragments cut section files by 40 lines each
Conditional snippets replaced 12 near-duplicate blocks
asset_url helpers ended broken image paths for good
settings inheritance and content_for_index hooks compose like LEGO
I rebuilt my Shopify theme last month and finished in roughly half the hours the previous one took. The difference was not speed of typing. It was five Liquid patterns that turned every section file from a wall of copy-paste into something I could compose from parts. Here is exactly what they are and how I use them.
Schema Fragments Stop The Copy-Paste Spiral
The first thing that ate my time on old builds was schema. Every section needed a heading setting, a subheading, a button label, a button link, a color picker. I would copy 40 lines of JSON from another section, rename half the IDs, miss two of them, and spend ten minutes hunting a typo that broke the editor.
Now I keep a folder of schema fragments as snippets. Not the full {% schema %} block (Liquid does not let you render schema from a snippet), but the JSON text itself stored as reference files I paste from. I have a fragment for a heading group (heading, subheading, alignment), one for a button group (label, link, style), and one for a section spacing group (padding top, padding bottom in a range slider).
Each fragment uses a consistent ID prefix. The heading fragment always uses heading, subheading, heading_alignment. Because the IDs never change between sections, my Liquid markup that reads them never changes either. I can drop the same {{ section.settings.heading }} block into any section and it works.
The result is concrete. A typical section schema used to run 90 to 110 lines. With fragments composed from three or four reusable groups, I am at 50 to 60 lines, and the IDs are correct on the first try because I am not renaming anything. I built a small index file that lists every fragment with its setting IDs so I never guess.
If you want a wider reference on how sections and schema fit together, I keep notes in Shopify Liquid Sections. The schema is where most beginners lose an afternoon, and it is the easiest part to systematize.
One caution. Keep your fragment IDs short and stable. The moment you start naming things heading_2_new_final, the whole point collapses. Pick a naming rule on day one and never break it. Mine is simple: object name, then property, lowercase, underscores. That is the entire convention and it has held up across four themes.
Conditional Snippets Replace Twelve Near-Duplicate Blocks
The second pattern saved me the most raw lines. I used to have separate sections for things that were 90 percent identical: image-left, image-right, image-full. Three files. Three schemas. Three places to fix a bug.
Now I have one section and one snippet that takes parameters. The snippet handles the layout variation through a passed-in setting. I render it like this:
{% render 'media-block', layout: section.settings.layout, image: section.settings.image, heading: section.settings.heading %}
Inside the snippet, a single case statement on layout decides the markup. Image-left, image-right, image-full all live in one file. When I fix the alt-text handling or the lazy loading, I fix it once. That single change used to mean editing twelve blocks across the theme.
The render tag matters here over the old include. With render, the snippet only sees the variables I explicitly pass. No surprise leakage from parent scope. That isolation is what makes the snippet safe to reuse everywhere. I know its inputs and I know its outputs.
I count my snippet inputs the same way I count function arguments. If a snippet needs more than six, it is doing too much and I split it. My media block takes five: layout, image, heading, body, and a button group object. That is the ceiling.
The payoff is measurable. My old theme had 28 section files. The rebuild has 17, and it does more. Eleven sections collapsed into reusable snippets called with different parameters. Less code is less surface for bugs, and the editor loads faster because there are fewer files to parse.
I covered the broader philosophy of building small composable pieces in Claude Blueprint, which is how I plan any system before writing a line. The same logic applies to Liquid: define the smallest reusable unit, then compose up from there instead of copying down.
If you run a store and want a clean place to test these on, Shopify gives you a development theme you can break freely without touching the live one. I do all my snippet refactoring there first.
asset_url Helpers End The Broken Image Path Era
The third pattern is small but it killed an entire class of bug. Hardcoded asset paths. I used to write /assets/icon-cart.svg directly in markup, and it would work locally and break the moment the theme was on a CDN or in a different environment.
Now every asset goes through a filter, always. {{ 'icon-cart.svg' | asset_url }} for files, {{ 'icon-cart.svg' | asset_url | img_tag }} when I want a full tag. For stylesheets it is {{ 'section-hero.css' | asset_url | stylesheet_tag }}. The filter resolves the correct CDN path every time, so the same code works in every environment.
I went one step further and built a tiny icon snippet. It takes a name and returns the inline SVG or a referenced one with the proper URL. I render {% render 'icon', name: 'cart' %} and never think about the path again. When I added a new icon set, I dropped the files in assets and added cases to the snippet. No markup anywhere else changed.
The same discipline applies to images uploaded through settings. I always pipe them through image_url with an explicit width, like {{ section.settings.image | image_url: width: 1200 }}, then let image_tag handle the responsive srcset. Hardcoding a width of the original upload meant shipping a 4000 pixel hero image to a phone. Now every image is sized to its container and the page weight dropped noticeably.
I will not pretend the path filters are exciting. They are plumbing. But plumbing that I never touch again is exactly what cuts build time, because the time you save is the time you do not spend debugging something that should have worked the first time.
For the heavy image work that feeds these themes, I generate and upscale source art in Magnific before it ever reaches Shopify, so the assets going through these filters are already at the resolution I want. Clean inputs, clean filters, no surprises in production. That whole chain, from generated art to filtered output, is what keeps the asset layer boring in the best way.
Settings Inheritance And content_for_index Hooks
The last two patterns are what make a theme feel composed rather than assembled.
Settings inheritance means I define defaults once in settings_schema.json and read them anywhere with {{ settings.brand_color }}. Section-level settings only exist when a section truly needs to override the global value. My old themes had a color picker on every single section, which meant changing the brand color was 28 edits. Now it is one. Sections fall back to the global setting unless explicitly told otherwise.
The pattern in Liquid is a default filter. {{ section.settings.button_color | default: settings.brand_color }}. If the merchant set a section override, it wins. If not, the global brand color flows down. The whole theme stays consistent for free, and merchants can still override one section when they want to.
The second pattern is content_for_index hooks, the engine behind dynamic sections on the homepage. Instead of hardcoding a fixed homepage layout, the index template just calls {{ content_for_index }} and merchants drag sections in the editor. I used to hand-build homepages. Now I build sections that work anywhere and let the merchant compose the page.
The trick that saved time was making every section index-safe from the start. That means no assumptions about position, no hardcoded references to other sections, and a sensible default when a setting is empty. Each section is a self-contained block that renders correctly whether it is first, last, or duplicated three times. I write a quick checklist for each: does it work alone, does it work duplicated, does it work with no image set. Three checks, every section, no exceptions.
When a section passes those three, it goes anywhere on the site with zero rework. That portability is the real time saver, because a section I build for the homepage also works on a landing page or a collection page untouched.
When the theme is done, I schedule the launch posts through Buffer so the build and the announcement run on the same calendar. The development discipline and the publishing discipline are the same idea: define the reusable unit once, then reuse it.
Bottom Line
These five patterns are not clever. Schema fragments, parameterized snippets, asset filters, settings inheritance, and index-safe sections are all boring on their own. Stacked together they changed how I work. I stopped copying and started composing. The build that used to mean editing a dozen near-identical files now means writing one snippet and calling it with different parameters.
The numbers held up across the full rebuild: 28 sections became 17, schema files dropped from 100 lines to 55, and the bugs I used to chase (broken paths, mismatched IDs, inconsistent colors) mostly stopped happening because the patterns made them impossible.
If you are starting a theme, pick one pattern and apply it everywhere before adding the next. Inconsistency is what kills these. Half-applied conventions are worse than none.
For the planning side of any build like this, I lean on Claude Blueprint, which is where I map the reusable units before I write Liquid. And for the section reference itself, Shopify Liquid Sections covers the structure underneath all of this. Build the parts once. Compose forever.
This article contains affiliate links. If you sign up through them, I may earn a small commission at no extra cost to you. (Ad)
Top comments (0)