moodle-external-api-development
Create and implement custom Moodle external web service APIs.
Install
mkdir -p .claude/skills/moodle-external-api-development && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/6528" && unzip -o skill.zip -d .claude/skills/moodle-external-api-development && rm skill.zipInstalls to .claude/skills/moodle-external-api-development
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Create custom external web service APIs for Moodle LMS. Use when implementing web services for course management, user tracking, quiz operations, or custom plugin functionality. Covers parameter validation, database operations, error handling, service registration, and Moodle coding standards.Key capabilities
- →Defines mandatory execute_parameters() method
- →Implements business logic in execute() method
- →Specifies return schemas in execute_returns()
- →Implements Moodle-compliant security checks
- →Handles Moodle API namespace registration
How it works
It generates a PHP class structure extending Moodle's base external_api that enforces the mandatory three-method framework (parameters, execution, returns).
Inputs & outputs
When to use moodle-external-api-development
- →Create custom Moodle plugins
- →Expose Moodle data to external systems
- →Implement REST endpoints for course management
About this skill
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:
execute_parameters()- Defines input parameter structureexecute()- Contains business logicexecute_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
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\externalormod_modname\external - Include the security check:
defined('MOODLE_INTERNAL') || die(); - Require externallib.php for base classes
Step 2: Define Input Parameters
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- IntegersPARAM_TEXT- Plain text (HTML stripped)PARAM_RAW- Raw text (no cleaning)PARAM_BOOL- Boolean valuesPARAM_FLOAT- Floating point numbersPARAM_ALPHANUMEXT- Alphanumeric with extended chars
Structures:
external_value- Single valueexternal_single_structure- Object with named fieldsexternal_multiple_structure- Array of items
Value Flags:
VALUE_REQUIRED- Parameter must be providedVALUE_OPTIONAL- Parameter is optionalVALUE_DEFAULT, defaultvalue- Optional with default
Step 3: Implement Business Logic
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:
- Always validate parameters using
validate_parameters() - Check context using
validate_context() - Verify capabilities using
require_capability() - Use parameterized queries to prevent SQL injection
- Return structured data matching return definition
Step 4: Define Return Structure
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
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 namemethodname- Always 'execute'type- 'read' (SELECT) or 'write' (INSERT/UPDATE/DELETE)ajax- Set true for AJAX/REST accesscapabilities- Required Moodle capabilitiesservices- Optional service bundles
Step 6: Implement Error Handling & Logging
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
// 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
// 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)
// 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()
---
*Content truncated.*
When not to use it
- →Non-Moodle web application development
- →Simple UI modifications without backend logic
- →Direct database modification bypassing Moodle core
Prerequisites
Limitations
- →Only supports Moodle-based plugin structures
- →Requires manual implementation of business logic within methods
How it compares
It follows specific Moodle coding standards and class patterns rather than generic REST API generation tools.
Compared to similar skills
moodle-external-api-development side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| moodle-external-api-development (this skill) | 1 | 6mo | Review | Intermediate |
| laravel-specialist | 12 | 3mo | No flags | Intermediate |
| developing-with-turbo-streams | 1 | 5mo | No flags | Intermediate |
| payment-integration | 1 | 6mo | Review | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by davila7
View all by davila7 →You might also like
laravel-specialist
Jeffallan
Use when building Laravel 10+ applications requiring Eloquent ORM, API resources, or queue systems. Invoke for Laravel models, Livewire components, Sanctum authentication, Horizon queues.
developing-with-turbo-streams
hotwired-laravel
Basics of developing with Turbo Streams in web applications. Activate when working on projects that utilize Turbo Streams for enhancing user experience through real-time updates, dynamic content changes, and partial page updates without full reloads.
payment-integration
mrgoonie
Integrate payments with SePay (VietQR), Polar, Stripe, Paddle (MoR subscriptions), Creem.io (licensing). Checkout, webhooks, subscriptions, QR codes, multi-provider orders.
developing-with-prism
prism-php
Guide for developing with Prism PHP package - a Laravel package for integrating LLMs. Activate or use when working with Prism features including text generation, structured output, embeddings, image generation, audio processing, streaming, tools/function calling, or any LLM provider integration (OpenAI, Anthropic, Gemini, Mistral, Groq, XAI, DeepSeek, OpenRouter, Ollama, VoyageAI, ElevenLabs). Activate for any Prism-related development tasks.
restapi-translations
mikopbx
Управление переводами REST API ключей (rest_*) для MikoPBX. Автоматически находит отсутствующие русские ключи в RestApi.php и синхронизирует их с исходным кодом. Использовать при проверке переводов API, после добавления новых endpoints или перед релизом.
laravel-query-builder
relaticle
Build filtered, sorted, and included API endpoints using spatie/laravel-query-builder. Activates when working with QueryBuilder, AllowedFilter, AllowedSort, AllowedInclude, or when the user mentions query parameters, API filtering, sorting, includes, or spatie/laravel-query-builder.