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.
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.
First, how the review actually works
- 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.
- 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.
- 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.
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.
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.
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
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
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.
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
โ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.
- Confirmation of what changed, in a sentence or two โ the position, not the diff
- If a rename is involved, an explicit request to reserve the new slug โ they do not do it on their own
- Your fallback option, if there is any doubt
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.
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.