Skip to main content

moodle-external-api-development

This skill guides you through creating custom external web service APIs for Moodle LMS, following Moodle's external API framework and coding standards.

Jump to install

Source facts

Repository
muhammedadnank/Antigravity-Skills
Last source activity
June 6, 2026 at 18:30
Detected SKILL.md language
English
Stars
13
Forks
1

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
moodle-external-api-development
description
This skill guides you through creating custom external web service APIs for Moodle LMS, following Moodle's external API framework and coding standards.
risk
unknown
source
community
date_added
2026-02-27
# Moodle External API Development This skill guides you through creating custom external web service APIs for Moodle LMS, following Moodle's external API framework and coding standards. ## When to Use This Skill - Creating custom web services for Moodle plugins - Implementing REST/AJAX endpoints for course management - Building APIs for quiz operations, user tracking, or reporting - Exposing Moodle functionality to external applications - Developing mobile app backends using Moodle ## Core Architecture Pattern Moodle external APIs follow a strict three-method pattern: 1. **`execute_parameters()`** - Defines input parameter structure 2. **`execute()`** - Contains business logic 3. **`execute_returns()`** - Defines return structure ## Step-by-Step Implementation ### Step 1: Create the External API Class File **Location**: `/local/yourplugin/classes/external/your_api_name.php` ```php <?php namespace local_yourplugin\external; defined('MOODLE_INTERNAL') || die(); require_once("$CFG->libdir/externallib.php"); use external_api; use external_function_parameters; use external_single_structure; use external_value; class your_api_name extends external_api { // Three required methods will go here } ``` **Key Points**: - Class must extend `external_api` - Namespace follows: `local_pluginname\external` or `mod_modname\external` - Include the security check: `defined('MOODLE_INTERNAL') || die();` - Require externallib.php for base classes ### Step 2: Define Input Parameters ```php public static function execute_parameters() { return new external_function_parameters([ 'userid' => new external_value(PARAM_INT, 'User ID', VALUE_REQUIRED), 'courseid' => new external_value(PARAM_INT, 'Course ID', VALUE_REQUIRED), 'options' => new external_single_structure([ 'includedetails' => new external_value(PARAM_BOOL, 'Include details', VALUE_DEFAULT, false), 'limit' => new external_value(PARAM_INT, 'Result limit', VALUE_DEFAULT, 10) ], 'Options', VALUE_OPTIONAL) ]); } ``` **Common Parameter Types**: - `PARAM_INT` - Integers - `PARAM_TEXT` - Plain text (HTML stripped) - `PARAM_RAW` - Raw text (no cleaning) - `PARAM_BOOL` - Boolean values - `PARAM_FLOAT` - Floating point numbers - `PARAM_ALPHANUMEXT` - Alphanumeric with extended chars **Structures**: - `external_value` - Single value - `external_single_structure` - Object with named fields - `external_multiple_structure` - Array of items **Value Flags**: - `VALUE_REQUIRED` - Parameter must be provided - `VALUE_OPTIONAL` - Parameter is optional - `VALUE_DEFAULT, defaultvalue` - Optional with default ### Step 3: Implement Business Logic ```php public static function execute($userid, $courseid, $options = []) { global $DB, $USER; // 1. Validate parameters $params = self::validate_parameters(self::execute_parameters(), [ 'userid' => $userid, 'courseid' => $courseid, 'options' => $options ]); // 2. Check permissions/capabilities $context = \context_course::instance($params['courseid']); self::validate_context($context); require_capability('moodle/course:view', $context); // 3. Verify user access if ($params['userid'] != $USER->id) { require_capability('moodle/course:viewhiddenactivities', $context); } // 4. Database operations $sql = "SELECT id, name, timecreated FROM {your_table} WHERE userid = :userid AND courseid = :courseid LIMIT :limit"; $records = $DB->get_records_sql($sql, [ 'userid' => $params['userid'], 'courseid' => $params['courseid'], 'limit' => $params['options']['limit'] ]); // 5. Process and return data $results = []; foreach ($records as $record) { $results[] = [ 'id' => $record->id, 'name' => $record->name, 'timestamp' => $record->timecreated ]; } return [ 'items' => $results, 'count' => count($results) ]; } ``` **Critical Steps**: 1. **Always validate parameters** using `validate_parameters()` 2. **Check context** using `validate_context()` 3. **Verify capabilities** using `require_capability()` 4. **Use parameterized queries** to prevent SQL injection 5. **Return structured data** matching return definition ### Step 4: Define Return Structure ```php public static function execute_returns() { return new external_single_structure([ 'items' => new external_multiple_structure( new external_single_structure([ 'id' => new external_value(PARAM_INT, 'Item ID'), 'name' => new external_value(PARAM_TEXT, 'Item name'), 'timestamp' => new external_value(PARAM_INT, 'Creation time') ]) ), 'count' => new external_value(PARAM_INT, 'Total items') ]); } ``` **Return Structure Rules**: - Must match exactly what `execute()` returns - Use appropriate parameter types - Document each field with description - Nested structures allowed ### Step 5: Register the Service **Location**: `/local/yourplugin/db/services.php` ```php <?php defined('MOODLE_INTERNAL') || die(); $functions = [ 'local_yourplugin_your_api_name' => [ 'classname' => 'local_yourplugin\external\your_api_name', 'methodname' => 'execute', 'classpath' => 'local/yourplugin/classes/external/your_api_name.php', 'description' => 'Brief description of what this API does', 'type' => 'read', // or 'write' 'ajax' => true, 'capabilities'=> 'moodle/course:view', // comma-separated if multiple 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE] // Optional ], ]; $services = [ 'Your Plugin Web Service' => [ 'functions' => [ 'local_yourplugin_your_api_name' ], 'restrictedusers' => 0, 'enabled' => 1 ] ]; ``` **Service Registration Keys**: - `classname` - Full namespaced class name - `methodname` - Always 'execute' - `type` - 'read' (SELECT) or 'write' (INSERT/UPDATE/DELETE) - `ajax` - Set true for AJAX/REST access - `capabilities` - Required Moodle capabilities - `services` - Optional service bundles ### Step 6: Implement Error Handling & Logging ```php private static function log_debug($message) { global $CFG; $logdir = $CFG->dataroot . '/local_yourplugin'; if (!file_exists($logdir)) { mkdir($logdir, 0777, true); } $debuglog = $logdir . '/api_debug.log'; $timestamp = date('Y-m-d H:i:s'); file_put_contents($debuglog, "[$timestamp] $message\n", FILE_APPEND | LOCK_EX); } public static function execute($userid, $courseid) { global $DB; try { self::log_debug("API called: userid=$userid, courseid=$courseid"); // Validate parameters $params = self::validate_parameters(self::execute_parameters(), [ 'userid' => $userid, 'courseid' => $courseid ]); // Your logic here self::log_debug("API completed successfully"); return $result; } catch (\invalid_parameter_exception $e) { self::log_debug("Parameter validation failed: " . $e->getMessage()); throw $e; } catch (\moodle_exception $e) { self::log_debug("Moodle exception: " . $e->getMessage()); throw $e; } catch (\Exception $e) { // Log detailed error info $lastsql = method_exists($DB, 'get_last_sql') ? $DB->get_last_sql() : '[N/A]'; self::log_debug("Fatal error: " . $e->getMessage()); self::log_debug("Last SQL: " . $lastsql); self::log_debug("Stack trace: " . $e->getTraceAsString()); throw $e; } } ``` **Error Handling Best Practices**: - Wrap logic in try-catch blocks - Log errors with timestamps and context - Capture SQL queries on database errors - Preserve stack traces for debugging - Re-throw exceptions after logging ## Advanced Patterns ### Complex Database Operations ```php // Transaction example $transaction = $DB->start_delegated_transaction(); try { // Insert record $recordid = $DB->insert_record('your_table', $dataobject); // Update related records $DB->set_field('another_table', 'status', 1, ['recordid' => $recordid]); // Commit transaction $transaction->allow_commit(); } catch (\Exception $e) { $transaction->rollback($e); throw $e; } ``` ### Working with Course Modules ```php // Create course module $moduleid = $DB->get_field('modules', 'id', ['name' => 'quiz'], MUST_EXIST); $cm = new \stdClass(); $cm->course = $courseid; $cm->module = $moduleid; $cm->instance = 0; // Will be updated after activity creation $cm->visible = 1; $cm->groupmode = 0; $cmid = add_course_module($cm); // Create activity instance (e.g., quiz) $quiz = new \stdClass(); $quiz->course = $courseid; $quiz->name = 'My Quiz'; $quiz->coursemodule = $cmid; // ... other quiz fields ... $quizid = quiz_add_instance($quiz, null); // Update course module with instance ID $DB->set_field('course_modules', 'instance', $quizid, ['id' => $cmid]); course_add_cm_to_section($courseid, $cmid, 0); ``` ### Access Restrictions (Groups/Availability) ```php // Restrict activity to specific user via group $groupname = 'activity_' . $activityid . '_user_' . $userid; // Create or get group if (!$groupid = $DB->get_field('groups', 'id', ['courseid' => $courseid, 'name' => $groupname])) { $groupdata = (object)[ 'courseid' => $courseid, 'name' => $groupname, 'timecreated' => time(), 'timemodified' => time() ]; $groupid = $DB->insert_record('groups', $groupdata); } // Add user to group if (!$DB->record_exists('groups_members', ['groupid' => $groupid, 'userid' => $userid])) { $DB->insert_record('groups_members', (object)[ 'groupid' => $groupid, 'userid' => $userid, 'timeadded' => time() ]); } // Set availability condition $restriction = [ 'op' => '&', 'show' => false,
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub