Skip to main content

Using Static Resources in Salesforce LWC (No CSP Required)

๐Ÿ’ฌ In plain words: Think of Static Resources as the native "luggage shelf" for your Salesforce app. Instead of hotlinking to an external server, you upload your JavaScript libraries, CSS, and images directly into Salesforce. You then load them into your Lightning Web Component (LWC) using loadScript or loadStyle. They are version-controlled, cached by the browser, and are the only approved way to bring third-party libraries into your org securely.

๐Ÿ”‘ Key Points

  • Static Resources store files locally inside your Salesforce org and serve them from Salesforce's built-in Content Delivery Network (CDN).
  • They are the officially approved method for integrating third-party libraries (like Chart.js or D3.js) into LWC, Aura, or Visualforce.
  • Unlike remote <script> tags, Static Resources do not require configuring Content Security Policy (CSP) Trusted Sites.
  • Use platformResourceLoader within a guarded renderedCallback to initialize the files.
  • Cache control limits (Public vs. Private) dictate how the browser stores the file.
๐Ÿ“Œ Real-Life Example: Your legal compliance team dictates that the org must use an approved PDF generation library rather than reaching out to an external public CDN. You download chartjs.zip, upload it to Salesforce as a Static Resource, and load it into your LWC using loadScript(this, CHARTJS) inside the renderedCallback. The file is versioned with your deployment, safely cached by the browser, and the CSP never throws an error.
๐Ÿง  Core Takeaway: Files live in the org. Never use a remote <script> tag. Use a Static Resource + loadScript to bypass CSP headaches and ensure deployment stability.

๐Ÿ› ️ How to Load a Static Resource in LWC

To safely load an external script, import the resource, utilize the platformResourceLoader, and ensure you place a "guard" inside the renderedCallback so the file doesn't reload infinitely.

// 1. Import the loadScript utility and the Static Resource
import { loadScript } from 'lightning/platformResourceLoader';
import CHARTJS from '@salesforce/resourceUrl/chartjs';
import { LightningElement } from 'lwc';

export default class ChartComponent extends LightningElement {
    _loaded = false; // The guard flag

    // 2. renderedCallback runs after EVERY render
    renderedCallback() {
        
        // 3. The Guard: Stop execution if the library is already loaded
        if (this._loaded) {
            return;
        }
        this._loaded = true;

        // 4. Load the script by appending the specific file path inside the ZIP
        loadScript(this, CHARTJS + '/chart.min.js')
            .then(() => {
                this.initChart(); // Initialize library after successful load
            })
            .catch(error => {
                this.error = error;
                console.error('Error loading ChartJS', error);
            });
    }

    initChart() {
        // Logic to build your chart goes here
    }
}

๐Ÿงญ 360 Card: Static Resource Strategy

  • Rule: Third-party assets must live in the org as a Static Resource, never via an external CDN URL.
  • Gain: You bypass CSP Trusted Site requirements, utilize Salesforce’s native CDN caching, and your assets are versioned alongside your metadata deployments.
  • Price: You are entirely responsible for upgrades. When the library updates, you must perform a metadata deployment rather than simply pointing to a new URL.
  • Limits: When loading inside renderedCallback, you must use a boolean guard variable. Without it, the script will reload endlessly.
  • Mirror (The bad way): Using a remote script tag requires CSP exceptions, creates vulnerability to external server outages, and is frequently blocked by Lightning Web Security (LWS).

๐Ÿ’ก Core Q&A & Self-Check

Q: You need to use a third-party JS charting library in an LWC. Why choose a Static Resource over a remote <script> tag, and how do you load it?
๐ŸŽฏ Say this first: Remote scripts are blocked by CSP and Lightning Web Security. A static resource is secure, cached, and approved. You load it once using loadScript inside a guarded renderedCallback.

If you use a remote script, you are forced to add the external URL to your CSP Trusted Sites, and if the external server goes down, your component breaks. By uploading the library as a Static Resource, you bundle the dependency directly into your Salesforce org. You implement this by importing the resource URL and calling loadScript inside the renderedCallback. Because the callback runs on every render, you must wrap the loadScript call in an if(!this._loaded) guard statement to prevent an infinite loading loop.

Q: You updated a Static Resource JS file, but users are still seeing the old version. Why, and how do you fix it?

Static resources with Public cache-control are aggressively cached by the Salesforce CDN and the user's browser. If you simply overwrite the file, the browser will continue to serve the stale cached version until its Time-To-Live (TTL) expires. The best practice is to use path versioning. Upload the new file with a distinct name (e.g., chartjs_v2) and update your LWC import statement. Because the URL path physically changes, the browser is forced to pull the new file immediately. If it's an internal asset that changes constantly, change the cache-control to Private to shorten the caching lifespan.

Q: A Static Resource ZIP contains dozens of files. How do you reference a specific image, and what is the trade-off versus uploading them separately?

To reference a specific file inside an uploaded ZIP archive, simply append the internal file path to the imported resource URL (e.g., RESOURCE_URL + '/images/logo.png').

Trade-off: A ZIP file is excellent for bundling cohesive assets (like a JS library and its required CSS file) into a single, clean deployment. However, you cannot set separate cache controls for individual files inside a ZIP, and updating a single icon requires re-uploading the entire ZIP archive. Upload files as a ZIP if they version together, but use separate resources for assets that update independently.