Crear un plugin de WordPress desde cero requiere PHP básico, una estructura de carpetas correcta y conocimiento de los hooks del sistema. Con esos tres elementos, cualquier desarrollador puede tener un plugin funcional en menos de una hora, listo para extender cualquier sitio sin tocar el núcleo de WordPress.
En 30 segundos
- Un plugin de WordPress es un archivo PHP con una cabecera especial, ubicado en
wp-content/plugins/, que se activa desde el panel de administración. - La estructura mínima necesita un archivo principal con metadatos y al menos un hook (
add_actionoadd_filter) para ejecutar código. - La seguridad del plugin depende de tres prácticas: sanitizar inputs con funciones como
sanitize_text_field(), escapar outputs conesc_html(), y proteger formularios con nonces. - El 93% de las vulnerabilidades en plugins provienen de datos no validados que llegan a la base de datos o al HTML sin pasar por ningún filtro.
- Para publicar en WordPress.org necesitás cuenta gratuita, código que cumpla los estándares de revisión y un archivo
readme.txtcon formato específico.
Un plugin de WordPress es un conjunto de archivos PHP (y opcionalmente CSS, JS e imágenes) que se carga junto con WordPress para agregar o modificar funcionalidades del sistema sin alterar el núcleo. Esta definición, que parece obvia, tiene implicancias concretas: si modificás directamente los archivos de WordPress o del tema para agregar lógica de negocio, eso no es un plugin, es una deuda técnica que te va a explotar en cada actualización.
Requisitos previos para crear un plugin WordPress
No necesitás mucho para empezar. PHP básico alcanza: variables, funciones, arrays, y algo de orientación a objetos si querés estructurar bien el código. Con eso ya podés escribir plugins útiles.
El entorno mínimo que funciona: un editor de código (VS Code con la extensión PHP Intelephense es suficiente, PhpStorm si trabajás profesionalmente), acceso al servidor vía FTP/SSH o un entorno local como LocalWP o XAMPP, y una instalación de WordPress operativa. No hace falta nada más para arrancar.
Estructura de carpetas y archivo principal
Todos los plugins viven en wp-content/plugins/. La convención es crear una carpeta con el mismo nombre que el plugin, sin espacios, usando guiones: mi-plugin-seguridad/. Dentro, el archivo principal lleva el mismo nombre: mi-plugin-seguridad.php.
Para un plugin simple, un solo archivo alcanza. Para algo más complejo, la estructura recomendada según el Codex oficial de WordPress es esta:
mi-plugin/mi-plugin.php— archivo principal con la cabecerami-plugin/includes/— clases y funciones auxiliaresmi-plugin/assets/css/yassets/js/— estilos y scriptsmi-plugin/admin/— lógica del panel de administración
Un detalle que se olvida seguido: poné un archivo index.php vacío en cada carpeta. Previene que un servidor mal configurado liste el contenido del directorio y exponga tu estructura interna. Más contexto en plugins populares como WPForms.
La cabecera del plugin: metadatos obligatorios
WordPress reconoce un archivo PHP como plugin gracias a un bloque de comentarios específico al inicio del archivo principal. Sin esto, el plugin no aparece en el panel de administración.
La cabecera mínima funcional:
Plugin Name:— el nombre que aparece en el listado de plugins (obligatorio)Version:— número de versión semántico (recomendado)Description:— descripción corta en el panelAuthor:yAuthor URI:— tu nombre y webText Domain:— identificador único para las traducciones, sin espacios
El campo Text Domain tiene que coincidir exactamente con el nombre de la carpeta del plugin si querés que las traducciones funcionen correctamente. Es un error común que genera horas de debugging innecesario (sí, en serio, lo veo seguido en auditorías).
Hooks de WordPress: acciones y filtros
Los hooks son el corazón del sistema de plugins de WordPress. Según la documentación de Kinsta sobre hooks, existen dos tipos con comportamientos distintos:
Acciones (Actions)
Ejecutan código en un momento específico del ciclo de WordPress. No devuelven valor, simplemente hacen algo. Se registran con add_action() y se disparan con do_action(). Ejemplos comunes: wp_enqueue_scripts para cargar CSS/JS, init para inicializar funcionalidades, save_post cuando se guarda un post.
Filtros (Filters)
Reciben un valor, lo modifican y lo devuelven. Se registran con add_filter() y se disparan con apply_filters(). Ejemplos: the_content para modificar el contenido de un post antes de mostrarlo, wp_title para alterar el título de la página.
La diferencia práctica: si tu plugin necesita hacer algo (mandar un email, guardar en la DB, redirigir), usá acciones. Si necesita modificar datos que ya existen, usá filtros. Mezclar los roles genera bugs silenciosos difíciles de rastrear.
Sanitización y validación: cimientos de seguridad
Ponele que tu plugin tiene un formulario donde el usuario ingresa su nombre. Tomás ese valor y lo guardás en la base de datos. Si no filtrás lo que llega, alguien puede mandar <script>alert('XSS')</script> como nombre y ya tenés un problema de seguridad real. Tema relacionado: automatizar el desarrollo con IA.
El 93% de las vulnerabilidades en plugins provienen exactamente de ahí: datos sin validar que terminan en la base de datos o en el HTML. Según el análisis de vulnerabilidades de Kinsta, XSS e inyección SQL son las dos más frecuentes, y ambas se previenen con las mismas funciones.
El orden importa: primero validás (¿es del tipo correcto?), después sanitizás (limpiás el valor), y al momento de mostrarlo, escapás (preparás para el contexto de output).
| Función | Uso | Cuándo aplicarla |
|---|---|---|
sanitize_text_field() | Texto plano sin HTML | Al recibir el input |
wp_kses() | HTML con etiquetas permitidas | Al recibir contenido HTML controlado |
absint() | Enteros positivos | IDs, cantidades, contadores |
esc_html() | Escapar para contexto HTML | Al imprimir en el template |
esc_url() | Escapar URLs | Al imprimir atributos href/src |
esc_attr() | Escapar para atributos HTML | Al imprimir en value=»…» |

Sanitizás una vez al guardar. Escapás cada vez que mostrás. Son operaciones distintas con propósitos distintos, y confundirlas deja huecos.
Nonces y protección contra ataques CSRF
Un nonce en WordPress no es exactamente lo que dice el nombre (number used once), ya que tiene una vida útil de 12 a 24 horas y puede usarse múltiples veces en ese período. Lo que sí hace es vincular una acción a un usuario y a un momento específico, lo que impide que un sitio externo engañe al navegador para que ejecute acciones en nombre del usuario sin que este lo sepa.
La implementación tiene dos pasos. En el formulario, generás el nonce con wp_create_nonce('mi-accion') y lo pasás como campo oculto. Al procesar el formulario en el servidor, verificás con wp_verify_nonce() antes de hacer cualquier operación.
Ojo con un error que veo con frecuencia: asumir que si el nonce es válido, el usuario tiene permisos para hacer lo que está pidiendo. No funciona así. El nonce confirma que el request es legítimo; current_user_can() confirma que el usuario tiene autorización. Las dos verificaciones van juntas, nunca una sola.
Otro punto a tener en cuenta: si usás wp_localize_script() para pasar el nonce al JavaScript del frontend, ese valor es visible en el código fuente de la página. No es un problema crítico por sí solo, pero en formularios de alta sensibilidad, considerá la alternativa de wp_nonce_field() directamente en el HTML del servidor.
Errores comunes de seguridad a evitar
Confiar en los datos del usuario sin filtrar
Todo lo que llega via $_GET, $_POST, o $_COOKIE puede venir manipulado. Siempre pasá esos valores por la función de sanitización correspondiente antes de usarlos. Para más detalles técnicos, mirá tipos de plugins que podés crear.
No proteger las llamadas AJAX con nonces
Los endpoints de wp_ajax_ y wp_ajax_nopriv_ son accesibles desde cualquier lugar si no se validan. Incluso en acciones «de solo lectura», agregá verificación de nonce para evitar abuso de recursos.