Skip to main content

How to Create and Load a Script Element Dynamically in JavaScript

Dynamically loading a JavaScript file is a powerful technique for improving performance and managing third-party libraries. Instead of including a <script> tag directly in your HTML, you can create and inject it with JavaScript when you need it. This is useful for loading analytics scripts after user consent, loading a library for a specific feature on-demand, or simply deferring non-critical scripts.

This guide will teach you the modern, standard method for creating a <script> element, setting its attributes, and safely handling both successful and failed loading events.

The Core Logic: Creating and Appending the Script Element

The process of creating a script element involves three basic steps:

  1. Create: Use document.createElement('script') to create a new, detached <script> element.
  2. Configure: Set its src attribute to the URL of the JavaScript file you want to load. You can also set other attributes like async or type.
  3. Append: Add the element to the document, typically at the end of the <head> or <body>. This action triggers the browser to start fetching and executing the script.

For example:

// 1. Create the element
const script = document.createElement('script');

// 2. Configure its attributes
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js';
script.async = true; // Fetches the script asynchronously

// 3. Append the script to the <head>
document.head.appendChild(script);

Handling Asynchronous Loading: The onload and onerror Events

The biggest challenge with dynamic script loading is that it's asynchronous. The rest of your code will continue to execute while the browser fetches the script in the background. If you try to use a function from the loaded script immediately after appending it, your code will fail because the script hasn't finished loading yet.

To solve this, you must use event listeners to wait for the script to be ready.

  • onload: This event fires when the script has been successfully fetched and executed.
  • onerror: This event fires if the script fails to load (e.g., due to a 404 error or a network issue).
const script = document.createElement('script');
script.src = 'path/to/my-library.js';
script.async = true;

script.onload = () => {
// This function will only be called after my-library.js has loaded.
console.log('✅ Script loaded successfully!');
// You can now safely use functions from the loaded script.
};

script.onerror = () => {
console.error('⛔️ Error loading script.');
};

document.head.appendChild(script);

The Modern Solution: A Reusable async Function (Best Practice)

The best practice is to wrap this asynchronous logic in a reusable function that returns a Promise. This allows you to use the clean and modern async/await syntax to wait for a script to load.

The reusable function:

/**
* Dynamically loads a script and returns a promise.
* @param {string} src The URL of the script to load.
* @returns {Promise<void>} A promise that resolves when the script loads or rejects on error.
*/
function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.async = true;

script.onload = () => resolve();
script.onerror = () => reject(new Error(`Failed to load script: ${src}`));

document.head.appendChild(script);
});
}

Practical Example: Loading an External Library

With our loadScript utility function, loading an external library and using it becomes incredibly simple and readable.

For example, we want to load the Chart.js library and then create a chart, but only after the library is ready.

// Problem: How to wait for Chart.js to be ready before using it?
const CHARTJS_URL = 'https://cdn.jsdelivr.net/npm/chart.js';

For example, by using async/await with our loadScript function, the code is clean, sequential, and easy to follow.

async function createChart() {
try {
console.log('Loading Chart.js library...');
await loadScript('https://cdn.jsdelivr.net/npm/chart.js');
console.log('✅ Chart.js loaded successfully!');

// Now it is safe to use the Chart object
const ctx = document.getElementById('myChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
labels: ['Red', 'Blue', 'Yellow'],
datasets: [{
label: '# of Votes',
data: [12, 19, 3],
}],
},
});
} catch (error) {
console.error(error);
}
}

// Call the function to start the process
createChart();

Conclusion

Dynamically loading scripts is a powerful technique for optimizing web performance and managing dependencies.

  • The core process involves creating, configuring, and appending a <script> element to the DOM.
  • Because script loading is asynchronous, you must use the onload and onerror events to know when the script is ready or has failed.
  • The best practice is to encapsulate this logic in a reusable, Promise-based function, which allows you to use the clean async/await syntax and makes your code more modular and readable.