# LinkMetadata Full Documentation This file concatenates the public LinkMetadata documentation, product pages, and generated API reference for LLM ingestion. --- # LinkMetadata LinkMetadata is a free API and browser component for extracting link-preview metadata from public URLs. ## What it returns - Normalized title, description, canonical URL, image, favicon, and page type. - Raw Open Graph and Twitter Card tags. - Readable safety tags: `adult`, `security_threat`, and `suspicious_domain`. ## API The metadata API is available at `https://api.linkmetadata.com/v1/metadata`. ```js const params = new URLSearchParams({ url: "https://example.com/article" }); const response = await fetch(`https://api.linkmetadata.com/v1/metadata?${params}`); const metadata = await response.json(); ``` The API is free to use. Requests are limited to 20 per 10 seconds per IP address, with a 10-second block when the limit is reached. ## Component ```html ``` ## Key documents - [Get started](https://linkmetadata.com/docs/tutorials/get-started.md) - [Metadata response](https://linkmetadata.com/docs/reference/metadata-response.md) - [Component reference](https://linkmetadata.com/docs/reference/component.md) - [OpenAPI JSON](https://linkmetadata.com/openapi.json) --- # LinkMetadata Documentation Learn LinkMetadata through task-focused tutorials, practical how-to guides, reference material, and conceptual explanations. ## Documentation sections ### Tutorials Start here when you are learning the product for the first time. - [Build your first link preview](https://linkmetadata.com/docs/tutorials/get-started.md): Add the LinkMetadata component to a page and fetch metadata from the API. ### How-to Guides Complete specific tasks once you know the basics. - [Customize the preview component](https://linkmetadata.com/docs/how-to/customize-the-preview-component.md): Change the card theme, layout, visibility, and styling. - [Render pre-fetched metadata](https://linkmetadata.com/docs/how-to/render-prefetched-metadata.md): Use data-meta to render cached or server-rendered metadata without another API request. - [Use LinkMetadata with React](https://linkmetadata.com/docs/how-to/use-with-react.md): Add the preview component to React or fetch metadata for a custom React card. - [Use LinkMetadata with Vue](https://linkmetadata.com/docs/how-to/use-with-vue.md): Add the preview component to Vue or fetch metadata for a custom Vue card. - [Use LinkMetadata with Svelte](https://linkmetadata.com/docs/how-to/use-with-svelte.md): Add the preview component to Svelte or fetch metadata for a custom Svelte card. - [Use LinkMetadata with Astro](https://linkmetadata.com/docs/how-to/use-with-astro.md): Add the preview component to Astro or fetch metadata during Astro rendering. ### Reference Look up component options, response fields, and API behavior. - [Component reference](https://linkmetadata.com/docs/reference/component.md): Attributes, CSS variables, and stable selectors for the preview component. - [Metadata API reference](https://linkmetadata.com/docs/reference/metadata-response.md): Request parameters, response fields, cache behavior, and errors for the metadata endpoint. ### Explanation Understand how metadata extraction, caching, images, and safety tags work. - [How LinkMetadata works](https://linkmetadata.com/docs/explanation/how-linkmetadata-works.md): How metadata extraction, caching, images, and safety tags fit together. - [Metadata extraction rules](https://linkmetadata.com/docs/explanation/metadata-extraction-rules.md): How LinkMetadata chooses titles, descriptions, URLs, images, favicons, and raw tag maps. - [Images and CORS](https://linkmetadata.com/docs/explanation/images-and-cors.md): How LinkMetadata analyzes preview images, marks CORS safety, and proxies images for browser cards. - [Safety tags](https://linkmetadata.com/docs/explanation/safety-tags.md): What safety tags mean, how they are produced, and how to use them in product decisions. ## API - [API Reference](https://linkmetadata.com/docs/api-reference.md) - [OpenAPI JSON](https://linkmetadata.com/openapi.json) --- --- title: "Build your first link preview" description: "Add the LinkMetadata component to a page and fetch metadata from the API." navTitle: "First link preview" category: "tutorials" date: "2025-06-04" tags: ["tutorials","getting-started"] published: true --- LinkMetadata gives you two ways to work with URLs: - Use the **preview component** when you want a ready-made link card. - Use the **metadata API** when you want JSON for your own UI. The API is free to use and does not require signup or API keys. Requests are limited to 20 per 10 seconds per IP address; when the limit is reached, that IP is blocked for 10 seconds. ## Add the component Load the component once on the page: ```html ``` Then add a preview wherever you want the card to appear: ```html ``` Multiple previews can appear on the same page. The component fetches metadata, renders a link card, and falls back to a plain link if metadata cannot be loaded. ## Fetch metadata directly Use the API when you want to render your own UI: ```js const params = new URLSearchParams({ url: "https://linkmetadata.com" }); const response = await fetch(`https://api.linkmetadata.com/v1/metadata?${params}`); const metadata = await response.json(); ``` The API is CORS-enabled, so browser apps can call it directly. ## Understand safety tags Responses include `safety_tags`, an array of readable labels. Possible values are `adult`, `security_threat`, and `suspicious_domain`. The array is empty when no tag applies or lookup fails. For guidance on how to use these labels in your product, see [Safety tags](/docs/explanation/safety-tags). ## Next steps - Customize the card in [Customize the preview component](/docs/how-to/customize-the-preview-component). - Render cached metadata with [Render pre-fetched metadata](/docs/how-to/render-prefetched-metadata). - Use LinkMetadata with [React](/docs/how-to/use-with-react), [Vue](/docs/how-to/use-with-vue), [Svelte](/docs/how-to/use-with-svelte), or [Astro](/docs/how-to/use-with-astro). - Look up every supported component option in [Component reference](/docs/reference/component). - Explore the generated [API Reference](/docs/api-reference). --- --- title: "Customize the preview component" description: "Change the card theme, layout, visibility, and styling." navTitle: "Customize component" category: "how-to" date: "2025-06-17" tags: ["how-to","component"] published: true --- Use attributes for behavior and CSS variables for presentation. ## Choose a preset ```html ``` Supported themes are `light`, `dark`, and `card`. ## Change the layout ```html ``` If `data-orientation` is omitted, the card uses a horizontal layout by default and switches to vertical on narrow screens. ## Hide optional elements ```html ``` ## Style one card ```html ``` ## Style every card ```html ``` CSS variables inherit into the component's shadow root, so global design tokens work as expected. ## Use a custom endpoint ```html ``` The component appends the encoded `data-url` to `data-api-endpoint`. For every attribute, CSS variable, and stable selector, see [Component reference](/docs/reference/component). --- --- title: "Render pre-fetched metadata" description: "Use data-meta to render cached or server-rendered metadata without another API request." navTitle: "Render data-meta" category: "how-to" date: "2026-06-20" tags: ["how-to","component","ssr"] published: true --- Use `data-meta` when your app already has metadata from your backend, cache, or server-side render. ## Render from JSON ```html ``` When `data-meta` is present, the component renders it directly and skips its own API request. ## Keep the original display URL If your stored metadata uses a canonical URL but you want to display the original host, include `data-original-url`: ```html ``` ## Update at runtime `data-meta` is reactive. Replacing the attribute re-renders the card: ```js const preview = document.querySelector("link-metadata-preview"); preview.dataMeta = { title: "Updated title", description: "Updated description", url: "https://example.com/updated", image: { url: null }, favicon: { url: null }, og: {}, twitter: {}, safety_tags: [] }; ``` Remove `data-meta` when you want the component to fetch fresh metadata from `data-url`. --- --- title: "Use LinkMetadata with React" description: "Add the preview component to React or fetch metadata for a custom React card." navTitle: "React" category: "how-to" date: "2026-06-20" tags: ["how-to","react","component"] published: true --- Use the hosted web component when you want a ready-made card. Use the metadata API when you want to render your own React UI. ## Use the preview component Load the component once in `index.html` or your app shell: ```html ``` Then render the custom element from React: ```jsx export function LinkPreview({ url }) { return ( ); } ``` If TypeScript does not recognize the custom element, add a small declaration file: ```ts // src/linkmetadata.d.ts declare namespace JSX { interface IntrinsicElements { "link-metadata-preview": { "data-url"?: string; "data-theme"?: "light" | "dark" | "card"; "data-meta"?: string; "data-show-branding"?: "true" | "false"; }; } } ``` ## Fetch metadata yourself Use the Fetch API when you want full control over markup, loading states, and errors. ```jsx import { useEffect, useState } from "react"; export function CustomLinkPreview({ url }) { const [metadata, setMetadata] = useState(null); const [error, setError] = useState(null); useEffect(() => { const controller = new AbortController(); const params = new URLSearchParams({ url }); async function loadMetadata() { try { const response = await fetch( `https://api.linkmetadata.com/v1/metadata?${params}`, { signal: controller.signal } ); if (!response.ok) { throw new Error(`Metadata request failed with ${response.status}`); } setMetadata(await response.json()); } catch (err) { if (err.name !== "AbortError") setError(err); } } loadMetadata(); return () => controller.abort(); }, [url]); if (error) return {url}; if (!metadata) return Loading preview...; return ( {metadata.title || metadata.url} {metadata.description &&

{metadata.description}

}
); } ``` The API is free to use and rate limited per IP address. For the full response contract, see [Metadata API reference](/docs/reference/metadata-response). --- --- title: "Use LinkMetadata with Vue" description: "Add the preview component to Vue or fetch metadata for a custom Vue card." navTitle: "Vue" category: "how-to" date: "2026-06-20" tags: ["how-to","vue","component"] published: true --- Vue can render the LinkMetadata web component directly, or you can call the API with `fetch` and build your own card. ## Configure the custom element If you use Vue with Vite, tell Vue that `link-metadata-preview` is a browser custom element: ```ts // vite.config.ts import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; export default defineConfig({ plugins: [ vue({ template: { compilerOptions: { isCustomElement: (tag) => tag === "link-metadata-preview" } } }) ] }); ``` Load the component script once in `index.html`: ```html ``` Then use it in a Vue template: ```vue ``` ## Fetch metadata yourself Use the Fetch API when you want to render a custom Vue component. ```vue ``` The API is free to use and rate limited per IP address. For image proxy behavior, see [Images and CORS](/docs/explanation/images-and-cors). --- --- title: "Use LinkMetadata with Svelte" description: "Add the preview component to Svelte or fetch metadata for a custom Svelte card." navTitle: "Svelte" category: "how-to" date: "2026-06-20" tags: ["how-to","svelte","component"] published: true --- Svelte can render the LinkMetadata custom element directly. Use the API directly when you want your own markup. ## Use the preview component Load the component in `app.html`, a layout, or the page head: ```svelte ``` For a reusable component: ```svelte ``` ## Fetch metadata yourself Use the Fetch API when you want to handle loading, errors, and rendering in Svelte. ```svelte {#if metadata} {metadata.title || metadata.url} {#if metadata.description}

{metadata.description}

{/if}
{:else if error} {url} {:else} Loading preview... {/if} ``` In SvelteKit, move the same `fetch` call into a `load` function when you want metadata before the page renders. The API is free to use and rate limited per IP address. For complete field details, see [Metadata API reference](/docs/reference/metadata-response). --- --- title: "Use LinkMetadata with Astro" description: "Add the preview component to Astro or fetch metadata during Astro rendering." navTitle: "Astro" category: "how-to" date: "2026-06-20" tags: ["how-to","astro","component"] published: true --- Astro works well with LinkMetadata because you can either render the hosted web component or fetch metadata while Astro renders the page. ## Use the preview component Load the component script and add the custom element in any `.astro` page or layout: ```astro --- const url = "https://example.com/article"; --- ``` If many pages use previews, place the script in your shared layout instead of repeating it on every page. ## Fetch metadata during render Use the Fetch API in Astro frontmatter when you want to fetch once during server rendering or static generation, then pass the result to the component with `data-meta`. ```astro --- const url = "https://example.com/article"; const params = new URLSearchParams({ url }); const response = await fetch(`https://api.linkmetadata.com/v1/metadata?${params}`); if (!response.ok) { throw new Error(`Metadata request failed with ${response.status}`); } const metadata = await response.json(); --- ``` This avoids a second browser-side metadata request. The component still handles image rendering, fallback behavior, and later runtime updates. ## Render your own Astro markup For complete control, fetch metadata and render normal Astro HTML. ```astro --- const url = "https://example.com/article"; const params = new URLSearchParams({ url }); const response = await fetch(`https://api.linkmetadata.com/v1/metadata?${params}`); const metadata = response.ok ? await response.json() : null; --- {metadata ? ( {metadata.title || metadata.url} {metadata.description &&

{metadata.description}

}
) : ( {url} )} ``` The API is free to use and rate limited per IP address. For caching and error details, see [Metadata API reference](/docs/reference/metadata-response). --- --- title: "Component reference" description: "Attributes, CSS variables, and stable selectors for the preview component." navTitle: "Component" category: "reference" date: "2026-06-20" tags: ["reference","component"] published: true --- Load the component once, then use `` anywhere you need a card. ```html ``` The component renders inside its own shadow root, so its internal markup does not collide with your page CSS. Presentation is customized through attributes and CSS variables. ## Runtime behavior On connection, the component follows this order: 1. If `data-meta` contains valid JSON, render that metadata immediately and skip fetching. 2. Otherwise, if `data-url` exists, render a plain fallback link first. 3. Fetch metadata from the API and replace the fallback with a full preview card. If `data-meta` is present but invalid, the component logs a console error and falls back to `data-url` when one is available. ## Request lifecycle The default API request is: ```text https://api.linkmetadata.com/v1/metadata?url={encoded data-url} ``` The API is free to use. Requests made by the component count toward the public per-IP limit: 20 requests per 10 seconds, followed by a 10-second block when the limit is reached. Multiple component instances with the same request URL share one in-flight fetch. After that request settles, the shared entry is removed; long-lived caching is handled by the browser and the LinkMetadata API. When an instance is removed from the DOM, changes `data-url`, or switches to valid `data-meta`, it releases its active fetch. If no other instance is waiting for that same request, the component aborts the fetch. Stale responses are ignored, so a slow previous URL will not overwrite a newer card. ## Fallbacks and failures The component avoids showing technical errors to end users. If the metadata request fails, it keeps a plain linked fallback. If the preview image fails to load, the image area is hidden. If the favicon fails to load, only the favicon is hidden. Card titles use the first useful value from `title`, `og["og:title"]`, or `twitter["twitter:title"]`. If no title exists, the component promotes a short description to the title. If neither title nor description exists, it displays the hostname. Images that are not marked `cors_safe: true` are loaded through LinkMetadata's image proxy. Favicons are proxied only when `favicon.cors_safe` is explicitly `false`. For details on `cors_safe`, supported proxy formats, and proxy errors, see [Images and CORS](/docs/explanation/images-and-cors). ## Attributes | Attribute | Type / Values | Default | What it does | | ----------| ------------- | --------|------------- | | `data-url` | *string* | - | The page you want to preview. Required unless `data-meta` is provided. | | `data-theme` | `"light"`, `"dark"`, `"card"` | `"light"` | Picks one of the preset colour systems. | | `data-orientation` | `"horizontal"`, `"vertical"` | `"horizontal"` | Layout: image on the left vs. image on top. If omitted, narrow screens use vertical layout automatically. | | `data-show-image` | `"true"`, `"false"` | `"true"` | Show or hide the image. | | `data-show-favicon` | `"true"`, `"false"` | `"true"` | Show or hide the favicon next to the title. | | `data-show-branding` | `"true"`, `"false"` | `"true"` | Show or hide the "Preview by LinkMetadata.com" line. | | `data-max-width` | CSS length | `"500px"` | Set the maximum card width, for example `"720px"` or `"100%"`. | | `data-image-aspect-ratio` | CSS ratio or number | - | Override the image container aspect ratio, for example `"16/9"` or `"1.91"`. | | `data-original-url` | *string* | - | Display this hostname instead of the canonical metadata URL. | | `data-api-endpoint` | *string* | `https://api.linkmetadata.com/v1/metadata?url=` | Prefix used before the encoded `data-url`. | | `data-meta` | JSON string | - | Render pre-fetched metadata and skip the component's API request. | All attributes are reactive. Boolean display attributes are string based. Use `"false"` to disable a feature; any other value behaves like enabled. ## JavaScript properties Every observed attribute also has a camel-cased property. | Property | Attribute | | -------- | --------- | | `dataUrl` | `data-url` | | `dataTheme` | `data-theme` | | `dataOrientation` | `data-orientation` | | `dataShowImage` | `data-show-image` | | `dataShowFavicon` | `data-show-favicon` | | `dataShowBranding` | `data-show-branding` | | `dataOriginalUrl` | `data-original-url` | | `dataApiEndpoint` | `data-api-endpoint` | | `dataMaxWidth` | `data-max-width` | | `dataImageAspectRatio` | `data-image-aspect-ratio` | | `dataMeta` | `data-meta` | ```js const preview = document.querySelector("link-metadata-preview"); preview.dataUrl = "https://example.com/next"; preview.dataTheme = "card"; preview.dataShowImage = false; preview.dataMeta = { title: "Cached title", description: "Rendered without a client-side API request.", url: "https://example.com/cached", image: { url: null }, favicon: { url: null }, og: {}, twitter: {}, safety_tags: [] }; ``` Setting `dataMeta` to an object serializes it to JSON. Setting it to `null` removes `data-meta`; if `data-url` is present, the component fetches fresh metadata again. ## Responsive layout `data-orientation="horizontal"` puts the image beside the content. `data-orientation="vertical"` puts the image above the content. If `data-orientation` is omitted, the component uses horizontal layout on wider screens and switches to vertical below 480px. Changing layout, theme, image visibility, favicon visibility, branding visibility, max width, or image ratio re-renders the existing metadata without making another API request. ## Server-rendered metadata Use `data-meta` when your backend, cache, or server-side render already has metadata. The component renders the card without making its own API request and still responds to later attribute changes. For a complete example, see [Render pre-fetched metadata](/docs/how-to/render-prefetched-metadata). ## CSS Variables | Variable | Purpose | Default value | | -------- | ------- | ------------- | | `--lm-border` | Card border shorthand | `1px solid #d7dce0` | | `--lm-radius` | Corner radius | `8px` (`12px` in `card` theme) | | `--lm-bg` | Card background color | `#fff` | | `--lm-shadow` | Base box-shadow | `0 1px 3px 0 rgba(0,0,0,.1), 0 1px 2px 0 rgba(0,0,0,.06)` | | `--lm-hover-shadow` | Shadow on hover | Theme-specific | | `--lm-transition` | Card transition | `box-shadow 0.2s ease-in-out` | | `--lm-max-width` | Maximum card width | `500px` | | `--lm-font-family` | Card font stack | System UI stack | | `--lm-content-padding` | Text block padding | `12px` | | `--lm-content-padding-mobile` | Text block padding under 480px | `12px` | | `--lm-title-color` | Title text color | `#111` | | `--lm-title-size` | Title font size | `1em` | | `--lm-title-weight` | Title font weight | `600` | | `--lm-title-line-height` | Title line height | `1.3` | | `--lm-title-lines` | Maximum title lines | `2` | | `--lm-desc-color` | Description text color | `#444` | | `--lm-desc-size` | Description font size | `0.875em` | | `--lm-desc-line-height` | Description line height | `1.4` | | `--lm-desc-lines` | Maximum description lines | `2` | | `--lm-url-color` | URL line color | `#555` | | `--lm-url-size` | URL line font size | `0.75em` | | `--lm-branding-color` | Branding line color | `#777` | | `--lm-branding-size` | Branding font size | `0.65em` | | `--lm-branding-padding-top` | Space above branding | `8px` | | `--lm-fallback-bg` | Missing-image background | `#f0f0f0` | | `--lm-fallback-icon-color` | Missing-image icon color | `#aaa` | | `--lm-image-width` | Thumbnail width in horizontal layout | `120px` | | `--lm-image-aspect-ratio` | Thumbnail aspect ratio | `1.5/1` horizontal, `16/9` vertical | | `--lm-image-fit` | Thumbnail `object-fit` | `cover` | | `--lm-image-position` | Thumbnail `object-position` | `center` | | `--lm-favicon-size` | Favicon width and height | `16px` | | `--lm-favicon-gap` | Gap between favicon and title | `8px` | ## Stable Selectors These `.lm-*` selectors are part of the styling contract. | Selector | What it is | | -------- | ---------- | | `.lm-card` | The anchor that forms the whole card | | `.lm-image-container` | The thumbnail wrapper | | `.lm-default-icon-container` | Wrapper shown when image is missing | | `.lm-icon-fallback svg` | Chain-link SVG shown when image is missing | | `.lm-content` | Text column | | `.lm-title-wrapper` | Favicon and title row | | `.lm-favicon` | Favicon next to the title | | `.lm-title` | Title line | | `.lm-desc` | Description line | | `.lm-url` | Hostname line | | `.lm-branding` | Branding tagline | --- --- title: "Metadata API reference" description: "Request parameters, response fields, cache behavior, and errors for the metadata endpoint." navTitle: "Metadata API" category: "reference" date: "2026-06-20" tags: ["reference","api"] published: true --- The metadata endpoint turns one public URL into a normalized JSON object for previews, search indexes, moderation workflows, or your own UI. ```http GET https://api.linkmetadata.com/v1/metadata?url=https://example.com ``` ## Query parameters | Parameter | Required | Values | Description | | --------- | -------- | ------ | ----------- | | `url` | Yes | Absolute URL | Page to fetch and inspect. HTTP and HTTPS pages are the supported use case. | | `prefer` | No | `og`, `twitter` | Preferred source for the normalized fields. Defaults to `og`. | | `allow_fallback` | No | `true`, `false` | Reserved for fallback control. Current responses use normal fallback behavior. | `prefer` affects `title`, `description`, `url`, `image`, and `type`. It does not remove raw `og` or `twitter` data from the response. ```http GET https://api.linkmetadata.com/v1/metadata?url=https://example.com&prefer=twitter ``` ## Fetch from JavaScript Use the Fetch API from browsers, workers, or server runtimes: ```js const params = new URLSearchParams({ url: "https://example.com", prefer: "og" }); const response = await fetch(`https://api.linkmetadata.com/v1/metadata?${params}`); if (response.status === 429) { throw new Error("Rate limit reached. Wait 10 seconds before retrying."); } if (!response.ok) { throw new Error(`Metadata request failed with ${response.status}`); } const metadata = await response.json(); ``` ## Access and rate limits The API is free to use. It does not require authentication, signup, or API keys. Requests are rate limited per IP address: | Limit | Block window | | ----- | ------------ | | 20 requests per 10 seconds | 10 seconds | When an IP reaches the limit, further requests from that IP are blocked for 10 seconds. Client apps should avoid tight retry loops and wait before trying again. ## Successful response A successful request returns `200 OK` with `application/json`. | Field | Type | Description | | ----- | ---- | ----------- | | `title` | `string \| null` | Best title found for the page. | | `description` | `string \| null` | Best page description. | | `url` | `string` | Canonical or representative URL. | | `image` | `ImageMetadata` | Primary preview image metadata. | | `favicon` | `ImageMetadata` | Favicon metadata. | | `type` | `string \| null` | Open Graph type or Twitter card type. | | `og` | `Record` | Raw Open Graph tags found on the page. | | `twitter` | `Record` | Raw Twitter Card tags found on the page. | | `safety_tags` | `string[]` | Readable safety labels. Empty when no tag applies or lookup fails. | ## Field selection For normalized fields, LinkMetadata reads the preferred metadata family first, then falls back to other page data. | Field | Main sources | | ----- | ------------ | | `title` | `og:title` or `twitter:title`, then the HTML ``. | | `description` | `og:description` or `twitter:description`, then `<meta name="description">`. | | `url` | `og:url` or `twitter:url`, then `<link rel="canonical">`, then the requested URL. | | `image` | `og:image`, `og:image:secure_url`, `og:image:url`, `twitter:image`, or `twitter:image:src`. | | `type` | `og:type` or `twitter:card`. | | `favicon` | Best icon link on the page, then `/favicon.ico`. | If the target response is not HTML, the API still returns a normalized object with unavailable fields set to `null` where possible. For the full priority order, see [Metadata extraction rules](/docs/explanation/metadata-extraction-rules). ## Image metadata | Field | Type | Description | | ----- | ---- | ----------- | | `url` | `string \| null` | Absolute image URL, or `null` when none is available. | | `type` | `string \| null` | Image MIME type when known. | | `width` | `number \| null` | Width in pixels when known. | | `height` | `number \| null` | Height in pixels when known. | | `size` | `number \| null` | Image size in bytes when known. | | `cors_safe` | `boolean \| undefined` | Whether the image response includes permissive browser CORS headers. | Image analysis is best effort. A page can still have a useful `image.url` even when width, height, MIME type, or size is unavailable. For proxy behavior and browser CORS guidance, see [Images and CORS](/docs/explanation/images-and-cors). ## Safety tags Possible `safety_tags` values: | Tag | Meaning | | --- | ------- | | `adult` | Known adult content. | | `security_threat` | Known malware, phishing, or other threat domain. | | `suspicious_domain` | Parked domain, new high-entropy domain, or IP URL. | Treat safety tags as signals for product decisions, not as a complete security verdict. For recommended handling patterns, see [Safety tags](/docs/explanation/safety-tags). ## Redirects and errors Requests without `url` redirect to the same endpoint with a showcase URL. API clients should pass `url` explicitly instead of relying on this behavior. ```http HTTP/1.1 302 Found Location: https://api.linkmetadata.com/v1/metadata?url=https%3A%2F%2Fauthpi.com ``` Application errors use a JSON object with `code` and `message`. | Status | When it happens | | ------ | --------------- | | `400` | The request is malformed or validation fails. | | `429` | The per-IP rate limit was reached. Wait before retrying. | | `422` | The URL is valid, but the page could not be fetched or parsed. | | `500` | An unexpected server error occurred. | ```json { "code": 422, "message": "Failed to fetch https://example.com: fetch failed" } ``` ## Caching Successful metadata responses are cacheable for 12 hours. ```http Cache-Control: public, max-age=43200 ``` The cache key includes the full request URL, so different query strings can produce separate cached entries. For generated OpenAPI details, see [API Reference](/docs/api-reference). --- --- title: "How LinkMetadata works" description: "How metadata extraction, caching, images, and safety tags fit together." navTitle: "How it works" category: "explanation" date: "2026-06-20" tags: ["explanation","concepts"] published: true --- LinkMetadata turns a URL into a normalized metadata response that can power link cards, previews, and safety checks. ## Metadata sources The API fetches the target page and prioritizes common metadata formats: - Open Graph tags such as `og:title`, `og:description`, and `og:image`. - Twitter Card tags such as `twitter:title` and `twitter:image`. - HTML fallbacks such as `<title>`, meta descriptions, canonical links, and favicons. When a page has incomplete metadata, the API returns the best fields it can find and uses `null` where a field is unavailable. For exact title, description, image, URL, type, and favicon priority rules, see [Metadata extraction rules](/docs/explanation/metadata-extraction-rules). ## Images and CORS Browsers often block direct cross-origin image inspection. The API records image dimensions, MIME type, and size when possible, and marks whether an image appears `cors_safe`. The preview component can use LinkMetadata's CORS proxy for images that are not safe to fetch directly from the browser. For supported proxy formats, error behavior, and browser rendering guidance, see [Images and CORS](/docs/explanation/images-and-cors). ## Caching The API caches metadata responses at the edge. Repeated requests for the same URL can be served quickly without re-fetching the target page. The web component only deduplicates matching requests that are in flight at the same time. It does not keep a permanent in-memory metadata cache; browser and API cache headers handle long-lived caching. ## Safety tags The API checks domain intelligence and lightweight signals to populate `safety_tags`. These tags are designed for product decisions such as warnings, moderation queues, or filtering. Possible values are: - `adult` - `security_threat` - `suspicious_domain` No tag is a guarantee of safety. Treat the field as a useful signal rather than a complete security verdict. For tag meanings, signal sources, and recommended UX patterns, see [Safety tags](/docs/explanation/safety-tags). --- --- title: "Metadata extraction rules" description: "How LinkMetadata chooses titles, descriptions, URLs, images, favicons, and raw tag maps." navTitle: "Extraction rules" category: "explanation" date: "2026-06-20" tags: ["explanation","metadata"] published: true --- LinkMetadata keeps the raw metadata it finds, then builds normalized fields for the common preview use case. ## Raw tags vs normalized fields The `og` and `twitter` objects contain raw tag values from the page. They are useful when you need every source field. The top-level fields are normalized: - URLs are resolved to absolute URLs. - Open Graph, Twitter Card, and HTML fallbacks are merged. - Missing fields use `null` where possible. - Image and favicon fields include extra inspection data when available. ## Preferred source The `prefer` query parameter controls which metadata family is checked first for normalized fields. ```js const params = new URLSearchParams({ url: "https://example.com", prefer: "twitter" }); const response = await fetch(`https://api.linkmetadata.com/v1/metadata?${params}`); const metadata = await response.json(); ``` | `prefer` value | Priority | | -------------- | -------- | | `og` | Open Graph, then Twitter Card, then HTML fallbacks. | | `twitter` | Twitter Card, then Open Graph, then HTML fallbacks. | The default is `og`. Raw `og` and `twitter` objects are returned either way. ## Titles and descriptions For `title`, LinkMetadata checks: 1. Preferred metadata title: `og:title` or `twitter:title`. 2. The other metadata title. 3. HTML `<title>`. For `description`, LinkMetadata checks: 1. Preferred metadata description: `og:description` or `twitter:description`. 2. The other metadata description. 3. `<meta name="description">`. ## Canonical URL The normalized `url` field is selected in this order: 1. Preferred metadata URL: `og:url` or `twitter:url`. 2. The other metadata URL. 3. `<link rel="canonical">`. 4. The originally requested URL. Relative URLs are resolved against the requested page URL. ## Preview image For Open Graph images, LinkMetadata prefers `og:image:secure_url` on HTTPS pages, then `og:image`, then `og:image:url`. For Twitter images, it checks `twitter:image`, then `twitter:image:src`. The final `image.url` follows the `prefer` setting: | `prefer` value | Image priority | | -------------- | -------------- | | `og` | Best Open Graph image, then Twitter image. | | `twitter` | Twitter image, then best Open Graph image. | When an image URL is found, LinkMetadata fetches it to inspect MIME type, dimensions, size, redirect target, and CORS safety. That inspection is best effort, so `width`, `height`, `type`, or `size` may still be `null`. For image CORS behavior, see [Images and CORS](/docs/explanation/images-and-cors). ## Type The normalized `type` field uses the preferred metadata family first: - `og:type` when Open Graph wins. - `twitter:card` when Twitter Card wins. If the preferred family is missing, the other family is used as a fallback. ## Favicons LinkMetadata scores favicon candidates and keeps the best one found. | Candidate | Priority | | --------- | -------- | | `apple-touch-icon` or `apple-touch-icon-precomposed` | Highest | | `mask-icon`, SVG icons, or `sizes="any"` | High | | Generic `rel="icon"` | Medium | | `rel="shortcut icon"` | Low | | `/favicon.ico` | Fallback | When two candidates have the same priority, the later tag wins. ## Non-HTML responses If the target URL returns non-HTML content, LinkMetadata still returns a normalized response shape. Page text fields are usually `null`, raw tag maps are empty, and the favicon falls back to `/favicon.ico`. --- --- title: "Images and CORS" description: "How LinkMetadata analyzes preview images, marks CORS safety, and proxies images for browser cards." navTitle: "Images and CORS" category: "explanation" date: "2026-06-20" tags: ["explanation","images","cors"] published: true --- Link previews usually need images from another domain. That creates two separate concerns: - The API needs to inspect an image server-side to learn its MIME type, dimensions, size, and final URL. - Browser apps need to render that image without exposing users to broken previews or CORS surprises. ## Image metadata is best effort When a page exposes an image through Open Graph or Twitter Card tags, LinkMetadata fetches the image and returns structured metadata. ```json { "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. ## What `cors_safe` means `cors_safe` describes whether the image response includes permissive browser CORS headers. Today, LinkMetadata marks an image as CORS-safe when the image response includes: ```http 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. ## How the component uses the proxy 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. ## Proxy endpoint Use the image proxy when you are building your own renderer and want a browser-friendly image URL. ```text 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. ```js 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: ```html <img src="https://api.linkmetadata.com/v1/cors-proxy?url=https%3A%2F%2Fexample.com%2Fcard.png" alt=""> ``` ## Proxy responses Successful proxy responses include: ```http 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`. | ## When to use each option | 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. --- --- title: "Safety tags" description: "What safety tags mean, how they are produced, and how to use them in product decisions." navTitle: "Safety tags" category: "explanation" date: "2026-06-20" tags: ["explanation","safety"] published: true --- Safety tags are readable labels that describe URL-level signals. They are designed for product behavior such as warnings, moderation queues, filtering, or analytics. ```json { "safety_tags": ["adult", "security_threat"] } ``` An empty array means LinkMetadata did not return a tag for that URL. It does not prove the URL is safe. ## Current tags | Tag | Meaning | Typical use | | --- | ------- | ----------- | | `adult` | The parent domain is known for adult content. | Hide previews by default, require a click-through, or block in restricted contexts. | | `security_threat` | The parent domain is known for malware, phishing, or another security threat. | Block navigation, show a strong warning, or send to review. | | `suspicious_domain` | The URL or domain has suspicious signals, such as an IP URL, parked domain, or newly registered high-entropy domain. | Warn users, reduce ranking, or send to moderation. | Tags can be combined. For example, a domain can return both `adult` and `security_threat`. ## How tags are produced LinkMetadata checks the registrable parent domain instead of only the exact subdomain. For example, metadata for `blog.example.com` is evaluated against `example.com` where possible. `adult` and `security_threat` come from known-domain intelligence. `suspicious_domain` can come from: - an IP address used as the URL host; - parked-domain signals from nameservers; - a newly registered domain with high-entropy naming patterns. Domain registration and nameserver lookups may be cached. If safety lookup fails, the metadata request can still succeed with an empty `safety_tags` array. ## Recommended handling Treat tags as signals, not verdicts. | Situation | Recommended UX | | --------- | -------------- | | `security_threat` is present | Do not auto-open the URL. Show a strong warning or block the action. | | `adult` is present | Respect product policy, user age settings, and workspace controls before rendering previews. | | `suspicious_domain` is present | Add friction: show the destination host, disable rich media, or route to review. | | No tags are present | Continue normal rendering, but do not claim the link has been certified safe. | For moderation systems, store both the tags and the inspected URL. Tags can change over time as intelligence updates. ## Fetch and branch on tags ```js const params = new URLSearchParams({ url: "https://example.com/post" }); const response = await fetch(`https://api.linkmetadata.com/v1/metadata?${params}`); if (!response.ok) { throw new Error(`Metadata request failed with ${response.status}`); } const metadata = await response.json(); const tags = new Set(metadata.safety_tags); if (tags.has("security_threat")) { showSecurityWarning(metadata.url); } else if (tags.has("adult")) { showSensitiveContentGate(metadata); } else { renderPreview(metadata); } ``` ## Example policy ```js function getLinkDecision(tags) { if (tags.includes("security_threat")) { return "block"; } if (tags.includes("adult")) { return "gate"; } if (tags.includes("suspicious_domain")) { return "warn"; } return "allow"; } ``` This is only an example. Your app should choose stricter or looser behavior based on audience, legal requirements, and abuse risk. ## Limitations Safety tags do not inspect every page in real time for all possible harms. They are domain-level and signal-based. A compromised legitimate site may not be tagged immediately, and a newly seen malicious URL may need time before intelligence catches up. Use safety tags alongside your own policy, reporting flow, and abuse monitoring. --- # LinkMetadata API Free API for extracting link-preview metadata from public web pages. The API returns normalized title, description, image, favicon, canonical URL, raw Open Graph tags, raw Twitter Card tags, and readable safety tags. It also includes an image proxy endpoint for preview images that need permissive CORS headers. No authentication, signup, or API key is required. Rate limit: 20 requests per 10 seconds per IP address. When the limit is reached, that IP is blocked for 10 seconds before retrying. OpenAPI version: 3.0.0 API version: 1.0.0 Base URL: https://api.linkmetadata.com ## Endpoints ### GET /v1/metadata Extract metadata for a URL Fetches a public URL and returns normalized link-preview metadata. The response includes normalized fields for title, description, canonical URL, image, type, favicon, raw Open Graph tags, raw Twitter Card tags, and readable safety tags. URL fields are resolved to absolute URLs where possible. The API is free to use and does not require authentication, signup, or API keys. Requests are rate limited to 20 requests per 10 seconds per IP address. When the limit is reached, that IP is blocked for 10 seconds before retrying. ```http GET https://api.linkmetadata.com/v1/metadata?url=https%3A%2F%2Fexample.com%2Farticle&prefer=og&allow_fallback=true ``` #### Parameters - `url` (query, required, string): Absolute HTTP or HTTPS URL of the page to inspect. - `prefer` (query, optional, string): Preferred source for normalized title, description, url, image, and type fields. Use "og" to prefer Open Graph tags before Twitter Card tags. Use "twitter" to prefer Twitter Card tags before Open Graph tags. HTML title, meta description, canonical link, and favicon values remain fallback sources. - `allow_fallback` (query, optional, boolean): Reserved for fallback control. Current responses use normal fallback behavior regardless of this value. #### Responses - `200`: Metadata extracted successfully. - `302`: Redirecting to a default showcase URL because the 'url' parameter was missing. The 'Location' header will contain the redirect target. - `400`: Input Validation Error - `422`: The URL was valid, but the target page could not be fetched or processed successfully. - `429`: The per-IP rate limit was reached. Wait 10 seconds before retrying. - `500`: An unexpected server error occurred while processing the request. ### GET /v1/cors-proxy Proxy a preview image with CORS headers Fetches an HTTP or HTTPS image URL, verifies that the upstream response is an image, and streams it back with permissive CORS headers. This is intended for preview images returned by the metadata endpoint when direct browser access is blocked by CORS. Only JPEG, PNG, GIF, and WebP image bytes are accepted. The endpoint is free to use and shares the public per-IP rate limit: 20 requests per 10 seconds, followed by a 10-second block when the limit is reached. ```http GET https://api.linkmetadata.com/v1/cors-proxy?url=https%3A%2F%2Fexample.com%2Fcard.png ``` #### Parameters - `url` (query, required, string): Absolute HTTP or HTTPS image URL to proxy. Missing, invalid, and non-image URLs return JSON errors. #### Responses - `200`: Image proxied successfully. - `400`: The URL was missing, invalid, not HTTP/S, not an image, empty, or not a supported image format. - `429`: The per-IP rate limit was reached. Wait 10 seconds before retrying. - `default`: The upstream image request failed. ### GET /component.js Load the link preview web component Returns the browser custom element bundle for <link-metadata-preview>. Include this script on a page, then render the component with a data-url attribute or pre-fetched metadata in data-meta. ```http GET https://api.linkmetadata.com/component.js ``` #### Responses - `200`: JavaScript module containing the preview web component. ## Schemas ### ImageMetadata Structured metadata for the primary image associated with the page. URL is resolved to absolute. - `url` (string | null): Absolute URL of the image. Null if no suitable image is found. - `type` (string | null): MIME type of the image (e.g., 'image/png'). Derived primarily from 'og:image:type'. - `width` (number | null): Width of the image in pixels. Derived primarily from 'og:image:width'. - `height` (number | null): Height of the image in pixels. Derived primarily from 'og:image:height'. - `size` (number | null): Size of the image in bytes. Derived from the 'og:image:size' tag if available, otherwise null. - `cors_safe` (boolean): Optional flag indicating if the image URL is CORS-safe. If true, browser JavaScript can fetch it cross-origin. If false or undefined, direct browser access may be blocked. ### SafetyTag Readable URL safety signal. ### Metadata - `title` (string | null): The primary title for the page. Prioritizes preferred source (OG/Twitter), then falls back to the HTML <title> tag. Null if none found. - `description` (string | null): The primary description for the page. Prioritizes preferred source (OG/Twitter), then falls back to the HTML <meta name="description"> tag. Null if none found. - `url` (string): The canonical or representative URL for the page. Prioritizes preferred source (OG/Twitter), then <link rel="canonical">, then the originally fetched URL. Always an absolute URL. - `image` (ImageMetadata): Structured metadata for the primary image associated with the page. URL is resolved to absolute. - `type` (string | null): The type of the primary object on the page (e.g., 'website', 'article', 'summary_large_image'). Derived from 'og:type' or 'twitter:card' based on preference. - `favicon` (ImageMetadata | value): Structured metadata for the primary image associated with the page. URL is resolved to absolute. Structured metadata for the page's favicon. Prioritizes apple-touch-icon, mask-icon, SVG icons, then other <link rel="icon"> tags, and finally falls back to '/favicon.ico'. Null if none found. - `og` (object): A key-value map of all Open Graph (og:*) meta tags found on the page. Values are the raw 'content' attributes. - `twitter` (object): A key-value map of all Twitter Card (twitter:*) meta tags found on the page. Values are the raw 'content' attributes. - `safety_tags` (array): Human-readable safety tags associated with the URL. Possible values: adult, security_threat, suspicious_domain. Empty when no tag applies or safety lookup fails. ### Error - `code` (integer): HTTP status code associated with the error. - `message` (string): Human-readable error message. ### ProxyError - `error` (string): Human-readable proxy error message. --- # Terms & Privacy LinkMetadata provides a metadata and preview API for public URLs. Do not use the service to process private, confidential, or illegal content. ## Data handling API requests may include the URL being inspected, request metadata, and operational logs needed to provide, secure, and debug the service. ## Service terms The public API is free to use without a service-level agreement. Requests are limited to 20 per 10 seconds per IP address, with a 10-second block when the limit is reached. ## Contact For legal, privacy, or account questions, contact the LinkMetadata team through the project repository or published support channel.