A broken image is one of the most common problems you’ll encounter when building websites — and one of the easiest to fix once you know where to look. This guide covers the most common causes, how to diagnose them using your browser’s built-in tools, how to prevent them from happening in the first place, and how to handle them gracefully when they do.
Contents
What Causes a Broken Image?
When a browser can’t load an image, it displays a broken image icon in its place. This happens for one of a few reasons:
- The image file doesn’t exist at the path specified
- The filename or file extension is incorrect
- The file path in your HTML is wrong
- The file has incorrect permissions on the server
- A CDN or external host is unavailable or the URL has changed
- The image is set to load lazily but never enters the viewport due to a layout issue
The fastest way to identify which of these is the problem is to check your browser’s DevTools before doing anything else.
Step 1: Diagnose with Browser DevTools
Open DevTools in Chrome or Firefox with F12 (Windows/Linux) or Cmd + Option + I (Mac). Go to the Network tab, reload the page, and filter by Img. Find the broken image in the list and check its status code:
- 404 — the file doesn’t exist at that path, or the path is wrong
- 403 — the file exists but the server is blocking access (a permissions issue)
- 200 — the file loaded successfully, which means the problem is likely a layout or lazy loading issue rather than a missing file
The Console tab will also log errors for broken images directly, which can be quicker to spot at a glance.
Step 2: Verify the File Exists
Before adjusting any code, confirm the image file is actually where you think it is. Open your project folder and navigate to the expected location. It’s a common mistake to think a file has been saved or uploaded when it hasn’t.
While you’re there, check the filename carefully:
cupcake.jpgandcupcake.jpegare different filesfeatured-image.jpgandfeatured_image.jpgare different files- Filenames are case-sensitive on most servers —
Cupcake.jpgandcupcake.jpgare not the same
Step 3: Check the File Path
If the file exists but the image is still broken, the path in your HTML is likely wrong. There are two types of file paths: relative and absolute.
Relative Paths
A relative path points to a file in relation to the current HTML file’s location. This is what you’ll use most of the time.
html
<img src="img/cupcake.jpg" alt="A chocolate cupcake on a white plate.">
This tells the browser: starting from where this HTML file lives, go into a folder called img and find cupcake.jpg.
If your CSS file needs to reference the same image, the path must be relative to the CSS file’s location — not the HTML file. To move up one directory level, use ..:
css
background-image: url('../img/cupcake.jpg');
Absolute Paths
An absolute path includes the full URL. Use this only when the image is hosted on a different domain, such as a CDN you control:
html
<img src="https://cdn.yoursite.com/img/cupcake.jpg" alt="A chocolate cupcake on a white plate.">
Never use a local file path — paths beginning with file:/// or containing C:\ or My Documents only work on your own machine. They will break for every other visitor to your site.
Avoid hotlinking images from other websites. Aside from potential copyright issues, you have no control over those images — the owner can remove them, rename them, or replace them with something else entirely.
Step 4: Fix File Permissions
If DevTools shows a 403 status for your image, the file exists on the server but something is blocking access to it. This is a permissions issue.
The correct permission for a publicly accessible image file is typically 644 — readable by everyone, writable only by the owner. You can check and update permissions using whatever tool you use to manage your server:
- FTP clients (such as FileZilla or Transmit): right-click the file and look for a “File Permissions” or “Get Info” option
- SSH: run
chmod 644 filename.jpgfrom the command line - Hosting control panels: most cPanel or Plesk interfaces have a file manager with a permissions editor
Step 5: Handle Broken Images Gracefully with JavaScript
Prevention is the goal, but images can still fail at runtime — a CDN goes down, a user-generated image gets deleted, a URL changes after deploy. Handling these failures gracefully means the rest of your page keeps working and users see something useful instead of a broken icon.
The onerror event
The <img> element fires an error event when it fails to load. You can use this to swap in a fallback:
html
<img src="img/cupcake.jpg" alt="A chocolate cupcake on a white plate." id="hero-image">
js
const img = document.querySelector('#hero-image');
img.addEventListener('error', () => {
img.src = 'img/fallback.jpg';
});
Managing fallbacks at scale with a data attribute
If you have many images across a page, hardcoding fallback paths in JavaScript doesn’t scale well. A cleaner pattern is to store the fallback path in a data- attribute on each image, then handle them all with one listener:
html
<img src="img/cupcake.jpg" data-fallback="img/fallback.jpg" alt="A chocolate cupcake on a white plate.">
<img src="img/muffin.jpg" data-fallback="img/fallback.jpg" alt="A blueberry muffin on a wooden board.">
js
document.querySelectorAll('img[data-fallback]').forEach(img => {
img.addEventListener('error', () => {
const fallback = img.dataset.fallback;
if (img.src !== fallback) {
img.src = fallback;
}
});
});
The if (img.src !== fallback) check prevents an infinite loop in the unlikely case that the fallback image itself also fails to load.
A Note on Alt Text
The alt attribute is your last line of defence when an image cannot be recovered. Write it as if the image will fail — because sometimes it will.
A good alt attribute describes what the image shows and why it’s there:
html
<!-- Weak: generic and unhelpful -->
<img src="img/cupcake.jpg" alt="image">
<!-- Better: describes the content -->
<img src="img/cupcake.jpg" alt="A chocolate cupcake with vanilla frosting on a white plate.">
For decorative images that convey no information, use an empty alt attribute (alt="") so screen readers skip them. Never omit the attribute entirely — that causes screen readers to announce the filename instead.
Summary
When you encounter a broken image, work through these steps in order:
- Open DevTools > Network tab and check the HTTP status of the image
- Confirm the file actually exists at the expected path
- Verify the filename, extension, and case are correct
- Check whether the path is relative or absolute and that it’s correct for the file doing the referencing
- If the status is 403, check file permissions on the server
- For runtime failures, use the
errorevent with a fallback image and adata-fallbackpattern for scale - Make sure every image has a descriptive
altattribute

I couldn’t figure this out for so long and I didn’t realize I was calling my background image from inside the css file inside the css directory! Two dots to make the path go back out made all the difference. Any problems I run into always seem to be the easiest solutions. I’m an absolute beginner and this page made my day.
Thanks for your post. I found this only worked for me. I really appreciate this.
$(window).load(function() {
$(“img”).each(function(){
var image = $(this);
if(image.context.naturalWidth == 0 || image.readyState == ‘uninitialized’){
$(image).unbind(“error”).hide();
}
});
});
Hello, I am trying to copy and paste from chrome into outlook after long hours of labor on a very difficult responsive email…long story short it only retains links if I do it from that browser…however now of course there is another issue as there always is. My images will not copy and paste along with it via Chrome only-the one that I need. Is there an answer to this- all my links are correct….is it a chrome issue that can be fixed?
Could use javascript to add a default placeholder for all broken images?
$(‘img’).on(‘error’, function (){
$(this).prop(‘src’, ‘img/broken-image.png’);
});
document.addEventListener(“DOMContentLoaded”, function(event) {
document.querySelectorAll(‘img’).forEach(function(img){
img.onerror = function(){
this.src = ‘img/share/default.png’;
};
})
});
Quite a useful function. Thank you!
This was really helpful and educative. Sending you good energy from Transylvania.
I just dealt with a case where I couldn’t see the images on a site because of the permission settings. I changed all the settings to 744 via the terminal to no effect. They only behaved when I changed the settings manually on my local machine, then uploaded the images again. Gremlins are suspected.
Thanks so much! I’m learning coding and I’m a total beginner and I was stuck with a broken image for the past 5 days. Your article helped me finally fix the problem!!
When i open Yahoo. the front page shows a bunch of broken pictures and i dont know why. ive tried disabling ad block, restarting, deleting history and cookies.
its just my yahoo. nothing else, any idea?
this is complicated as balls
Nick are you sure its perfect solution? i did’t face this issue yet may be i face issue in feature so i want to just confirm it.
Thanks for sharing
Thank, very useful!!
Does it work?
Hi,
I published a website last week and figured it out after realizing that the folder holding my photos was labeled “IMG” but I was referencing a non-existent folder named “img”. Chrome loaded the photos anyway, but Safari didn’t! Interesting!
Good content here Nick! Love your Treehouse videos as well. I’ve tried to get rid of most of my bad habits when it comes to broken images. Most of the time I upload the file to the wrong directory once going live!
This also happens when I have clients send me pictures with uppercase extensions (like .JPG) and I link them lowercase (like .jpg). That can be annoying as well!
Thanks Nick. This can come in handy.
Cheers,
Carmon
Thanks, Nick!
You discussed document-relative URLs here but I much prefer root-relative URLs! So instead of:
“img/cupcake.jpg” in index.html
and
“../img/cupcake.jpg” in /FAQ/calories.html
you can use “/img/cupcake.jpg” in both.
That first slash does the magic. Root-relative is great for templates and reusable, modular designs where the html fragment might be used in documents with unknown directory depth. Plus you don’t have to fiddle with the dots if you decide to reorganize the document locations.
Thanks,
Chris