ScreenshotNeo

BlogHow-to

How to Use Microlink Screenshots in a WordPress Website Preview Plugin

Build a WordPress link preview plugin with Microlink screenshots, safe URL handling, caching, and graceful error handling.

By the ScreenshotNeo team4 October 202611 min read

A WordPress website preview plugin can request a screenshot from Microlink by sending the target page URL with screenshot capture enabled. Microlink returns a JSON response containing a hosted screenshot asset URL and metadata. Your plugin can make the request with WordPress’s HTTP API, cache the result with Transients, and render the image URL. If all you need is an image source, Microlink also supports a direct-image response through its embed=screenshot.url option. See the Microlink API overview and WordPress safe HTTP request reference.

This guide implements a server-side PHP helper suitable for a WordPress plugin, explains the security and caching choices, and includes cURL, Python, and Node.js request examples. It assumes the plugin receives a URL from a user or editor; adapt the capability and exposure rules to your own product.

1. Choose the preview flow

Use the JSON response when the plugin needs metadata or wants to validate the screenshot result before rendering it. Use direct-image embedding when the page only needs an image URL and does not need to inspect JSON. The JSON flow gives your plugin a place to handle transport failures, non-success HTTP statuses, malformed data, or a missing screenshot field before displaying anything.

Choice Use it when Trade-off
JSON response You need the screenshot URL plus response status or other metadata. Your plugin must decode and validate the response.
Direct image embed You only need an image source for markup. You do not receive the normal JSON body in that request.

For a link card, a viewport screenshot is usually a compact preview. Use full-page capture when the entire page is the intended content. Use element capture when the card should show a particular region. These are product-design choices; image dimensions and capture time depend on the target page and selected options.

2. Add the PHP request and cache helper

Place the following functions in your plugin PHP file or an included module. This implementation uses wp_safe_remote_get() because the destination URL is user-controlled. It hashes the normalized request inputs for the transient key, checks for transport and HTTP errors, validates the JSON structure, and returns a WP_Error on failure.

<?php
/**
 * Return a Microlink screenshot URL for a page, caching successful responses.
 *
 * @param string $target_url The page to capture.
 * @param array  $options   Supported values: fullPage, type, quality, element.
 * @return string|WP_Error Screenshot asset URL or an error.
 */
function my_plugin_microlink_screenshot( $target_url, $options = array() ) {
    $target_url = esc_url_raw( trim( $target_url ) );

    if ( ! $target_url || ! wp_http_validate_url( $target_url ) ) {
        return new WP_Error( 'invalid_target_url', 'Enter a valid public HTTP or HTTPS URL.' );
    }

    $defaults = array(
        'fullPage' => false,
        'type'     => 'png',
    );
    $options = wp_parse_args( $options, $defaults );

    // Keep option values within the documented screenshot settings.
    $type = strtolower( (string) $options['type'] );
    if ( ! in_array( $type, array( 'png', 'jpeg' ), true ) ) {
        return new WP_Error( 'invalid_image_type', 'Screenshot type must be png or jpeg.' );
    }

    $params = array(
        'url'              => $target_url,
        'screenshot'       => 'true',
        'screenshot.fullPage' => $options['fullPage'] ? 'true' : 'false',
        'screenshot.type'  => $type,
    );

    if ( 'jpeg' === $type && isset( $options['quality'] ) ) {
        $quality = absint( $options['quality'] );
        if ( $quality > 100 ) {
            return new WP_Error( 'invalid_quality', 'JPEG quality must be from 0 to 100.' );
        }
        $params['screenshot.quality'] = (string) $quality;
    }

    if ( isset( $options['element'] ) && is_string( $options['element'] ) && '' !== trim( $options['element'] ) ) {
        $params['screenshot.element'] = trim( $options['element'] );
    }

    // Include all capture settings in the key so variants do not collide.
    $cache_key = 'myplugin_ml_' . md5( wp_json_encode( $params ) );
    $cached    = get_transient( $cache_key );
    if ( false !== $cached ) {
        return $cached;
    }

    $request_url = add_query_arg( $params, 'https://api.microlink.io/' );
    $response    = wp_safe_remote_get(
        $request_url,
        array(
            'timeout'     => 30,
            'redirection' => 3,
            'headers'     => array( 'Accept' => 'application/json' ),
        )
    );

    if ( is_wp_error( $response ) ) {
        return new WP_Error( 'microlink_transport_error', 'The screenshot service could not be reached.', array( 'cause' => $response->get_error_message() ) );
    }

    $status = wp_remote_retrieve_response_code( $response );
    $body   = wp_remote_retrieve_body( $response );
    if ( $status < 200 || $status >= 300 ) {
        return new WP_Error( 'microlink_http_error', 'The screenshot service returned an unsuccessful HTTP status.', array( 'status' => $status ) );
    }

    $data = json_decode( $body, true );
    if ( ! is_array( $data ) || JSON_ERROR_NONE !== json_last_error() ) {
        return new WP_Error( 'microlink_invalid_json', 'The screenshot service returned an unreadable response.' );
    }

    if ( ! isset( $data['status'] ) || 'success' !== $data['status'] ) {
        $message = isset( $data['message'] ) ? sanitize_text_field( $data['message'] ) : 'The screenshot request did not succeed.';
        return new WP_Error( 'microlink_capture_failed', $message );
    }

    $image_url = isset( $data['data']['screenshot']['url'] ) ? esc_url_raw( $data['data']['screenshot']['url'] ) : '';
    if ( ! $image_url || ! wp_http_validate_url( $image_url ) ) {
        return new WP_Error( 'microlink_missing_screenshot', 'The response did not include a valid screenshot URL.' );
    }

    // Choose a lifetime that matches how often link previews should refresh.
    set_transient( $cache_key, $image_url, 6 * HOUR_IN_SECONDS );
    return $image_url;
}

The request uses the documented screenshot parameters: fullPage defaults to false, type supports PNG or JPEG, JPEG quality ranges from 0 to 100, and element selects a DOM element to capture. The element is documented as waiting until it is visible. See the Microlink documentation for the current parameter reference.

3. Call it and render the preview safely

For an authenticated editor-only feature, check capabilities before calling the helper. For public previews, impose appropriate abuse controls and request limits; a public endpoint can expose your service quota. Escape the returned URL in the HTML attribute context.

<?php
if ( ! current_user_can( 'edit_posts' ) ) {
    return;
}

$target = isset( $_POST['preview_url'] )
    ? esc_url_raw( wp_unslash( $_POST['preview_url'] ) )
    : '';

$image_url = my_plugin_microlink_screenshot(
    $target,
    array(
        'fullPage' => false,
        'type'     => 'jpeg',
        'quality'  => 80,
    )
);

if ( is_wp_error( $image_url ) ) {
    // Fail closed: show a useful editor message, not a broken image.
    echo '<p class="notice notice-error">' . esc_html( $image_url->get_error_message() ) . '</p>';
} else {
    echo '<img class="my-plugin-preview" src="' . esc_url( $image_url ) . '" alt="Website preview" loading="lazy">';
}

If this runs from a custom authenticated REST route or manual authenticated AJAX request, use WordPress’s nonce guidance to protect against CSRF. A nonce is not a replacement for authorization: still check the user’s capability. For public routes, decide explicitly how to control unauthorized or excessive requests.

When markup only needs an image source, Microlink can return the selected screenshot field as the response body. The documented pattern is embed=screenshot.url; the API overview shows the same delivery mode for image embedding. Do not put an API key in a URL. The following is a markup example for a target URL you control:

<img
  src="https://api.microlink.io/?url=https%3A%2F%2Fexample.com&screenshot=true&meta=false&embed=screenshot.url"
  alt="Website preview"
  loading="lazy"
>

For a plugin, construct query parameters with WordPress’s URL helpers rather than concatenating untrusted input. Direct embedding skips your JSON validation path, so use the JSON method when you need to decide how failures are presented or cached.

5. Request examples in other languages

These examples show the same basic Microlink API request. The WordPress integration should use the built-in HTTP API as shown above; cURL, Python, and Node.js are useful for diagnosing or prototyping the remote request outside WordPress.

cURL

curl -G 'https://api.microlink.io/' \
  --data-urlencode 'url=https://example.com' \
  --data-urlencode 'screenshot=true' \
  --data-urlencode 'screenshot.fullPage=false' \
  --data-urlencode 'screenshot.type=png'

Python

import requests

response = requests.get(
    "https://api.microlink.io/",
    params={
        "url": "https://example.com",
        "screenshot": "true",
        "screenshot.fullPage": "false",
        "screenshot.type": "png",
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()
if data.get("status") != "success":
    raise RuntimeError(data.get("message", "Screenshot request failed"))
print(data["data"]["screenshot"]["url"])

Node.js

const params = new URLSearchParams({
  url: 'https://example.com',
  screenshot: 'true',
  'screenshot.fullPage': 'false',
  'screenshot.type': 'png',
});

const response = await fetch(`https://api.microlink.io/?${params}`);
if (!response.ok) {
  throw new Error(`Microlink returned HTTP ${response.status}`);
}
const data = await response.json();
if (data.status !== 'success') {
  throw new Error(data.message || 'Screenshot request failed');
}
console.log(data.data.screenshot.url);

6. Configure screenshot scope, format, and caching

Setting Behavior Practical choice
screenshot.fullPage Captures the full scrollable page when true; documented default is false. Use false for compact cards; use true when the full page is the preview.
screenshot.type Selects PNG or JPEG; documented default is PNG. Choose based on the preview’s visual content and desired file format.
screenshot.quality JPEG quality from 0 through 100; documented default is 80 and it applies only to JPEG. Only send it when type is JPEG.
screenshot.element Captures a selected DOM element, waiting for it to become visible. Use a stable selector for the specific region your preview needs.

Cache using a key derived from the target URL and every screenshot option that changes the output. Otherwise a viewport image can be returned for a full-page request, or one format can be returned for another. The example uses a six-hour transient expiration as a configurable editorial choice, not a Microlink requirement. Shorter expiration improves freshness; longer expiration reduces repeated API calls for stable pages. WordPress Transients are temporary cached values with an expiration, and the underlying storage may vary by site configuration. See the WordPress Transients API guide.

7. Security, reliability, and cost considerations

Protect the server-side request

  • Treat user-supplied URLs as untrusted. WordPress documents wp_safe_remote_get() for arbitrary URLs and validates the URL and redirects to reduce SSRF risk. Accept only intended HTTP or HTTPS targets and handle a rejected URL as a normal validation error.
  • For an editor feature, gate it with a capability check. If exposed publicly, add controls appropriate to your product, such as request limits and abuse monitoring.
  • For authenticated REST or manual AJAX requests, follow WordPress cookie and nonce guidance, and separately verify permissions.
  • Keep API credentials out of browser-visible URLs if you add authenticated requests; Microlink documentation identifies the API key header as the authentication mechanism.

Make failures non-fatal

A remote screenshot should not break the surrounding editor or preview page. Handle transport errors, non-success HTTP responses, invalid JSON, unsuccessful API status, and missing or invalid screenshot URLs. Show a fallback message or omit the preview, and consider logging a request identifier or sanitized error context for diagnosis. Do not display raw remote response content to site visitors.

Plan for latency and quota

Screenshot generation requires fetching and rendering the remote page, so a cache miss adds remote work to the preview request. Set a finite timeout, avoid generating the same screenshot repeatedly, and consider whether the screenshot should be generated during an editor action or asynchronously in your own architecture. The sources do not establish a universal capture-time benchmark for this plugin flow. Microlink’s screenshot guide describes 25 requests per day without an API key; its plan limits and features can change, so verify current terms before relying on a quota for production. Caching reduces repeated calls from your plugin but does not establish a specific upstream retention period.

8. Troubleshooting

Symptom Likely cause Fix
The helper rejects an otherwise familiar URL. The input is malformed, uses an unsupported scheme, or WordPress safe URL validation rejects its destination or redirect. Require a valid public HTTP or HTTPS URL. Do not bypass safe validation for user-controlled destinations.
WordPress returns a transport error. Network or TLS failure, DNS trouble, or the request exceeded its timeout. Inspect the underlying WP_Error server-side, confirm outbound HTTPS works, and retry through a controlled path rather than rendering a broken preview.
The API returns a non-2xx status. The remote request was rejected or the service returned an unsuccessful HTTP response. Record the status for diagnosis, check the current Microlink response and account terms, and show a fallback.
JSON decoding fails. The response body was not JSON, was incomplete, or the request used direct-image embed mode. Use the JSON endpoint without embed for this code path and validate the content before decoding.
The API response is not successful or has no screenshot URL. The target could not be captured, the response reports failure, or the response shape is not what the plugin expects. Check the API status and message, avoid assuming the asset exists, and keep the preview page usable without it.
The wrong screenshot variant appears. The cache key does not include all screenshot settings. Hash the full set of capture parameters, including target, full-page choice, format, quality, and element selector.
The preview image is stale. The transient lifetime is longer than the acceptable freshness window. Reduce the chosen transient expiration or add an explicit refresh action for editors.
Requests are exhausted sooner than expected. Repeated misses or public traffic are generating requests. Cache successful results, rate-limit exposed routes, and verify the current Microlink quota and plan details.

9. Or skip the browser setup

ScreenshotNeo provides a screenshot API and MCP server for developers. A single GET request can return a PNG, JPEG, WebP, or PDF. For a WordPress plugin, the same kind of server-side request can replace managing a browser capture setup. See the ScreenshotNeo API documentation.

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)
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}`);

Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up free and get 1,000 screenshots a month with no card.

10. Frequently asked questions

Do I need to install a browser package in my WordPress plugin?

No. This integration sends an HTTP request to Microlink and receives a hosted screenshot URL; the plugin does not run a local browser.

Should a preview plugin capture the full page?

Only when a full-page image suits the preview. A compact link card commonly benefits from a viewport capture, while a page archive or review workflow may need the full scrollable page.

Can I use the API response URL as a permanent media-library asset?

The reviewed documentation establishes a hosted asset URL, but it does not establish a permanent retention period. If your plugin requires durable ownership, verify the service’s current retention behavior and design an explicit storage workflow.

What should happen when a screenshot cannot be generated?

Keep the surrounding WordPress feature usable: return a fallback state, allow retry where appropriate, and avoid emitting an empty or invalid image source.