html2wp / Docs

Documentation

Everything from installing the plugin to handing over a theme you have read every page of, pixel by pixel, against the original. This is the plugin's own README, laid out for reading.

What needs a key and what does not

Everything below works on the free tier, which is open to everyone: install it and convert, three conversions of up to five pages each and five re-runs to share between them, both counted per IP address, no key. A licence, which is what client work, twenty-page sites and WooCommerce need, is in closed beta and comes by request. How to ask for one.

Part oneSetting it up

Install

The same plugin is published twice, because the two hosts read different manifests. Take the repository for the tool you use. They carry identical scripts and the same version number, and the wrong one simply will not be found.

HostRepository
Claude CodeiOSDevSK/html2wp-cc-plugin
CodexiOSDevSK/html2wp-codex-plugin

Codex finds the bundled html2wp skill through .agents/plugins/marketplace.json, which is why that is the -codex repository: the Claude one is laid out for Claude Code and carries no .agents/ manifest for Codex to read. No Claude namespace and no Claude environment variable is involved.

Without either plugin system

For Claude Code the plugin is the repository root, so the skill is skills/html2wp/:

Claude Code
git clone --depth 1 https://github.com/iOSDevSK/html2wp-cc-plugin /tmp/html2wp \ && cp -R /tmp/html2wp/skills/html2wp ~/.claude/skills/ \ && rm -rf /tmp/html2wp

For Codex that repository is a marketplace wrapping the plugin, so the skill sits one level in, at plugins/html2wp/skills/html2wp/:

Codex
git clone --depth 1 https://github.com/iOSDevSK/html2wp-codex-plugin /tmp/html2wp \ && cp -R /tmp/html2wp/plugins/html2wp/skills/html2wp ~/.codex/skills/ \ && rm -rf /tmp/html2wp

Copy the inner skill directory rather than cloning a repository over ~/.codex/skills/html2wp or ~/.claude/skills/html2wp: the repository root carries the plugin manifests, not the skill. Installed this way it is a plain skill rather than a plugin skill, so it is invoked directly by name, or /html2wp in Claude Code.

Keeping it current

The two hosts do not use the same word. Codex takes upgrade, Claude Code takes update.

Worth doing once instead

Open /plugin in Claude Code, find html2wp under Marketplaces, and turn auto-update on. Claude Code leaves auto-update off by default for marketplaces that are not its own, so without that step this plugin only changes when you ask it to, and some releases are security fixes to code that runs on your machine and reads your project directory.

Check what you are running with codex plugin list, or under /plugin, Marketplaces, html2wp. If the Codex version does not change after an update, Codex is serving a cached copy or a second install is shadowing this one:

Codex, clearing a cached copy
rm -rf ~/.codex/plugins/cache/html2wpcodex plugin marketplace upgrade && codex plugin add html2wp@html2wp

codex plugin marketplace list shows every marketplace Codex is reading. If you see an older html2wp@<something-else> from a manual install, remove it with codex plugin remove html2wp@<that-name> so it cannot shadow this one.

Half of html2wp is the service, and that half updates itself: a fix there reaches your next conversion with nothing to install. What waits for you is the part running locally, which is the payload filter, the gates and the scripts. The changelog for each version is the commit history, and releases are tagged.

Requirements

Node.js20 or newer
Python 3with Playwright (chromium) and Pillow
Dockerplus docker compose, used for the verification WordPress
Alsophp-cli, jq, curl, bash
TargetWordPress 6.6 or newer

You do not have to work that list out yourself. Ask for a conversion and the first thing it does is check the machine and tell you exactly what is missing:

checking this machine first
Node.js                ok        v22.14.0
Python                 ok        3.12.4
Playwright             MISSING   mirroring, prerendering and every screenshot
Docker                 NOT RUNNING  installed, but the daemon is not up

Anything user-local, such as the Python packages or the chromium download, it offers to install for you, one command at a time, each one asking first. Anything that changes the machine, such as Docker Desktop or a Node upgrade, it tells you about and then waits, because that is your call and not its.

If you would rather do it by hand:

By hand
python3 -m pip install playwright pillow && python3 -m playwright install chromium

Which model to run it on

A conversion is a long agent run with a lot of judgment inside it: which page is which, what a failed gate is actually telling you, whether a screenshot pair shows a difference a client would notice. The model you run it on moves the result more than any setting on this page does.

HostWhat to run
Claude CodeOpus 5, with Fable 5 alongside it as an advisor.
CodexLuna at xhigh reasoning effort.

The cheap route is not the weak one

Codex with Luna at xhigh is the inexpensive way to run a conversion, and the results at that effort level are above average. If what a conversion costs to run matters to you, that is the combination to reach for rather than a compromise to apologise for.

The Claude Code pairing is the other end of it: Opus 5 does the work and Fable 5 is consulted at the decision points, which is where a conversion goes wrong if it is going to.

Using a licence key

On the free tier there is nothing to set up: skip this section. If you hold a licence, set the key up first, once per machine, from any directory, before you ask for a conversion.

Once per machine
mkdir -p ~/.config/html2wpprintf '%s' 'YOUR-KEY' > ~/.config/html2wp/licencechmod 600 ~/.config/html2wp/licence

H2WP_KEY works too and takes precedence over the file. The file exists so the key never lands in your shell history.

Two kinds of key go in that file. An html2wp licence key, which is what the closed beta issues, and a Visual Edit Pro key, which the converter also accepts: if you already hold one for the editor, it converts as well. The client does not decide either way; it forwards whatever key it finds and the service answers with what that key is entitled to.

Set it up beforehand, not halfway through

The key is sent when the conversion reaches the service, which is well into the run, but the page allowance is checked at the very first stage. Without a key in place the conversion is planned against the unlicensed allowance, and finding the key later does not unplan that.

What a beta licence carries, and how to ask for one, is on the licences page.

Part twoRunning a conversion

Converting a project

In your terminal, in the directory you want converted:

Open your agent in the project
git clone https://github.com/YOU/YOUR-LOVABLE-APPcd YOUR-LOVABLE-APPcodex

Use claude instead of codex on that last line if Claude Code is your host.

Then one line, one enter:

That is the whole of it. No npm install, no npm run build, no configuration. Bolt, v0, shadcn and Next.js export projects take the same line. Asking Codex to use html2wp, or invoking /html2wp:html2wp on its own in Claude Code, works too; it then asks what to convert.

What the first minutes look like

the first minutes
> convert this project

  Checking this machine first…
    Node.js     ok    v22.14.0
    Playwright  MISSING
  Two Python packages are missing. Shall I install them? (they go in your
  user directory, no root)

> yes

  … installed. Building the project, then prerendering it.
  7 routes found: /, /about, /pricing, /blog, /blog/launch, /contact, /faq
  Before I go on: is /pricing a normal page, or part of the blog?

That last question is the one that matters.

Pointing it at something else

convert this project covers the common case. If the files live elsewhere, say where: a path is a complete answer, such as convert ./dist.

What you haveWhat to say
A project that builds to a site: Lovable, Bolt, v0, Vite, Astro, Next exportconvert this project
A folder of finished .html files with their assetsconvert ./that-folder

The input is always something on your disk. A live URL is not an input: convert what the site is built from, not what a browser happens to render.

What it does with a Lovable project

A Lovable app is React: one index.html with an empty mount node and a script. There is nothing 1:1 to convert in that, so the skill builds the project, opens the built app in a real browser, and records each route as flat HTML, including the state that only exists after JavaScript has run: open accordions, scroll-triggered classes, dropdown contents. Those become the pages it converts.

The decision it makes for you

Which page is which

This is the one call that changes the result: which page is the home page, which one is the blog listing, which are articles, which are products. The coding agent reads it out of the markup, writes down what it decided, and keeps going — it does not stop to ask you first. It stops only where no defensible default exists: a site larger than your allowance, or two pages claiming to be the same one.

Getting this wrong is also the cheapest thing to fix. Correcting it and running again is a re-run, and re-runs do not spend a conversion.

Then it runs mostly unattended

Two to three hours on average, depending on how complex the HTML is and how many pages the site has. It builds the site, checks the build against the original, sends it for conversion, installs the theme into a throwaway WordPress in Docker on your machine, and drives that WordPress to test it.

The part you cannot skip

Near the end you are shown, for every page, the original and the converted page side by side in one image. You read each one and say what you see.

Why a person, and not the numbers

The numeric gates pass things a person would reject. A dropped below-the-fold section once scored 0.4% on the pixel comparison, comfortably green. Scripts measure; this step judges. It is the last stage for a reason, and the skill will not call a conversion finished until it is done.

What you end up with

  • The theme, as an installable .zip. WordPress, Appearance, Themes, Add New, Upload. It refuses to build a broken one: PHP that does not lint, a missing content bundle, a wrong-sized screenshot, or a shop you could not actually buy from.
  • A conversion report. Pages converted, menus wired, every finding from your review, every warning from every stage including the ones judged acceptable, and anything left for you to do.
  • visual-edit.zip, while your Visual Edit licence is live. Without it the conversion ships the theme alone and that theme is fully usable: the editor is an extra, not a missing piece.

The theme is standalone: pages, blog, forms, menus, SEO and redirects are the theme's own code and work with no plugin at all. What comes back is yours, as readable PHP, CSS and JS, with no lock-in, and it calls nothing at runtime. For point-and-click editing there is Visual Edit.

What leaves your machine

Your machine does the browser work: prerendering, screenshot and structural comparison, and a throwaway WordPress in Docker for the final checks. The html2wp service generates the theme, so your built site is uploaded to it, because that is what the conversion is performed on, and the theme comes back.

The gates run on your machine, against a WordPress the service never sees, so the last step sends the gate results back, and this is required: the next conversion is refused until the previous one has reported.

  • What goes: gate names, pass or fail, page counts, the worst fidelity percentage, and the page keys that failed, which are the short names you chose yourself, like about or pricing.
  • What does not: no URL, no domain, no markup, no copy, no screenshots, no file paths, no licence key, no site name. The payload is a whitelist, not a filter, so a field nobody anticipated cannot leak through it.
  • See for yourself: send-verdicts.sh <workspace> --dry-run prints the exact payload and sends nothing. It is one readable shell script.

No other telemetry exists, and the theme you receive reports nothing at all. The full description, including retention, is on the data page.

Reporting a defect

When the generator itself is wrong, report it:

A generator defect
curl -sS -X POST https://api.html2wp.dev/v1/report \ -H 'content-type: application/json' \ -d '{"subject":"what went wrong","body":"what you saw","evidence":"page keys, warnings"}'

A person reads those, and fixes ship as service updates, so a defect you report is fixed for everyone.

A security problem goes somewhere else

Not here, and not in a GitHub issue. See the security page.