Shopify Sidekick 'Tool Not Found'? How to Win the App Bridge Timing Race
Ever hit that frustrating "tool not found" error when you're building a Shopify app, especially with the newer App Bridge features like Sidekick? It's a head-scratcher, right? You've registered your tool, the logs say it's there, but Sidekick just isn't seeing it. We recently saw a great discussion in the Shopify community about exactly this, and the insights shared are golden for any developer grappling with these kinds of timing issues.
Our friend blueliner kicked off the conversation, facing a classic race condition. Their app uses sidekick-import with admin.app.intent.link. The scenario? A merchant is in another embedded app (like an eBay Importer), triggers an intent, which causes a full-page navigation to blueliner's app. Sidekick immediately calls the registered tool (preview_amazon_product), but BAM! "Tool not found."
The Smoking Gun: A Timing Race
The key piece of information blueliner provided was that their tool registration was happening around 2380ms from page load. This, as Mindaugas_LM pointed out, was the "smoking gun."
What does that mean? It's a classic timing race.
When you navigate to a different embedded app, and then an intent redirects to yours, your app reloads from scratch. Sidekick, being eager, dispatches the tool call almost immediately. But your app, with its bundle downloading, parsing, and evaluating, takes time to boot up. Even if your shopify.tools.register(...) call is at the module level, before React mounts, that entire JavaScript bundle needs to process first. Those 2380 milliseconds are your app's bundle parse/eval time, creating a window where Sidekick calls for a tool that simply hasn't been registered yet.
This explains why it worked perfectly when the merchant was already on a native Shopify Admin page or already inside blueliner's app. In those cases, there's either more "slack" before the tool is invoked, or the app context is already loaded, preventing the race.
Community-Driven Diagnosis: Early Registration is Key
Mindaugas_LM offered some excellent initial troubleshooting steps, which are always good to keep in mind for these kinds of problems:
- Register as early as physically possible: Move the
registercall to the very top of your entry file, right after App Bridge is available – before React/Vue mounts or any async operations. - Ensure App Bridge loads first: Use the Shopify-hosted CDN script for App Bridge in your
, not a bundled/deferred version. This ensuresshopifyis available synchronously. - App-wide Registration: Make sure your tool registration code runs on any landing route, especially deep-linked ones. Register tools globally, not per-page.
- Decouple "Registered" from "Ready": If your tool needs data or context that loads asynchronously, register a thin wrapper immediately. The actual handler can then
awaitits dependencies. This way, the tool exists at call time even if it's not fully "ready" to perform its work yet. - Sanity-check the name string: Double-check that the tool name (e.g.,
preview_amazon_product) matches exactly between your registration and the intent call.
Blueliner confirmed their setup followed many of these best practices: App Bridge from CDN in , and tool registration at the top of their index.js file, module level, before React mounted. Yet, the problem persisted on cross-app navigation, confirming the 2.38-second delay was indeed the culprit.
import {previewProductApi} from "./api/api";
import store from "./store";
import {previewPopupChange} from "./store/action-creator";
// Runs at module load — before React render
if (typeof shopify !== 'undefined' && shopify.tools) {
shopify.tools.register('preview_amazon_product', async (input) => {
const result = await previewProductApi(input.amazonProductUrl);
// ... dispatch to Redux store
return {ok: true, title: result.title};
});
}
This code snippet shows exactly where blueliner was registering their tool. While seemingly early, it was still part of the main bundle, subject to that ~2.38-second load time.
The Solution: Inline Registration in Your
The definitive fix, as proposed by Mindaugas_LM, is to move the tool registration outside your main JavaScript bundle entirely. This means placing a tiny inline
Add the inline registration script: Immediately after the App Bridge CDN script, insert a new
This elegant solution decouples "tool exists" from "tool is ready to work." The window.__appReady Promise acts as a buffer. If Sidekick calls your tool before your main app bundle has fully loaded, the handler will simply await this Promise, holding the call until your app is ready to process it, instead of dropping it with a "tool not found" error.
Resolve the Promise in your main app bundle: In your main JavaScript bundle (e.g., index.js), once your Redux store, API, and other core app components are fully initialized and ready to handle requests, resolve the Promise. You'll pass an object containing the actual function that performs the tool's work.
For example, if your app has a method previewAmazonProduct that does the heavy lifting, you'd call:
// ... once Redux/api are ready in your main bundle ...
window.__resolveAppReady({ previewAmazonProduct: yourAppInstance.previewAmazonProduct });
Make sure yourAppInstance.previewAmazonProduct is the function that actually contains the logic you want to execute, replacing the placeholder app.previewAmazonProduct(input) from the inline script.
Diagnostic Check (Optional but Recommended): To truly confirm the fix, Mindaugas_LM suggested adding high-resolution timestamps (performance.now()) at the inline register line and when your handler actually fires. This helps you verify that registration indeed happens much earlier.
What if it Still Fails?
If, even with sub-100ms registration in your , the tool still fails on the cross-app path, then it's likely not a timing issue. It points to a "session/instance mismatch." This means the App Bridge context Sidekick is calling into after the cross-app hop isn't the same one your fresh page registered on. In this rare case, it's a platform-level behavior, and you'd want to take that reproduction straight to Shopify Partner support or file a dev-platform bug report.
This community discussion really highlights the nuances of working with modern app development on platforms like Shopify. While it might seem complex, understanding these timing races and how to manage them, especially with crucial tools like Sidekick, is vital. Implementing this early, decoupled registration strategy can save you a lot of headaches and ensure a smooth experience for your merchants, no matter how they interact with your app. Keep experimenting, and don't hesitate to dive into the community when you hit a wall – there's always someone with an insightful perspective!