LearnFrenzy
Sign in Take a free test

Errors, Debugging and Security · Lesson 21 of 25

Lightning Web Security and Third-Party JavaScript

5 min read Updated Nov 8, 2026 Saurabh Samir

On a Salesforce page, your component runs next to components from Salesforce, from managed packages and from other teams. The platform can't let one of them read another's data or rewrite another's DOM. Lightning Web Security (LWS) and the page's Content Security Policy (CSP) enforce that – and they shape how you write JavaScript, especially when you bring in a library.

In this lesson

  • What Lightning Web Security does
  • LWS vs the older Lightning Locker
  • CSP: external scripts and API calls
  • Loading libraries with loadScript and loadStyle

What LWS does

How LWS isolates code

  1. Namespace sandboxescode from each namespace (c, a managed package…) runs in its own JavaScript sandbox
  2. Virtual globalseach sandbox gets its own window and document view, so globals one namespace sets don't leak into another
  3. Distortionsrisky APIs are modified to behave safely or are blocked in the sandbox
  4. Standard JavaScripteverything else – classes, modules, promises, array methods – works as in any browser

For day-to-day component code, LWS is invisible: it's built on standard browser security and lets you use modern JavaScript and most libraries unchanged. You notice it when code tries to cross boundaries – reaching into another namespace's components, reading global state that someone else set, or using APIs the sandbox distorts.

LWS vs Lightning Locker

Lightning Locker (older)Lightning Web Security
Approachwraps DOM objects in "secure" wrapperssandboxes per namespace with distortions
Third-party librariesmany broke on the wrappersfar better compatibility
Cross-namespace componentsrestrictedcomponents from different namespaces can be composed and import each other's modules where exposed
Todaylegacythe default for LWC; enabled in Session Settings

If an older org still runs Locker, an admin enables "Use Lightning Web Security for Lightning web components" in Session Settings – test in a sandbox first, since a few behaviours change.

Write code that respects the sandbox

  • Don't use globals for communication. window.myAppState = … won't be visible to another namespace; use events, Lightning Message Service or a shared module within your namespace.
  • Stay inside your own DOM. Use this.template, never document.querySelector into other components.
  • Avoid generating code from strings. No eval, new Function or string arguments to setTimeout – they're blocked or restricted and they're an injection risk anyway.
  • Don't build HTML from data. Use the template, or lightning-formatted-rich-text for rich text.

CSP: external scripts and API calls

Lightning pages use a strict Content Security Policy. Two consequences matter for JavaScript:

  • No scripts from external URLs or CDNs. Libraries must be uploaded as static resources and loaded with lightning/platformResourceLoader.
  • fetch() to another domain is blocked unless an admin adds it as a CSP Trusted Site (with connect-src). Often it's better to call external APIs from Apex using Named Credentials – secrets stay on the server, and the call isn't limited by browser CORS rules.

Loading a third-party library

Say you upload chartjs.zip (containing chart.umd.min.js) as a static resource named chartjs:

// salesChart.js
import { LightningElement } from 'lwc';
import { loadScript } from 'lightning/platformResourceLoader';
import CHARTJS from '@salesforce/resourceUrl/chartjs';

export default class SalesChart extends LightningElement {
    chart;
    libraryLoaded = false;
    error;

    async renderedCallback() {
        if (this.libraryLoaded) return;           // renderedCallback runs after every render
        this.libraryLoaded = true;
        try {
            await loadScript(this, `${CHARTJS}/chart.umd.min.js`);   // loads once, even if many components ask
            this.initChart();
        } catch (e) {
            this.error = 'Could not load the chart library';
        }
    }

    initChart() {
        const canvas = this.refs.canvas;          // <canvas lwc:ref="canvas"></canvas> inside lwc:dom="manual"
        // eslint-disable-next-line no-undef
        this.chart = new window.Chart(canvas.getContext('2d'), {
            type: 'bar',
            data: { labels: ['Q1', 'Q2', 'Q3'], datasets: [{ label: 'Revenue', data: [120, 180, 150] }] },
        });
    }

    disconnectedCallback() {
        this.chart?.destroy();                    // free the library's resources
    }
}
<!-- salesChart.html -->
<template>
    <div lwc:dom="manual" class="chart-box">
        <canvas lwc:ref="canvas"></canvas>
    </div>
    <template lwc:if={error}><p>{error}</p></template>
</template>
  • loadScript and loadStyle return promises; load several files in parallel with Promise.all.
  • Load in renderedCallback (or connectedCallback) behind a flag so it happens once per component.
  • lwc:dom="manual" tells LWC that a library will manage the contents of that element, so the engine leaves them alone.
  • Use the library's UMD/IIFE build that attaches to window; under LWS that global lives in your namespace's sandbox.
  • Check the library's license and keep it updated – a static resource doesn't receive security fixes on its own.

Practice

Try it yourself

  1. Your component calls fetch('https://api.exchangerates.example/latest') and the console shows a CSP error. What are your two options?

    Show answer

    Ask an admin to add the domain as a CSP Trusted Site for connect-src – or, usually better, move the call to Apex with a Named Credential and call that Apex method from the component.

  2. A library loads fine, but a chart is drawn twice and the second copy flickers. What's the likely cause?

    Show answer

    The initialisation runs in renderedCallback without a guard flag, so every re-render creates another chart. Add a "loaded" flag (and destroy the chart in disconnectedCallback).

Quick check

How does LWS isolate code?

It runs each namespace's code in its own sandbox with its own view of the globals.

Can you load a library from a public CDN URL?

No – upload it as a static resource and use loadScript.

What does lwc:dom="manual" do?

Lets a library or your code manage that element's contents without LWC overwriting them.

Summary

  • LWS sandboxes each namespace and distorts risky APIs; modern JS works as normal.
  • Don't rely on globals or other components' DOM; never generate code or HTML from strings.
  • CSP blocks external scripts and unapproved fetch targets – use static resources, Trusted Sites or Apex.
  • Load libraries once with loadScript, use lwc:dom="manual", and clean up on disconnect.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Search LearnFrenzy

Esc

Quick links