{"id":4421,"date":"2026-06-10T08:55:30","date_gmt":"2026-06-10T06:55:30","guid":{"rendered":"https:\/\/davidperezgar.com\/en\/?p=4421"},"modified":"2026-06-10T09:20:14","modified_gmt":"2026-06-10T07:20:14","slug":"how-to-prepare-your-wordpress-directory-plugin","status":"publish","type":"post","link":"https:\/\/davidperezgar.com\/en\/blog\/talks\/how-to-prepare-your-wordpress-directory-plugin\/","title":{"rendered":"How to prepare your plugin for the WordPress.org directory"},"content":{"rendered":"\n<figure class=\"wp-block-embed is-type-video is-provider-wordpress-tv-embed wp-block-embed-wordpress-tv-embed wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"VideoPress Video Player\" aria-label=\"VideoPress Video Player\" width=\"1200\" height=\"675\" src=\"https:\/\/video.wordpress.com\/embed\/CtUEkQF1?hd=1&amp;cover=1\" frameborder=\"0\" allowfullscreen allow=\"clipboard-write\"><\/iframe><script src='https:\/\/v0.wordpress.com\/js\/next\/videopress-iframe.js?m=1770107250'><\/script>\n<\/div><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">At the WordCamp Europe in Krakow, Fran Torres and I were speakers. Just after lunch, with the audience fighting against the midday siesta, but eagerly. The talk revolved around something that we both experience every week: reviewing plugins for the WordPress.org directory.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We&#8217;re members of the Plugins Team, I&#8217;m sponsored by Hostinger and Fran by SiteGround, and what we basically do is review the plugins that make it to the directory, detect problems, report them to the author, and walk them until the plugin is ready to be published. It sounds simple, but the volume is considerable: in one week we manage about 700 new mailings, make about 2,000 revisions and respond to around 4,000 emails. A year ago there were 200 weekly shipments. The leap has a lot to do with AI generating code and plugins at full speed. In fact, we already use AI ourselves in part of the review process.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The idea of the talk was not to teach how to make a plugin from scratch, but to show what fails the most when a plugin reaches the computer, with real examples of vulnerable code found in plugins with hundreds of thousands or millions of active installations.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>What we see most as unfulfilled<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">First things are the most basic: the code has to be readable. The directory is open source, with a GPL license, and that means that the code has to be readable and modified. We do not allow obfuscated or encrypted code. Minified files are fine, but it always has to be accompanied by the source code.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Another point that generates a lot of confusion is the functionality without artificial restrictions. The WordPress.org directory is not a marketplace. You can&#8217;t cram internal paywalls or intentional limitations into the plugin, such as limiting the number of slides you can create to four. If you want to monetize, the right way is an external add-on or a paid service, not to put a restriction inside the plugin itself. On the contributor day just before the talk we reviewed a plugin that needed to connect to an external server to generate a random number. No, it is not reasonable.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There are also frequent problems with the name. If you use another brand or project&#8217;s name in a way that looks like an official integration, you need to be able to prove it. If you&#8217;re not WooCommerce, your plugin can&#8217;t be called something that sounds like it is. And generic names like &#8220;SEO&#8221; already have over 2,000 results in the directory, so all you get is make your plugin impossible to find. A quick trick before you send: Google the name next to &#8220;WordPress&#8221; and see what comes up.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As for external calls, JS libraries have to go inside the plugin, not load from external CDNs. There are security, performance, and geographic availability reasons for this. Tracking and telemetry must be disabled by default and require explicit opt-in. And any external services you use have to be documented in the README: who provides it, what data is sent, and links to terms of service and privacy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Security nightmares<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This was the most technical part, and the one that generated the most attention. We show real code of vulnerabilities detected in plugins with between 600,000 and 5 million active installations. The bottom line is that many times the security hole is not something sophisticated. It is simply an input that goes directly to the database without sanitizing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The rule is clear: sanitize at the entrance, escape at the exit. No exceptions. Do not trust any source, neither <code>$_POST<\/code>, nor <code>$_SERVER<\/code>external APIs, nor the database itself. WordPress has features for everything: <code>sanitize_text_field()<\/code>, , <code>sanitize_url()<\/code>, <code>esc_attr()<\/code><code>esc_html()<\/code>, <code>esc_url()<\/code>, <code>$wpdb-&gt;prepare()<\/code>&#8230; We showed a plugin with 5 million installs where the solution to a critical vulnerability was to add a single line. And a case of SQL injection into a plugin with a million active installations, caused simply by not using <code>$wpdb-&gt;prepare()<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With endpoints, we saw even more striking things. The permission callback must always be there. Simply putting <code>return true<\/code> , it indicates that security has not been thought of. We showed an endpoint that returned the content of any post without verifying if the user had access, which included drafts, password-protected posts, and WooCommerce orders. We showed a payment endpoint where the amount came directly from <code>$_POST['amount']<\/code> and the only control was that it was greater than zero, which allowed you to pay practically whatever you wanted. And the most serious case: a payment confirmation endpoint that read the status of the transaction from the user&#8217;s input, allowing anyone to mark any order as paid with a simple manipulated request.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For endpoints, the recommendation is REST API as the first option, Ajax Admin with nonce and capacity checking if necessary, and staying away from standalone PHP files and XML-RPC.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">About nonces, which is something that many people do not understand well: they are used to verify the origin of a request and prevent CSRF attacks. We showed a case where the difference between using <code>&amp;&amp;<\/code> and <code>||<\/code> verifying nonce left a CSV export endpoint completely open without authentication. The visual difference was minimal, the consequence was not. The functions are <code>wp_create_nonce()<\/code> and <code>wp_verify_nonce()<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">And prefixes, which also cause serious problems. All functions, classes, global variables, shortcodes, post types, Ajax endpoints, and in-database options must use a unique and distinctive prefix. Without a prefix, two plugins declaring the same function cause a fatal error, and if they overwrite the same option in the database, you can lose data. We have a list of prefixes that we see constantly and that should be avoided: <code>set<\/code>, , <code>get<\/code>, <code>loop<\/code><code>google<\/code><code>save<\/code><code>ai<\/code><code>ajax<\/code><code>admin<\/code><code>elementor<\/code>and even image extensions such as .<code>.jpg<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Tools we recommend<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To review your plugin before submitting it, the first option is Plugin Check (PCP), the official team plugin, available on WordPress.org. It detects most checks automatically and exports the results to Markdown, making it very easy to use with AI tools. PHPCS with WPCS is another solid option, integrable in the editor and CI\/CD pipelines. And if you use AI to develop, you can ask it directly if the plugin is safe using the WordPress Coding Standards AI Skill.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The review process and timing<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When a plugin arrives, we send a report with everything that needs to be corrected. The author fixes and forwards it, and the cycle repeats until the plugin is ready. Currently the time for the first check-up is around 5 days, although two months ago it was two weeks and three years ago it was three months. Fluctuations depend on the number of active volunteers and the volume of submissions. In total, if you follow the guidelines well, between two weeks and a month is a reasonable time. Once approved, it&#8217;s up to you to decide when to post.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One important thing: don&#8217;t ping the team&#8217;s email asking for the status of your shipment. We manage 700 shipments per week and there is a queue. <\/p>\n\n\n\n<figure class=\"wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex frontblocks-gallery-grid\" data-layout=\"grid\" data-columns=\"3\" data-gutter=\"20\" data-lightbox=\"false\">\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" data-id=\"4642\" src=\"https:\/\/davidperezgar.com\/wp-content\/uploads\/wceu-talk-09-1082x721.avif\" alt=\"wceu talk 09\" class=\"wp-image-4642\" title=\"\"><figcaption class=\"wp-element-caption\">WordCamp Europe 2026 &#8211; Talks Day 1 Afternoon<br>David Perez, Fran Torres: Get your plugin ready for submission to the directory<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" data-id=\"4641\" src=\"https:\/\/davidperezgar.com\/wp-content\/uploads\/wceu-talk-08-1082x721.avif\" alt=\"wceu talk 08\" class=\"wp-image-4641\" title=\"\"><figcaption class=\"wp-element-caption\">WordCamp Europe 2026 &#8211; Talks Day 1 Afternoon<br>David Perez, Fran Torres: Get your plugin ready for submission to the directory<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" data-id=\"4640\" src=\"https:\/\/davidperezgar.com\/wp-content\/uploads\/wceu-talk-07-1082x721.avif\" alt=\"wceu talk 07\" class=\"wp-image-4640\" title=\"\"><figcaption class=\"wp-element-caption\">WordCamp Europe 2026 &#8211; Talks Day 1 Afternoon<br>David Perez, Fran Torres: Get your plugin ready for submission to the directory<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" data-id=\"4639\" src=\"https:\/\/davidperezgar.com\/wp-content\/uploads\/wceu-talk-06-1082x721.avif\" alt=\"wceu talk 06\" class=\"wp-image-4639\" title=\"\"><figcaption class=\"wp-element-caption\">WordCamp Europe 2026 &#8211; Talks Day 1 Afternoon<br>David Perez, Fran Torres: Get your plugin ready for submission to the directory<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" data-id=\"4638\" src=\"https:\/\/davidperezgar.com\/wp-content\/uploads\/wceu-talk-05-1082x721.avif\" alt=\"wceu talk 05\" class=\"wp-image-4638\" title=\"\"><figcaption class=\"wp-element-caption\">WordCamp Europe 2026 &#8211; Talks Day 1 Afternoon<br>David Perez, Fran Torres: Get your plugin ready for submission to the directory<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" data-id=\"4637\" src=\"https:\/\/davidperezgar.com\/wp-content\/uploads\/wceu-talk-04-1082x721.avif\" alt=\"wceu talk 04\" class=\"wp-image-4637\" title=\"\"><figcaption class=\"wp-element-caption\">WordCamp Europe 2026 &#8211; Talks Day 1 Afternoon<br>David Perez, Fran Torres: Get your plugin ready for submission to the directory<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" data-id=\"4636\" src=\"https:\/\/davidperezgar.com\/wp-content\/uploads\/wceu-talk-03-1082x721.avif\" alt=\"wceu talk 03\" class=\"wp-image-4636\" title=\"\"><figcaption class=\"wp-element-caption\">WordCamp Europe 2026 &#8211; Talks Day 1 Afternoon<br>David Perez, Fran Torres: Get your plugin ready for submission to the directory<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" data-id=\"4635\" src=\"https:\/\/davidperezgar.com\/wp-content\/uploads\/wceu-talk-02-1-1082x721.avif\" alt=\"wceu talk 02\" class=\"wp-image-4635\" title=\"\"><figcaption class=\"wp-element-caption\">WordCamp Europe 2026 &#8211; Talks Day 1 Afternoon<br>David Perez, Fran Torres: Get your plugin ready for submission to the directory<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" data-id=\"4634\" src=\"https:\/\/davidperezgar.com\/wp-content\/uploads\/wceu-talk-02-1082x721.avif\" alt=\"wceu talk 02\" class=\"wp-image-4634\" title=\"\"><figcaption class=\"wp-element-caption\">WordCamp Europe 2026 &#8211; Talks Day 1 Afternoon<br>David Perez, Fran Torres: Get your plugin ready for submission to the directory<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" data-id=\"4633\" src=\"https:\/\/davidperezgar.com\/wp-content\/uploads\/wceu-talk-01-1082x721.avif\" alt=\"wceu talk 01\" class=\"wp-image-4633\" title=\"\"><figcaption class=\"wp-element-caption\">WordCamp Europe 2026 &#8211; Talks Day 1 Afternoon<br>David Perez, Fran Torres: Get your plugin ready for submission to the directory<\/figcaption><\/figure>\n<\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>At the WordCamp Europe in Krakow, Fran Torres and I were speakers. Just after lunch, with the audience fighting against the midday siesta, but eagerly. The talk revolved around something that we both experience every week: reviewing plugins for the WordPress.org directory. We&#8217;re members of the Plugins Team, I&#8217;m sponsored by Hostinger and Fran by SiteGround, and what we basically do is review the plugins that make it to the directory, detect problems, report them to the author, and walk them until the plugin is ready to be published. It sounds simple, but the volume is considerable: in one week we manage&#8230; <a title=\"How to prepare your plugin for the WordPress.org directory\" class=\"read-more\" href=\"https:\/\/davidperezgar.com\/blog\/charlas\/como-preparar-tu-plugin-para-el-directorio-de-wordpress-org\/\" aria-label=\"Read more about How to prepare your plugin for the WordPress.org directory\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":4423,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"_ayudawp_aiss_exclude":false,"_ayudawp_aiss_summary":"","_ayudawp_aiss_summary_provider":"","_ayudawp_aiss_summary_hash":"","webmentions_disabled_pings":false,"webmentions_disabled":false,"editor_notices":[],"footnotes":""},"categories":[169],"tags":[],"class_list":["post-4421","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-talks","generate-columns","tablet-grid-50","mobile-grid-100","grid-parent","grid-50"],"_links":{"self":[{"href":"https:\/\/davidperezgar.com\/en\/wp-json\/wp\/v2\/posts\/4421","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/davidperezgar.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/davidperezgar.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/davidperezgar.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/davidperezgar.com\/en\/wp-json\/wp\/v2\/comments?post=4421"}],"version-history":[{"count":2,"href":"https:\/\/davidperezgar.com\/en\/wp-json\/wp\/v2\/posts\/4421\/revisions"}],"predecessor-version":[{"id":4428,"href":"https:\/\/davidperezgar.com\/en\/wp-json\/wp\/v2\/posts\/4421\/revisions\/4428"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/davidperezgar.com\/en\/wp-json\/wp\/v2\/media\/4423"}],"wp:attachment":[{"href":"https:\/\/davidperezgar.com\/en\/wp-json\/wp\/v2\/media?parent=4421"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/davidperezgar.com\/en\/wp-json\/wp\/v2\/categories?post=4421"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/davidperezgar.com\/en\/wp-json\/wp\/v2\/tags?post=4421"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}