{"id":4613,"date":"2026-06-10T08:54:16","date_gmt":"2026-06-10T06:54:16","guid":{"rendered":"https:\/\/davidperezgar.com\/?p=4613"},"modified":"2026-06-10T08:54:38","modified_gmt":"2026-06-10T06:54:38","slug":"como-preparar-tu-plugin-para-el-directorio-de-wordpress-org","status":"publish","type":"post","link":"https:\/\/davidperezgar.com\/es\/como-preparar-tu-plugin-para-el-directorio-de-wordpress-org\/","title":{"rendered":"C\u00f3mo preparar tu plugin para el directorio de WordPress.org"},"content":{"rendered":"\n<figure class=\"wp-block-embed is-type-video is-provider-wordpress-tv wp-block-embed-wordpress-tv 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=\"500\" height=\"281\" src=\"https:\/\/video.wordpress.com\/embed\/CtUEkQF1?hd=1&amp;cover=1\" frameborder=\"0\" allowfullscreen allow=\"clipboard-write; presentation\"><\/iframe><script src='https:\/\/v0.wordpress.com\/js\/next\/videopress-iframe.js?m=1790791280'><\/script>\n<\/div><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">En la WordCamp Europe de Cracovia estuvimos de ponentes Fran Torres y yo. Justo despu\u00e9s de comer, con el p\u00fablico luchando contra la siesta del mediod\u00eda, pero con ganas. La charla giraba en torno a algo que los dos vivimos cada semana: revisar plugins para el directorio de WordPress.org.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Somos miembros del Plugins Team, yo patrocinado por Hostinger y Fran por SiteGround, y lo que hacemos b\u00e1sicamente es revisar los plugins que llegan al directorio, detectar problemas, reportarlos al autor y acompa\u00f1arle hasta que el plugin est\u00e1 listo para publicarse. Suena sencillo, pero el volumen es considerable: en una semana gestionamos unos 700 env\u00edos nuevos, hacemos cerca de 2.000 revisiones y respondemos alrededor de 4.000 correos. Hace un a\u00f1o eran 200 env\u00edos semanales. El salto tiene mucho que ver con la IA generando c\u00f3digo y plugins a toda velocidad. De hecho, nosotros mismos ya usamos IA en parte del proceso de revisi\u00f3n.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La idea de la charla no era ense\u00f1ar a hacer un plugin desde cero, sino mostrar qu\u00e9 es lo que falla m\u00e1s cuando un plugin llega al equipo, con ejemplos reales de c\u00f3digo vulnerable encontrado en plugins con cientos de miles o millones de instalaciones activas.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Lo que m\u00e1s vemos incumplido<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Lo primero es lo m\u00e1s b\u00e1sico: el c\u00f3digo tiene que ser legible. El directorio es open source, con licencia GPL, y eso significa que el c\u00f3digo tiene que poder leerse y modificarse. No permitimos c\u00f3digo ofuscado ni encriptado. Los archivos minificados est\u00e1n bien, pero siempre tiene que venir acompa\u00f1ado del c\u00f3digo fuente.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Otro punto que genera mucha confusi\u00f3n es la funcionalidad sin restricciones artificiales. El directorio de WordPress.org no es un marketplace. No se pueden meter paywalls internos ni limitaciones intencionales dentro del plugin, como limitar a cuatro el n\u00famero de slides que puedes crear. Si quieres monetizar, el camino correcto es un add-on externo o un servicio de pago, no meter una restricci\u00f3n dentro del propio plugin. En el contributor day justo antes de la charla revisamos un plugin que necesitaba conectarse a un servidor externo para generar un n\u00famero aleatorio. No, no es razonable.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Con el nombre tambi\u00e9n hay problemas frecuentes. Si usas el nombre de otra marca o proyecto de una forma que parezca una integraci\u00f3n oficial, tienes que poder demostrarlo. Si no eres WooCommerce, tu plugin no puede llamarse algo que suene a que lo es. Y los nombres gen\u00e9ricos como \"SEO\" ya tienen m\u00e1s de 2.000 resultados en el directorio, as\u00ed que lo \u00fanico que consigues es que tu plugin sea imposible de encontrar. Un truco r\u00e1pido antes de enviar: busca el nombre en Google junto a \"WordPress\" y mira qu\u00e9 aparece.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En cuanto a las llamadas externas, las librer\u00edas JS tienen que ir dentro del plugin, no cargarse desde CDNs externos. Hay razones de seguridad, rendimiento y disponibilidad geogr\u00e1fica para ello. El tracking y la telemetr\u00eda tienen que estar desactivados por defecto y requerir opt-in expl\u00edcito. Y cualquier servicio externo que uses tiene que estar documentado en el README: qui\u00e9n lo presta, qu\u00e9 datos se env\u00edan, y enlaces a t\u00e9rminos de servicio y privacidad.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Los security nightmares<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Esta fue la parte m\u00e1s t\u00e9cnica, y la que m\u00e1s atenci\u00f3n gener\u00f3. Mostramos c\u00f3digo real de vulnerabilidades detectadas en plugins con entre 600.000 y 5 millones de instalaciones activas. La conclusi\u00f3n es que muchas veces el agujero de seguridad no es algo sofisticado. Es simplemente un input que va directo a la base de datos sin sanitizar.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La regla es clara: sanitizar en la entrada, escapar en la salida. Sin excepciones. No confiar en ninguna fuente, ni <code>$_POST<\/code>, ni <code>$_SERVER<\/code>, ni APIs externas, ni la propia base de datos. WordPress tiene funciones para todo: <code>sanitize_text_field()<\/code>, <code>sanitize_url()<\/code>, <code>esc_html()<\/code>, <code>esc_attr()<\/code>, <code>esc_url()<\/code>, <code>$wpdb-&gt;prepare()<\/code>... Mostramos un plugin con 5 millones de instalaciones donde la soluci\u00f3n a una vulnerabilidad cr\u00edtica era a\u00f1adir una sola l\u00ednea. Y un caso de SQL injection en un plugin con un mill\u00f3n de instalaciones activas, causado simplemente por no usar <code>$wpdb-&gt;prepare()<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Con los endpoints vimos cosas todav\u00eda m\u00e1s llamativas. El permission callback tiene que estar siempre. Poner <code>return true<\/code> sin m\u00e1s ya nos indica que no se ha pensado en la seguridad. Mostramos un endpoint que devolv\u00eda el contenido de cualquier post sin verificar si el usuario ten\u00eda acceso, lo que inclu\u00eda borradores, posts protegidos con contrase\u00f1a y pedidos de WooCommerce. Mostramos un endpoint de pago donde el importe ven\u00eda directamente de <code>$_POST['amount']<\/code> y el \u00fanico control era que fuera mayor que cero, lo que permit\u00eda pagar pr\u00e1cticamente lo que quisieras. Y el caso m\u00e1s grave: un endpoint de confirmaci\u00f3n de pago que le\u00eda el estado de la transacci\u00f3n desde el input del usuario, lo que permit\u00eda a cualquiera marcar cualquier pedido como pagado con una simple petici\u00f3n manipulada.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Para endpoints, la recomendaci\u00f3n es REST API como primera opci\u00f3n, Admin Ajax con nonce y verificaci\u00f3n de capacidades si es necesario, y alejarse de ficheros PHP standalone y de XML-RPC.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sobre los nonces, que es algo que mucha gente no termina de entender bien: sirven para verificar el origen de una petici\u00f3n y evitar ataques CSRF. Mostramos un caso donde la diferencia entre usar <code>&amp;&amp;<\/code> y <code>||<\/code> en la verificaci\u00f3n del nonce dejaba un endpoint de exportaci\u00f3n de CSV completamente abierto sin autenticaci\u00f3n. La diferencia visual era m\u00ednima, la consecuencia no. Las funciones son <code>wp_create_nonce()<\/code> y <code>wp_verify_nonce()<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Y los prefijos, que tambi\u00e9n causan problemas graves. Todas las funciones, clases, variables globales, shortcodes, post types, endpoints Ajax y opciones en base de datos tienen que usar un prefijo \u00fanico y distintivo. Sin prefijo, dos plugins que declaren la misma funci\u00f3n provocan un fatal error, y si sobreescriben la misma opci\u00f3n en base de datos, puedes perder datos. Tenemos una lista de prefijos que vemos constantemente y que hay que evitar: <code>set<\/code>, <code>get<\/code>, <code>save<\/code>, <code>admin<\/code>, <code>ai<\/code>, <code>google<\/code>, <code>loop<\/code>, <code>ajax<\/code>, <code>elementor<\/code>, e incluso extensiones de imagen como <code>.jpg<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Herramientas que recomendamos<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Para revisar tu plugin antes de enviarlo, la primera opci\u00f3n es Plugin Check (PCP), el plugin oficial del equipo, disponible en WordPress.org. Detecta la mayor\u00eda de los checks autom\u00e1ticamente y exporta los resultados a Markdown, lo que lo hace muy f\u00e1cil de usar con herramientas de IA. PHPCS con WPCS es otra opci\u00f3n s\u00f3lida, integrable en el editor y en pipelines de CI\/CD. Y si usas IA para desarrollar, puedes preguntarle directamente si el plugin es seguro usando el WordPress Coding Standards AI Skill.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>El proceso de revisi\u00f3n y los tiempos<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Cuando llega un plugin, enviamos un informe con todo lo que hay que corregir. El autor lo arregla y reenv\u00eda, y el ciclo se repite hasta que el plugin est\u00e1 listo. Actualmente el tiempo para la primera revisi\u00f3n est\u00e1 en torno a 5 d\u00edas, aunque hace dos meses era de dos semanas y hace tres a\u00f1os llegaba a los tres meses. Las fluctuaciones dependen del n\u00famero de voluntarios activos y del volumen de env\u00edos. En total, si sigues bien las directrices, entre dos semanas y un mes es un tiempo razonable. Una vez aprobado, eres t\u00fa quien decide cu\u00e1ndo publicar.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Una cosa importante: no hagas ping al email del equipo preguntando por el estado de tu env\u00edo. Gestionamos 700 env\u00edos semanales y hay una cola. <\/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 loading=\"lazy\" decoding=\"async\" width=\"1082\" height=\"721\" data-id=\"5066\" src=\"\/wp-content\/uploads\/2026\/10\/wceu-talk-09-1082x721-1.avif\" alt=\"wceu talk 09\" class=\"wp-image-5066\" title=\"\"><figcaption class=\"wp-element-caption\">WordCamp Europe 2026 - 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 loading=\"lazy\" decoding=\"async\" width=\"1082\" height=\"721\" data-id=\"5064\" src=\"\/wp-content\/uploads\/2026\/10\/wceu-talk-08-1082x721-1.avif\" alt=\"wceu talk 08\" class=\"wp-image-5064\" title=\"\"><figcaption class=\"wp-element-caption\">WordCamp Europe 2026 - 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 loading=\"lazy\" decoding=\"async\" width=\"1082\" height=\"721\" data-id=\"5062\" src=\"\/wp-content\/uploads\/2026\/10\/wceu-talk-07-1082x721-1.avif\" alt=\"wceu talk 07\" class=\"wp-image-5062\" title=\"\"><figcaption class=\"wp-element-caption\">WordCamp Europe 2026 - 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 loading=\"lazy\" decoding=\"async\" width=\"1082\" height=\"721\" data-id=\"5060\" src=\"\/wp-content\/uploads\/2026\/10\/wceu-talk-06-1082x721-1.avif\" alt=\"wceu talk 06\" class=\"wp-image-5060\" title=\"\"><figcaption class=\"wp-element-caption\">WordCamp Europe 2026 - 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 loading=\"lazy\" decoding=\"async\" width=\"1082\" height=\"721\" data-id=\"5058\" src=\"\/wp-content\/uploads\/2026\/10\/wceu-talk-05-1082x721-1.avif\" alt=\"wceu talk 05\" class=\"wp-image-5058\" title=\"\"><figcaption class=\"wp-element-caption\">WordCamp Europe 2026 - 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 loading=\"lazy\" decoding=\"async\" width=\"1082\" height=\"721\" data-id=\"5056\" src=\"\/wp-content\/uploads\/2026\/10\/wceu-talk-04-1082x721-1.avif\" alt=\"wceu talk 04\" class=\"wp-image-5056\" title=\"\"><figcaption class=\"wp-element-caption\">WordCamp Europe 2026 - 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 loading=\"lazy\" decoding=\"async\" width=\"1082\" height=\"721\" data-id=\"5054\" src=\"\/wp-content\/uploads\/2026\/10\/wceu-talk-03-1082x721-1.avif\" alt=\"wceu talk 03\" class=\"wp-image-5054\" title=\"\"><figcaption class=\"wp-element-caption\">WordCamp Europe 2026 - 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 loading=\"lazy\" decoding=\"async\" width=\"1082\" height=\"721\" data-id=\"5050\" src=\"\/wp-content\/uploads\/2026\/10\/wceu-talk-02-1-1082x721-1.avif\" alt=\"wceu talk 02\" class=\"wp-image-5050\" title=\"\"><figcaption class=\"wp-element-caption\">WordCamp Europe 2026 - 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 loading=\"lazy\" decoding=\"async\" width=\"1082\" height=\"721\" data-id=\"5052\" src=\"\/wp-content\/uploads\/2026\/10\/wceu-talk-02-1082x721-1.avif\" alt=\"wceu talk 02\" class=\"wp-image-5052\" title=\"\"><figcaption class=\"wp-element-caption\">WordCamp Europe 2026 - 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 loading=\"lazy\" decoding=\"async\" width=\"1082\" height=\"721\" data-id=\"5046\" src=\"\/wp-content\/uploads\/2026\/10\/wceu-talk-01-1082x721-1.avif\" alt=\"wceu talk 01\" class=\"wp-image-5046\" title=\"\"><figcaption class=\"wp-element-caption\">WordCamp Europe 2026 - Talks Day 1 Afternoon<br>David Perez, Fran Torres: Get your plugin ready for submission to the directory<\/figcaption><\/figure>\n<\/figure>\n","protected":false},"excerpt":{"rendered":"<p>En la WordCamp Europe de Cracovia estuvimos de ponentes Fran Torres y yo. Justo despu\u00e9s de comer, con el p\u00fablico luchando contra la siesta del mediod\u00eda, pero con ganas. La charla giraba en torno a algo que los dos vivimos cada semana: revisar plugins para el directorio de WordPress.org. Somos miembros del Plugins Team, yo [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":4770,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":15,"footnotes":""},"categories":[169],"tags":[],"class_list":["post-4613","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-charlas"],"_links":{"self":[{"href":"https:\/\/davidperezgar.com\/es\/wp-json\/wp\/v2\/posts\/4613","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/davidperezgar.com\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/davidperezgar.com\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/davidperezgar.com\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/davidperezgar.com\/es\/wp-json\/wp\/v2\/comments?post=4613"}],"version-history":[{"count":1,"href":"https:\/\/davidperezgar.com\/es\/wp-json\/wp\/v2\/posts\/4613\/revisions"}],"predecessor-version":[{"id":5978,"href":"https:\/\/davidperezgar.com\/es\/wp-json\/wp\/v2\/posts\/4613\/revisions\/5978"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/davidperezgar.com\/es\/wp-json\/wp\/v2\/media\/4770"}],"wp:attachment":[{"href":"https:\/\/davidperezgar.com\/es\/wp-json\/wp\/v2\/media?parent=4613"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/davidperezgar.com\/es\/wp-json\/wp\/v2\/categories?post=4613"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/davidperezgar.com\/es\/wp-json\/wp\/v2\/tags?post=4613"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}