Fixing JavaScript Issues in Shopify
On a Shopify store, JavaScript comes from four places: the theme, apps switched on as app embeds, code that older apps pasted into theme files, and scripts Shopify adds itself.
When the variant picker stops changing the price, the cart drawer shows the wrong count or product pages feel slow, one of those sources is usually to blame, and it is often an app you uninstalled months ago. This guide shows how to tell which source owns each script and error, test changes safely on a copy of your theme, remove what is left behind and load what remains without slowing the store.
Where JavaScript on a Shopify store comes from
| Source | How it gets onto the page | Where you control it |
|---|---|---|
| Your theme | Script tags in layout/theme.liquid, sections and snippets, loading
files from the theme's assets folder. | Online Store, Themes, Edit code. |
| App embeds | Apps built as theme app extensions add their script through an embed that the merchant switches on. | Theme editor, App embeds panel, per theme. |
| App blocks | Blocks added to a section in the theme editor, such as reviews or size charts. | Theme editor, inside the section. |
| Code pasted by older apps | Some older apps, or their support teams, edited theme files directly to add snippets and script tags. | Edit code. It stays after the app is uninstalled. |
| Shopify and app script tags | Output through {{ content_for_header }} in the
theme layout. | Not editable. Controlled by which apps and sales channels are installed. |
| Pixels | App pixels and custom pixels added under Customer events. | Settings, Customer events. |
The difference between the rows matters. App embeds can be switched off per theme without uninstalling
anything, which makes testing easy. Pasted code does not know the app has gone, so it keeps loading files
that may no longer exist or calling functions nobody defines. And content_for_header must
stay in the layout: removing it breaks Shopify features, so the way to reduce what it loads is to remove
the apps behind it.
Telling the sources apart in DevTools
Open the product page with DevTools, go to the Console panel and reload. Every error shows a file name on the right. Click it to open the Sources panel, then look at where the file came from:
- Theme files are served from the theme's assets, with
/cdn/shop/t/orcdn.shopify.comandassetsin the address, and names such asglobal.js,theme.jsorproduct-form.js. - App extension files usually have
extensionsin the address, along with an identifier for the app. - Other app files load from the app developer's own domain, which normally names the app.
- Inline code shows the page address itself as the source. Search the theme code for a distinctive word from that code to find which file prints it.
The JavaScript Checker lists every script on the page with its size and execution time and captures
the console errors, so running it on the home, collection, product and cart pages gives you the full
inventory in one place. In the console, typing Shopify.theme shows the name and ID of the
theme being viewed, which is a quick way to confirm you are testing the right copy.
chrome-extension:// source.
Ignore them, or test in a private window with extensions disabled so they do not cloud the picture.Common Shopify JavaScript errors and what they mean
| Console message | Usual cause on Shopify | Fix |
|---|---|---|
$ is not defined or jQuery is not defined | An app or pasted snippet assumes jQuery, but modern themes such as Dawn do not load it. | Ask the app developer for a version without jQuery, or remove the snippet. Avoid adding jQuery back just for one script. |
404 for a .js file | A snippet still references a file from an uninstalled app. | Delete the reference from the theme. |
Cannot read properties of null in an app file | The app looks for a theme element by a class name your theme does not use, often after a theme change. | Ask the app to support your theme, or use its app block instead of automatic placement. |
Identifier has already been declared | The same script is included twice, for example by an old snippet and a new app embed. | Remove the old snippet. |
| Custom element already defined | Two copies of a theme script register the same web component. | Check for duplicate script tags in the layout and sections. |
Some problems throw no error at all. The classic Shopify example is an upsell or bundle app that adds an item to the basket through the cart API while the theme's cart drawer and item count stay unchanged, because the theme only refreshes when its own code makes the change. That needs the app to trigger the theme's refresh, or a setting in the app for your theme; it is a compatibility question for the app developer rather than something to patch in theme code yourself.
The fix, step by step
Step 1
Map every script to its owner
Run the JavaScript Checker on your home, collection, product and cart pages and export the list of scripts and console errors. For each file, note whether it belongs to the theme, an app embed, a pasted snippet, Shopify itself or a pixel, using its address and the Sources panel. Mark which app each non-theme file belongs to, and flag any file from an app you no longer have installed.
Step 2
Work on a duplicate theme
In
Online Store, Themes, duplicate the live theme and rename the copy with today's date. Make every change
below on the copy and view it through its preview link, confirming with Shopify.theme in
the console that you are on the right theme. Customers never see a broken test, and if something goes
wrong you still have the untouched original to fall back on. Publish the copy only after the full journey
has been tested.
Step 3
Switch app embeds off one at a time
In the theme editor of the duplicate, open App embeds and turn off one embed, save, and reload the preview with the console open. If the error disappears or the page gets noticeably lighter in the checker, you have found the culprit. Turn it back on and move to the next. For the culprit, check for a newer version, a setting for your theme, or contact the developer with the exact console message.
Step 4
Remove code left by uninstalled apps
In Edit code, search the whole theme for the names and domains of apps you have removed,
and look in theme.liquid, product sections and the snippets folder
for render tags and script tags you do not recognise. Comment out one reference at a time, preview and
test, then delete it once you are sure. Remove orphaned snippet files too, and keep a note of what you
removed and where.
Step 5
Load theme and snippet scripts with defer
Find script tags in the layout and sections that load without defer, including
any written with the script_tag filter, which outputs a blocking tag. Replace them with
a plain script tag using asset_url for the address and add defer. Check that
inline scripts which call those files wait for them, for example by listening for DOMContentLoaded.
Do not try to edit or defer what content_for_header outputs.
Step 6
Move tracking into customer events
List
the analytics and advertising codes pasted into theme.liquid. Where the platform offers
an official Shopify app or sales channel with a pixel, use that instead. For the rest, add a custom pixel
under Settings, Customer events, which runs in a sandbox away from your theme and subscribes to events
such as product viewed and checkout completed. Then remove the pasted code so each event is not counted
twice.
Step 7
Retest the buying journey on every template
On the duplicate theme, with the console open, choose a variant and check the price, image and stock message update, add to cart, change quantities in the drawer and cart page, apply a discount and reach checkout. Test on a phone as well as a desktop. Re-run the JavaScript Checker on each template to confirm the errors have gone and the script list is shorter, then publish the copy.
Loading theme scripts correctly
Most current themes already load their own files with defer. Older themes, and code added
by apps or developers, often do not. A blocking script in the head of theme.liquid delays
the whole page on every template.
{%- comment -%} Before: script_tag outputs a blocking tag {%- endcomment -%}
{{ 'slider.js' | asset_url | script_tag }}
{%- comment -%} After: same file, does not block the page {%- endcomment -%}
<script src="{{ 'slider.js' | asset_url }}" defer></script>
Load scripts only where they are used. A size-guide script belongs in the product section that needs it, not in the layout for every page. Scripts placed inside a section file load only on templates that include that section.
If you write your own theme code, remember that the theme editor reloads individual sections as merchants
change settings. Code that sets things up once on page load will stop working in the editor after a change.
Themes handle this by listening for the shopify:section:load event and setting the section
up again, and by using web components that initialise themselves when inserted.
Checkout, thank-you pages and old additional scripts
Shopify has moved checkout customisation to checkout extensibility, with tracking handled by pixels
under Customer events. Stores that still have code in the older additional scripts box or in checkout.liquid
should check the upgrade notices in their admin, because older methods are being retired on Shopify's
published timetable. Once tracking runs as a pixel, remove the old code so conversions are not reported
twice.
Pixels run in a sandboxed environment, which means they cannot read or change your storefront's page, and they can respect the customer privacy settings you configure. That isolation is exactly why moving tracking into them tends to make storefront pages lighter and less error-prone.
Keeping the store clean
- Prefer apps that use app embeds and app blocks. Their code can be switched off in the theme editor and is removed properly when you uninstall.
- Before uninstalling an app, ask the developer whether it edited your theme and how to remove its code, and check the app's own uninstall instructions.
- Keep a change log of theme edits, including who made them and why, so pasted snippets can be traced later.
- Test theme updates on a copy and re-run the JavaScript Checker before publishing.
- Review your app list every quarter and remove anything no one can name a current use for.
For layout shift and loading speed on Shopify specifically, see the Shopify Core Web Vitals guide.
1. Why does my Shopify store still load scripts from an app I uninstalled?
Because some apps, particularly older ones, added code directly to your theme files, and uninstalling the app does not remove edits made to the theme. The leftover snippet keeps requesting the app's files, which can produce 404 errors or run outdated code. Search the theme code editor for the app's name and domain, remove the references on a duplicate theme, test, and publish. Apps built with app embeds and app blocks do not leave this kind of residue.
2. How do I find which Shopify app is causing a JavaScript error?
Open the console in your browser's developer tools and look at the file name next to the error. App extension files usually have extensions in the address, and other app files load from the developer's own domain. If the source is unclear, duplicate your theme, open the theme editor, and switch app embeds off one at a time, reloading the preview after each. The error disappearing tells you which app to take up with its developer.
3. Can I remove or defer the scripts in content_for_header?
No. The content_for_header object must remain in your theme layout, and Shopify does not support editing what it outputs. It includes scripts Shopify needs and scripts registered by installed apps and sales channels. The way to reduce it is to uninstall apps and channels you do not use. Trying to strip or rewrite its output with theme code can break analytics, checkout features and apps, and changes to it can happen at any time.
4. Is it safe to add defer to my Shopify theme scripts?
Usually, as long as the scripts do not need to run before the page has finished parsing. Deferred scripts still run in the order they appear, so dependencies between theme files are kept. The risk is inline code in a section that calls a function from a deferred file before that file has run. Wrap such code so it waits for DOMContentLoaded, test on a duplicate theme, and check the variant picker and cart drawer before publishing.
5. Why does my cart count not update when an app adds a product?
The theme only refreshes its cart drawer and item count when its own code changes the cart, or when it receives an event it listens for. When an upsell or bundle app adds an item through the cart API directly, the basket changes but the theme is not told. Check the app's settings for an option matching your theme, and ask the developer for support if there is none. Avoid patching theme code to watch every network request.
6. Should I put Google Analytics or Meta Pixel code in theme.liquid?
It is better not to. Where the platform has an official Shopify app or sales channel, use that, because it connects to Shopify's customer events and privacy settings. For other tools, add a custom pixel under Settings, Customer events, which runs in a sandbox separate from your theme. Code pasted into theme.liquid runs on every page, competes with your theme for the main thread, and does not cover checkout on current Shopify checkouts.
7. Do Shopify speed apps fix JavaScript problems?
Some help, but they work by changing when scripts run, not by removing the scripts or fixing errors. Delaying all JavaScript until the first interaction can make test scores look better while breaking variant pickers, sliders and app widgets, and it can make the first tap slower. Fixing the causes, which means removing unused apps and leftover code and deferring theme scripts properly, gives more reliable results. If you use such an app, test every key journey after enabling it.
The JavaScript Checker loads your store page in headless Chromium, captures console errors and warnings, lists theme and app scripts with their size and execution time, flags render-blocking files and shows the cost of third-party tags.
Open JavaScript Checker Fixes โ