Guide to configuring Sentry in Shopify Checkout UI extensions

Nadine Thery
UmamiTech Blog
5 min readMay 23, 2023

--

UI extensions run inside Webworkers, making the Sentry installation a bit different than a regular web project.

Shopify gives us some insights about Error Handling for checkout extensions, making Sentry the recommended one. And that’s actually, what we use within all our apps and webs, here at Umamicart.com.

The snippet Shopify provides it’s ok to jump-start, but will soon find out that errors captured in Sentry lack meaningful information unless you properly and specifically configure the context.

Basic project and package installation

1. Set up a project in Sentry

Let’s assume you already have a Sentry account. Login into your account > projects and create a new one.

In our case, we are building with React, so we selected a Browser with React project.

Select a Browser project with React or the technology you used

You will be directed to a website with some instructions for installing. You will need the DSN number. If you missed this page, you will able to find this number in the project settings later.

2. Install Sentry Browser in your app

Go back to your app’s code, and install the Sentry Browser’s package.

Yarn: yarn add @sentry/browser

Npm: npm i @sentry/browser

3. Initialize Sentry in each UI extension

According to the docs, you need to initialize Sentry inside each extension in order to make Sentry start tracking whenever any of them is running at checkout.

import * as Sentry from "@sentry/browser";

Sentry.init({
dsn: "YourDSNKey",
defaultIntegrations: false
});
  1. Import the package
  2. Initialize the project with Sentry.init()
  3. Add your DSN key (see the previous step), and don’t forget to add the property defaultIntegrations: false . Otherwise, it probably won’t work. And you will get an error like this one:
Uncaught (in promise) DOMException: Failed to execute ‘importScripts’ on ‘WorkerGlobalScope’: The script at…

At this point, you could leave it like that and Sentry would capture the unhandled errors. But I highly recommend handling the errors from API responses separately with more context information. Same as other errors.

⚠️Your extension needs to have network access in order to be able to use Sentry. Don’t forget to enable that in your Partner’s Dashboard and also add it to your extension’s TOML file under capabilities.

type = "checkout_ui_extension"
name = "Umamipoints Widget"

extension_points = [
'Checkout::CartLines::RenderAfter'
]

[capabilities]
api_access = true
network_access = true

Disabling default integrations?

Disabling all these, as Shopify’s docs state, implies that there is some info that you won’t receive unless you specify so. If you feel curious, check out what the defaultIntegrations include in Sentry’s config.

One of the things you are disabling is the default capturing of the exceptions error and unhandledRejection . That’s why you need to specifically control them (see next step).

4. Add event listeners for the exceptions.

import * as Sentry from "@sentry/browser";

Sentry.init({
dsn: "YourDSNKey",
defaultIntegrations: false
});

// For unhandled promise rejections
self.addEventListener("unhandledrejection", (error) => {
Sentry.captureException(new Error(error.reason.stack));
});

// For other exceptions
self.addEventListener("error", (error) => {
Sentry.captureException(new Error(error.reason.stack));
});

// The rest of the app code goes below this.

The unhandledrejection and error event listeners are native listeners to the Window Web API events associated with errors. By specifically listening to them and sending the error to Sentry with Sentry.captureException we will be sending all the tracing we need along with the scope.

Add context to the Sentry errors

At this point, you will be probably receiving your exceptions, but information such as the user agent, device, environment, or user information won’t be arriving to your errors, differently from what you usually receive in a regular Sentry exception in any project.

Let’s see how to add some additional information

Set user agent information (browser, os, IP address…)

The defaultIntegrations we set to false in the Sentry.init() disables also the HttpContext() integration, which is responsible for collecting all the information related to specific OS, browser, and version information.

To enable this, you need to initiate a new instance of the integration inside the Sentry.init() inside the integrations property.

Sentry.init({
dsn: "yourDSNNumber",
defaultIntegrations: false,
integrations: [new Sentry.Integrations.HttpContext()],
environment: "production",
initialScope: {
tags: { appName: "yourAppName" },
},
});

Set user info

In order to identify the user, you can use useCustomer() hook in order to retrieve the id and the email, if you need them.

Then, inside your app (otherwise you cannot use the hook), set the user in Sentry, wrapped in a UseEffect().

const userInfo = useCustomer();

useEffect(() => {
if (!userInfo?.id) {
return;
}

Sentry.setUser({ id: userInfo.id, email: userInfo?.email });

}, [userInfo?.id]);

Set the environment

If you are running these extensions in different stores, like development, and production, you can set the environment tag dynamically by using the shop URL as an identificator. You can make use of the useShop() hook provided by Shopify.

  const shop = useShop();
let environment = "development";

// Sentry config after init
useEffect(() => {
if (shop?.myshopifyDomain) {
if (shop.myshopifyDomain.includes("umamicart.com")) {
environment = "production";
} else if (shop.myshopifyDomain.includes("umamicart.store")) {
environment = "test";
}
}

Sentry.configureScope((scope) =>
scope.addEventProcessor(
(event) =>
new Promise((resolve) =>
resolve({
...event,
environment,
})
)
)
);
}, [shop]);

In the above snippet, we are configuring the Sentry scope by adding the environment variable after Sentry has initiated.

If you don’t need this, Sentry will automatically set the production tag. Or you can define it manually in the Sentry.init().

Sentry.init({
dsn: "yourDSNNumber",
defaultIntegrations: false,
environment: "production"
});

Additional error info

When you are inside your app, you can still send extra information about the exception or the error, by adding it to the scope with the setContext. For example:

 fetch(customInfoService)
.then((response) => {...}
})
.catch((e) => {
Sentry.captureException(
new Error("Umamipoints fetch try fail"),
(scope) => {
scope.setContext("error", e);
return scope;
}
);
})
.finally(() => {
setInitialLoading(false);
});

With these additional configurations, your exceptions should contain more meaningful information. Let me know in the comments if you find out more configs or if something needs to be reviewed!

Peace & Code ✌️

Nadine

--

--

Nadine Thery
UmamiTech Blog

Frontend Developer 👩🏻‍💻, videogamer 🎮, boardgamer 🎲, plant-lady 🌿, mother-of-cat-and-dogs 🐱🐶🐶, environment-concerned🌎, youtuber, ocassional podcaster