Fix 'Cannot set properties of null (setting innerHTML)' in JS
This error means you're trying to set innerHTML on an element that doesn't exist yet. Usually the script runs before the DOM is ready. The fix is simple: wrap your code in a DOMContentLoaded event or move the script tag.
You're trying to set innerHTML on an element that doesn't exist yet. Infuriating, right?
This error tripped me up the first time too. The good news: it's a simple timing issue. Your JavaScript is running before the HTML element it's targeting has been parsed and added to the DOM. Here's the fix.
The Fix: Move your script or wrap it in DOMContentLoaded
You have two solid options. Pick the one that fits your setup.
Option 1: Move the script tag to the bottom of the body
Place your <script> tag right before the closing </body> tag. This ensures all elements above it are already loaded.
<!DOCTYPE html>
<html>
<body>
<div id="myDiv"></div>
<script>
document.getElementById('myDiv').innerHTML = 'Hello!';
</script>
</body>
</html>This works because the browser parses HTML from top to bottom. By the time the script runs, #myDiv is in the DOM. I use this approach for small projects—it's dead simple.
Option 2: Use DOMContentLoaded (most reliable)
Wrap your code in a DOMContentLoaded event listener. This fires after the DOM is fully parsed but before images and stylesheets finish loading.
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('myDiv').innerHTML = 'Hello!';
});I prefer this in larger apps. It keeps scripts in the head and still works even if you later move them. On Chrome 120+ and Firefox 120+, this is the standard approach for interactive scripts.
Option 3: Use the defer attribute on script tags
If you're loading external scripts, add defer to the script tag in the head:
<script src="app.js" defer></script>This tells the browser to download the script in parallel but execute it only after the DOM is ready. It's a clean solution for modern sites. Note: defer doesn't work for inline scripts—only for external ones.
Why this error happens
JavaScript runs synchronously by default. When your browser encounters a script tag, it stops parsing HTML, runs the script, then continues. If the script runs before the target element is parsed, document.getElementById('myDiv') returns null. Setting .innerHTML on null throws that TypeError.
Here's a real-world trigger: I once saw this in a React app where someone tried to manipulate a placeholder div before the component mounted. The script was in the head, targeting a <div id="root"></div> that didn't exist yet. Same error.
Less common variations of the same issue
Misspelled ID or class name
Check your selector. document.getElementById('myDiv') returns null if the ID is mydiv or my-div. JavaScript is case-sensitive, and so is getElementById. I've wasted 20 minutes on a typo like this—we've all been there.
// Wrong — ID is 'mainContent', not 'maincontent'
document.getElementById('maincontent').innerHTML = 'test';Element inside a shadow DOM or iframe
If your target element lives inside a shadow DOM or an iframe, document.getElementById won't find it. You need to access the shadow root or iframe content document first.
// For shadow DOM
const host = document.getElementById('shadow-host');
const shadowRoot = host.shadowRoot;
shadowRoot.getElementById('inner-element').innerHTML = 'works';
// For iframe
const iframe = document.getElementById('myIframe');
const innerDoc = iframe.contentDocument || iframe.contentWindow.document;
innerDoc.getElementById('target').innerHTML = 'works';Script runs before dynamic content loads
If you're fetching data and inserting an element dynamically, your script might try to set innerHTML on an element that hasn't been appended yet. Use Promise or async/await to wait for the insertion.
fetch('/data')
.then(response => response.json())
.then(data => {
const newDiv = document.createElement('div');
newDiv.id = 'dynamic-content';
document.body.appendChild(newDiv);
newDiv.innerHTML = data.content;
});How to prevent this error in the future
- Always check if the element exists before setting properties. Use optional chaining or a simple
ifcheck.
const el = document.getElementById('myDiv');
if (el) {
el.innerHTML = 'Hello!';
} else {
console.warn('Element not found');
}- Use consistent naming conventions. Pick a case—camelCase, kebab-case—and stick with it. I've committed to kebab-case for HTML IDs and camelCase for JavaScript variables. No mixups.
- Always place scripts that manipulate the DOM at the bottom of the body or use
deferfor external files. This is a habit that saves you dozens of debugging hours. - Test on slower connections. In development, scripts often run before the DOM loads because your local server is instant. Simulate a slow network in DevTools to catch these timing bugs early.
That's the whole story. The error is a timing issue, and now you know how to fix it every time.
Was this solution helpful?