ScreenshotNeo

BlogHow-to

How to View an Image from a URL with JavaScript

Display remote images with JavaScript using img.src, fetch and Blob URLs, or canvas—with CORS, cleanup, errors, accessibility, and practical examples.

By the ScreenshotNeo team1 October 20268 min read

The shortest way to view an image from a URL is to create an img element, set its src, add useful alternative text, and append it to the page:

const img = document.createElement('img');
img.src = 'https://example.com/photo.jpg';
img.alt = 'Example photo';
document.querySelector('#preview').append(img);

This works when the browser is allowed to embed the image. Use fetch() followed by response.blob() when JavaScript must inspect the response or handle HTTP errors before displaying it. Use canvas only when you need to transform or read pixels; cross-origin images require CORS permission.

1. Display a URL directly with img.src

Create a container in your HTML:

<div id="preview"></div>

Then append an image from JavaScript:

const preview = document.querySelector('#preview');
const img = document.createElement('img');

img.src = 'https://example.com/photo.jpg';
img.alt = 'Example photo';
img.width = 800;
img.height = 533;

preview.append(img);

The browser starts loading as soon as src is assigned. The element is visible after it is appended. The Image() constructor is an equivalent way to create a detached HTMLImageElement (see MDN’s Image() documentation).

Use new Image()

const img = new Image();
img.alt = 'Example photo';
img.onload = () => console.log('Image loaded', img.naturalWidth, img.naturalHeight);
img.onerror = () => console.error('Image failed to load');
img.src = 'https://example.com/photo.jpg';
document.querySelector('#preview').append(img);

Show loading and error states

const preview = document.querySelector('#preview');
const status = document.querySelector('#status');
const img = new Image();

img.alt = 'Product photograph';
img.addEventListener('load', () => {
  status.textContent = `Loaded ${img.naturalWidth} × ${img.naturalHeight}`;
});
img.addEventListener('error', () => {
  status.textContent = 'The image could not be loaded.';
});

status.textContent = 'Loading…';
preview.replaceChildren(img);
img.src = 'https://example.com/product.jpg';

Do not infer the format from the filename. The server’s response headers and bytes determine whether the browser can decode the resource.

2. Fetch the image, then display a Blob URL

Use this approach when you need to check the HTTP status, inspect headers, authenticate the request, or process binary data before display. Response.blob() is asynchronous and returns a Blob; URL.createObjectURL() creates a temporary URL accepted by img.src (see MDN’s Response.blob() documentation).

async function showImage(url, target) {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }

  const blob = await response.blob();
  const objectUrl = URL.createObjectURL(blob);
  const img = new Image();
  img.alt = 'Downloaded image';
  img.src = objectUrl;
  target.replaceChildren(img);

  // Revoke the previous object URL when replacing or removing this image.
  img.addEventListener('load', () => {
    URL.revokeObjectURL(objectUrl);
  }, { once: true });

  return img;
}

showImage('https://example.com/photo.jpg', document.querySelector('#preview'))
  .catch(error => console.error(error));

If you replace an image repeatedly, keep the URL and revoke it during cleanup:

let currentObjectUrl;

async function replaceImage(url) {
  const response = await fetch(url);
  if (!response.ok) throw new Error(`HTTP ${response.status}`);

  const blob = await response.blob();
  const nextObjectUrl = URL.createObjectURL(blob);
  const img = new Image();
  img.alt = 'Preview';
  img.src = nextObjectUrl;

  const preview = document.querySelector('#preview');
  preview.replaceChildren(img);

  if (currentObjectUrl) URL.revokeObjectURL(currentObjectUrl);
  currentObjectUrl = nextObjectUrl;
}

function clearImage() {
  document.querySelector('#preview').replaceChildren();
  if (currentObjectUrl) {
    URL.revokeObjectURL(currentObjectUrl);
    currentObjectUrl = undefined;
  }
}

Revoke object URLs when they are no longer needed to release the associated resource (see MDN’s revokeObjectURL() documentation).

3. Direct image versus fetch and Blob

Approach Use it when Important details
img.src = url You only need to show the image. Smallest implementation; browser handles loading and decoding.
fetch() → blob() → createObjectURL() You need status checks, headers, authentication, byte inspection, or transformations. Handle response.ok; revoke the object URL during cleanup.
Canvas You must resize, filter, draw, or read pixels. Cross-origin images need CORS; otherwise the canvas becomes tainted.

4. Cross-origin images and CORS

A remote image can usually be displayed in an <img> without giving JavaScript access to its pixels. Pixel access is different: the image server must explicitly allow your origin with CORS, and the image must request CORS before src is assigned.

const img = new Image();
img.crossOrigin = 'anonymous'; // Set before src.

img.onload = () => {
  const canvas = document.querySelector('canvas');
  canvas.width = img.naturalWidth;
  canvas.height = img.naturalHeight;
  const context = canvas.getContext('2d');
  context.drawImage(img, 0, 0);

  // These require a CORS-approved image.
  context.getImageData(0, 0, canvas.width, canvas.height);
};

img.onerror = () => console.error('Image or CORS request failed');
img.src = 'https://cdn.example.com/photo.jpg';

If the server does not authorize the requesting origin, the CORS request can fail. Drawing an image loaded from another origin without CORS approval taints the canvas; later calls such as getImageData(), toBlob(), or toDataURL() raise a SecurityError (see MDN’s CORS-enabled images guide).

Server-side CORS requirement

The image response must include an appropriate header such as Access-Control-Allow-Origin: https://your-site.example (or a suitable policy for your application). Adding crossOrigin in JavaScript cannot grant permission that the image server does not send.

5. Complete browser example

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Image URL preview</title>
  <style>
    img { max-width: 100%; height: auto; display: block; }
    #status { min-height: 1.5em; }
  </style>
</head>
<body>
  <p id="status" role="status"></p>
  <div id="preview"></div>
  <script>
    const url = 'https://example.com/photo.jpg';
    const status = document.querySelector('#status');
    const preview = document.querySelector('#preview');
    const img = new Image();

    img.alt = 'Example photo';
    img.addEventListener('load', () => {
      status.textContent = `Loaded ${img.naturalWidth} × ${img.naturalHeight}`;
    });
    img.addEventListener('error', () => {
      status.textContent = 'Unable to load this image.';
    });

    status.textContent = 'Loading…';
    preview.append(img);
    img.src = url;
  </script>
</body>
</html>

6. Useful options and edge cases

  • Alternative text: Set concise alt text that communicates the image’s purpose. Use alt="" for purely decorative images.
  • Dimensions: Set known width and height (or CSS aspect-ratio) to reduce layout movement.
  • Lazy loading: For below-the-fold content, set img.loading = 'lazy'. Do not lazy-load the main image if it delays the initial view.
  • Decoding: img.decoding = 'async' can let the browser decode without blocking other work.
  • Responsive images: Use srcset and sizes when you control multiple resolutions.
  • Data URLs: A data: URL embeds bytes directly but can increase HTML and memory size.
  • Authentication: A protected image may require credentials or a signed URL. A direct img request cannot add arbitrary authorization headers; fetch the bytes from an allowed endpoint instead.
  • Referrer policy: If the destination’s access policy depends on the referrer, set an appropriate referrerPolicy value before loading.
  • Content Security Policy: Your site’s img-src policy must allow the image origin, or the browser will block it.
  • Object URL lifetime: Blob URLs are temporary browser URLs. Revoke them after replacement or removal.

7. Troubleshooting

Symptom Likely cause Fix
Nothing appears The element was never appended, or the selector returned null. Check document.querySelector() and append the image after the container exists.
error event fires DNS failure, timeout, 4xx/5xx response, invalid bytes, or blocked request. Open the URL directly, inspect the Network panel, and verify the response content type and status.
Fetch resolves but image display fails You did not check response.ok, or the response is HTML/error text instead of image bytes. Check status and Content-Type before calling blob().
Canvas throws SecurityError The image was loaded without CORS approval and tainted the canvas. Configure server CORS and set crossOrigin before src; otherwise do not read canvas pixels.
CORS request fails after adding crossOrigin The server does not send a matching Access-Control-Allow-Origin header. Change the server or use a same-origin backend proxy you control. A client-side flag cannot bypass CORS.
Blob previews consume memory Object URLs remain alive after images are replaced. Store each URL and call URL.revokeObjectURL() during cleanup.
Image is stretched CSS dimensions do not match the intrinsic aspect ratio. Use height: auto, an aspect ratio, or the image’s natural dimensions.
Works locally but not in production Different origin, CSP, HTTPS, authentication, or referrer policy. Compare production request headers and browser console messages with local behavior.

8. Performance, reliability, and security

  • Choose the smallest path: Direct img.src avoids an extra JavaScript step when you only need visual display. There is no universal performance winner for every browser and network condition.
  • Avoid duplicate downloads: Do not fetch an image and then assign the original URL to another image unless you intentionally need both requests.
  • Handle failures explicitly: Use load/error handlers for UI state and check response.ok for Fetch.
  • Limit untrusted URLs: If users provide URLs, validate allowed protocols and consider an allowlist to reduce tracking, internal-network access, and unexpected data transfer.
  • Use HTTPS: A secure page can block insecure image requests as mixed content.
  • Protect privacy: Loading a third-party image can reveal a visitor’s IP address and request metadata to that host.
  • Manage large files: Prefer appropriately sized images; fetching a huge source only to display a thumbnail wastes bandwidth and memory.

9. Or skip the browser setup

If your goal is to obtain a clean screenshot of a page or image URL rather than build browser loading code, ScreenshotNeo returns a PNG, JPEG, WebP, or PDF from one request. See the ScreenshotNeo documentation for all options.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

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)

Node.js

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(`HTTP ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
require('node:fs').writeFileSync('shot.webp', bytes);

ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots each month without a card; paid plans start at $5 for 3,000.

Create a free ScreenshotNeo account to get 1,000 screenshots per month with no card.

10. FAQ

Can I set an image URL directly in JavaScript?

Yes. Assign the URL to img.src, set alt, and append the element.

Why does an image work in an <img> but fail on canvas?

Displaying an image does not grant pixel access. Canvas reads require a CORS-approved response and crossOrigin set before src.

When should I use a Blob URL?

Use it when you need to inspect status or headers, authenticate through Fetch, or transform the downloaded bytes before display.

Do Blob URLs need cleanup?

Yes. Call URL.revokeObjectURL() when the image is replaced or removed.

Does a filename ending in .jpg guarantee a JPEG?

No. The response headers and actual bytes determine whether the browser can decode the resource.