On hold

Your WordPress plugin is on hold: what each rejection reason actually means

The review team's email is short and vague on purpose. Here is what each reason actually means, what triggers it, and what genuinely gets you unblocked.

You submitted a plugin to wordpress.org. Two weeks later, an email arrives from plugins@wordpress.org telling you your plugin has been held for the following reasons, followed by three or four short paragraphs that sound like they should be self-explanatory and are not.

I have been on the receiving end of that email. Twice, on the same plugin, for two different reasons. What follows is what each reason actually means โ€” including the two things I got wrong and paid for in weeks of waiting.

Do not chase them

Their email says it, and it is true: status requests slow down the queue. Review takes one to six weeks. There is no expedite.

Do not argue

The review team is not a support desk you can negotiate with. Every minute spent explaining why your case is different is a minute not spent fixing it.

Instant read

Paste the email you got.

The review team writes in English whatever your language. Paste their message and this page tells you which of the seven reasons you are actually facing โ€” and what each one wants from you.

Runs entirely in your browser. Nothing is sent anywhere, nothing is stored, there is no server behind this.

Want the specifics?

The reader above gives you the category. Forward me the actual email and I will tell you in writing what they are objecting to in your case โ€” free, no strings, no call.

Send me the email


First, how the review actually works

  1. 01

    They re-review everything

    They do not diff your resubmission against the previous version. A problem they did not mention the first time can be raised on the second pass โ€” and a detailed changelog in your reply is wasted effort.

  2. 02

    They read the source

    Not just the readme, not just the headers. Moving a check somewhere less obvious or renaming a function does not work, and it turns a technical objection into a trust problem.

  3. 03

    Their email is terse on purpose

    They handle a large queue with volunteers. Every clause carries weight โ€” so re-read their email as its own pass, clause by clause, after you think you are done.


The seven reasons

Pick the one that matches your email. Each card jumps to what triggers it and what gets you through.

01 Guideline 5 โ€” trialware You capped something that runs on the user's own server. 02 Name and trademark The name collides โ€” and it is not only the name they check. 03 The Contributors line Your own account is missing from the readme. 04 Broken Plugin URI The URL in your header must open, today, on this plugin. 05 External services Every outbound call has to be documented, and match the code. 06 A secret in the ZIP Your licence SDK wrote a key into the public archive. 07 Crowded category A remark, not a compliance point. Answer with facts.
01

Guideline 5, โ€œtrialwareโ€

Plugins may not limit functionality that runs entirely on the user's own installation.

What triggers it

  • A feature running entirely on the user's server, artificially capped, with a paid tier to unlock it
  • The textbook case: a โ€œbring your own API keyโ€ mode capped at N uses per month โ€” the user supplies the key, the user pays the provider, the computation costs you nothing
  • Note what is not the problem: charging money is fine, having a Pro version is fine

What passes

  • Remove the limit on anything that runs locally
  • Move the feature server-side, at your own cost โ€” then the limit reflects a real expense, and Guideline 6 explicitly allows it
  • Sell support, updates, or a hosted service โ€” anything except unlocking code that already runs on the user's machine
What actually worked, and it surprised me

My first fix kept the local mode and simply removed its cap, running it alongside a new managed service. It was compliant. It was also fragile to explain, because it left a reviewer having to evaluate two code paths and take my word on which one the pricing applied to.

The version that went through removed the local mode entirely. If there is no local code, Guideline 5 does not apply at all. A simpler compliance position beats a defensible one.

Scale check: this is not a patch. For me it meant deleting a whole mode, standing up a Cloudflare Worker holding my own API key, wiring licence webhooks with signature validation, adding per-install quotas, and rewriting the plugin's entire generation path. Knowing the answer took an hour. Shipping it took days.

02

Name and trademark collision

not just in the name โ€” includes โ€ฆ the URLs of this plugin

What triggers it

  • The displayed name, the slug, the text domain, the Plugin URI, every link in your admin UI, the readme, the contributor username, and the domain itself
  • A conflict outside WordPress entirely: unrelated technology projects, GitHub, npm, SaaS products, trademark registries
  • A replacement name that is not demonstrably yours โ€” permission from the other owner does not resolve it

What passes

  • A purely descriptive name cannot collide with anyone's brand
  • Safest move: take the descriptive half of their own suggested name and drop the handle โ€” they wrote it, so they can hardly call it misleading
  • Put your fallback in the same email, or you buy another one-to-six-week round trip
  • Check a slug is free: api.wordpress.org/plugins/info/1.2/?action=plugin_information&request[slug]=YOUR-SLUG returns โ€œPlugin not found.โ€ when it is available
Three renames that survived a grep I had declared clean

The name split across an HTML tag. My site logo was written Plug<span class="dot">Forge</span>. No search for the full name finds that โ€” and it was the most visible element on every page.

An even older slug. Dozens of links still pointed at the plugin's first name, abandoned two renames earlier, including the main call-to-action. Search for every dead name, not just the most recent one.

What goes out over the network. A background worker was sending the old name in its User-Agent to every third-party site it touched. Invisible on screen, entirely real to the servers receiving it.

03

The Contributors line

What triggers it

  • The owner of the wordpress.org account submitting the plugin is not in the Contributors: line of the readme
  • Frequently missed precisely because the line already exists with other names in it โ€” a framework author, a billing SDK โ€” so it does not look empty

What passes

  • Add your submitting account handle to Contributors:
  • If you are also resolving a trademark issue, check that line too โ€” a contributor handle built on the rejected name contradicts the entire rename
04

Broken Plugin URI

What triggers it

  • The URL returns a 404 โ€” a technical fix
  • The URL still contains the conflicting name โ€” that is a trademark problem, see reason 02
  • The URL points at your agency homepage rather than at this plugin specifically

What passes

  • Verify it live, not in the file: a URL corrected in your source but never deployed is still a rejection
  • Before you reply, actually open every URL your submission contains
05

Undocumented external services

What triggers it

  • An outbound call not documented in an External Services section of the readme: what is sent, when, and to which provider
  • A description that no longer matches the submitted code โ€” worse than no description, because it reads as concealment rather than oversight

What passes

  • Write that section last, after your architecture has stopped moving
  • Check any privacy policy you link from it says the same thing
  • Name every endpoint the plugin actually calls, on the domain it actually calls
The one that nearly slipped through

My privacy policy still said the plugin sent an image URL long after the code had switched to sending the image itself. Two official documents contradicting each other about what leaves the user's server is exactly the kind of discrepancy this section exists to catch. I found it an hour before resubmitting, on a final pass, after everything else had been declared clean.

06

A secret shipped in the public ZIP

What triggers it

  • Some licensing SDKs write a plaintext secret into your main plugin file โ€” the generated code sometimes says so itself, in a comment
  • A ZIP assembled by hand rather than by the SDK's own build tool, which strips the secret automatically

What passes

  • If you build your own archive, unzip it and grep it before you upload
  • Ten seconds of checking against a failure mode that is permanent once published
07

โ€œThis category is crowdedโ€

What triggers it

  • A remark that your category is saturated โ€” this is not a compliance point and does not block approval on its own

What passes

  • Answer with a verifiable technical difference, not a marketing claim
  • Resist โ€œbetter than Xโ€: it is unfalsifiable, and a review team reading it will trust the rest of your submission less

How to reply

Their instruction is worth following literally: keep it short, and do not list your changes. They re-review everything from scratch, so a changelog is noise.

  1. Confirmation of what changed, in a sentence or two โ€” the position, not the diff
  2. If a rename is involved, an explicit request to reserve the new slug โ€” they do not do it on their own
  3. Your fallback option, if there is any doubt
On the mechanics

Upload the corrected version through the โ€œAdd your pluginโ€ page while logged into the submitting account โ€” not as an email attachment. A page on make.wordpress.org from 2015 says otherwise; it is out of date. The โ€œText Domainโ€ warning you get on upload is normal until they reserve your slug.

The checklist I run before resubmitting

Every line came from getting one of them wrong. Tick them off โ€” your browser remembers.

That last line is the one that mattered most: thirteen review passes over my own code missed three blocking bugs, and every one surfaced in the first five minutes of actually running it. Read the code to find what is wrong. Run it to find what is missing.

What this actually costs

For a plain compliance fix โ€” documentation, headers, contributors, URLs โ€” a few hours is realistic.

For Guideline 5, be honest with yourself: you are changing your product's business model and probably building server-side infrastructure you did not have. Between my first rejection and my final resubmission there were nine days, two rejected names, one Cloudflare Worker, a webhook integration, and three bugs that only appeared when I stopped reading and started running.

The guidelines are not arbitrary, and once you see the pattern behind them they are consistent: the directory hosts free software that works, and everything is judged against that.

If your plugin is held right now

I do this for other plugin authors: every point in the rejection email addressed, the guidelines they have not raised yet checked too, and the reply written for you to send.

What that involves