How LinkMetadata analyzes preview images, marks CORS safety, and proxies images for browser cards.
Link previews usually need images from another domain. That creates two separate concerns:
When a page exposes an image through Open Graph or Twitter Card tags, LinkMetadata fetches the image and returns structured metadata.
{
"image": {
"url": "https://example.com/card.png",
"type": "image/png",
"width": 1200,
"height": 630,
"size": 84214,
"cors_safe": false
}
}
Some fields can be null. For example, a server may omit Content-Length, block image inspection, redirect to an unsupported response, or stream an image whose dimensions cannot be determined from the first bytes read.
cors_safe meanscors_safe describes whether the image response includes permissive browser CORS headers. Today, LinkMetadata marks an image as CORS-safe when the image response includes:
Access-Control-Allow-Origin: *
If the field is false or missing, do not assume browser JavaScript can safely fetch, inspect, or reuse the image across origins. You can still store and display the url, but direct browser access may be limited.
The preview component makes this decision for you:
image.url is proxied unless image.cors_safe is true.favicon.url is proxied only when favicon.cors_safe is explicitly false.data: image URLs are used directly and are not proxied.If an image still fails to load, the component hides the image area. If a favicon fails, only the favicon is hidden.
Use the image proxy when you are building your own renderer and want a browser-friendly image URL.
GET https://api.linkmetadata.com/v1/cors-proxy?url={encoded image URL}
The proxy accepts only HTTP and HTTPS image URLs. It verifies the upstream Content-Type starts with image/ and checks the response bytes before streaming the image back with permissive CORS headers.
Supported proxied formats are JPEG, PNG, GIF, and WebP.
The proxy is part of the free public API and shares the same per-IP limit: 20 requests per 10 seconds, followed by a 10-second block when the limit is reached.
const imageUrl = "https://example.com/card.png";
const proxyUrl = new URL("https://api.linkmetadata.com/v1/cors-proxy");
proxyUrl.searchParams.set("url", imageUrl);
const response = await fetch(proxyUrl);
if (!response.ok) {
throw new Error(`Image proxy failed with ${response.status}`);
}
const blob = await response.blob();
const objectUrl = URL.createObjectURL(blob);
document.querySelector("img").src = objectUrl;
For normal image tags, you can also use the proxied URL directly:
<img
src="https://api.linkmetadata.com/v1/cors-proxy?url=https%3A%2F%2Fexample.com%2Fcard.png"
alt="">
Successful proxy responses include:
Access-Control-Allow-Origin: *
Cache-Control: public, max-age=86400
Content-Type: image/png
Common error cases include:
| Status | Reason |
|---|---|
400 | Missing URL, invalid URL, unsupported protocol, non-image content, empty response, or unsupported image bytes. |
| Upstream status | The remote image request failed, for example 404 or 403. |
| Situation | Recommended approach |
|---|---|
You use <link-metadata-preview> | Let the component decide when to proxy. |
| You render your own card in the browser | Use image.url directly when cors_safe is true; otherwise use /v1/cors-proxy. |
| You render server-side only | Use the metadata URL directly unless your frontend needs browser-side CORS access. |
| You need dimensions or MIME type | Read image.width, image.height, and image.type from the metadata response; do not re-fetch unless your app needs to. |
The proxy is for public preview images. Do not use it for private files, authenticated assets, or arbitrary non-image downloads.