Five review rounds at wordpress.org, and the four things that actually cost me time
I submitted my first plugin to the wordpress.org directory at the end of August and it was approved on 8 September, after five rounds. Almost none of the delay was code quality. Writing it down because most of what held me up is the kind of thing you only find out when it happens to you.
1. The name was rejected twice, and that cost the most
The first submission was auto-rejected within a second: a display name or permalink cannot begin with "wp". Fine, I renamed.
The second attempt cleared the automated scan and then a human turned it down. "Toolkit" is a generic description, and "Ultimate" is a promotional modifier rather than a distinctive identifier. Two lines in that reply were worth the whole round:
- "The absence of an exact match in the directory does not make the name distinctive." Checking that your name is free is not the test.
- "Other published plugins do not set a precedent for this submission; some were accepted under earlier standards." I had a list of near-identical names approved as recently as last year. It does not count.
What passes is a coined word or a personal/brand identifier, and it has to be at the front, not the back. I ended up inventing one. Remember that the slug is permanent while the display name is not, so keep the slug short and brandable and put the descriptive part in the name.
2. Your prefix has to be more than four characters
Mine was three. That meant renaming namespaces, constants, option names, post type slugs, hooks, script handles and every public template function. Do it before you submit. If your plugin is already out there, this is a breaking change for everyone who uses it.
A trap I hit doing that rename, and this one applies whether or not you ever submit anything: WordPress builds a submenu page's screen ID from the menu title, not the slug — sanitize_title( $menu_title ) . '_page_' . $submenu_slug. I had that prefix hardcoded in 31 comparisons. After the rename not one of them matched, so every admin screen of the plugin lost its CSS and JS. No error, no warning, php -l clean. It just looked like the styling had evaporated.
3. You can no longer ship an editor for arbitrary CSS, JavaScript or PHP
I had a code snippets feature. The reply was blunt: plugins that let users save arbitrary custom CSS, JS or PHP are not accepted, please remove it.
I asked whether a CSS-only version would be acceptable, since that is narrower than what the Customizer already offers. No, and they named the Customizer as exactly the reason CSS is covered too.
If you have that feature, plan for it not existing in your free version at all.
4. Escaping happens at the point of output, and nothing goes inline
52 notices, two patterns behind them.
Escape where you echo, not where you build. A helper that escapes attributes earlier in the chain does not count, even when it is provably safe. I annotated 105 of those in one directory and proved the rendered HTML was byte-identical before and after, which is a good way to convince yourself you did not break anything while touching a hundred files.
Every <style> and <script> goes through the enqueue chain. I argued for one exception, an engine that printed CSS on wp_head at priority 99 so user CSS would land after the theme, and I was wrong: enqueuing at priority 999 keeps exactly the same order.
Two exceptions I explained in my reply and that survived: a block render callback returning HTML the theme developer wrote themselves, and a standalone page that builds a whole document and exits, the way core's wp_die() does.
Two things I broke myself while fixing the above
- **
sanitize_text_field()strips percent-encoded characters.**/caf%C3%A9/comes back as/caf/. Harmless onREMOTE_ADDR, fatal on a URL path: a redirect or a route with a non-ASCII slug silently stops matching, and nothing errors. Useesc_url_raw()for anything that is a URL. I introduced this by following the review instruction literally instead of choosing the function that fits the value. - Core owns the id
{metabox-id}-title. It gives every metabox heading that id. My box wasultivo-seoand my own title input wasultivo-seo-title, so two elements shared an id andgetElementByIdreturned the<h2>. The character counter sat at 0/60 with text in the field, and the live preview never followed that input. It went unnoticed for months because the field right below it had no collision.
One more, if your URIs point at your own domain
If the Author URI or Plugin URI is a domain that is not obviously connected to the email you registered with, they will ask you to prove you own it. A TXT record on the root is enough. Add it next to your SPF record: a root domain can hold several TXT records, and replacing the SPF one instead of adding to it breaks your mail.
None of this is a complaint. The reviewers were right on every point except the one where I pushed back and lost, and the plugin is better for it. But if I had known the name rule and the prefix rule before submitting, I would have saved about two weeks.
The plugin was Ultivo Toolkit, if anyone wants to see what came out the other end. Not linking it, that is not what this post is for.
[link] [comments]