Your Shopify Theme Broke After Installing an App: How to Find the Culprit
Add to cart stopped working, the layout shifted, or the page throws a console error. A systematic way to find which app did it and what to do about it.
Disclosure: I spent four years at Debutify building Shopify themes, finishing as CDO. A meaningful share of the support requests we handled were app conflicts rather than theme bugs, which is why I wanted to write down the method.
The short answer
Open the browser console on the broken page. The error usually names the offending script's domain, and that domain maps to an app.
Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
at rebuy-cart.js:1:4821
If the console is clean but the behaviour is wrong, disable app embeds one at a time in the theme editor and reload after each. Start with anything that touches the cart.
Do all of this on a duplicated theme, never on the live one.
Why apps break themes
Shopify apps inject JavaScript into your storefront through one of three mechanisms, and each fails differently.
App embeds are the modern way. The app registers a block that the merchant toggles on in the theme editor under App embeds. Clean, removable, and the app cannot silently persist after uninstall.
Script tags are the older API. The app registers a script that Shopify injects into every page. These are the ones that get left behind after an uninstall.
Theme code injection. Some apps, particularly during install, edit your theme files directly and add a snippet or a line to theme.liquid. This is the most invasive and the hardest to clean up, and it is why you take a theme backup before installing anything.
The conflicts themselves fall into a small number of categories.
The five conflict patterns
1. Two scripts fighting over the same element
The most common. Your theme has a cart drawer that binds to [data-add-to-cart]. An upsell app also binds to it, calls preventDefault(), does its own thing, and never lets your theme's handler run. Add to cart appears to do nothing, or adds the item without opening the drawer.
The tell: the button works with JavaScript disabled for the app, and the console is silent because nothing errored. The handler simply never ran.
To confirm, inspect the button in DevTools, open the Event Listeners panel, and look at what is bound to click. You will usually see two listeners with different source files.
2. A script running before the element exists
Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
The app's script runs at the top of body, queries for an element your theme renders further down, gets null, and throws. Everything after that line in the app's bundle never runs, which is why the symptom is often "the app half works".
This gets worse with themes that render sections asynchronously, since the element may genuinely not exist at any point the app is looking.
3. jQuery and library version collisions
Older apps assume jQuery is present and global. Modern themes do not ship jQuery. So the app loads its own copy, and now you have two, and whichever loads last wins window.$. Plugins registered against the first one vanish.
The tell is $(...).someMethod is not a function where someMethod is a plugin, or $ is not defined intermittently depending on load order.
4. CSS specificity wars
Not a script problem but it presents like one. The app injects styles with high specificity or !important, and your theme's layout shifts. Buttons change size, a modal sits behind the header, the cart drawer gains an unexpected margin.
Find it by inspecting the affected element and reading the Styles panel top to bottom. The winning rule shows its source file, and if that file is on an app's CDN you have your answer.
5. Cart API race conditions
The nastiest category, because it corrupts data rather than just looking wrong.
Your theme calls /cart/add.js and then /cart.js to refresh the drawer. An upsell app also calls /cart/add.js at the same moment for its bundled item. Shopify's cart endpoints are not transactional across separate requests, so the two interleave, and the resulting cart is missing an item or has the wrong quantity.
The tell: the bug is intermittent, more common on slow connections, and reproduces more reliably if you throttle the network in DevTools. Anything that gets worse under throttling should make you think about ordering.
The method
Step one: duplicate the theme
Admin, Online Store, Themes, Actions, Duplicate
Work in the duplicate's preview. Never debug on the live theme. This takes fifteen seconds and it is the difference between an investigation and an incident.
Step two: read the console properly
Open the broken page in the preview, open DevTools before reloading, and reload.
Look at the first error, not the last. Later errors are frequently consequences of the first one. Expand the stack trace and note the filename and domain, which usually identifies the app immediately.
If the console is clean, the failure is behavioural rather than an exception, so move to step four.
Step three: check what is loading
In the Network tab, filter to JS and list the third party domains. Map each to an app. If you find a domain you cannot account for, that is your first suspect, and it is often an app somebody uninstalled months ago whose script tag persists.
To see the script tags registered against your store, you need the Admin API, but a practical proxy is searching your theme:
Edit code, search theme.liquid for "<script"
Anything hardcoded there that is not part of your theme is a candidate.
Step four: bisect the app embeds
Theme editor, App embeds panel in the left sidebar
Turn them all off. Reload. Is the bug gone?
If yes, turn them back on one at a time, reloading after each, until it returns. The one that brings it back is your culprit.
If the bug persists with all embeds off, the app is injecting through a script tag or has edited your theme code, so go to step five.
Step five: find injected theme code
Search your theme files for the app's name, and for suspicious blocks. Apps that inject code usually leave a comment marker:
<!-- BEGIN app-name -->
{% render 'app-name-snippet' %}
<!-- END app-name -->
Check theme.liquid, product.liquid or the product section, and the cart templates. Compare against a clean copy of your theme, which is the fastest method by far if you have one. Download both and diff them:
diff -r theme-clean/ theme-live/
This finds every injected line in seconds and it is the strongest argument for keeping an unmodified copy of your theme in version control.
Step six: confirm by isolation
Once you have a suspect, prove it. Disable only that app's embed or comment out only its script tag, reload, and verify the bug is gone. Then re enable and verify it returns.
Confirming in both directions matters, because a surprising number of "fixed" app conflicts were actually fixed by the cache clear that happened along the way.
Fixing it, in order of preference
Ask the app developer first. Send them the console error, the theme name and version, and a preview link with the bug reproducing. Good app developers fix these, and many of these conflicts are already known to them with a documented setting to change. This costs you one email and is frequently the whole solution.
Change the app's settings. Many conflicts come from an app being configured to attach to the wrong selector or to run on all pages when it only needs product pages. Check the app's settings for a custom selector field or a page restriction.
Control the load order. If two scripts fight, and yours can run last, it usually wins. Defer your theme's handler until after the app has bound:
document.addEventListener('DOMContentLoaded', () => {
requestAnimationFrame(() => {
initCartDrawer();
});
});
This is a hack and I want to be honest that it is a hack. It works, and it is fragile against the app changing its own timing.
Scope the app's script to where it is needed. If the app injects through your theme, wrap it:
{%- if template.name == 'product' -%}
{% render 'app-snippet' %}
{%- endif -%}
Fewer pages loading it means fewer chances to conflict, and it is a performance win as well.
Remove the app. Sometimes the honest answer. If an app breaks your cart and the developer is unresponsive, the app is costing you more than it earns.
Cleaning up after an uninstall
Uninstalling an app in admin does not always remove its footprint. After any uninstall:
Check App embeds in the theme editor for a leftover toggle. Search your theme code for the app's name. Look for orphaned snippets in the snippets directory. Check theme.liquid for script tags pointing at the app's CDN. Check your settings_data.json for leftover app configuration blocks.
I audited a store last year that was loading 340KB of JavaScript from four apps that had all been uninstalled. Nobody had checked, because uninstalling felt like it should be enough.
Prevention
Keep your theme in version control. Use the Shopify CLI to pull your theme into a git repository. Then every app that edits your theme produces a visible diff, and you can revert precisely rather than guessing.
shopify theme pull --store yourstore.myshopify.com
git diff
This single practice turns app conflict debugging from archaeology into a code review.
Install apps on a duplicate theme first. Test, verify the cart and checkout still work, then publish. Most merchants install directly on live because the app store makes it a one click action, and that is where the incidents come from.
Test the whole purchase flow after every install. Not just the page the app affects. Add to cart, view cart, update quantity, remove item, proceed to checkout. Cart bugs are the expensive ones and they do not always show up on the page where the app lives.
Keep an app inventory. A short document listing each app, what it does, who asked for it, and what it costs per month. Review it quarterly. Stores accumulate apps the way laptops accumulate browser extensions, and nobody ever removes one because nobody remembers why it is there.