Errors, Debugging and Security · Lesson 21 of 25
Lightning Web Security and Third-Party JavaScript
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
- Namespace sandboxescode from each namespace (c, a managed package…) runs in its own JavaScript sandbox
- Virtual globalseach sandbox gets its own window and document view, so globals one namespace sets don't leak into another
- Distortionsrisky APIs are modified to behave safely or are blocked in the sandbox
- 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 | |
|---|---|---|
| Approach | wraps DOM objects in "secure" wrappers | sandboxes per namespace with distortions |
| Third-party libraries | many broke on the wrappers | far better compatibility |
| Cross-namespace components | restricted | components from different namespaces can be composed and import each other's modules where exposed |
| Today | legacy | the 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, neverdocument.querySelectorinto other components. - Avoid generating code from strings. No
eval,new Functionor string arguments tosetTimeout– 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-textfor 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>
loadScriptandloadStylereturn promises; load several files in parallel withPromise.all.- Load in
renderedCallback(orconnectedCallback) 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
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.
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
renderedCallbackwithout a guard flag, so every re-render creates another chart. Add a "loaded" flag (and destroy the chart indisconnectedCallback).
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.