Skip to main content
moodle-external-api-development 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.
Aller à l'installation Skills Marketplace Découvrez et explorez les compétences IA créées par la communauté.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Copier le promptAfficher les détails du prompt Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
npx skills add https://github.com/davila7/claude-code-templates --skill moodle-external-api-developmentLa commande reste sur une seule ligne. Faites défiler horizontalement pour la vérifier avant de la copier.
Vous préférez une copie locale ? Téléchargez les fichiers actuellement disponibles dans SkillsMP.
Télécharger Zip Téléchargement... Plus depuis ce dépôt Build AI agents that interact with computers like humans do - viewing screens, moving cursors, clicking buttons, and typing text. Covers Anthropic's Computer Use, OpenAI's Operator/CUA, and open-source alternatives. Critical focus on sandboxing, security, and handling the unique challenges of vision-based control. Use when: computer use, desktop automation agent, screen control AI, vision-based agent, GUI automation.
Expert in building products that wrap AI APIs (OpenAI, Anthropic, etc.) into focused tools people will pay for. Not just 'ChatGPT but different' - products that solve specific problems with AI. Covers prompt engineering for products, cost management, rate limiting, and building defensible AI businesses. Use when: AI wrapper, GPT product, AI tool, wrap AI, AI SaaS.
github-workflow-automation Automate GitHub workflows with AI assistance. Includes PR reviews, issue triage, CI/CD integration, and Git operations. Use when automating GitHub workflows, setting up PR review automation, creating GitHub Actions, or triaging issues.
Métiers associés SOC
Basé sur la classification professionnelle SOC
name moodle-external-api-development description 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.
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 structure
execute() - Contains business logic
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
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
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 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)
]);
}
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
external_value - Single value
external_single_structure - Object with named fields
external_multiple_structure - Array of items
VALUE_REQUIRED - Parameter must be provided
VALUE_OPTIONAL - Parameter is optional
VALUE_DEFAULT, defaultvalue - Optional with default
Step 3: Implement Business Logic public static function execute ($userid , $courseid , $options = [] ) {
global $DB , $USER ;
$params = self ::validate_parameters (self ::execute_parameters (), [
'userid' => $userid ,
'courseid' => $courseid ,
'options' => $options
]);
$context = \context_course::instance ($params ['courseid' ]);
self ::validate_context ($context );
require_capability ('moodle/course:view' , $context );
if ($params ['userid' ] != $USER ->id) {
require_capability ('moodle/course:viewhiddenactivities' , $context );
}
$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' ]
]);
$results = [];
foreach ($records as $record ) {
$results [] = [
'id' => $record ->id,
'name' => $record ->name,
'timestamp' => $record ->timecreated
];
}
return [
'items' => $results ,
'count' => count ($results )
];
}
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' )
]);
}
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' ,
'ajax' => true ,
'capabilities' => 'moodle/course:view' ,
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE]
],
];
$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 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 " );
$params = self ::validate_parameters (self ::execute_parameters (), [
'userid' => $userid ,
'courseid' => $courseid
]);
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 ) {
$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 = $DB ->start_delegated_transaction ();
try {
$recordid = $DB ->insert_record ('your_table' , $dataobject );
$DB ->set_field ('another_table' , 'status' , 1 , ['recordid' => $recordid ]);
$transaction ->allow_commit ();
} catch (\Exception $e ) {
$transaction ->rollback ($e );
throw $e ;
}
Working with Course Modules
$moduleid = $DB ->get_field ('modules' , 'id' , ['name' => 'quiz' ], MUST_EXIST);
$cm = new \stdClass ();
$cm ->course = $courseid ;
$cm ->module = $moduleid ;
$cm ->instance = 0 ;
$cm ->visible = 1 ;
$cm ->groupmode = 0 ;
$cmid = add_course_module ($cm );
$quiz = new \stdClass ();
$quiz ->course = $courseid ;
$quiz ->name = 'My Quiz' ;
$quiz ->coursemodule = $cmid ;
$quizid = quiz_add_instance ($quiz , null );
$DB ->set_field ('course_modules' , 'instance' , $quizid , ['id' => $cmid ]);
course_add_cm_to_section ($courseid , $cmid , 0 );
Access Restrictions (Groups/Availability)
$groupname = 'activity_' . $activityid . '_user_' . $userid ;
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 );
}
if (!$DB ->record_exists ('groups_members' , ['groupid' => $groupid , 'userid' => $userid ])) {
$DB ->insert_record ('groups_members' , (object )[
'groupid' => $groupid ,
'userid' => $userid ,
'timeadded' => time ()
]);
}
$restriction = [
'op' => '&' ,
'show' => false ,
'c' => [
[
'type' => 'group' ,
'id' => $groupid
]
],
'showc' => [false ]
];
$DB ->set_field ('course_modules' , 'availability' , json_encode ($restriction ), ['id' => $cmid ]);
Random Question Selection with Tags private static function get_random_questions ($categoryid , $tagname , $limit ) {
global $DB ;
$sql = "SELECT q.id
FROM {question} q
INNER JOIN {question_versions} qv ON qv.questionid = q.id
INNER JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
INNER JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
JOIN {tag_instance} ti ON ti.itemid = q.id
JOIN {tag} t ON t.id = ti.tagid
WHERE LOWER(t.name) = :tagname
AND qc.id = :categoryid
AND ti.itemtype = 'question'
AND q.qtype = 'multichoice'" ;
$qids = $DB ->get_fieldset_sql ($sql , [
'categoryid' => $categoryid ,
'tagname' => strtolower ($tagname )
]);
shuffle ($qids );
return array_slice ($qids , 0 , $limit );
}
Testing Your API
1. Via Moodle Web Services Test Client
Enable web services: Site administration > Advanced features
Enable REST protocol: Site administration > Plugins > Web services > Manage protocols
Create service: Site administration > Server > Web services > External services
Test function: Site administration > Development > Web service test client
2. Via curl
curl -X POST "https://yourmoodle.com/login/token.php" \
-d "username=admin" \
-d "password=yourpassword" \
-d "service=moodle_mobile_app"
curl -X POST "https://yourmoodle.com/webservice/rest/server.php" \
-d "wstoken=YOUR_TOKEN" \
-d "wsfunction=local_yourplugin_your_api_name" \
-d "moodlewsrestformat=json" \
-d "userid=2" \
-d "courseid=3"
3. Via JavaScript (AJAX) require (['core/ajax' ], function (ajax ) {
var promises = ajax.call ([{
methodname : 'local_yourplugin_your_api_name' ,
args : {
userid : 2 ,
courseid : 3
}
}]);
promises[0 ].done (function (response ) {
console .log ('Success:' , response);
}).fail (function (error ) {
console .error ('Error:' , error);
});
});
Common Pitfalls & Solutions
1. "Function not found" Error
Purge caches: Site administration > Development > Purge all caches
Verify function name in services.php matches exactly
Check namespace and class name are correct
2. "Invalid parameter value detected"
Ensure parameter types match between definition and usage
Check required vs optional parameters
Validate nested structure definitions
3. SQL Injection Vulnerabilities
Always use placeholder parameters (:paramname)
Never concatenate user input into SQL strings
Use Moodle's database methods: get_record(), get_records(), etc.
4. Permission Denied Errors
Call self::validate_context($context) early in execute()
Check required capabilities match user's permissions
Verify user has role assignments in the context
5. Transaction Deadlocks
Keep transactions short
Always commit or rollback in finally blocks
Avoid nested transactions
Debugging Checklist
Plugin Structure Checklist local/yourplugin/
├── version.php # Plugin version and metadata
├── db/
│ ├── services.php # External service definitions
│ └── access.php # Capability definitions (optional)
├── classes/
│ └── external/
│ ├── your_api_name.php # External API implementation
│ └── another_api.php # Additional APIs
├── lang/
│ └── en/
│ └── local_yourplugin.php # Language strings
└── tests/
└── external_test.php # Unit tests (optional but recommended)
Examples from Real Implementation
Simple Read API (Get Quiz Attempts) <?php
namespace local_userlog \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 get_quiz_attempts extends external_api {
public static function execute_parameters ( ) {
return new external_function_parameters ([
'userid' => new external_value (PARAM_INT, 'User ID' ),
'courseid' => new external_value (PARAM_INT, 'Course ID' )
]);
}
public static function execute ($userid , $courseid ) {
global $DB ;
self ::validate_parameters (self ::execute_parameters (), [
'userid' => $userid ,
'courseid' => $courseid
]);
$sql = "SELECT COUNT(*) AS quiz_attempts
FROM {quiz_attempts} qa
JOIN {quiz} q ON qa.quiz = q.id
WHERE qa.userid = :userid AND q.course = :courseid" ;
$attempts = $DB ->get_field_sql ($sql , [
'userid' => $userid ,
'courseid' => $courseid
]);
return ['quiz_attempts' => (int )$attempts ];
}
public static function execute_returns ( ) {
return new external_single_structure ([
'quiz_attempts' => new external_value (PARAM_INT, 'Total number of quiz attempts' )
]);
}
}
Complex Write API (Create Quiz from Categories) See attached create_quiz_from_categories.php for a comprehensive example including:
Multiple database insertions
Course module creation
Quiz instance configuration
Random question selection with tags
Group-based access restrictions
Extensive error logging
Transaction management
Quick Reference: Common Moodle Tables Table Purpose {user}User accounts {course}Courses {course_modules}Activity instances in courses {modules}Available activity types (quiz, forum, etc.) {quiz}Quiz configurations {quiz_attempts}Quiz attempt records {question}Question bank {question_categories}Question categories {grade_items}Gradebook items {grade_grades}Student grades {groups}Course groups {groups_members}Group memberships {logstore_standard_log}Activity logs
Additional Resources
Guidelines
Always validate input parameters using validate_parameters()
Check user context and capabilities before operations
Use parameterized SQL queries (never string concatenation)
Implement comprehensive error handling and logging
Follow Moodle naming conventions (lowercase, underscores)
Document all parameters and return values clearly
Test with different user roles and permissions
Consider transaction safety for write operations
Purge caches after service registration changes
Keep API methods focused and single-purpose