Custom code is a promise you have to keep
Every store that outgrows its theme collects custom work: a badge on the product card, a size chart, a bundle box, a shipping line in the cart. The build is the easy part. The cost arrives later, when someone clicks Update on the theme and a year of small edits does not come along with it.
So the useful question is not "can we customize this?" — almost always, yes. It is where the change lives. Content a merchandiser edits in the theme editor behaves differently from code written into theme files, and knowing which side of that line an edit sits on is the whole decision.
Theme updates replace files, they do not merge them
Shopify does not update a theme in place. It installs the new version as a separate theme in your library and leaves the old one untouched. Anything merchant-facing — template JSON, saved theme settings — carries over, because Shopify treats that as merchant property. Everything in layout, sections, snippets, and assets, plus any schema you edited, is code. Code is replaced wholesale. There is no merge, no conflict prompt, and no "keep mine" option.
The old theme staying in the library is why custom work is usually not deleted, it is stranded. That matters because the built-in rollback is not a spare safety net: version history restores one file at a time, cannot bring back deleted files, and does not last forever. Rebuilding from a stranded theme during launch week is the expensive version of this problem.
Keep custom work in its own files
The pattern that survives is boring and small. Give every custom feature its own snippet — say custom-product-badge or custom-announcement — and give custom styling its own stylesheet instead of appending to the theme's. Then reference each one from the upstream file with a single line, a render tag or a stylesheet include. Ten custom features become ten files you own plus ten one-line references, rather than hundreds of edits scattered through the theme's own code.
Three files deserve extra caution. theme.liquid loads the global styles and scripts and wraps every page, so a mistake there can take down the whole storefront rather than one template. The settings data file holds every theme-editor choice a merchant has saved, and a stray character in it can lock them out of the editor entirely. Checkout-adjacent files are restricted for good reason. Do not hand-edit those; make the change through the editor or an API. And when an app insists you add a snippet, try the theme editor's Custom Liquid section first — it does the same job without touching a theme file.
Buy the functionality before you build it
Most custom requests are one good app's worth of work: reviews, size charts, bundles, back-in-stock alerts. On a modern theme, apps that ship app blocks and app embeds inject their UI through the theme editor without ever entering your theme code. Merchants can move them, hide them, or turn them off, and theme updates leave them alone. That is worth more than a feature comparison when you pick an app — the ones still asking you to paste a script into theme.liquid are the ones that will cost you again at the next release.
Watch the exits, too. Uninstalling an app often leaves orphaned snippets, dead script references, and styling for elements that no longer render. Audit after every uninstall. Leftover app residue is a common source of the vague "the site feels slow lately" complaint and of styles that fight each other for no visible reason.
Make the content live on the product, not in the theme
Custom code is not the only thing that gets stranded. Section settings live in the theme version, which is fine for page-level choices and awkward for anything per-product. If a headline, spec list, or care guide needs to differ across eighty products, do not build eighty blocks — the editor sidebar becomes unusable and the content vanishes with the theme. Put it in metafields on the product instead, where the merchandising team can edit it from the product page and it travels with the product through any rebuild.
Build a fallback chain while you migrate: metafield first, then the section setting, then a hardcoded default. Products that have not been filled in yet keep rendering the old content instead of a blank space, so the switch is invisible to shoppers. Content living on the product survives theme changes; content living in the theme does not.
Version control turns the next update into a merge
The discipline that pays for itself is small: duplicate the live theme before touching anything, pull it down with the Shopify CLI, commit it to Git, and keep a short changelog listing your custom files and which upstream files carry the include lines. With that list, a theme release becomes a single focused task — read the new version's changed files, re-apply the include lines, run the update on a duplicate, and publish only when the storefront checks out.
Before publishing any theme change, walk the same short checklist: product page with add-to-cart and variants, cart and drawer, mobile navigation, and one completed test order. Then judge the setup on the right number — not how quickly the customization shipped, but how long the next theme update takes and how much of the custom work had to be rebuilt. Done well, that answer is minutes, not days.
