Skip to content

Embed: Metadata API with custom HTML/CSS

When the SDK is too opinionated and the iframe parameter is too provider-specific, you can render previews entirely from your own markup. The metadata API gives you the raw fields — title, description, image, logo, palette — and you decide everything else.
This is the right path when:
  • You need previews to match an existing design system exactly.
  • You want server-rendered HTML with no client-side JavaScript.
  • You're embedding inside an email, RSS feed, or static site generator.
  • You need to A/B test layouts without changing your data layer.
Want ready-made layouts to paste into Cursor or Claude Code? See generate custom previews with AI — six recipes (hero card, one-line, tweet-style, telegram-style, notification, chat bubble) built on the same payload this page documents.

The minimum metadata call

A bare metadata request returns everything most previews need:

The following examples show how to use the Microlink API with CLI, cURL, JavaScript, Python, Ruby, PHP & Golang, targeting 'https://stripe.com' URL:

CLI Microlink API example

microlink https://stripe.com

cURL Microlink API example

curl -G "https://api.microlink.io" \
  -d "url=https://stripe.com"

JavaScript Microlink API example

import mql from '@microlink/mql'

const { data } = await mql('https://stripe.com')

Python Microlink API example

import requests

url = "https://api.microlink.io/"

querystring = {
    "url": "https://stripe.com"
}

response = requests.get(url, params=querystring)

print(response.json())

Ruby Microlink API example

require 'uri'
require 'net/http'

base_url = "https://api.microlink.io/"

params = {
  url: "https://stripe.com"
}

uri = URI(base_url)
uri.query = URI.encode_www_form(params)

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Get.new(uri)
response = http.request(request)

puts response.body

PHP Microlink API example

<?php

$baseUrl = "https://api.microlink.io/";

$params = [
    "url" => "https://stripe.com"
];

$query = http_build_query($params);
$url = $baseUrl . '?' . $query;

$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_URL => $url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => "",
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => "GET"
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
    echo "cURL Error #: " . $err;
} else {
    echo $response;
}

Golang Microlink API example

package main

import (
    "fmt"
    "net/http"
    "net/url"
    "io"
)

func main() {
    baseURL := "https://api.microlink.io"

    u, err := url.Parse(baseURL)
    if err != nil {
        panic(err)
    }
    q := u.Query()
    q.Set("url", "https://stripe.com")
    u.RawQuery = q.Encode()

    req, err := http.NewRequest("GET", u.String(), nil)
    if err != nil {
        panic(err)
    }

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }

    fmt.Println(string(body))
}
The response payload looks like this:
"data": {
  "title": "Stripe | Financial Infrastructure to Grow Your Revenue",
  "description": "Stripe is a financial services platform that helps all types of businesses accept payments, build flexible billing models, and manage money movement.",
  "url": "https://stripe.com/",
  "publisher": "stripe.com",
  "lang": "en",
  "image": {
    "url": "https://images.stripeassets.com/fzn2n1nzq965/XtX984S1GJVsVOXFC7kMu/01988281e867728dfb09aa7793a6e3b9/Stripe.jpg?q=80",
    "type": "jpg",
    "size": 312818,
    "height": 1024,
    "width": 2048,
    "size_pretty": "313 kB"
  },
  "date": "2026-05-19T13:38:48.000Z",
  "author": "Kurtis Moyer, Lead Product Manager of Payments, Mindbody",
  "logo": {
    "url": "https://images.stripeassets.com/fzn2n1nzq965/4vVgZi0ZMoEzOhkcv7EVwK/8cce6fdcf2733b2ec8e99548908847ed/favicon.png?w=180&h=180",
    "type": "png",
    "size": 3143,
    "height": 180,
    "width": 180,
    "size_pretty": "3.14 kB"
  }
},
The default response includes title, description, image, logo, publisher, and url.
Read those fields directly from data and pass them into your template. No SDK install, no script tag, no iframe.

Render a custom card from JSON

Fetch the metadata server-side, then render whatever HTML you want:
import mql from '@microlink/mql'

export async function renderCard (url) {
  const { data } = await mql(url)

  return `
    <a class="link-card" href="${data.url}" rel="noopener noreferrer">
      <img class="link-card__image" src="${data.image.url}" alt="" loading="lazy" />
      <div class="link-card__body">
        <span class="link-card__publisher">${data.publisher ?? ''}</span>
        <h3 class="link-card__title">${data.title}</h3>
        <p class="link-card__description">${data.description ?? ''}</p>
      </div>
    </a>
  `
}
Pair it with a stylesheet — no framework required:
.link-card {
  display: grid;
  grid-template-columns: 200px 1fr;
  border: 1px solid #e1e8ed;
  border-radius: 12px;
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  background: #fff;
}

.link-card__image { width: 100%; height: 100%; object-fit: cover; }
.link-card__body { padding: 16px; }
.link-card__publisher { font-size: 11px; text-transform: uppercase; color: #6b7280; }
.link-card__title { margin: 6px 0 4px; font-size: 16px; font-weight: 700; }
.link-card__description {
  font-size: 13px;
  color: #4b5563;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
The same data can power any layout. See generate custom previews with AI for ready-made recipes (one-line, hero, tweet-style, notification, chat bubble).

Direct embed — no JSON parsing

If you only need a single asset URL (the image, the logo, the screenshot), use embed to make the API URL behave like that asset:
<img
  src="https://api.microlink.io?url=https://stripe.com&embed=image.url"
  alt="Stripe"
  loading="lazy"
/>
The same trick works for the logo:
<img src="https://api.microlink.io?url=https://stripe.com&embed=logo.url" alt="" />
Or a real screenshot when og:image is missing or low-quality:
<img
  src="https://api.microlink.io?url=https://stripe.com&screenshot&meta=false&embed=screenshot.url"
  alt="Stripe homepage"
/>
See the embed reference for every supported field. The dot notation (screenshot.url, image.url, logo.url, pdf.url, video.url) follows the response payload.

Open Graph and social cards

A common production use case — generate dynamic OG images for any page:
<meta
  property="og:image"
  content="https://api.microlink.io?url=https://your-site.com/blog/post&screenshot&meta=false&embed=screenshot.url"
/>
<meta
  name="twitter:image"
  content="https://api.microlink.io?url=https://your-site.com/blog/post&screenshot&meta=false&embed=screenshot.url"
/>
Every share on Twitter, Slack, Discord, or LinkedIn now gets a fresh capture of the page. Combined with caching, those images are served from the edge instead of regenerating per share.

Add brand colors with palette

For UI that adapts to each link's brand, request palette: true:

The following examples show how to use the Microlink API with CLI, cURL, JavaScript, Python, Ruby, PHP & Golang, targeting 'https://stripe.com' URL with 'palette' API parameter:

CLI Microlink API example

microlink https://stripe.com&palette

cURL Microlink API example

curl -G "https://api.microlink.io" \
  -d "url=https://stripe.com" \
  -d "palette=true"

JavaScript Microlink API example

import mql from '@microlink/mql'

const { data } = await mql('https://stripe.com', {
  palette: true
})

Python Microlink API example

import requests

url = "https://api.microlink.io/"

querystring = {
    "url": "https://stripe.com",
    "palette": "true"
}

response = requests.get(url, params=querystring)

print(response.json())

Ruby Microlink API example

require 'uri'
require 'net/http'

base_url = "https://api.microlink.io/"

params = {
  url: "https://stripe.com",
  palette: "true"
}

uri = URI(base_url)
uri.query = URI.encode_www_form(params)

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Get.new(uri)
response = http.request(request)

puts response.body

PHP Microlink API example

<?php

$baseUrl = "https://api.microlink.io/";

$params = [
    "url" => "https://stripe.com",
    "palette" => "true"
];

$query = http_build_query($params);
$url = $baseUrl . '?' . $query;

$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_URL => $url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => "",
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => "GET"
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
    echo "cURL Error #: " . $err;
} else {
    echo $response;
}

Golang Microlink API example

package main

import (
    "fmt"
    "net/http"
    "net/url"
    "io"
)

func main() {
    baseURL := "https://api.microlink.io"

    u, err := url.Parse(baseURL)
    if err != nil {
        panic(err)
    }
    q := u.Query()
    q.Set("url", "https://stripe.com")
    q.Set("palette", "true")
    u.RawQuery = q.Encode()

    req, err := http.NewRequest("GET", u.String(), nil)
    if err != nil {
        panic(err)
    }

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }

    fmt.Println(string(body))
}
Each image and logo gets palette, background_color, color, and alternative_color fields with WCAG-aware contrast.
Use those fields directly:
const { data } = await mql('https://stripe.com', { palette: true })

card.style.background = data.image.background_color
card.style.color = data.image.color
card.querySelector('.accent-bar').style.background = data.image.palette[0]
See the palette reference for details.

Filter the JSON response

If you only need a few fields, filter reduces the payload to exactly those:

The following examples show how to use the Microlink API with CLI, cURL, JavaScript, Python, Ruby, PHP & Golang, targeting 'https://stripe.com' URL with 'filter' API parameter:

CLI Microlink API example

microlink https://stripe.com&filter=title,description,image.url,logo.url

cURL Microlink API example

curl -G "https://api.microlink.io" \
  -d "url=https://stripe.com" \
  -d "filter=title,description,image.url,logo.url"

JavaScript Microlink API example

import mql from '@microlink/mql'

const { data } = await mql('https://stripe.com', {
  filter: "title,description,image.url,logo.url"
})

Python Microlink API example

import requests

url = "https://api.microlink.io/"

querystring = {
    "url": "https://stripe.com",
    "filter": "title,description,image.url,logo.url"
}

response = requests.get(url, params=querystring)

print(response.json())

Ruby Microlink API example

require 'uri'
require 'net/http'

base_url = "https://api.microlink.io/"

params = {
  url: "https://stripe.com",
  filter: "title,description,image.url,logo.url"
}

uri = URI(base_url)
uri.query = URI.encode_www_form(params)

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Get.new(uri)
response = http.request(request)

puts response.body

PHP Microlink API example

<?php

$baseUrl = "https://api.microlink.io/";

$params = [
    "url" => "https://stripe.com",
    "filter" => "title,description,image.url,logo.url"
];

$query = http_build_query($params);
$url = $baseUrl . '?' . $query;

$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_URL => $url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => "",
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => "GET"
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
    echo "cURL Error #: " . $err;
} else {
    echo $response;
}

Golang Microlink API example

package main

import (
    "fmt"
    "net/http"
    "net/url"
    "io"
)

func main() {
    baseURL := "https://api.microlink.io"

    u, err := url.Parse(baseURL)
    if err != nil {
        panic(err)
    }
    q := u.Query()
    q.Set("url", "https://stripe.com")
    q.Set("filter", "title,description,image.url,logo.url")
    u.RawQuery = q.Encode()

    req, err := http.NewRequest("GET", u.String(), nil)
    if err != nil {
        panic(err)
    }

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }

    fmt.Println(string(body))
}
Useful for high-volume preview pipelines where every byte over the wire counts.
See the filter reference for dot-notation rules.

Keep credentials safe in markup

The embed URL is publicly visible in HTML, CSS, and <meta> tags. Never put an API key, cookie, or proxy URL into a public embed:
  • For unauthenticated targets, use the free https://api.microlink.io endpoint.
  • For authenticated targets, render the URL server-side and fetch the asset through your own backend, or front it with / .
For the full security model, see private pages and proxy.

Choose between metadata API and the other approaches

If you needUse
Server-rendered HTML, no client-side JS, full controlMetadata API + custom HTML/CSS (this page)
Pre-built layouts and prompts to feed your AI coding assistantGenerate custom previews with AI
A drop-in component with fetching, lazy-loading, and theming built inSDK
The provider's interactive player (real YouTube embed, Spotify track)iframe parameter
Mixing them is fine — the metadata API powers all four.

Next step

If you'd rather have your AI assistant write the markup so it matches your existing design system, see generate custom previews with AI.