> ## Content Index
> Fetch the complete content index at: https://www.bulkifiedthinking.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Use Third-Party JavaScript Code With Lightning Web Components
- URL: https://www.bulkifiedthinking.com/use-third-party-javascript-code-with-lightning-web-components/
- Published: 2026-07-19T17:10:50.000Z
- Updated: 2026-08-16T23:04:09.000Z
- Description: Learn how to use third-party JavaScript libraries and components in Salesforce LWC, integrate them correctly, and solve common compatibility, security, and styling challenges.
- Author: Andrii Sukhetskyi
- Tags: Developers, JavaScript, Favorites

Just to make things clear, Salesforce provides an excellent guides titled [Use Third-Party JavaScript Libraries](https://developer.salesforce.com/docs/platform/lwc/guide/js-third-party-library.html?ref=bulkifiedthinking.com) and [Use Third-Party Web Components in LWC](https://developer.salesforce.com/docs/platform/lwc/guide/create-use-third-party-components.html?ref=bulkifiedthinking.com), which covers everything thoroughly. In 80% of cases, this information is enough to get started, but if you’re in those 20% who can’t get it to work — let’s dig in, my fellow Salesforce Developers!

The information provided was compiled during the [Lightning Web Development workshop](https://www.youtube.com/playlist?list=PLTyqynuY2hfljsP4KcY7KgT2X6bC0vU0%5F&ref=bulkifiedthinking.com) and offers detailed guidance on how to get third-party JavaScript code working with your Salesforce org.

---

## Types of Third-Party JavaScript Code

To begin, let’s distinguish clearly between the two types of JavaScript code that you can use with your Lightning Web Components.

### **Third-Party Web Components**

This is a JavaScript file that defines a class extending the native HTMLElement class. This class is responsible for creating a custom HTML element and provides a visible User Interface (UI) element.

It could be something familiar, like a datatable, developed outside of Salesforce. You upload it as a static resource and use it within your LWC. This same datatable might include features like lazy-loading and corresponding data handling. See an example below:

```javascript
class MyCustomElement extends HTMLElement {
  constructor() {
    // Always call super first in constructor
    super();

    // Create a shadow root
    this.shadowRoot = this.attachShadow({ mode: "open" });
  }

  connectedCallback() {
    // Element is added
    this.shadowRoot.innerHTML = "<div></div>";
  }
  
  // ...
}

// Registers the element
customElements.define("my-custom-element", MyCustomElement);
```

### **Third-Party JavaScript Libraries**

This is a JavaScript file that provides reusable functions or objects. It’s not designed to represent visual elements directly, like HTML tags. Instead, it’s more like a “utils.js”, you call its functions when you need them. Some libraries may create visual output, like charts or animations, but they do so programmatically and don’t define custom HTML elements. For instance:

```javascript
(function (global) {
    function Chart(ctx, config) {
        // Very simplified chart renderer
        ctx.fillStyle = config.data.datasets[0].backgroundColor[0];
        ctx.fillRect(10, 10, 100, 100);
    }

    global.Chart = Chart;
})(window);
```

## Why Isn’t My Third-Party Web Component Working?

> Lightning Web Security (LWS) must be enabled in the Salesforce org because Lightning Locker doesn’t support third-party web components.

Let’s say you found the perfect component on [webcomponents.org](https://www.webcomponents.org/?ref=bulkifiedthinking.com), added it to your static resources, imported it into your Lightning Web Component using [loadScript](https://developer.salesforce.com/docs/component-library/bundle/lightning-platform-resource-loader/documentation?ref=bulkifiedthinking.com), and used the lwc:external attribute in your markup to indicate that it’s an external web component. And yet, it doesn’t work as expected.

```javascript
import { LightningElement } from 'lwc';
import yourstaticresource from '@salesforce/resourceUrl/webcomponent';
import { loadScript } from 'lightning/platformResourceLoader';

export default class MyComponent extends LightningElement {
    componentLoaded = false;

    renderedCallback() {
        if (!this.componentLoaded) {
          this.componentLoaded = true;
          loadScript(this, yourstaticresource)
            .then(() => {
              console.log('Custom web component loaded');
            })
            .catch(error => {
              console.error('Failed to load web component:', error);
            });
        }
    }
}
```

```javascript
<template>
    <your-web-component lwc:external></your-web-component>
</template>
```

In most cases, you’ll need to modify the code of the web component itself to make it work. So, let’s look at the most common problems:

### Open Shadow DOM

You’ve probably heard that one of the “favorite things” among Salesforce Developers is the [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web%5Fcomponents/Using%5Fshadow%5FDOM?ref=bulkifiedthinking.com). This Shadow DOM can operate in two modes: *open* and *closed.*

Now, here’s the crucial part, your 3rd-party web component **CANNOT** use the open Shadow DOM, and if it does — expect it to behave incorrectly in Salesforce.

But what does that actually mean?

An open Shadow DOM allows external sources to access the component’s shadow tree via the `shadowRoot` property on the element. In contrast, a closed Shadow DOM prevents such direct access, keeping the internal structure fully encapsulated.

Salesforce enforces the use of the closed shadow mode. To ensure compatibility, open the source code of your Web Component and locate the following line:

```javascript
class MyCustomElement extends HTMLElement {
  constructor() {
    // ...

    // Create an open shadow root
    this.shadowRoot = this.attachShadow({ mode: "open" });
  }

// ...
```

Your task is to replace the `mode: "open”` attribute with `mode: "closed”`

Next, identify all occurrences of the `shadowRoot` property in the code, including the initial assignment line, and replace them with `_shadow` (or any other custom name, but not `shadowRoot`) as demonstrated below:

```javascript
❌ this.shadowRoot = this.attachShadow({ mode: 'open' });
❌ this.shadowRoot.innerHTML = `<p>Hello from shadow DOM</p>`;

✅ this._shadow = this.attachShadow({ mode: 'closed' });
✅ this._shadow.innerHTML = `<p>Hello from CLOSED shadow DOM</p>`;
```

### Global Access Is Restricted

If a 3rd-party web component uses the global `document` object to retrieve an existing element by ID (note: creating a new element is acceptable), this can and will lead to unexpected behavior.

To resolve this issue, open the source code of the web component and locate all instances where an element is queried by ID. For example:

```javascript
document.getElementById('some-id');
```

There are many possible approaches here, but one of the simplest, and my preferred approach, is to use the component’s Shadow DOM instead of accessing the global document object. For instance:

```javascript
class MyCustomElement extends HTMLElement {
  constructor() {
      this._shadow = this.attachShadow({ mode: 'closed' });
  }

  someFunction() {
      ❌ document.getElementById('some-id');
      ✅ this._shadow.getElementById('some-id');
  }

// ...
```

### Dependencies or Modules

If you’ve reached this point and your web component still doesn’t work, the most likely issue is that the component uses or relies on external dependencies or ES modules. **What does this mean?**

For example, suppose your Web Component includes the following line of code:

```
<script type="module" src="https://cdn.example.com/button-component.js"></script>
```

What’s actually happening here is that the Web Component is attempting to load a JavaScript module directly from an external source, a resource that hasn’t been uploaded as a static resource in your Salesforce org. The rule is simple: **only scripts that have been added as static resources and loaded using `loadScript()`** can be used in Lightning Web Components (LWC). Anything else, such as external `<script type="module">` tags, won't work.

A similar issue occurs when trying to use Node.js packages or modules that have been imported directly into a web component. While this is not a common use case, since web components are generally expected to be self-contained, it still happens. For instance:

```javascript
import someLibrary from 'some-npm-lib';
```

**So, what’s the solution?**

Admittedly, this can be a bit tricky — but never say never.

These libraries often consist of multiple interdependent JavaScript files, so the only solution is to bundle them into a single JS file. In other words, analyze the dependencies and references, then compile everything into one standalone JavaScript file.

You can do this manually or use dedicated tools like [Browserify](https://browserify.org/?ref=bulkifiedthinking.com) or [Webpack](https://webpack.js.org/?ref=bulkifiedthinking.com), and I highly recommend the latter.

Since the process can be a bit time-consuming, [I’ve prepared a detailed tutorial walking you through every step, check it out.](https://youtu.be/hHpw1Dzb3d8?ref=bulkifiedthinking.com)

In the meantime, here are the basic commands you can use with Browserify:

```
# Note: Chart.js was taken as an example only

# 1. Install Browserify as a local development dependency
npm install browserify

# 2. This is just a path to Chart.js’s package.json file — to find the main .js file
node_modules/chart.js/package.json

# 3. Bundle the main file of Chart.js module into a single browser-compatible JavaScript file
npx browserify node_modules/chart.js/dist/chart.cjs -o chart.bundled.js

# 4. Same as above, but exposes the bundled library as a global variable named "TEST" (i.e., accessible via window.TEST)
npx browserify node_modules/chart.js/dist/chart.cjs -o chart.bundled2.js -s TEST
```

## Why Isn’t My JavaScript Library Working?

> Lightning Web Security (LWS) is recommended to be enabled in Salesforce organizations because Lightning Locker has more restrictions. We will consider this case with LWS in mind.

Let’s say you found the perfect library, such as [docx-merger](https://www.npmjs.com/package/docx-merger?ref=bulkifiedthinking.com), added it to your static resources, and imported it into your Lightning Web Component using [loadScript](https://developer.salesforce.com/docs/component-library/bundle/lightning-platform-resource-loader/documentation?ref=bulkifiedthinking.com). What's next?

On a positive note, I can say that almost all libraries should work as is, unless, of course, they have any dependencies. However, what is important to understand when using third-party libraries within the Lightning Web Security (LWS) scope is the following:

1. Your library will never have access to the global `window` object.
2. Your library will be placed in a so-called “LWS Sandbox”, a kind of isolated container where it seems to be unrestricted, but it does not get access to the global `window` object, only to its copy that is inside the sandbox.
3. The functions and objects of your library will be accessible to the Lightning Web Component using the `window` object. And although it can work without it, it is still recommended to use it this way.

Let’s look at this example using an object and a function from our [docx-merger](https://www.npmjs.com/package/docx-merger?ref=bulkifiedthinking.com) library that we have already loaded into static resources:

```
import { LightningElement } from 'lwc';
import docxmerger from "@salesforce/resourceUrl/docxmerger";
import { loadScript } from "lightning/platformResourceLoader";

export default class MergeDocx extends LightningElement {
  docxLoaded = false;

  renderedCallback() {
    if (this.docxLoaded) return;

    loadScript(this, docxmerger)
      .then(() => {
        this.docxLoaded = true;
        // The library was added to the 'window' object
        console.log('DocxMerger library loaded:', window.DocxMerger);
      });
  }

  async handleMerge() {
    // ...
    const mergedBlob = await this.mergeWithDocxMerger(base64Blobs, true, true);
  }

  async mergeWithDocxMerger(documents, isBase64Conversion, isDownload) {
    const base64Documents = isBase64Conversion
      ? this.encodeBlobToBase64Format(documents)
      : documents;

      // Call the object & functions from the 'window' object
      const merger = new window.DocxMerger({}, base64Documents);
      merger.save('blob', function (data) {
        // ...
      });
  }

  encodeBlobToBase64Format(documents) {
    return documents.map(base64 => this.base64ToArrayBuffer(base64));
  }

  base64ToArrayBuffer(base64) {
    const binaryString = window.atob(base64);
    const len = binaryString.length;
    const bytes = new Uint8Array(len);
    for (let i = 0; i < len; i++) {
      bytes[i] = binaryString.charCodeAt(i);
    }
    return bytes.buffer;
  }
}
```

In this case, we follow the documentation of the specific library almost exactly, with one small change: when calling the `DocxMerger` object, we specify that it is located in the `window` object. The standard documentation does not mention this, as it is specific to Lightning Web Security (LWS).

### Fallback Mechanism

As you can see from [this](https://developer.salesforce.com/docs/platform/lightning-components-security/guide/lws-js.html?ref=bulkifiedthinking.com) documentation, and as I mentioned earlier, your third-party JavaScript code does not have access to the global scope because it is sandboxed. However, your library is likely unaware of this.

If you encounter difficulties with your library, for example, if the global variable is *undefined,* you need to provide a fallback mechanism. To do this, open the source code of your JavaScript library and locate the assignment of the global scope, which will look something like this:

```javascript
(function (global) {
  // ...
})(this);
```

In this code, `this` is passed to the closure function as `global`. With Lightning Web Security (LWS), `this` and `global` are not defined, so this is likely to cause issues. To fix this, you need to modify the code as follows:

```javascript
(function (global) {
  global = global || self;
  global.myFunction = function () {};
})(this);
```

That is, we have added a new condition: if `global` is undefined, assign it to `self`, in other words, the scope of sandbox. This allows your library to function as expected while complying with security requirements.

---

These problems with third-party JavaScript components or libraries can sometimes arise. Do you think it’s worth spending time modifying the source code, or is it better to settle for less ideal but functional solutions?

Share your thoughts in the comments!