---
title: "HTML to Image API for WordPress | Automatic Open Graph Images"
description: "Give every WordPress post and page its own Open Graph image, rendered in real Chrome. Five designs, Yoast, Rank Math, AIOSEO and SEOPress support, plus filters."
url: "https://html2img.com/integrations/wordpress/"
---

# HTML to Image API for WordPress

The official WordPress plugin gives every post and page its own share image, built from the title, author, excerpt, date and featured image, rendered in real Chrome and saved into your media library.

- **Install:** `wp plugin install html2img --activate`
- **Registry:** [WordPress.org](https://wordpress.org/plugins/html2img/)
- **Source:** https://github.com/html2img/wordpress
- **Licence:** GPL-2.0-or-later
- **Requires:** WordPress 6.2 or newer, PHP 7.4 or newer

Links shared without an image get scrolled past. The plugin gives every post and
page its own share image, built from the title, author, excerpt, date and featured
image the moment you publish. No design work per post, no image editor, no page
builder to fight with.

The official html2img plugin, **Auto OG Images**, is in the
[WordPress plugin directory](https://wordpress.org/plugins/html2img/), so it
installs and updates from the Add Plugins screen like any other plugin.

## What the plugin does

On publish, it builds a card from the post title, author, excerpt, date and
featured image, renders it through [`POST /api/html`](https://html2img.com/docs/parameters/html/) in
real Chrome, and saves the PNG into your media library. The correct `og:image` and
`twitter:image` tags are then output on the post, or handed to Yoast SEO, Rank
Math, All in One SEO or SEOPress when one of those is active, so the tags are never
written twice.

Five designs ship with it (Classic, Split, Photo, Editorial and Minimal), each
taking an accent colour, a background colour, an optional logo and toggles for the
author name and site name. A custom template option accepts your own HTML with
documented placeholders.

Because the rendering happens in a real browser, proper fonts, emoji in titles and
photographic featured images all come out right. Nothing is drawn with GD or
ImageMagick on your host, and no front-end JavaScript is added to your pages.

## Where to get it

| Route | Status |
| --- | --- |
| WordPress plugin directory | [wordpress.org/plugins/html2img](https://wordpress.org/plugins/html2img/) |
| GitHub repository | [html2img/wordpress](https://github.com/html2img/wordpress), GPL-2.0-or-later |
| Current version | 1.1.0 |

## Requirements

| Requirement | Version |
| --- | --- |
| WordPress | 6.2 or newer (tested up to 7.0) |
| PHP | 7.4 or newer |
| API key | Free, from your [dashboard](https://app.html2img.com/register) |

The plugin makes no front-end requests to the API and never exposes your key to
the browser: the key lives in the `html2img_settings` option and is used only from
the admin and from background tasks.

## Installing

1. In your admin, open **Plugins → Add New** and search for **Auto OG Images**
   (or **html2img**). Click **Install Now**, then **Activate**. On the command
   line, `wp plugin install html2img --activate` does the same.
2. Open **Settings → OG Images** and paste an API key from your
   [dashboard](https://app.html2img.com/dashboard).
3. Pick a design, choose your post types, and save.

Installing from the directory means updates arrive on the Plugins screen like
any other plugin. If you prefer to install from source, the
[repository](https://github.com/html2img/wordpress) can be downloaded as a ZIP
(**Code → Download ZIP**) and uploaded under **Plugins → Add New → Upload
Plugin**; a source install does not receive automatic updates.

The plugin validates the key with a call to [`GET /api/me`](https://html2img.com/docs/account/), which
costs nothing, and then shows your plan and remaining credits on the settings
screen.

New posts get an image on publish. Existing posts can be generated in bulk from
**Tools → OG Images**.

## Settings

Everything lives in one option, `html2img_settings`, edited under **Settings → OG
Images**:

| Setting | Default | What it does |
| --- | --- | --- |
| API key | empty | Validated against `GET /api/me` when saved. |
| Post types | `post`, `page` | Which public post types get images. Any public type can be enabled, products included. |
| Design | `classic` | One of Classic, Split, Photo, Editorial, Minimal, or your own template. |
| Accent colour | `#6366f1` | Passed to the design as `{{accent_color}}`. |
| Background colour | `#0b1220` | Passed to the design as `{{background_color}}`. |
| Logo | none | A media library image, passed to the design as `{{logo}}`. |
| Show author | on | Whether the author name appears on the card. |
| Show site name | on | Whether the site name appears on the card. |
| Custom template | empty | Your own HTML document, using the placeholders below. |
| Storage | `media` | `media` saves into the media library, `cdn` keeps the hosted URL. |
| Always override | off | Whether the generated image beats a social image set by hand in your SEO plugin. |
| Show in media library | off | Whether generated images appear in the media grid. |
| Delete on uninstall | off | Whether uninstalling removes the plugin's data. |

The settings screen also carries a live design preview drawn by your own browser,
not the API, so trying designs and colours costs nothing.

## Post types

Any public post type can be enabled, not just posts and pages. Custom post types
appear in the list once registered as public, and WooCommerce products work the
same way: the bundled designs show the product title and its featured image.
Prices and other product fields are not included out of the box, but the
[`html2img_variables` filter](#hooks-for-developers) and a custom template can add
them.

## Open Graph and Twitter tags

With no SEO plugin active, the plugin outputs the tags itself. With Yoast SEO,
Rank Math, All in One SEO or SEOPress active, it feeds the generated image to that
plugin through its own filters and outputs nothing, so the tags are never written
twice. The settings screen states which plugin currently controls your social
tags.

A social image chosen by hand in your SEO plugin wins by default, and the
generated image fills the gap on every post that has none. Turning on **Always
override** flips that round.

## Generating and regenerating

**On publish.** An image is generated in the background when a post is published
or updated. Publishing never waits on the API; the editor panel shows progress,
and if background tasks are disabled on your host the panel offers a manual run
button.

**Per post.** The Gutenberg sidebar panel and the classic editor metabox both show
the current image, a regenerate button and a per-post off switch. The posts list
adds a row action for a single post and a bulk action for a selection.

**In bulk.** **Tools → OG Images** counts how many images are out of date, shows
what a full regeneration will cost in credits, and asks before spending anything.
The run happens in batches, shows progress, can be resumed if interrupted, and
stops cleanly if credits run out.

## How generation is decided

Every render stores two hashes in post meta, and this is what keeps the plugin
cheap to run:

- A **content hash** of the variable payload the design consumes: title, site
  name, author, excerpt, date and featured image identity. A save re-renders only
  when this hash changes, so saving a post without touching any of those costs
  nothing.
- A **design fingerprint** of the active design, its template HTML, the
  customisation settings and the render dimensions. Posts whose stored fingerprint
  differs from the current one are stale. The Tools screen counts them and offers
  a bulk regeneration with a credit estimate before anything runs.

A render also happens when the stored attachment has been deleted. Nothing
regenerates silently when you change the design: you are always asked first.

## Credits and the free plan

One render is one credit, the same as any API call. Free accounts get 50 credits,
a one-time allowance rather than a monthly one, and paid plans are on the
[pricing page](https://html2img.com/pricing/).

Renders made on the free plan stay on the CDN for seven days, but the plugin
stores every image in your media library by default, so nothing on your site
expires. If you switch storage to `cdn` on a free account, the plugin warns you
about the seven-day lifetime. Paid plan renders are kept permanently, and
upgrading makes every earlier render permanent too.

When an account runs out of credits, generation pauses. Existing images stay
exactly as they are, posts published in the meantime fall back to whatever your
SEO plugin or theme would do anyway, and a single admin notice tells you how many
posts are waiting. They are generated when credits are available again, either on
their next save or from the Tools screen.

> **Tip: Keep media library storage**
>
> The default setting downloads each render into `wp-content/uploads` and serves it
> from your own domain. Your share images then keep working whatever happens to the
> account, which is why it is the recommended option on every plan.

## Custom templates

The custom template option takes a complete HTML document with placeholders. Text
values are HTML-escaped before substitution.

| Placeholder | Value |
| --- | --- |
| `{{title}}` | Post title, entity decoded |
| `{{title_class}}` | `title-l`, `title-m`, `title-s` or `title-xs` by title length, for stepped font sizes |
| `{{site_name}}` | Site name, empty when hidden in settings |
| `{{tagline}}` | Site tagline |
| `{{author}}` | Author display name, empty when hidden in settings |
| `{{excerpt}}` | Manual excerpt, or the first 28 words of the content |
| `{{featured_image}}` | Data URI or URL of the featured image, empty when there is none |
| `{{logo}}` | Data URI or URL of the logo chosen in settings |
| `{{accent_color}}` | Validated hex colour |
| `{{background_color}}` | Validated hex colour |

Conditional sections show or hide markup based on whether a value is empty:

```html
{{#author}}<span>{{author}}</span>{{/author}}
{{^featured_image}}<div class="fallback"></div>{{/featured_image}}
```

Templates must be complete HTML documents, self-contained apart from Google Fonts
loaded with `link` tags. Size the page with `width: 100vw; height: 100vh` and use
`vw` units throughout, and one template works at any configured dimensions:

```html
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;800&display=swap">
    <style>
        * { box-sizing: border-box }
        body {
            margin: 0; width: 100vw; height: 100vh; padding: 6vw;
            display: flex; flex-direction: column; justify-content: space-between;
            font-family: Inter, system-ui, sans-serif;
            background: {{background_color}}; color: #fff;
        }
        h1 { margin: 0; font-weight: 800; line-height: 1.1; letter-spacing: -0.02em }
        .title-l { font-size: 5.4vw } .title-m { font-size: 4.6vw }
        .title-s { font-size: 3.8vw } .title-xs { font-size: 3.2vw }
        .rule { height: 0.5vw; width: 8vw; background: {{accent_color}} }
        .meta { font-size: 1.7vw; color: #aeb7c6 }
    </style>
</head>
<body>
    <div class="rule"></div>
    <h1 class="{{title_class}}">{{title}}</h1>
    <div class="meta">
        {{#author}}<span>{{author}}</span> &middot; {{/author}}
        <span>{{site_name}}</span>
    </div>
</body>
</html>
```

> **Warning: Local URLs are invisible to the renderer**
>
> The API renders your HTML on its own servers, so every URL in a custom template
> must be reachable from the public internet. A logo on a staging site behind HTTP
> auth, or on `localhost`, comes out missing. Featured images and the settings logo
> are inlined as data URIs by the plugin, which is why they work; anything else you
> reference must be public. Google Fonts always work.

## Hooks for developers

### Filters

```php
// Change or extend the variables a design receives. Values added here
// join the content hash, so changes to them trigger re-renders exactly
// like core fields.
add_filter( 'html2img_variables', function ( array $variables, int $post_id ): array {
	$variables['price'] = get_post_meta( $post_id, '_price', true );
	return $variables;
}, 10, 2 );

// Final say on whether a post gets an image.
add_filter( 'html2img_should_generate', function ( bool $should, int $post_id, WP_Post $post ): bool {
	return $should && ! has_term( 'no-og', 'category', $post );
}, 10, 3 );

// Adjust the render arguments sent to the API: width, height, dpi, html.
add_filter( 'html2img_render_args', function ( array $args, int $post_id ): array {
	$args['dpi'] = 1;
	return $args;
}, 10, 2 );

// Register or replace designs. Each entry maps a slug to a name and the
// absolute path of a template file.
add_filter( 'html2img_designs', function ( array $designs ): array {
	$designs['brand'] = [
		'name' => 'Brand',
		'file' => get_stylesheet_directory() . '/og-designs/brand.html',
	];
	return $designs;
} );

// Change the render dimensions globally.
add_filter( 'html2img_dimensions', function ( array $dimensions ): array {
	return [ 'width' => 1200, 'height' => 630, 'dpi' => 1 ];
} );

// Cap for inlining images as data URIs, in bytes of the source file.
add_filter( 'html2img_inline_image_max_bytes', fn () => 2000000 );

// Provide the API key from configuration instead of the database.
add_filter( 'html2img_api_key', fn () => defined( 'HTML2IMG_API_KEY' ) ? HTML2IMG_API_KEY : '' );
```

The last one is worth knowing about on a managed host: define `HTML2IMG_API_KEY`
in `wp-config.php` and the key never touches the database or a settings export.

### Actions

```php
// After an image was generated and stored.
add_action( 'html2img_after_generate', function ( int $post_id, int $attachment_id, array $response ): void {
	// $attachment_id is 0 in CDN storage mode.
	// $response is the full API response body.
}, 10, 3 );
```

### Post meta

All keys are prefixed `_html2img_` and hidden from custom fields: `image_id`,
`cdn_url`, `render_id`, `expires_at`, `content_hash`, `fingerprint`,
`generated_at`, `status`, `error`, `disabled` and `queued_at`. Generated
attachments carry `_html2img_generated`.

## Rolling your own

If you would rather not run a plugin, the API is one HTTP request from any hook.
This is the plugin's whole idea in twenty lines:

```php
add_action( 'transition_post_status', function ( $new, $old, $post ) {
	if ( 'publish' !== $new || 'publish' === $old ) {
		return;
	}

	$html = sprintf(
		'<!doctype html><html><body style="margin:0;width:1200px;height:630px;padding:80px;
		 box-sizing:border-box;display:flex;align-items:center;font:800 64px/1.1 system-ui;
		 background:#0f172a;color:#fff">%s</body></html>',
		esc_html( get_the_title( $post ) )
	);

	$response = wp_remote_post( 'https://app.html2img.com/api/html', [
		'timeout' => 35,
		'headers' => [
			'Content-Type' => 'application/json',
			'X-API-Key'    => HTML2IMG_API_KEY,
		],
		'body'    => wp_json_encode( [ 'html' => $html, 'width' => 1200, 'height' => 630 ] ),
	] );

	if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
		return;
	}

	$body = json_decode( wp_remote_retrieve_body( $response ), true );
	update_post_meta( $post->ID, '_og_image_url', $body['url'] );
}, 10, 3 );
```

```php
add_action( 'wp_head', function () {
	if ( ! is_singular() ) {
		return;
	}

	$url = get_post_meta( get_the_ID(), '_og_image_url', true );

	if ( $url ) {
		printf( '<meta property="og:image" content="%s">', esc_url( $url ) );
	}
} );
```

That covers the happy path. What the plugin adds on top: change detection so
unchanged saves cost nothing, background generation so publishing never waits,
media library storage, five designs, SEO plugin hand-off, bulk regeneration with a
credit estimate, and clean behaviour when credits run out. The
[PHP integration page](https://html2img.com/integrations/php/) covers the underlying API in full,
including screenshots, [HTML to PDF conversion](https://html2img.com/html-to-pdf/) and named
templates.

## Troubleshooting

**The key is rejected on the settings screen.** The plugin validates it against
`GET /api/me`. A rejected key is usually a copy-paste with whitespace, or a key
from a different account. Re-copy it from your
[dashboard](https://app.html2img.com/dashboard).

**No image appears after publishing.** Generation runs in the background through
WP-Cron. On a site where cron is disabled or unreliable, the editor panel offers a
manual run button; use that to confirm the pipeline works, then look at whether
`DISABLE_WP_CRON` is set without a real cron job replacing it.

**The image is there but the tags are not.** An active SEO plugin owns the head.
Check the settings screen, which names whichever plugin currently controls your
social tags, and look for a social image set by hand on that post: it wins unless
**Always override** is on.

**A custom template renders with missing images.** Anything you reference has to
be publicly reachable. Featured images and the settings logo are inlined as data
URIs by the plugin, which is why they always work; a logo referenced by URL from a
staging site will not.

**Every save spends a credit.** It should not: the content hash skips unchanged
posts. If it does, something in the payload changes on every save, often a
`html2img_variables` filter adding a timestamp or a random value.

**Changing the design did not regenerate anything.** By design. Go to **Tools → OG
Images**, which counts the stale posts and tells you what a regeneration will cost
before it starts.

**Bulk regeneration stopped part way.** It stops cleanly when credits run out and
can be resumed from the same screen once you have more. Progress is saved, so
nothing is re-rendered twice.

**Multisite.** The plugin works per site on a network, each site with its own
settings and API key. There is no network-level configuration yet.

## FAQ

**Is the plugin on WordPress.org?**
Yes: [wordpress.org/plugins/html2img](https://wordpress.org/plugins/html2img/).
Install it from **Plugins → Add New** by searching for **Auto OG Images**, and
updates arrive there like any other plugin. The
[repository](https://github.com/html2img/wordpress) remains available for
source installs.

**Does it work with Yoast SEO or Rank Math?**
Yes, and also with All in One SEO and SEOPress. The plugin feeds the generated
image to whichever is active through its own filters and outputs nothing itself,
so tags are never written twice.

**What if I already set a social image on a post by hand?**
It is left alone. A manually chosen social image wins by default; the generated
image only fills the gap on posts without one. **Always override** changes that.

**Do the images expire?**
Not the ones on your site. Images are saved into your media library by default and
stay there. The copies on the CDN expire after seven days on the free plan, which
only matters if you switch on CDN storage. Paid plan renders never expire.

**Can I customise the design?**
Each bundled design takes an accent colour, a background colour, a logo and toggles
for the author name and site name. Beyond that, the custom template option accepts
your own HTML with the placeholders listed above, and the `html2img_designs` filter
registers a design file from your theme.

**What data is sent to the API?**
Only what the image shows: the post title, the excerpt, the author display name,
the site name and tagline, the date, the featured image and your design settings
including the logo. Nothing is sent for post types you have not enabled or posts
you have switched off, and nothing is sent from your site's public pages. The
[privacy policy](https://html2img.com/privacy/) and [terms](https://html2img.com/terms/) cover the service itself.

**Does publishing wait for the image?**
No. The render runs in the background and typically finishes within seconds.

**Does it slow down my public pages?**
No. It adds a handful of meta tags and no front-end JavaScript. Everything else
happens in the admin.

**What happens if I deactivate it?**
Existing images stay in the media library. Uninstalling only removes the plugin's
data if you have turned on the **Delete on uninstall** setting.
