A store I audited last spring had eleven apps showing as installed in the admin. The theme grep returned references to nineteen vendors. Eight of them had been uninstalled, some more than once. The product page was loading scripts from CDNs that belonged to apps the marketing team had no memory of trialing.
That gap is what this checklist closes. Shopify's Uninstall button revokes an OAuth token and stops billing. It does not touch the theme, the metafield catalog, the customer-account JSON templates, or the webhook registry. Each of those is a category where a five-second uninstall leaves residue. After eighteen months of app churn, the residue compounds.
The 6-category checklist
Run this in order. Pre-flight first, then the six categories. The whole pass takes me about forty-five minutes per app on a moderately clean theme, longer if the app has been installed and uninstalled multiple times.
Pre-flight (five minutes, before clicking Uninstall):
- Screenshot every active App embed in the theme customizer. The admin loses this list the moment the app is gone.
- Note the vendor's CDN domain. Inspect a PDP, find the scripts loading from the vendor, write the domain on a sticky note. You will grep for it later.
- Export anything the app owns: review content, customer notes, segment definitions, custom metafield values. Most vendors offer a CSV export inside their dashboard. After the uninstall, that dashboard is read-only or gone.
- List the vendor's metafield namespace. Admin → Settings → Custom data shows app-owned namespaces if the app declared them via the Metafields API.
The six categories:
- Scripts (hardcoded
<script>tags inlayout/theme.liquid, plus Web Pixels API entries) - Theme blocks and sections (app embed blocks, section overrides, block schema additions)
- Snippets and assets (
snippets/app-*.liquid, orphan JS, CSS, fonts inassets/) - Metafields (app-owned namespaces and the values still attached to products, customers, orders)
- Customer-account templates (
customer.json,customers/account.json, login overrides, portal links) - Webhooks and notifications (Admin → Notifications, subscription topics in the app's former scope)
Each category has its own grep pattern and its own removal step. Most apps touch three or four of the six. Reviews apps and loyalty apps touch all six.

Why each category matters
Scripts
The orphans here are the expensive ones. A hardcoded <script src="https://cdn.vendor-foo.com/widget.js"> in theme.liquid keeps loading on every page until you delete the line. Browser caches help repeat visitors, but every new visitor pays the round trip. I have measured 180kb of orphan vendor JavaScript on a single PDP that the operator had no idea was loading.
Web Pixels API entries are the second flavor. Apps that registered a customer-pixel through the Pixels API can keep firing inside the Web Pixels sandbox even after the app is gone if the pixel definition was not removed. Admin → Customer events shows what is registered. Anything pointing at an uninstalled vendor should be deleted manually.
Theme blocks and sections
App embed blocks are the cleanest case. If you turned them off in the customizer before uninstalling (the pre-flight step), they stop rendering. If you forgot, the theme still tries to render them and silently no-ops, which costs you nothing visible but leaves dead code paths in the section schema.
Section overrides are messier. Some apps modify your existing sections by injecting their CSS class names or data attributes directly into the Liquid. A reviews app might add <div class="reviews-vendor-widget" data-product-id="..."> inside sections/main-product.liquid. The block keeps trying to mount, the JavaScript that animated it is gone, and you end up with empty wrappers shifting layout on first paint.
Snippets and assets
The app's snippet files are the easiest to find and the safest to delete once you confirm nothing references them. The trap is naming. Some vendors prefix every file with their brand (snippets/judge-me-widget.liquid). Others use generic names (snippets/reviews.liquid) that you might confuse with a snippet you wrote yourself. Read the file before deleting, every time.
Orphan JS in assets/ is harmless until something accidentally references it. Orphan CSS is also harmless, just dead weight. Fonts are the trap: some apps upload them with names like assets/Montserrat-Regular.woff2, and your own theme might be loading the exact same font from the exact same path. Confirm the file's MD5 matches what you expect before deleting.
Metafields
This category is the most often missed. When an app installs, it can register metafield definitions on the products, customers, orders, or store-level metafield resources. The values written into those metafields persist after uninstall. The definitions also persist.
The harm is two-sided. The metafield values are still queryable through the Storefront API and the Admin API, so any code reading them keeps reading. The schema also takes up space in your metafield catalog, and if you ever want to use that namespace yourself, the orphan blocks you. The Shopify metafield schema migration playbook covers the additive-only rule that protects you from this on the deliberate side. The cleanup side, for orphans, has no playbook from Shopify. You have to find them and delete them.
Customer-account templates
This is the category that surprises operators when I show it to them. Many subscription apps, loyalty apps, and review apps modify the customer account portal by editing templates/customers/account.json, templates/customers/account.liquid, or the newer customer.json templates. They add a section that links to their hosted portal, embeds an iframe, or injects a script that fetches the customer's loyalty balance.
When the app is uninstalled, the section is still there. It tries to mount, the underlying API is dead, and your customer sees a broken loyalty card or a redirect loop into a vendor portal that returns 404. I have seen customer-account pages that took eighteen seconds to render because three orphan iframes were waiting on dead endpoints.
Webhooks and notifications
Webhooks are the cheapest to clean and the easiest to forget. Shopify will keep firing webhooks at the URL the app registered until something explicitly removes the subscription. The endpoint is dead, the webhook fails, Shopify retries, the merchant's app log fills up, and nobody notices until the log volume becomes a billing line.
Notification webhooks (the kind you set in Admin → Settings → Notifications) are merchant-managed and obvious. Subscription webhooks created through the Webhooks API by the app are less obvious. The Admin GraphQL webhookSubscriptions query lists them. Anything pointing at the uninstalled vendor's domain gets deleted.

The grep-and-remove ritual
One pattern, run six times per app. I do this in a theme branch, never against the live theme.
# 1. Get the theme locally
shopify theme pull --store mystore.myshopify.com --theme [theme-id]
# 2. Define the vendor signature once
VENDOR="judgeme" # or whatever you grepped from the script tag
VENDOR_CDN="cdn.judge.me" # the domain you noted in pre-flight
# 3. Grep across the whole theme for both signals
rg -i "$VENDOR" .
rg -i "$VENDOR_CDN" .
# 4. For each category, narrow the search
rg -n "<script" layout/theme.liquid # category 1: scripts
rg -i "$VENDOR" sections/ blocks/ # category 2: blocks
ls snippets/ | grep -i "$VENDOR" # category 3: snippets
ls assets/ | grep -iE "$VENDOR|\.woff|\.woff2" # category 3: assets
rg -i "$VENDOR" templates/customers/ # category 5: customer
Categories 4 and 6 are not in the theme repo. Metafield definitions are in Admin → Settings → Custom data, or via the Admin GraphQL metafieldDefinitions query. Webhook subscriptions are in Admin → Notifications, plus the GraphQL webhookSubscriptions query for app-created subscriptions.
Confirm before deleting, every time. Run the inverse grep against your templates/, sections/, layout/, and snippets/ to make sure nothing live still references the file you are about to remove. The deletion itself is one line; the confirmation is what saves you from a broken cart on a Tuesday afternoon.
# Before deleting snippets/judge-me-widget.liquid:
rg -F "render 'judge-me-widget'" .
rg -F "include 'judge-me-widget'" .
# Both empty? Safe to delete.
Commit the cleanup as its own theme version with a descriptive message. If something breaks, you can roll back to the previous version in seconds. If the cleanup is mixed in with a feature commit, the rollback is painful.
Why uninstall-then-reinstall accumulates
This is the pattern that drives the eighteen-month residue. Each install writes new artifacts. The uninstall removes none of them. The next install of the same app writes more.
I worked on a store where the same reviews app had been installed three times over twenty months. Different team members each tried it, decided it wasn't right, uninstalled it, then a year later someone else tried it again. The vendor had renamed their snippet between version 1 and version 2 of the app, and again between version 2 and version 3. The theme had three different snippets/<vendor>-widget.liquid files, two of them with broken Liquid syntax that referenced a Liquid object the vendor had since deprecated. None of them were live. All three were loading. The newest one rendered the widget. The two older ones each made a 404 fetch against the vendor's CDN on every PDP.
The compounding effect is worst on apps that change names, change hosting domains, or change snippet conventions between major versions. Reviews apps and subscriptions apps are the worst offenders because they tend to ship breaking changes. Tracking apps are usually cleaner because their footprint is just a script tag.
If you reinstall an app you previously uninstalled, run the cleanup audit before the reinstall. Do not skip this step. The reinstall will not reset what the previous uninstall left behind.

What happens if you skip the audit
Four flavors of cost, in roughly the order they hurt.
Page weight creep. Orphan scripts add up. I have seen 400kb of orphan JS on a PDP, accumulated over eighteen months of app churn. The operator's Core Web Vitals had been drifting for a year and nobody could explain why. The audit traced it to seven uninstalled apps still loading scripts. This is the same accumulation pattern I describe in the Shopify app page weight audit, where the cleanup pass usually finds more dead weight than the live weight from currently-installed apps.
Security surface. A script from an uninstalled vendor is a script from a vendor you no longer have a contract with. If their CDN gets compromised or their domain expires and is bought by an attacker, that attacker now executes arbitrary code on every page load on your store. The exposure is silent. You will not know until something happens.
Compliance drift. Consent management vendors apply consent flags to the apps you tell them about. Your active app list is what you tell them about. The orphans are off the consent radar. An EU visitor lands on a PDP, the orphan tracker fires before the consent banner resolves, and you are out of compliance without realizing it.
Analytics noise. Orphan tracking pixels still fire. They might be firing into a vendor account you no longer pay for, which means the data goes nowhere useful, or they might be double-counting events your active stack is already capturing. Either way, the data quality drops by an amount you cannot easily measure without doing the audit you have been avoiding.
For the broader uninstall workflow, the theme cleanup checklist after app removal covers the per-app steps. This article is the audit pattern that runs across the whole store, every time, after every app comes out. For where uninstall hygiene sits in the larger picture of running a clean app stack, see the Shopify app stack decisions hub.
This is one of the modules in the DTC Stack Audit that finds the most surprises. Operators expect to discuss their live apps. They are usually unprepared for the conversation about the apps they thought were gone.
“A script from an uninstalled vendor is a script from a vendor you no longer have a contract with. The exposure is silent until it isn't.
”
How is this different from the per-app uninstall checklist?
The per-app checklist tells you what to remove for one specific app you just uninstalled. This audit assumes you have a backlog of uninstalls that were never cleaned up. The grep pattern works across the whole theme catalog and finds residue you don't remember leaving. Run the audit at least once a year, more often if the app stack has been churning.
Can I automate the audit?
Partially. A script can run the rg patterns and produce a report. The deletion step still needs a human, because confirming "nothing live references this file" is judgment work. I have seen automated cleanup tools delete a file that a custom theme template was actively rendering, and the cart broke for two hours. The grep is automatable. The remove is not.
What about apps that don't write to the theme at all?
Some apps run entirely as embedded admin apps and never touch the theme. Those are safe to uninstall without the theme audit. The catch is metafields and webhooks, which are not in the theme. Even an admin-only app can leave metafield definitions and webhook subscriptions behind. Categories 4 and 6 of the checklist still apply.
How do I find an app's metafield namespace if the app is already gone?
Admin → Settings → Custom data lists every metafield definition on every resource type. Filter by namespace. Anything you don't recognize is a candidate. You can also query the Admin GraphQL metafieldDefinitions endpoint and filter by ownerType to find definitions on products, customers, orders, and so on. The vendor's old documentation usually lists the namespaces they used; check the Wayback Machine if their site is gone.
Should I clean orphan webhooks even if they don't break anything?
Yes. Orphan webhooks fire against dead URLs and accumulate retry traffic. Shopify's webhook system will keep retrying for up to 48 hours per failed delivery before giving up. Multiplied by every order, every customer update, every product change, the volume adds up. It also pollutes your app's debug logs in a way that makes finding real failures harder.
What if the theme has been edited by multiple developers over years?
Then the audit is more important and more time-consuming. Block out a half day, work in a theme branch, and document every removal in the commit message. If you can't tell whether a snippet was written by an uninstalled app or by a developer who has since left the team, leave it for one pass and grep for it again next quarter. The repeat audit will tell you whether anyone actually uses it.
Sources and specifics
- Pattern observed across mid-market DTC Shopify stores with 18+ months of app churn, audited 2024 through 2026.
- The 45-minute-per-app figure reflects a moderately clean theme; complex themes with long edit histories run longer.
- The 400kb orphan JavaScript figure is from a single PDP audit; typical residue is 80 to 250kb across two or three uninstalled apps.
- Metafield definitions persist via the Shopify Admin Metafields API; uninstall does not invoke the deletion endpoint for app-owned definitions.
- Webhook retry behavior documented in Shopify's Admin API reference: failed webhook deliveries retry for up to 48 hours before being abandoned.
- For the per-app uninstall steps, see the theme cleanup checklist after app removal. For page weight context, see the Shopify app page weight audit. The full audit is packaged in the DTC Stack Audit product.
