How it works
A three-way merge, explained with real theme files.
What ThemeMerge compares, why the baseline matters, how each kind of file merges, and the four ways the result leaves the app.
The three-way model
Every merge has three inputs. The baseline is the live theme as it was when the working copy was made. The live theme is where the client kept editing. The working copy is your redesign. Comparing each side against the baseline tells ThemeMerge what that side changed, and only those changes are merged.
Every file falls into one of five cases:
| What changed since the baseline | Result |
|---|---|
| Neither side | Left alone. Not written. |
| Only live | Take live. The client’s edit carries into the merged theme. |
| Only the working copy | Keep copy. Your edit stays. |
| Both, in different keys or lines | Merged automatically. Both edits survive. |
| Both, in the same key or lines | A conflict. You decide it. |
Baseline vs timestamps
Without a baseline, a merge tool has two files and two modified dates. It can see that templates/index.json differs between the themes, but not whether the client changed it, you changed it, or both of you did. Attribution becomes a guess based on which theme was edited last.
| Situation | Three-way, with a baseline | Two-way, by timestamp |
|---|---|---|
| The client added a countdown section; you didn’t touch the file | Take live. No question asked. | The files differ. The newer side wins, which is right only if the client edited last. |
| The client added a countdown; you added reviews | Both sections kept. Zero conflicts. | The whole file is a conflict. One side’s section is lost unless you merge by hand. |
| You deleted a section the client never touched | It stays deleted. | It looks like live added it, so it can come back. |
Use Duplicate + record baseline in Compare themes, or Record baseline right after duplicating yourself. The second option is only correct if nobody has edited the live theme since the copy was made.
Theme-editor JSON merges key by key
templates/*.json, section groups and config/settings_data.json are parsed and merged as objects, not as text.
{ "sections": { "hero": { "type": "image-banner", "settings": { "heading": "New season" } }, "featured": { "type": "featured-collection" } }, "order": ["hero", "featured"]}
{ "sections": { "hero": { "type": "image-banner", "settings": { "heading": "Black Friday — up to 40% off" } }, "countdown": { "type": "countdown-timer" }, "featured": { "type": "featured-collection" } }, "order": ["hero", "countdown", "featured"]}
{ "sections": { "hero": { "type": "image-banner", "settings": { "heading": "New season essentials" } }, "featured": { "type": "featured-collection" }, "reviews": { "type": "product-reviews" } }, "order": ["hero", "featured", "reviews"]}
{ "sections": { "hero": { "type": "image-banner", "settings": { "heading": "New season essentials" } }, "countdown": { "type": "countdown-timer" }, "featured": { "type": "featured-collection" }, "reviews": { "type": "product-reviews" } }, "order": ["hero", "countdown", "featured", "reviews"]}
The countdown section and the reviews section are different keys under sections, so both survive. The heading is the same key changed on both sides, so it’s the only thing you decide.
Ordered arrays merge as ordered sets
A plain JSON merge treats an array as one value, so one side’s order would replace the other’s. ThemeMerge merges order and block_order as ordered sets: additions from both sides survive, each placed next to the entries it was added beside.
base ["title", "price", "buy_buttons"]live ["title", "price", "inventory", "buy_buttons"]copy ["title", "price", "buy_buttons", "reviews_badge"]merged ["title", "price", "inventory", "buy_buttons", "reviews_badge"]
Liquid, CSS and JS merge line by line
Code files use a three-way diff3 merge. Changes to different regions of a file combine. When both sides changed the same lines, you get a hunk with four choices: live, copy, both with live first, or both with copy first. You can also edit the merged file by hand.
.card-grid { gap: 16px; }.card { border-radius: 8px; padding: 16px; }.button { min-height: 44px; }
.card-grid { gap: 16px; }.card { border-radius: 8px; padding: 12px; background: #fff8f0; }.button { min-height: 48px; }
.card-grid { gap: 24px; }.card { border-radius: 14px; padding: 24px; }.button { min-height: 44px; }
.card-grid { gap: 24px; }hunk 1: .card changed on both sides.button { min-height: 48px; }
Binary assets compare by checksum
Images, fonts and other binaries are hashed. If only one side’s hash moved from the baseline, that side’s file is used. If both moved, you choose. Nothing is merged byte by byte.
Shipping the result
Four ways out. ThemeMerge never writes the live theme in any of them.
Download full theme
ProEvery file of the merged theme as a zip. Upload it under Online Store → Themes → Add theme → Upload zip file and Shopify creates a new unpublished theme.
Download changed files
Free and ProOnly the files that differ, plus a MERGE-REPORT.md with the exact shopify theme push --only … command.
Create merged theme
Pro · needs theme-writeDuplicates the working copy and writes the merged result into the duplicate. The original copy is untouched.
Apply to working copy
Needs theme-writeWrites the merged result into the working copy, after snapshotting it so the write can be rolled back.
Writing theme files from an app requires Shopify’s Online Store protected scope exemption, reviewed per app. Until it’s granted, the two in-app options stay disabled and export is how you ship.