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.
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:
<?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
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
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')
]);
}
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"
Solution:
Ensure parameter types match between definition and usage
Check required vs optional parameters
Validate nested structure definitions
3. SQL Injection Vulnerabilities
Solution:
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
Solution:
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
Solution:
Keep transactions short
Always commit or rollback in finally blocks
Avoid nested transactions
Debugging Checklist
Check Moodle debug mode: Site administration > Development > Debugging
Review web services logs: Site administration > Reports > Logs
Check custom log files in $CFG->dataroot/local_yourplugin/
Verify database queries using $DB->set_debug(true)
Test with admin user to rule out permission issues
Clear browser cache and Moodle caches
Check PHP error logs on server
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: