DEV Community

seller-mind
seller-mind

Posted on

How I Built a Branded Shopify Tracking Widget From Scratch

When I first started helping a friend manage their growing Shopify store, I thought the hardest part was driving initial traffic and getting those first sales. I was wrong. The real challenge started after the checkout screen.

About a week after launch, the orders started rolling in. And right behind them came the emails. "Where is my order?" "Did you ship my stuff yet?" "Why is my tracking not updating?"

I was spending hours every day copy-pasting tracking numbers and explaining carrier delays. It was a massive drain on time, and honestly, it felt like a customer service black hole. I realized that the post-purchase experience was completely broken. So I decided to build something.

The Problem with Post-Purchase Silence

In e-commerce, "WISMO" (Where Is My Order) is the most common support query. But it’s more than just a nuisance; it’s a symptom of post-purchase anxiety.

When a customer hands over their credit card, they enter a waiting phase. If your store goes completely silent after checkout, they start to worry. Did the transaction go through? Did the store actually ship it?

The cost of ignoring this phase is high. First, it drains your support resources. If you are answering dozens of WISMO emails a day, that’s hours lost. Second, it fragments your brand. If you just send a plain-text email with a link to the USPS or FedEx website, the customer leaves your ecosystem. They land on a generic, ad-filled carrier page that has nothing to do with your brand. Finally, it hurts retention. A stressful post-purchase experience makes customers hesitant to buy from you again.

The Technical Approach

To fix this, I needed a way to keep the customer engaged on a branded tracking page without requiring them to dig through emails. The goal was to build a customizable tracking widget that matched the store's theme and updated in real-time.

The biggest technical decision was how to inject this into Shopify. I could have modified the theme code directly, but that’s fragile. Every time the merchant updates their theme, the custom code breaks. Instead, I opted for Shopify App Embed blocks. This allows the widget to be injected dynamically without touching the core theme files.

Here is a simplified look at the Liquid and JavaScript used to render the widget on the order status page:

{% comment %}
  Renders shipment tracking widget on order status pages
{% endcomment %}

<div id="parcelglance-widget" 
     data-order-id="{{ order.id }}"
     data-order-number="{{ order.name }}"
     data-store-name="{{ shop.name }}"
     style="min-height: 200px;">
</div>

<script>
(function() {
  const script = document.createElement('script');
  script.src = 'https://cdn.parcelglance.com/widget.js';
  script.async = true;
  script.onload = function() {
    Parcelglance.init({
      orderId: document.getElementById('parcelglance-widget').dataset.orderId,
      orderNumber: document.getElementById('parcelglance-widget').dataset.orderNumber,
      storeName: document.getElementById('parcelglance-widget').dataset.storeName,
      container: 'parcelglance-widget',
    });
  };
  document.head.appendChild(script);
})();
</script>
Enter fullscreen mode Exit fullscreen mode

Let's break down the architecture here. First, we use Liquid to pass the order.id, order.name, and shop.name into data attributes on a container div. This keeps the order data cleanly scoped to the DOM element rather than polluting the global JavaScript scope.

Next, we use an Immediately Invoked Function Expression (IIFE) to load the tracking script. Notice that script.async = true; is set. This is crucial. We don't want the tracking widget to block the rendering of the rest of the order confirmation page. By appending the script to the document.head dynamically, we ensure the main thread stays free.

Once the script loads, the onload callback initializes the widget. The Parcelglance.init function takes the container ID and the order details, fetches the real-time tracking status from the backend, and renders the UI.

A major trade-off I had to navigate was how to fetch the tracking data. Initially, I tried to poll the carrier APIs directly from the client-side widget. This was a terrible idea. It exposed API keys, hit rate limits instantly, and caused CORS headaches. I pivoted to a server-side webhook approach. When Shopify fires an order fulfillment webhook, my backend securely verifies the HMAC signature, parses the tracking number, and stores it. The client-side widget then just asks my backend for the status, keeping all carrier API interactions secure and server-side.

Practical Results

Implementing this approach changed the post-purchase dynamic completely. What worked really well was keeping the customer on a branded page. Because the widget inherited the store's fonts and colors, it felt like a natural extension of the checkout, not a jarring redirect.

What surprised me was how much just providing visibility reduced anxiety. Even if a package was delayed, customers who could see the real-time status on a branded page were much more patient. They didn't feel the need to email support just to confirm the order was still moving.

If I were to do it again, I would spend more time on the initial theme matching. Making the widget look exactly like the store's native UI takes a bit of CSS tweaking, but it's worth the effort. I also learned to heavily rely on Shopify's App Embed blocks rather than trying to be clever with theme hooks. It made the installation process infinitely easier for merchants.

Wrapping Up

If you are running a Shopify store and drowning in WISMO tickets, you don't need to build this from scratch. I packaged the core functionality of this approach into a free tool called Parcelglance.

It adds a branded, real-time tracking widget to your store using App Embed blocks, keeping your customers informed and your support inbox quiet. You can check it out and install the free Shipment Tracking Widget at parcelglance.com.


Full disclosure: I'm the developer of Parcelglance. I built this because I needed it myself.

Top comments (0)