diff --git a/.upgradenotes/MDL-84142-2025021801283101.yml b/.upgradenotes/MDL-84142-2025021801283101.yml
new file mode 100644
index 00000000000..206b9a8ae65
--- /dev/null
+++ b/.upgradenotes/MDL-84142-2025021801283101.yml
@@ -0,0 +1,9 @@
+issueNumber: MDL-84142
+notes:
+ core_enrol:
+ - message: >-
+ Plugins implementing enrol_page_hook() method are encouraged to use the
+ renderable \core_enrol\output\enrol_page to produce HTML for the
+ enrolment page. Forms should be displayed in a modal dialogue. See
+ enrol_self plugin as an example.
+ type: improved
diff --git a/enrol/classes/output/enrol_page.php b/enrol/classes/output/enrol_page.php
new file mode 100644
index 00000000000..dcfaa7dc30b
--- /dev/null
+++ b/enrol/classes/output/enrol_page.php
@@ -0,0 +1,70 @@
+.
+
+declare(strict_types=1);
+
+namespace core_enrol\output;
+
+use core\output\named_templatable;
+use core\output\renderable;
+use core\output\single_button;
+
+/**
+ * Allows to render a widget provided by enrol_plugin::enrol_page_hook()
+ *
+ * @package core_enrol
+ * @copyright Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class enrol_page implements named_templatable, renderable {
+
+ /**
+ * Constructor
+ *
+ * @param \stdClass $instance
+ * @param string|null $header
+ * @param string|null $body
+ * @param array $buttons
+ */
+ public function __construct(
+ /** @var \stdClass */
+ protected \stdClass $instance,
+ /** @var string|null */
+ protected ?string $header = null,
+ /** @var string|null */
+ protected ?string $body = null,
+ /** @var single_button[] */
+ protected array $buttons = []
+ ) {
+ }
+
+ #[\Override]
+ public function export_for_template(\core\output\renderer_base $output) {
+ return [
+ 'enrol' => $this->instance->enrol,
+ 'instanceid' => $this->instance->id,
+ 'header' => $this->header,
+ 'body' => $this->body,
+ 'buttons' => array_map(fn($b) => $b->export_for_template($output), $this->buttons),
+ 'hasbuttons' => !empty($this->buttons),
+ ];
+ }
+
+ #[\Override]
+ public function get_template_name(\core\output\renderer_base $renderer): string {
+ return 'core_enrol/enrol_page';
+ }
+}
diff --git a/enrol/templates/enrol_page.mustache b/enrol/templates/enrol_page.mustache
new file mode 100644
index 00000000000..aa95afdc42e
--- /dev/null
+++ b/enrol/templates/enrol_page.mustache
@@ -0,0 +1,52 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see