Article 8 min read

Building Custom Moodle Plugins: A Step-by-Step Guide for PHP Developers

Aug 6, 2026 8 views
Building Custom Moodle Plugins: A Step-by-Step Guide for PHP Developers

TL;DR

Building custom plugins is a great way to tailor Moodle to your exact educational needs. Learn how to create a custom Motivational Quotes block plugin step-by-step with PHP, access checks, language strings, and XMLDB integration.

Table of Contents

    Building custom plugins is the single most effective way to make Moodle LMS work exactly how your institution or business needs. When developers first start customizing Moodle, the framework architecture can seem complex—but once you break it down into modular steps, creating custom blocks and tools is straightforward and highly manageable.

    In this practical tutorial, we will walk you through building your first custom Moodle block plugin from scratch. We'll cover folder setup, version management, security access controls, language internationalization, block class logic, and advanced features like database storage and custom CSS/JS styling.

    Custom Moodle Plugin Development Architecture and PHP Workflow Guide
    Click to enlarge

    What You'll Need Before Starting

    Before diving into code, ensure your local development workspace meets these prerequisites:

    • A working Moodle installation (local development environment like XAMPP, MAMP, or Docker for sandbox testing).
    • Basic PHP knowledge (object-oriented PHP basics, array handling, and class inheritance).
    • Understanding of HTML & JavaScript for rendering frontend layout elements.
    • A code editor such as Visual Studio Code, PHPStorm, or Sublime Text.
    • A bit of patience—mastering Moodle's boilerplate structure pays off immediately!

    Understanding Moodle Plugin Types

    Moodle features a highly extensible modular architecture. Depending on your project requirements, Moodle supports several primary plugin types:

    Activity Modules

    Add interactive learning tasks, assignments, quizzes, or custom gradeable tools directly into course sections.

    Sidebar Blocks

    Display auxiliary information, student widgets, progress trackers, or interactive tools on sidebars or dashboards.

    Themes

    Customize the visual appearance, color scheme, typography, and responsive grid system across the entire LMS site.

    Authentication & Admin Tools

    Control SSO login flows, OAuth integration, user sync, automated reporting, and bulk admin operations.

    For this tutorial, we will build a Sidebar Block plugin named motivational_quotes. Blocks are the best plugin type to start with because they cover core Moodle APIs (capabilities, lang strings, renderers) with minimal boilerplate.

    Step 1: Set Up Your Plugin Folder Structure

    First, let's create our plugin directory inside the Moodle codebase:

    1. Open your Moodle installation directory.
    2. Navigate to the /blocks folder.
    3. Create a new directory named motivational_quotes.

    Inside your new /blocks/motivational_quotes/ directory, set up the required file structure:

    Moodle Motivational Quotes Plugin Directory Tree Structure in VSCode
    Click to enlarge
    • block_motivational_quotes.php — The primary class file containing block logic and rendering code.
    • version.php — Plugin metadata, release versioning, and core Moodle version requirements.
    • db/access.php — Capability definitions, user roles, and security permissions.
    • lang/en/block_motivational_quotes.php — English internationalization language strings.

    Step 2: Define Your Plugin Version (version.php)

    Open version.php and insert the following code:

    <?php
    defined('MOODLE_INTERNAL') || die();
    
    $plugin->component = 'block_motivational_quotes';
    $plugin->version   = 2025051100; // YYYYMMDDXX format date version
    $plugin->requires  = 2023042400; // Minimum required Moodle release
    $plugin->release   = 'v1.0';
    $plugin->maturity  = MATURITY_STABLE;
    

    How it works: The initial line defined('MOODLE_INTERNAL') || die(); prevents direct browser access to PHP files for security. The $plugin->component sets the frankenstyle plugin name, $plugin->version specifies the release timestamp, and $plugin->requires enforces minimum Moodle core compatibility.

    Step 3: Set Up Language Strings (lang/en/block_motivational_quotes.php)

    Never hardcode text in Moodle files. Use language files so your plugin can easily be localized into multi-language sites. Create lang/en/block_motivational_quotes.php:

    <?php
    $string['pluginname'] = 'Motivational Quotes';
    $string['motivational_quotes'] = 'Motivational Quotes';
    $string['motivational_quotes:addinstance'] = 'Add a new Motivational Quotes block';
    $string['motivational_quotes:myaddinstance'] = 'Add a new Motivational Quotes block to Dashboard';
    

    These strings define user-facing labels in Moodle's UI. The pluginname key identifies your block in the admin menu, while addinstance and myaddinstance define capability labels for course pages and personal user dashboards.

    Step 4: Define Access Capabilities (db/access.php)

    Security permissions are controlled via db/access.php. Add the following code:

    <?php
    defined('MOODLE_INTERNAL') || die();
    
    $capabilities = array(
        'block/motivational_quotes:addinstance' => array(
            'riskbitmask' => RISK_SPAM | RISK_XSS,
            'captype' => 'write',
            'contextlevel' => CONTEXT_BLOCK,
            'archetypes' => array(
                'editingteacher' => CAP_ALLOW,
                'manager' => CAP_ALLOW
            ),
            'clonepermissionsfrom' => 'moodle/site:manageblocks'
        ),
        'block/motivational_quotes:myaddinstance' => array(
            'captype' => 'write',
            'contextlevel' => CONTEXT_SYSTEM,
            'archetypes' => array(
                'user' => CAP_ALLOW
            ),
            'clonepermissionsfrom' => 'moodle/my:manageblocks'
        ),
    );
    

    This configuration grants editing teachers and site managers permission to add the block on course sections, while allowing general students/users to add the block to their individual Moodle Dashboard.

    Step 5: Create the Main Block Class (block_motivational_quotes.php)

    Now, write the primary block logic in block_motivational_quotes.php:

    <?php
    defined('MOODLE_INTERNAL') || die();
    
    class block_motivational_quotes extends block_base {
        
        public function init() {
            $this->title = get_string('pluginname', 'block_motivational_quotes');
        }
    
        public function get_content() {
            if ($this->content !== null) {
                return $this->content;
            }
    
            $this->content = new stdClass();
            
            $quotes = array(
                '"The secret of getting ahead is getting started." – Mark Twain',
                '"Don\'t watch the clock; do what it does. Keep going." – Sam Levenson',
                '"Learning is a treasure that will follow its owner everywhere." – Chinese Proverb',
                '"The more that you read, the more things you will know." – Dr. Seuss',
                '"Education is the passport to the future." – Malcolm X'
            );
    
            $randomQuote = $quotes[array_rand($quotes)];
            $this->content->text = '<div class="quote-box" style="padding:15px; background:#f0fdf4; border-left:4px solid #10b981; font-style:italic;">' . $randomQuote . '</div>';
            $this->content->footer = '';
    
            return $this->content;
        }
    
        public function applicable_formats() {
            return array('all' => true);
        }
    
        public function instance_allow_multiple() {
            return true;
        }
    }
    

    Class breakdown:

    • init(): Sets the block's title dynamically using get_string().
    • get_content(): Generates the HTML body. It selects a random quote from our array and caches output in $this->content.
    • applicable_formats(): Specifies that the block can be added anywhere across Moodle pages.
    • instance_allow_multiple(): Permits multiple instances of this block on a single page.

    Step 6: Install & Test Your Plugin

    Now install your newly created Moodle plugin:

    1. Log into your Moodle site as an Administrator.
    2. Navigate to Site Administration > Notifications (or Dashboard).
    3. Moodle will automatically detect block_motivational_quotes and prompt you to run the upgrade check.
    4. Click Upgrade Moodle database now to register capabilities and version info.
    5. Navigate to any course or your Dashboard, click Turn editing on, and click Add a block.
    6. Select Motivational Quotes from the menu!
    Motivational Quotes Block Displayed on Moodle LMS Student Dashboard Interface
    Click to enlarge

    Making Your Plugin More Advanced

    Once your basic block is working, here is how you can take it to a production level:

    1. Adding Admin Configuration Options (settings.php)

    To allow admins to input custom quotes instead of static array values, create a settings.php file inside your plugin folder. This renders form fields in Site Administration > Plugins > Blocks > Motivational Quotes, allowing admins to edit quotes dynamically. Your block then retrieves settings using get_config('block_motivational_quotes').

    2. Creating Custom Database Tables (db/install.xml)

    If your plugin needs to store complex data (e.g., student response logs or quote voting history), create a db/install.xml file. Use Moodle's built-in XMLDB Editor (found under Site administration > Development > XMLDB editor) to generate table definitions visually.

    Once installed, interact with your table using Moodle's global $DB object instead of raw SQL queries:

    // Inserting a record safely
    global $DB;
    $record = new stdClass();
    $record->quote = "Knowledge is power.";
    $record->author = "Francis Bacon";
    $record->timecreated = time();
    $DB->insert_record('block_motivational_quotes', $record);
    
    // Fetching records
    $user_quotes = $DB->get_records('block_motivational_quotes', array('author' => 'Mark Twain'));
    

    3. Custom Styling & Interactive JavaScript

    Add a styles.css file in your plugin root for custom CSS styles (Moodle automatically loads styles.css for active blocks). For interactive AJAX features or animations, load modular JavaScript assets via $PAGE->requires->js('/blocks/motivational_quotes/script.js');.

    Common Moodle Developer Pitfalls to Avoid

    1. Skipping Capability Checks

    Always verify permissions using has_capability() or require_capability() before allowing users to perform actions or view sensitive course data.

    2. Hardcoding User Interface Text

    Always wrap UI text in get_string(). Hardcoded strings break Moodle multi-language sites and translation plugins.

    3. Writing Raw SQL Queries

    Never write direct SQL string concatenation. Use Moodle's $DB API parameter binding (e.g. $DB->get_records_select()) to prevent SQL injection vulnerabilities and cross-database errors between PostgreSQL, MySQL, and MariaDB.

    4. Not Testing Across Different Moodle Themes

    Custom themes like Boost, Classic, or Remui handle grid layouts differently. Test your blocks across multiple themes to ensure responsive display.

    Need Professional Moodle Plugin Development & Migration?

    Whether you need custom plugin development, bespoke theme integration, LMS performance tuning, or seamless site migration, our expert team builds and delivers enterprise-grade Moodle solutions tailored for your business.

    Wrapping Up

    Moodle plugin development isn't as intimidating as it first appears. By starting small with basic blocks, understanding Moodle's folder conventions, and adhering to core security APIs, you can construct powerful extensions for any e-learning platform.

    When you run into roadblocks, the official Moodle Developer Documentation and Moodle Community Forums provide an active ecosystem of guidance. Happy coding!

    Share this article:

    Was this article helpful?

    Comments

    Loading comments...