Shopify app notes · 18

Debugging Your App

A method for finding out what is actually wrong — where each kind of code prints its errors, how to inspect an app hidden inside the Shopify admin, and a lookup table of the messages you will meet.

logs DevTools

The first question: where does this code run?

Beginners lose hours looking for logs in the wrong place. Your app's code runs in three different places, and each prints its output somewhere different.

CodeRuns onIts logs appear in
loader, action, *.server.js, jobsYour serverThe terminal running shopify app dev — or docker compose logs in production
Your React componentsThe merchant's browserThe browser's DevTools console
Webhook deliveries, extensionsShopifyshopify app logs and the Dev Dashboard
Trap — "my console.log does nothing"

You add console.log in a loader, open the browser console, and see nothing. It did print — in your terminal, because loaders run on the server.

This single misunderstanding causes more lost time than any other. Before hunting for output, ask: server or browser?


DevTools inside the Shopify admin

Your app is an iframe inside the admin, which changes how you use the browser's developer tools.

The console

By default the console shows the Shopify admin's page, not yours. At the top of the Console tab is a context dropdown, usually reading top. Switch it to your app's frame to see your own logs and run commands in your page.

The network tab

This shows every request your page makes — the most useful debugging view there is. React Router's data requests end in .data, so filter for that:

.data

Click a request to see what was sent and, crucially, what came back. A status of 401, 404 or 500 tells you immediately which layer failed, and the response body often contains the real error message.

The fastest diagnosis

When "saving does nothing", open the network tab and press save. No request at all → the problem is in the browser (a button, an event). A request with an error → the problem is on the server. That one observation halves the search.


A method that always works

  1. Reproduce it reliably

    Find the exact steps that cause the problem every time. A bug you cannot trigger on demand, you cannot confirm you fixed.

  2. Find which layer broke

    Browser, your server, the database, or Shopify? The network tab and the table above usually answer this in a minute.

  3. Read the actual error

    The whole message, not the first few words. In a stack trace, find the first line that points at your file rather than a library.

  4. Check your assumptions with real values

    Log what the code actually receives. The bug is almost always a value that is not what you assumed — a string instead of a number, undefined, a different shape.

  5. Change one thing, then test

    Several changes at once means you will not know which one fixed it — or which one broke something else.

  6. Confirm the original steps now work

    Repeat step 1 exactly. Then check you did not break anything nearby.


Before anything else: is this the code that is running?

A surprising share of "impossible" bugs are this. You are reading one version of the code, and a different one is running.

  • Right branch? git branch --show-current
  • Right config? shopify app info — dev app, or production?
  • Database client current? npx prisma generate after switching branches.
  • Server restarted after editing .env?
  • In production: was the container rebuilt (--build), or is it still the old image?
  • Settings change? Was shopify app deploy actually run?
  • Browser serving a cached page? Hard-refresh.

Thirty seconds on this list saves hours of debugging code that is not even running.


Logging that actually helps

// Useless — prints [object Object]
console.log("data: " + data);

// Useful — the whole structure, readable
console.log("[products loader] data:", JSON.stringify(data, null, 2));
  • Prefix every log with where it came from — [products loader]. With several logs flying past, you need to know which is which.
  • Log the whole object with JSON.stringify(x, null, 2), not string concatenation.
  • Log the full Shopify response when an API call misbehaves — the answer is usually in userErrors or a top-level errors array you were not reading.
  • Remove noisy debug logs before committing. Keep a few meaningful ones, such as job failures.
Trap — logging secrets

console.log(session) writes the shop's access token into your logs, which are often kept for weeks and seen by more people than your database.

Log ids and shop names — session.shop — never tokens, secrets, or customers' personal details.


Your debugging tools

ToolUse it to
npx prisma studioSee exactly what is in your database. Settles "did it save?" instantly.
shopify app graphiqlRun an Admin API query by hand and see the real response shape.
shopify app logsWatch webhook deliveries and extension activity live.
shopify app webhook triggerFire a webhook on demand instead of waiting for a real event.
Dev Dashboard monitoringSee whether Shopify sent a webhook, and how your app replied.
Dev Console (in a dev store's admin)See active previews, clean them up, open the app in the dashboard.
docker compose logs -fFollow your production server's output.

"It works on my machine"

Your dev setup is kinder than production in three ways, and each hides a class of bug:

Your machineProductionBug it hides
One shop installedMany shopsQueries missing shop — leaking data between shops (Note 04)
A handful of productsHundreds or thousandsMissing pagination; lists silently cut off (Note 06)
Fast connection, small filesSlow connections, large photosUpload timeouts and 401s (Note 08)

So test the way production behaves: a second dev store, demo data, a large image, and your browser's network throttling set to a slow connection.


Debugging in production

You cannot open the merchant's browser console, so:

  • Start with your server logs. Most production errors are server-side, and they are already recorded.
  • Ask for specifics. The exact steps, the time it happened, a screenshot of any error.
  • Reproduce on a dev store with similar data — often the difference is the size or shape of their catalogue.
  • Record errors where you can find them — a lastError column for jobs, logged failures with the shop name. You cannot debug what nobody wrote down.
Trap — fixing the symptom

Wrapping a failing call in a retry, or catching an error and returning success, makes the symptom disappear while the cause stays. The bug returns later, harder to trace.

Only add a retry once you understand why it fails and know that retrying is genuinely correct — as it is for rate limiting (Note 06).


Error lookup table

The messages you are most likely to meet, and where the explanation is.

You seeUsually meansNote
401 Unauthorized on a big uploadSession token expired during a slow request08
401 everywhereOpened outside the admin, or the shop uninstalled04
App Bridge … missing required configuration fields: shopOpened the app URL directly, not through the admin04
Unexpected token '<' … JSONYou fetched a page route and got HTML03
db.x is undefinedStale Prisma client02, 05
Data too long for columnURL in a short String — needs @db.Text05
Unique constraint failedDuplicate of a unique value — use upsert05
Record to update not foundupdate/delete matched nothing05
Save "succeeds" but nothing changesUnread userErrors06
Only some items show upMissing pagination, or picker truncation06, 09
Form fields arrive emptyMissing name, or Polaris fields not submitted manually07
Components render as blank spacePolaris script not loaded on that page09
Webhook fires repeatedlyHandler too slow, or returning an error10
Change not live in productionOnly one of the two deploys was done11
New permission does nothingMerchant has not approved the new scopes12
Paying merchant sent to pricing pageA failed subscription check treated as "unpaid"16
App embed does nothingEmbed not switched on by the merchant17

When you are truly stuck

  • Explain the problem out loud, step by step, to a colleague or even to nobody. You will often spot the wrong assumption mid-sentence.
  • Make the smallest version that still fails. Strip away everything unrelated until the bug has nowhere to hide.
  • Take a break. Genuinely. Fresh eyes find in five minutes what tired ones missed for an hour.
  • When you ask for help, include: what you expected, what happened instead, the exact error, and what you have already tried.

Cheat sheet

# where are my logs?
loader / action / jobs   → terminal (dev) · docker compose logs (prod)
components               → browser console — switch context to your frame
webhooks / extensions    → shopify app logs · Dev Dashboard

# first checks
network tab, filter ".data"      no request = browser · error = server
is this the code running?        branch · config · prisma generate · rebuild

# log well
console.log("[where]", JSON.stringify(x, null, 2))
never log tokens, secrets or personal data

# method
reproduce → find the layer → read the whole error
→ check real values → change one thing → confirm