Webulux
Back to all articles
Speed Engineering13 min readBy Muhammad Usama

How Ghost Scripts from Deleted Shopify Apps Are Silently Destroying Your Store's Performance

TL;DR: The Quick Read

Uninstalling a Shopify app removes it from the dashboard, not the code. Leftover ScriptTags, orphaned App Embeds, and direct Liquid writes keep loading on every page. One store found 23 external domains firing despite only 8 active apps, costing 847KB, 4 seconds of LCP, and a 135% CVR hit until removed.

  • Three injection paths, three fixes: legacy scripts need a ScriptTag GraphQL deletion, App Embeds need Network-tab verification (not just a toggle), and direct Liquid writes need a manual theme search, unlike front-end cleanup alone.
  • Check mobile first: ghost scripts hit weaker mobile CPUs hardest, so if your numbers look off, run the diagnostic in why your mobile CVR is half your desktop CVR.
  • iOS Safari breaks differently: an 8-pixel shift invisible on desktop Chrome can push the Apple Pay button off-screen on iPhone; see why your checkout fails on iOS Safari.
  • Sequence matters: remove dead scripts before compressing images so the LCP gain isn't muddied, following the order in the Shopify CRO checklist, or skip the DIY audit with Shopify Speed Optimization.

A founder pulled up his Shopify Apps dashboard last month. Eight active apps. Reasonable, by any standard. He checked that against the usual "too many apps" warnings floating around the internet and felt fine about it.

Then he opened Chrome DevTools. Connection throttled to Fast 4G. Homepage reloaded. The Network waterfall filled with requests from 23 different external domains.

Review platforms he'd switched away from two years earlier. Marketing tools nobody on the team could name. Upsell software with a subscription canceled since before COVID.

His reaction, word for word: "Wait, we deleted that app."

Then it happened again. Then again.

Eight apps on the dashboard. Twenty-three domains loading in the browser. That gap is the entire discovery this post is built around.

Uninstalling an App Doesn't Mean Its Code Is Gone

Most merchants assume a Shopify app is a self-contained unit. Install it, it does its job. Delete it, it's gone. That mental model is wrong, and it's wrong in a specific, technical way.

A single app rarely installs into one tidy location. It can touch theme.liquid, snippets, sections, App Embeds, ScriptTags, metafields, checkout extensions, and customer event tracking, all at once. Months later, the merchant uninstalls it. The app disappears from the dashboard. The code it wrote into the theme usually doesn't. Each of those injection paths, ScriptTag API calls, orphaned App Embeds, and direct Liquid writes, needs its own removal method, which is exactly why a single theme search rarely catches everything.

There's rarely one culprit. Across real engagements, we've found leftover ScriptTag remnants, orphaned App Embeds still toggled on in the theme editor, and direct Liquid writes left sitting in snippets and sections, sometimes all three on the same store. One gadget brand's history makes this explicit. Of 13 apps that had touched that store over its lifetime, most were already deleted. What remained was "legacy script tags, unused Liquid includes, orphaned snippet references, and JavaScript that still initialized on every page load despite the app behind it being gone for good."

That's the mechanism. Now let's walk through each injection path separately, because each one needs a different fix.

Diagram showing three Shopify app injection methods: ScriptTag API, App Embeds, and direct Liquid writes, each surviving app uninstall differently

Method 1: The Legacy ScriptTag API

Older Shopify apps, especially ones built before theme app extensions became the standard, register scripts through Shopify's ScriptTag API instead of writing directly into your theme. These scripts live at the API layer, not inside your theme files, which is exactly why a manual theme code search can miss them entirely.

This is the injection method that explains why a founder can grep their entire theme.liquid file, find nothing suspicious, and still watch dead scripts fire in the Network tab. The code isn't in the theme. It's registered against your store through the API, and it keeps loading on every storefront page until something explicitly deletes it.

This is also the exact structure Google's own AI Overview for "shopify deleted app scripts" already surfaces as Method 2 of three: front-end theme cleanup, ScriptTag GraphQL deletion, and reinstall-to-autoclean. Almost nobody writing about this topic covers the middle one in any technical depth. Here's the actual mutation.

mutation ScriptTagDelete($id: ID!) {
  scriptTagDelete(id: $id) {
    deletedScriptTagId
    userErrors {
      field
      message
    }
  }
}

Pass the target ScriptTag's GID in the variables config: { "id": "gid://shopify/ScriptTag/12345678" }. You'll need to query Shopify's Admin GraphQL API first to list existing ScriptTags and find the IDs of anything that doesn't map to a currently installed app. This is developer-level work, not a toggle in your theme editor, which is exactly why it's the most overlooked of the three removal paths.

Method 2: The Orphaned App Embed

Modern Shopify apps built on theme app extensions use App Embeds: toggleable blocks visible in your Theme Customizer under App Embeds. These are supposed to be cleaner. In practice, they create a different failure mode.

When an app is uninstalled, its App Embed doesn't always disappear from the theme editor. Sometimes it stays listed, sometimes toggled on, sometimes silently loading its initialization script even though the backend service behind it is gone and returning nothing useful. Toggling something off in the theme editor isn't the same as confirming it stopped loading. You have to verify in the Network waterfall that the script actually stopped firing.

Method 3: The Direct Liquid Write

This is the messiest of the three and the one most manual audits are actually built to catch. An app writes directly into theme.liquid, product templates, snippets, or section files during install. Delete the app, and unless the developer built a genuinely clean uninstallation routine, that code sits in your theme forever.

Theme code search (Ctrl+F for the app's name or file prefixes like {% include 'app-name' %} or {% render 'app-name' %}) is the right tool here. It's also the only one of the three methods that's fully manual, no API call fixes it. You find it, you verify it's dead, you delete the snippet inclusion by hand.

Get a Ghost Script Audit — Free

We cross-reference your ScriptTags, App Embeds, and theme.liquid against your currently installed apps, then hand you an exact removal list with file sizes attached. Manual work. 48 hours. No automated scans.

Get My Free Ghost Script Audit →

What This Actually Costs: The 847KB Number

Real numbers, from a real engagement: 847KB of unnecessary JavaScript loading on every page, LCP moving from 5.4s to 1.4s, CVR climbing from 4.3% to 10.1% (a 135% lift) after removal.

The detection method was the same waterfall-first approach covered throughout this post: requests flowing to domains from apps deleted over a two-year period, while the Apps dashboard showed fewer than ten active integrations. Total network request count is a separate, useful signal in the same audit. A clean Shopify store runs 30 to 60 requests on the homepage. Above 100 signals serious bloat.

It's worth being honest about scope here. That 847KB figure is an aggregate: the total dead weight found, the two-year span it accumulated over, and the sub-10 active-app count on the dashboard at the time of audit. It doesn't break down which specific named script contributed the most individually, and no single script "shocked" the team more than the total did. If that level of per-script forensic detail exists somewhere in a raw engagement file, it isn't something we're going to invent here to make the story tidier. The aggregate number is the real, verified finding, and it's damning enough on its own.

For the full mechanics of why dead JavaScript competes for the main thread even when it's technically "just sitting there," see how ghost scripts affect your CVR, and for the exact LCP triage order these fixes fit into, the Shopify LCP guide covers why script removal almost always comes before image compression.

Before and after chart showing 847KB of ghost scripts removed and LCP dropping from 5.4 seconds to 1.4 seconds

The One Mechanism iOS Safari Exposes That Chrome Never Will

Ghost scripts don't just slow pages down. On iOS Safari specifically, they can shift layout in ways that break checkout entirely, and this is the most precisely documented technical failure we've traced.

On a health and wellness store, a third-party trust badge initialized asynchronously after the checkout DOM had already rendered, inserting itself above the payment section and pushing the entire payment block downward. On desktop Chrome, that shift measured 8 pixels. Invisible. Nothing a standard QA pass would catch.

Safari's DVH (dynamic viewport height) handling, combined with the browser's address bar expand-and-collapse cycle, amplified shifts that other browsers absorbed without visual disruption. On a 390-pixel-wide iPhone screen, that same 8-pixel shift was enough to physically move the Apple Pay button below the visible viewport at the exact moment a customer's thumb was completing its tap. Chrome and Android weren't running fundamentally different code. They were just rendering timing-forgiving enough to swallow the same shift that Safari's viewport recalculation behavior exposed.

CLS at the payment step measured 0.31 against Google's 0.1 passing threshold, over three times the limit at the single most expensive moment in the funnel. As the store's own post-fix notes put it: "If we had only tested on desktop Chrome, we would have found nothing." The full breakdown of why iOS Safari and Chrome disagree on this so dramatically is in why your checkout fails on iOS Safari.

Proving It Was the Scripts, Not Something Else

Ghost script removal is only credible if you can defend that the script removal, specifically, caused the metric to move, and not seasonality, a redesign, or something else changing in parallel. Three engagements compared side by side rule out a simple confound because they don't follow a straight line.

Apparel: LCP 4.8s to 1.2s, CVR +22%. CPG: LCP 5.4s to 1.4s, CVR +135%. Gadget brand: LCP 6s to 1s, CVR +230%. If the lift were driven by something generic across all three at once, you wouldn't expect the apparel brand, which had the largest absolute LCP improvement of the three, to post the smallest CVR lift by a factor of ten.

The explanation: LCP alone doesn't determine lift size. A store's total stacked technical debt does. A store with multiple compounding bottlenecks (ghost scripts plus Liquid bloat plus bad sequencing) unlocks more suppressed demand when fixed than a store where LCP was closer to being the only remaining issue.

The isolation discipline for reporting to a client: build the ghost script inventory, match every domain to an installed app, confirm the unmatched ones, verify the 404s (a red row is a confirmed ghost script, hitting a server that no longer exists, not a suspicion), remove only that, retest before touching anything else like images. Image compression comes after script removal, deliberately, so the LCP delta from script removal isn't muddied by a second variable changing in the same sprint.

It's also fair to flag the limit of this evidence. There's no claim here that every Shopify store carries exactly 300 to 900 kilobytes of dead code, because that number hasn't been measured on every engagement. The defensible claim is narrower: this specific defect was measured, isolated, and removed, and the corresponding metric moved. Not a guaranteed universal percentage. Pricing, ad spend, and other variables during any live commercial engagement are never perfectly frozen, and pretending otherwise would be dishonest.

The Weekend Framework: Find Your Own Ghost Scripts

This is the exact ordered sequence for finding dead code without risking your live store. It deliberately separates finding ghost code from safely removing it, which is the split that separates a professional audit from a DIY attempt gone wrong.

  1. Export your current app list. Document every installed app before touching anything. Don't rely on memory.
  2. Open Chrome DevTools. Network tab, disable cache, throttle to Fast 4G, reload the homepage. Record every external domain firing a request.
  3. Compare domains against installed apps. Anything loading that doesn't clearly belong to an active app is a candidate.
  4. Search the theme. Check theme.liquid, Snippets, Sections, and Assets for references to those domains or old app names.
  5. Review App Embeds. Many apps leave embeds toggled on in the theme editor long after the software behind them goes inactive.
  6. Use the Coverage tab. Reload and look for JS files with very low execution coverage. Low usage doesn't automatically mean removable, but it tells you where to look next.
  7. Verify business purpose before deleting anything. Ask what customer-facing feature still depends on the code. If you can't confidently answer, don't touch it yet.
  8. Duplicate the theme. Never investigate on production. Every cleanup happens on a copy first.
  9. Test the full purchase journey on the duplicate: homepage, product page, cart, checkout, payment, and analytics, on real devices, not just desktop Chrome.
  10. Publish only after verification.

Where this trips people up, stated plainly: finding ghost code is a detective exercise, removing it safely is an engineering one. The specific fear that stalls most DIY attempts at step 7: nobody hesitates to delete an app, but developers hesitate to delete leftover code because they genuinely can't tell whether it's dead or quietly supporting something critical like analytics attribution, subscription logic, or checkout behavior. Delete the wrong script without verifying it first, and you don't just save load time. You can silently break revenue tracking or the cart with zero warning before it happens. That's exactly why step 8 is never skipped in the professional version of this process, and it's the step most rushed DIY attempts are most tempted to shortcut.

For the broader 27-check version of this sequence, including where script removal fits relative to Liquid architecture and checkout testing, see the Shopify CRO checklist.

Ten-step Weekend Framework for finding and safely removing ghost scripts from a Shopify theme

App Count Is the Wrong Metric

The gadget brand's 8-apps-vs-23-domains gap isn't an outlier. It's the rule. A dashboard showing eight active apps tells you almost nothing about what a customer's browser is actually downloading. Execution footprint, not app count, is the real risk signal, and that's the entire thesis behind why app count is the wrong metric and what to check instead. Mobile CPUs suffer this disproportionately. Akamai's research ties a 100 millisecond delay in load time to a measurable conversion drop, and that math compounds fastest on the weaker processors most of your mobile traffic is actually running.

The mobile-desktop gap ghost scripts create is one of the clearest symptoms an audit can point to. If your mobile conversion rate sits well below desktop, that gap deserves the same waterfall investigation described above, and why your mobile CVR is half your desktop CVR walks through the diagnostic in full.

Get a Ghost Script Audit — Free

We check all three injection paths, ScriptTag API, App Embeds, and direct Liquid writes, and hand you an exact removal list with file sizes and revenue impact attached. Manual work. 48 hours. No automated scans.

Get My Free Ghost Script Audit →

Where This Fits in the Bigger Picture

Ghost script removal is rarely a one-and-done project. Every new app you install carries the same three-injection-path risk described above. If you don't want to run the Weekend Framework yourself on a recurring basis, this is exactly the discipline built into our Shopify Speed Optimization service: a full ScriptTag, App Embed, and theme audit before any performance work starts, so we're not compressing images on top of 847 kilobytes of code nobody's using anymore.

Baymard Institute puts average ecommerce cart abandonment above 70%. Some meaningful share of that isn't a pricing objection or a trust problem. It's a browser choking on a decade of accumulated dead code from apps nobody remembers installing. The dashboard tells you what Shopify knows. Only DevTools tells you what your customers are actually downloading.

Muhammad Usama
Article by

Muhammad Usama

Founder & Head Conversion Engineer

Founder & Head Conversion Engineer with 8+ years of technical engineering experience. I bridge the gap between full-stack development and e-commerce growth, specializing in tearing down Shopify architectures, eliminating code-level friction, and building high-performance infrastructure for 7- and 8-figure brands.

Every Second Costs You Conversions.

If your Shopify store takes longer than 2.5s to load, you are actively losing money on paid ads. Let us run a deep-dive performance profile to find the scripts and layout shifts tanking your CVR.

Get a Free Performance Audit →
Get Free Audit