La compatibilidad de plugins con Moodle 5.3 no se demuestra porque un plugin funcione hoy en Moodle 4.5 LTS. Depende del código, sí, pero también de las APIs que utilizas, PHP, la base de datos, las dependencias, los callbacks heredados y, sobre todo, de cuánto hayas probado fuera del happy path.

El plugin se instala.
La página carga.
No aparece ningún error.
Entonces alguien da por terminada la prueba de compatibilidad.
Precisamente por eso, ese es el momento en el que yo empezaría a desconfiar.
Cuando actualizamos Moodle solemos concentrar la atención en el core: requisitos del servidor, base de datos, plugins pendientes de actualizar, copia de seguridad y ejecución del upgrade. Pero hay otra capa menos visible: las decisiones técnicas que hemos ido acumulando dentro de nuestros propios plugins.
Además, si quieres ver cómo encajan version.php, XMLDB, capacidades, Form API y Privacy API en un caso completo, en este tutorial para crear un plugin para Moodle con formulario, permisos y base de datos recorro ese circuito de principio a fin.
Funciones que siguen existiendo pero están deprecated. Callbacks antiguos que Moodle mantiene por compatibilidad. Templates que dependen demasiado de la estructura de un tema. JavaScript construido sobre APIs que han cambiado. Observers acoplados a otro componente. Dependencias que nunca declaramos porque «siempre estaban instaladas».
Mientras no cambiamos de versión, muchas de esas decisiones permanecen escondidas.
En cambio, una actualización las pone a prueba todas a la vez.
TL;DR
La compatibilidad de plugins con Moodle 5.3 no se demuestra instalándolos y comprobando que no explotan.
Hay que revisar requisitos, dependencias, APIs, hooks, callbacks, observers, deprecaciones, templates, JavaScript y pruebas automatizadas. Después hay que ejecutar una instalación limpia y, sobre todo, un upgrade real con datos existentes.
El pequeño plugin que utilizaré como ejemplo
Para no ir saltando entre ejemplos inconexos voy a utilizar durante todo el artículo un plugin local ficticio llamado local_miplugin.
Su estructura irá creciendo a medida que avancemos:
local/miplugin/
├── classes/
│ ├── local/
│ │ ├── compatibility.php
│ │ └── hook_callbacks.php
│ └── observer.php
├── db/
│ ├── events.php
│ ├── hooks.php
│ ├── install.xml
│ └── upgrade.php
├── lang/
│ └── en/
│ └── local_miplugin.php
├── tests/
│ ├── behat/
│ │ └── compatibility.feature
│ └── local/
│ └── compatibility_test.php
├── index.php
└── version.phpAdemás, no necesitamos un plugin enorme para encontrar problemas de compatibilidad. De hecho, cuanto más pequeño sea el ejemplo, más fácil resulta entender qué estamos comprobando realmente.
Antes de comprobar la compatibilidad de plugins con Moodle 5.3: la versión todavía no está cerrada
A fecha de publicación de este artículo, Moodle 5.3 sigue siendo una versión no publicada de la rama main. El code freeze está previsto para el 31 de agosto de 2026 y el lanzamiento final para el 5 de octubre de 2026.
Estamos, por tanto, en un momento especialmente interesante para probar compatibilidad: suficientemente cerca del lanzamiento para trabajar contra la próxima LTS, pero todavía demasiado pronto para tratar esa rama como una plataforma inmutable.
Los requisitos publicados actualmente para Moodle 5.3 incluyen:
- upgrade soportado desde Moodle 4.5 o posterior;
- PHP 8.3 como versión mínima;
- compatibilidad también con PHP 8.4;
- extensión PHP
sodiumobligatoria; max_input_varsigual o superior a 5000;- PHP de 64 bits.
Nota de compatibilidad: sodium no aparece por primera vez en Moodle 5.3. Moodle 4.5 ya la requería. Antes de realizar una migración debemos comprobar siempre las release notes oficiales de Moodle 5.3.
Por tanto, la compatibilidad de plugins con Moodle 5.3 empieza también por la plataforma que los ejecuta. En mi guía sobre gestión estratégica de Moodle mediante auditoría y roadmap desarrollo precisamente esa idea: core, infraestructura, integraciones y extensiones deben evaluarse como un sistema, no como piezas aisladas.
La primera compatibilidad ni siquiera está dentro del plugin
Sin embargo, podemos pasar horas buscando funciones deprecated cuando el primer problema puede estar una capa por debajo.
El servidor sobre el que ejecutaremos Moodle también cambia.
| Componente | Moodle 4.5 | Moodle 5.3 | Qué revisar |
|---|---|---|---|
| PHP | 8.1+ | 8.3+ | Runtime, extensiones y dependencias |
| MySQL | 8.0+ | 8.4+ | Versión y SQL propio |
| MariaDB | 10.6.7+ | 10.11+ | Consultas y configuración |
| PostgreSQL | 13+ | 16+ | Consultas específicas |
| Oracle | Soportado | No soportado desde Moodle 5.0 | Migración de motor |
Además, un plugin puede no utilizar nada especialmente exótico y aun así romperse porque contiene consultas SQL manuales, librerías externas o código PHP que nunca hemos ejecutado bajo PHP 8.3.
Antes de probar el plugin, prueba la plataforma sobre la que va a ejecutarse.
Compatibilidad de plugins con Moodle 5.3: empieza por version.php

El primer fichero que revisaría es version.php.
No porque certifique la compatibilidad, sino porque define parte del contrato entre el plugin y Moodle.
De hecho, esta revisión encaja con una regla más amplia: la compatibilidad se diseña antes de la actualización. En mi guía de arquitectura, gobernanza y desarrollo seguro de plugins Moodle 4.5+ profundizo en estructura, seguridad, eventos, tests, MUC, tareas y CI.
<?php
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'local_miplugin';
$plugin->version = 2026082600;
// Moodle 4.5 como versión mínima.
$plugin->requires = 2024100700;
// Declaramos explícitamente el rango que queremos soportar.
// Desde Moodle 4.5 hasta Moodle 5.3.
$plugin->supported = [405, 503];
$plugin->maturity = MATURITY_STABLE;
$plugin->release = '1.4.0';
$plugin->dependencies = [
'mod_forum' => 2024100700,
];Por tanto, aquí hay dos conceptos que se suelen confundir.
$plugin->requiresestablece la versión mínima necesaria.$plugin->supportedpermite declarar explícitamente el rango de ramas soportadas.
Si declaramos:
$plugin->supported = [405, 503];estamos diciendo que nuestro objetivo es soportar las ramas comprendidas entre Moodle 4.5 y Moodle 5.3.
Pero declarar algo no equivale a haberlo probado.
No conviertas supported en documentación aspiracional. Si declaras compatibilidad con una rama, debería existir al menos una estrategia reproducible para comprobarla.
Cuidado con las dependencias implícitas
Supongamos que nuestro plugin escucha eventos de otro componente y utiliza sus clases directamente.
use mod_forum\event\discussion_created;Si esa dependencia es necesaria para funcionar, conviene declararla:
$plugin->dependencies = [
'mod_forum' => 2024100700,
];De lo contrario nuestro plugin depende de una casualidad operativa: que el otro componente siga instalado y que su API continúe siendo compatible.
Hooks, callbacks y observers: tres cosas que conviene no mezclar

Además, esta es probablemente una de las revisiones que más valor aporta en plugins que llevan varias versiones de Moodle acumulando cambios.
La Hooks API permite registrar callbacks mediante db/hooks.php.
Ejemplo: registrar un hook
Creamos primero:
local/miplugin/db/hooks.php<?php
defined('MOODLE_INTERNAL') || die();
$callbacks = [
[
'hook' =>
\core\hook\output\before_standard_top_of_body_html_generation::class,
'callback' => [
\local_miplugin\local\hook_callbacks::class,
'before_body',
],
'priority' => 100,
],
];Y colocamos la lógica en una clase autocargable.
<?php
namespace local_miplugin\local;
use core\hook\output\before_standard_top_of_body_html_generation;
defined('MOODLE_INTERNAL') || die();
final class hook_callbacks {
public static function before_body(
before_standard_top_of_body_html_generation $hook
): void {
if (!isloggedin() || isguestuser()) {
return;
}
$html = \html_writer::div(
'local_miplugin activo',
'local-miplugin-status'
);
$hook->add_html($html);
}
}No me interesa especialmente añadir ese mensaje a todas las páginas. El código sirve para visualizar la arquitectura:
Moodle
↓
dispatch del hook
↓
db/hooks.php
↓
hook_callbacks::before_body()
↓
nuestro códigoAsí, la lógica deja de estar desperdigada en un gran lib.php y pasa a una clase concreta y autocargable.
Los observers utilizan db/events.php
Un event observer es otro mecanismo.
Para reaccionar, por ejemplo, cuando un usuario inicia sesión podemos declarar:
<?php
defined('MOODLE_INTERNAL') || die();
$observers = [
[
'eventname' => '\core\event\user_loggedin',
'callback' => '\local_miplugin\observer::user_loggedin',
],
];en: local/miplugin/db/events.php
La implementación puede vivir en classes/observer.php:
<?php
namespace local_miplugin;
defined('MOODLE_INTERNAL') || die();
final class observer {
public static function user_loggedin(
\core\event\user_loggedin $event
): void {
if ($event->userid <= 0) {
return;
}
set_user_preference(
'local_miplugin_lastlogin_observed',
time(),
$event->userid
);
}
}Por tanto, es un ejemplo pequeño, pero nos permite probar algo muy importante durante una actualización: que los eventos continúan disparándose y que nuestro observer continúa recibiendo los datos que espera.
Mi criterio: si encuentro callbacks legacy para los que ya existe un hook equivalente, planifico la migración. No porque Moodle 5.3 vaya a romper automáticamente todo lib.php, sino porque mantener APIs heredadas aumenta el coste del siguiente salto.
Matiz de producción: un hook también puede despacharse durante la instalación o el upgrade. El callback debe comprobar que el plugin está inicializado y que puede utilizar la base de datos antes de ejecutar lógica propia. De lo contrario, una migración aparentemente inocua puede dejar inutilizable la pantalla de actualización.
Busca deprecaciones antes de buscar errores

Además, una aplicación puede funcionar perfectamente y estar avisándonos al mismo tiempo de que su mantenimiento futuro se está complicando.
Para eso existen las deprecaciones.
Activaría debugging en el laboratorio:
// config.php del entorno de desarrollo.
$CFG->debug = E_ALL;
$CFG->debugdisplay = 1;
@error_reporting(E_ALL);
@ini_set('display_errors', '1');Y después recorrería los flujos principales del plugin.
También podemos buscar estáticamente
Una búsqueda simple no sustituye a una herramienta de análisis, pero resulta útil para encontrar zonas sospechosas:
grep -Rni "deprecated" local/miplugin
grep -Rni "before_standard_top_of_body_html" local/miplugin
grep -Rni "require_once" local/miplugin/classes
grep -Rni "\$DB->" local/mipluginSin embargo, no todas esas coincidencias serán errores.
La utilidad está en reducir el espacio que debemos revisar manualmente.
Una deprecación es básicamente una conversación entre el framework y el desarrollador: «esto todavía funciona, pero no construyas el futuro encima».
El upgrade de los datos también forma parte de tu plugin
De hecho, esta es una de las partes que más fácilmente quedan fuera de una prueba superficial.
Por ejemplo, una instalación limpia puede funcionar perfectamente y un upgrade romperse porque la base de datos ya contiene información creada por versiones anteriores del plugin.
Supongamos que añadimos un campo source a una tabla existente.
El paso de actualización viviría en local/miplugin/db/upgrade.php
<?php
defined('MOODLE_INTERNAL') || die();
function xmldb_local_miplugin_upgrade(
int $oldversion
): bool {
global $DB;
$dbman = $DB->get_manager();
if ($oldversion < 2026082601) {
$table = new xmldb_table(
'local_miplugin_log'
);
$field = new xmldb_field(
'source',
XMLDB_TYPE_CHAR,
'50',
null,
XMLDB_NOTNULL,
null,
'web',
'userid'
);
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
upgrade_plugin_savepoint(
true,
2026082601,
'local',
'miplugin'
);
}
return true;
}Además, al modificar algo bajo db/ debemos incrementar también la versión del plugin:
$plugin->version = 2026082601;Para cambios reales de esquema utiliza el XMLDB Editor de Moodle. El ejemplo sirve para entender el flujo, pero el propio Moodle recomienda generar las modificaciones de esquema mediante XMLDB.
Y esto nos lleva a una prueba que considero obligatoria:
no pruebes solo instalar la nueva versión. Prueba actualizar datos generados por la anterior.
Templates y JavaScript también forman parte de la compatibilidad
Cuando pensamos en compatibilidad solemos buscar PHP roto.
Sin embargo, es una visión demasiado estrecha.
Un plugin puede superar perfectamente una actualización de base de datos y comenzar a fallar después porque un template dependía de una estructura HTML concreta, porque un selector JavaScript ha dejado de encontrar el elemento esperado o porque utilizamos una API de frontend deprecated.
Revisaría especialmente:
- templates Mustache sobrescritos;
- selectores JavaScript acoplados al markup del tema;
- módulos AMD antiguos;
- imports desde componentes internos;
- overrides de renderers;
- child templates;
- JavaScript que genere avisos de deprecación.
La superficie de compatibilidad de un plugin es bastante mayor que sus clases PHP.
Este mismo problema aparece cuando un bloque depende demasiado del tema, de Mustache o de la estructura del DOM. En la guía profesional para crear bloques Moodle 4.5 explico cómo separar lógica y presentación, trabajar con capacidades y reducir ese acoplamiento.
Cómo probar la compatibilidad de plugins con Moodle 5.3

Por tanto, aquí está, para mí, la diferencia entre comprobar compatibilidad y tener una estrategia de compatibilidad.
Si todas nuestras pruebas consisten en entrar como administrador, abrir dos pantallas y confirmar que «parece funcionar», cada versión de Moodle nos obliga a empezar desde cero.
En cambio, con pruebas automatizadas, parte de ese conocimiento queda codificado.
Un ejemplo pequeño con PHPUnit
Podemos encapsular incluso una política sencilla de compatibilidad dentro de classes/local/compatibility.php
<?php
namespace local_miplugin\local;
defined('MOODLE_INTERNAL') || die();
final class compatibility {
private const MIN_BRANCH = 405;
private const MAX_BRANCH = 503;
public static function supports_branch(
int $branch
): bool {
return $branch >= self::MIN_BRANCH
&& $branch <= self::MAX_BRANCH;
}
}Y podemos convertir esa decisión en una prueba en tests/local/compatibility_test.php:
<?php
namespace local_miplugin\local;
defined('MOODLE_INTERNAL') || die();
final class compatibility_test
extends \basic_testcase {
public function test_moodle_45_is_supported(): void {
$this->assertTrue(
compatibility::supports_branch(405)
);
}
public function test_moodle_53_is_supported(): void {
$this->assertTrue(
compatibility::supports_branch(503)
);
}
public function test_moodle_60_is_not_supported(): void {
$this->assertFalse(
compatibility::supports_branch(600)
);
}
}El ejemplo es deliberadamente pequeño. Lo interesante es que nuestra política deja de vivir únicamente en un README.
Podemos ejecutarla.
vendor/bin/phpunit \
local/miplugin/tests/local/compatibility_test.phpY los flujos reales con Behat
PHPUnit no sustituye a las pruebas funcionales.
Si nuestro plugin añade una pantalla de administración, podemos expresar el comportamiento esperado con Behat.
tests/behat/compatibility.feature:
@local @local_miplugin
Feature: Administrators can inspect plugin compatibility
Scenario: Administrator opens the plugin status
Given I log in as "admin"
When I navigate to
"Plugins > Local plugins > Mi plugin"
in site administration
Then I should see "Estado del plugin"
And I should see "Moodle compatible"Y podemos ejecutar únicamente los escenarios de nuestro plugin:
vendor/bin/behat --tags=@local_mipluginPHPUnit responde principalmente a:
«¿mi código hace lo que espero?»
Behat responde a otra pregunta:
«¿el usuario todavía puede completar el flujo?»
Una buena señal de madurez: poder lanzar la misma suite contra diferentes ramas de Moodle y detectar una regresión sin depender de memoria, intuición o una tarde entera haciendo clics.
Montaría un laboratorio antes de tocar staging

No empezaría las pruebas sobre el staging que utiliza todo el equipo.
Montaría primero un entorno desechable.
Además, Docker resulta especialmente cómodo porque permite fijar versiones, reconstruir bases de datos y repetir una migración tantas veces como necesitemos.
Así, el laboratorio convierte la compatibilidad de plugins con Moodle 5.3 en una comprobación repetible y no en una sensación basada en dos pantallas que «parecen funcionar».
Mi laboratorio mínimo tendría:
- una instalación limpia de Moodle 4.5;
- una instalación de la rama Moodle 5.3;
- PHP 8.3 en el entorno objetivo;
- el mismo motor de base de datos que producción;
- el plugin bajo prueba;
- sus dependencias reales;
- datos sintéticos o una copia anonimizada;
- debugging activado;
- PHPUnit;
- Behat;
- logs accesibles.
Después ejecutaría tres tipos de prueba.
1. Instalación limpia
¿El plugin se instala directamente sobre Moodle 5.3?
php admin/cli/upgrade.php --non-interactive2. Upgrade real
¿Qué ocurre cuando partimos de Moodle 4.5 con una versión antigua del plugin y datos reales?
# Punto de partida.
Moodle 4.5
local_miplugin 1.3.0
datos existentes
↓ upgrade
Moodle 5.3
local_miplugin 1.4.0
datos migrados3. Comportamiento funcional
¿Los hooks continúan ejecutándose? ¿Los observers reaccionan? ¿Los permisos siguen siendo correctos? ¿Cron procesa las tareas? ¿Los formularios guardan? ¿La interfaz sigue funcionando?
La segunda prueba es especialmente importante.
Hay plugins que funcionan perfectamente cuando se instalan desde cero y fallan únicamente cuando deben transformar datos acumulados durante años.
El upgrade también es una funcionalidad de tu plugin.
Una matriz permite saber dónde empieza a romperse

Sin embargo, intentar soportar todas las versiones de Moodle, PHP, bases de datos y navegadores puede convertir nuestra matriz en un monstruo.
En la práctica, una matriz acotada convierte la compatibilidad de plugins con Moodle 5.3 en escenarios concretos: qué rama, qué runtime y qué comportamiento esperamos validar.
Por eso empezaría definiendo qué prometemos soportar.
| Escenario | Moodle | PHP | Objetivo |
|---|---|---|---|
| LTS de partida | 4.5 | 8.1–8.3 | Evitar regresiones |
| Rama intermedia | 5.1 | 8.2+ | Localizar cambios 5.x |
| Stable actual | 5.2 | 8.3+ | Validar nuevo runtime |
| Próxima LTS | 5.3 | 8.3+ | Validar objetivo final |
Si nuestro plugin funciona en 4.5 y 5.1 pero comienza a fallar en 5.2, acabamos de reducir enormemente el espacio de búsqueda.
Lo que la demo no enseña
Supongamos que ya hemos hecho todo lo anterior.
El plugin se instala en 5.3, no genera avisos relevantes y supera las pruebas.
¿Podemos actualizar producción?
Todavía revisaría algunas cosas que rara vez aparecen en una demo:
Por ejemplo, si el plugin genera informes, exportaciones o procesos periódicos, conviene probar también el comportamiento asíncrono. En mi guía para automatizar informes en Moodle con PHP, CSV y cron desarrollo ese patrón con un caso más específico.
- cron y tareas programadas
- capabilities y permisos
- integraciones externas
- colas y tareas adhoc
- rendimiento de consultas
- logs y warnings
- rollback
- migración de datos mediante upgrade.php
Sobre el papel, una actualización termina cuando Moodle indica que el upgrade ha finalizado.
En producción termina cuando sabemos que podemos volver a confiar en el sistema.
Mi checklist antes de declarar compatible un plugin
En resumen, esta checklist concentra los puntos que utilizaría para validar la compatibilidad de plugins con Moodle 5.3 antes de plantear un despliegue real.

- Confirmar los requisitos de servidor de Moodle 5.3.
- Probar con PHP 8.3.
- Revisar
version.php. - Revisar
$plugin->requires. - Definir
$plugin->supported. - Revisar dependencias explícitas.
- Buscar dependencias implícitas.
- Localizar callbacks legacy de
lib.php. - Comprobar si existe un Hook API equivalente.
- Revisar
db/hooks.php. - Revisar
db/events.php. - Buscar APIs deprecated.
- Ejecutar el plugin con debugging.
- Revisar templates Mustache.
- Revisar JavaScript y AMD.
- Ejecutar PHPUnit.
- Ejecutar Behat.
- Probar instalación limpia.
- Probar upgrade desde Moodle 4.5.
- Probar
db/upgrade.phpcon datos existentes. - Probar cron y tareas adhoc.
- Validar capabilities con usuarios no administradores.
- Revisar logs después del upgrade.
- Documentar las versiones realmente soportadas.
Un plugin compatible no es el que todavía funciona
En este punto, Moodle hace bastante trabajo para evitar que cada versión mayor destruya el ecosistema de plugins.
Además, hay políticas de deprecación, APIs públicas, periodos de transición y mecanismos para mantener compatibilidad entre ramas.
Pero esa estabilidad puede tener un efecto secundario peligroso: permitir que una decisión antigua continúe funcionando durante tanto tiempo que acabemos confundiéndola con una decisión correcta.
Además, no toda decisión heredada merece una reescritura inmediata. En No toda la deuda técnica debe pagarse explico cómo separar código simplemente antiguo de deuda que realmente introduce fricción, riesgo o coste.
Por eso yo no utilizaría Moodle 5.3 únicamente como una versión a la que hay que migrar.
La utilizaría como una oportunidad para revisar el plugin.
Qué APIs consume.
De qué depende.
Qué partes seguimos manteniendo por herencia.
Qué sabemos que funciona porque tenemos pruebas y qué creemos que funciona porque nadie ha abierto todavía una incidencia.
Son dos cosas muy diferentes.
La compatibilidad no consiste en conseguir que el código sobreviva a la próxima versión. Consiste en reducir la incertidumbre cada vez que llega una nueva.
¿Mantienes plugins propios de Moodle?
Por eso, no esperes al día del lanzamiento de Moodle 5.3. Clona uno de tus plugins, monta un entorno desechable y ejecuta esta checklist.
Lo interesante no será descubrir si funciona.
Será poder explicar por qué funciona y saber qué partes deberías corregir antes del siguiente salto.
Preguntas frecuentes
¿Puedo actualizar directamente de Moodle 4.5 a Moodle 5.3?
Sí. Los requisitos publicados actualmente para Moodle 5.3 indican que el upgrade está soportado desde Moodle 4.5 o una versión posterior. Esto no elimina la necesidad de comprobar PHP, base de datos, plugins y personalizaciones.
¿Moodle 5.3 necesita PHP 8.3?
Sí. La documentación actual establece PHP 8.3 como versión mínima y soporta también PHP 8.4.
¿Tengo que sustituir todos los callbacks de lib.php por hooks?
No de forma indiscriminada. Conviene comprobar qué callbacks legacy utilizados por tu plugin disponen ya de un hook equivalente y migrarlos progresivamente.
¿Hooks y event observers son lo mismo?
No. Ambos ayudan a desacoplar componentes, pero son mecanismos diferentes. Los callbacks de hooks se registran mediante db/hooks.php. Las suscripciones a eventos se declaran mediante db/events.php.
¿Una API deprecated dejará de funcionar necesariamente en Moodle 5.3?
No. Moodle utiliza un proceso gradual de deprecación. Una API puede continuar funcionando durante un periodo mientras genera avisos antes de su eliminación posterior.
¿Cómo comprobar la compatibilidad de plugins con Moodle 5.3?
Para validar la compatibilidad de plugins con Moodle 5.3, combina revisión estática, debugging, comprobación de requisitos y dependencias, PHPUnit, Behat, instalación limpia y un upgrade real desde una copia de Moodle 4.5 con datos existentes.


Deja una respuesta