How to Load an HTML Image from a URL
Display a remote image with HTML, or use JavaScript when you need to read its bytes, process it, or draw it to a canvas.

To display an image from a URL, put the URL in an HTML <img> element’s src attribute:
<img src="https://example.com/photo.jpg"
alt="A mountain lake at sunrise"
width="800"
height="600">
That is enough for the browser to request and display an image hosted elsewhere. Use JavaScript fetch() only when you need to inspect or transform the image bytes, attach authorization, or load the bytes into another API. A cross-origin fetch() requires the image server to permit your page through CORS; an ordinary <img> display generally does not.
1. Choose the right way to load the image
First decide what you need to do with the URL. Displaying pixels on a page, reading image bytes in JavaScript, and accessing pixels from a canvas have different browser security requirements.

| Need | Use | Cross-origin requirement |
|---|---|---|
| Show an image on the page | <img src> |
Usually no CORS permission needed for display |
| Read or transform the response in JavaScript | fetch(), Blob, object URL |
Server must allow your origin with CORS |
| Draw it to canvas and read or export pixels | crossorigin="anonymous" plus canvas |
Server must allow CORS; otherwise canvas is tainted |
| Load a protected image with credentials | Authenticated request or controlled server proxy | Configure credentials and server CORS deliberately |
The <img> element is the standard way to embed an image in a document. Its URL can be absolute, such as https://cdn.example.com/photo.jpg, or relative to the page, such as /images/photo.jpg. Relative URLs resolve against the document’s base URL.
2. Display a remote image with HTML
Use a descriptive alt value for meaningful images. For decorative images, use alt="" so assistive technology can skip them. Add the image’s intrinsic width and height when known: the browser can reserve its space before the image arrives, reducing layout shifts.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Remote image example</title>
</head>
<body>
<img
src="https://images.example.com/lake.jpg"
alt="A mountain lake at sunrise"
width="1200"
height="800"
style="max-width: 100%; height: auto;"
>
</body>
</html>
Replace the example URL with a direct image resource. A URL that opens a web page containing an image is not necessarily the image file itself. The browser needs a response it can decode as an image format such as JPEG, PNG, GIF, WebP, or SVG.
Relative paths and URL details
<!-- Absolute URL -->
<img src="https://cdn.example.com/assets/chart.webp" alt="Monthly chart">
<!-- Relative to the current page -->
<img src="../assets/chart.webp" alt="Monthly chart">
<!-- Relative to the site root -->
<img src="/assets/chart.webp" alt="Monthly chart">
Use HTTPS for externally hosted images when your page is HTTPS. Browsers may block insecure HTTP subresources on a secure page. If a URL contains query parameters, keep them as part of the URL; signed CDN URLs often rely on them.
3. Choose responsive sources and loading behavior
For multiple image sizes, give the browser candidates with srcset and describe the rendered slot with sizes. The browser can select an appropriate resource for the viewport and display density.
<img
src="/photos/shore-800.jpg"
srcset="/photos/shore-480.jpg 480w,
/photos/shore-800.jpg 800w,
/photos/shore-1600.jpg 1600w"
sizes="(max-width: 600px) 100vw, 800px"
width="1600"
height="1067"
alt="Rocky shoreline viewed from above"
style="max-width: 100%; height: auto;"
>
The width descriptors in srcset identify the candidate resource widths. The sizes value tells the browser how wide the image is expected to appear. Make sure these values describe your actual layout; otherwise the browser may download a needlessly large image or choose one that looks soft.
For below-the-fold images, loading="lazy" lets the browser defer fetching until the image approaches the viewport:
<img src="/gallery/detail.webp" alt="Close-up of a ceramic glaze"
width="900" height="600" loading="lazy">
Do not lazy-load a prominent hero image that should appear immediately. Deferring that request can delay the content the reader came to see. Keep explicit dimensions even for lazy images, so the page layout does not jump when they load.
4. Fetch image bytes in JavaScript
Use fetch() if you need to inspect the HTTP response, transform the file, or pass it to code that expects a Blob. This complete example reports failures, creates a temporary object URL, and releases that URL after the browser loads the image.
<img id="preview" alt="Remote image preview" width="800" height="600">
<p id="status" role="status"></p>
<script>
async function loadImage(url, img, status) {
let objectURL;
try {
status.textContent = "Loading image…";
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Image request failed: HTTP ${response.status}`);
}
const blob = await response.blob();
if (!blob.type.startsWith("image/")) {
throw new Error(`Expected an image, received ${blob.type || "unknown content type"}`);
}
objectURL = URL.createObjectURL(blob);
img.onload = () => {
URL.revokeObjectURL(objectURL);
status.textContent = "Image loaded.";
};
img.onerror = () => {
URL.revokeObjectURL(objectURL);
status.textContent = "The response could not be decoded as an image.";
};
img.src = objectURL;
} catch (error) {
status.textContent = error.message;
if (objectURL) URL.revokeObjectURL(objectURL);
}
}
loadImage(
"https://cdn.example.com/photo.jpg",
document.querySelector("#preview"),
document.querySelector("#status")
);
</script>
For a remote URL, the fetch request is governed by CORS. If the other server does not return a suitable Access-Control-Allow-Origin response header, the browser will prevent JavaScript from reading the response. The server hosting the image must grant access; a page cannot override that policy.
Why no-cors does not fix fetch
Setting mode: "no-cors" produces an opaque response. JavaScript cannot inspect its status, headers, or body, so it cannot turn that response into a readable image Blob. It is not a workaround for missing CORS permission. If you only need to show the image, set its URL directly as the img.src instead.
5. Load a remote image into canvas
Canvas has a stricter rule when you want to read pixels or export the result. Set the image’s CORS mode before assigning its URL, and ensure the image host allows your page’s origin.

<img id="source" crossorigin="anonymous" alt="Source image">
<canvas id="canvas" width="800" height="600"></canvas>
<script>
const source = document.querySelector("#source");
const canvas = document.querySelector("#canvas");
const context = canvas.getContext("2d");
source.crossOrigin = "anonymous"; // Must be set before src.
source.onload = () => {
context.drawImage(source, 0, 0, canvas.width, canvas.height);
try {
const png = canvas.toDataURL("image/png");
console.log("Canvas export ready", png.slice(0, 40));
} catch (error) {
console.error("Canvas export blocked:", error);
}
};
source.onerror = () => console.error("Could not load the source image");
source.src = "https://cdn.example.com/photo.jpg";
</script>
The image server must return an Access-Control-Allow-Origin header permitting your origin (or a suitable wildcard for a public, non-credentialed resource). Without that permission, the browser can display the image in some cases, but drawing it makes the canvas tainted. Operations such as getImageData(), toDataURL(), and toBlob() then fail for security reasons.
For authenticated cross-origin canvas access, use the appropriate credential mode and server CORS configuration. Credentialed CORS cannot use a wildcard origin; the server needs to name the permitted origin and allow credentials. Avoid exposing long-lived secrets in browser code. A server-side proxy you control can fetch protected or non-CORS images and serve them to your page; protect that proxy with authentication, caching, response-size limits, and content-rights controls.
6. Handle authentication and transformations safely
Some image hosts require a signed URL, cookies, or an authorization header. A plain <img> element cannot attach an arbitrary bearer token header. If the host supports signed URLs or same-site cookies, those may work for direct display. Otherwise, fetch through a backend you control, verify that the requesting user is allowed to access the resource, and return only permitted image content.
Never build an unrestricted proxy that accepts any URL from an unauthenticated caller. Such a service can be abused to request internal network addresses or consume excessive bandwidth. Validate allowed hosts, restrict response size and content type, set timeouts, and cache only according to the source’s privacy and authorization rules.
If JavaScript needs to resize or alter pixels, choose an appropriately sized source before downloading where possible. Resizing a huge image after transfer still pays the download and decode cost. For routine page display, native <img> with responsive sources is simpler and avoids holding an additional Blob and object URL in application code.
7. Troubleshooting common image loading failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Broken image icon or blank area | Bad URL, 404, denied access, or response is HTML rather than an image | Open the exact URL; inspect Network status, redirects, and Content-Type |
| Works in address bar, fails on an HTTPS page | Image URL uses insecure HTTP and is blocked as mixed content | Use an HTTPS image endpoint |
<img> works but fetch() reports a CORS error |
Image server does not allow the page origin for scripted reads | Configure Access-Control-Allow-Origin on that server or fetch through an authorized backend |
| Canvas displays image but export throws a security error | Canvas is tainted by an image loaded without successful CORS permission | Set crossorigin="anonymous" before src and configure CORS at the image host |
fetch() returns 200 but preview stays blank |
Response may be an HTML error page, unsupported format, or corrupt image | Check response Content-Type and the image decode error; verify the bytes are valid |
| Only some visitors see the image | Expired signed URL, hotlink protection, cookie/session requirements, or referrer rules | Review the source host’s access policy; generate fresh URLs or serve through an authorized backend |
| Image is unexpectedly huge or soft | srcset/sizes candidates do not match rendered width or pixel density |
Supply correctly sized variants and accurate slot sizes |
| Layout jumps after the image appears | No intrinsic dimensions or CSS aspect ratio reserved space | Set accurate width and height or a stable aspect-ratio |
| Browser says resource is blocked | Content Security Policy excludes the image host | Add the trusted host to the page’s img-src directive |
| Memory grows after repeated previews | Object URLs are retained after their Blob is no longer needed | Call URL.revokeObjectURL() after load or replacement |
In browser developer tools, open the Network panel, filter by images, reload, and inspect the failing request. Check the final URL after redirects, HTTP status, response type, console messages, and whether the browser reports CSP, mixed-content, or CORS blocking. This separates URL/access problems from script security problems.
8. Performance, reliability, and cost considerations
- Prefer direct display for display.
<img>lets the browser schedule image requests and decode them without an extra JavaScript fetch/Blob step. - Transfer fewer bytes. Provide width variants through
srcset, and use a sensiblesizesexpression. Consider modern formats when your image pipeline supports them. - Reserve layout space. Set dimensions or an aspect ratio so text and controls do not move when the image arrives.
- Lazy-load selectively. Use
loading="lazy"for below-the-fold content; load the main visible image promptly. - Cache deliberately. Let a CDN or browser cache repeated public images. For user-specific or signed images, honor access rules and cache headers; do not accidentally serve private content across users.
- Expect the origin to fail sometimes. Remote images can be removed, throttled, or temporarily unavailable. Use a stable host you control for essential product content and provide useful fallback text or a placeholder.
- Account for bytes and processing. Image delivery consumes network traffic, while JavaScript Blob and canvas paths add memory and CPU work. Download only the resolution and format needed.
For debugging a page’s visual result rather than embedding an image, a screenshot capture can show what the browser actually rendered. [ScreenshotNeo](https://screenshotneo.com) is a website screenshot API and MCP server; see the [API documentation](https://screenshotneo.com/docs/) for options.
9. FAQ
Can I put any image URL in src?
Use a URL that resolves to an image response the browser can access and decode. Some hosts block embedding or require authorization, and a page URL is not always a direct image URL.
Do I need CORS to show an image in HTML?
Usually not for ordinary display with <img>. You do need CORS permission when JavaScript reads a cross-origin fetch response or when canvas pixel access/export must remain allowed.
Can I use a URL from another website without downloading it first?
Yes. Set that URL as src; the browser requests it when rendering your page. The host’s availability and embedding policy still apply.
Why does the image appear but canvas export fail?
The browser can display a cross-origin image while withholding pixel access. Configure CORS on the image server and set crossorigin before the source URL.
Does loading="lazy" fix a slow image host?
No. It can postpone offscreen requests, but it does not make the host respond faster or change CORS policy. Responsive candidates and a reliable image CDN address different needs.
Or skip the browser setup
If your goal is to capture a page as an image or PDF, ScreenshotNeo turns one URL into a screenshot without setting up a browser. Its clean-shot process accepts consent banners and removes 60+ known consent platforms, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers identify the page verdict and billing. An MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots per month with no card, and paid plans start at $5 for 3,000.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
See the ScreenshotNeo docs for request options, then sign up free for 1,000 screenshots a month with no card.


