Custom parser functions and hooks are powerful tools for MediaWiki developers aiming to extend functionality with precise control. Leveraging these mechanisms helps you integrate deeply with the core while maintaining clean, maintainable code. This detailed tutorial walks through creating a custom parser function and hook integration in MediaWiki, and explains best practices around security, performance, and maintenance.
A parser function in MediaWiki allows you to introduce new syntaxes in wiki markup. A simple example is building a function that calculates and displays the square of a number. You register the function via extension.json
, mapping a key such as “square” to a PHP handler class. In your class method, you accept the parser, frame, and parameters. Use intval
to sanitize input and call return intval( $params[0] ) * intval( $params[0] )
. Registering it makes the wiki recognize syntax like {{#square: 5}}
and output “25”.
Creating a hook requires a similar setup. Hooks allow your code to run at specific points, such as when a page is displayed or when content is saved. Registering involves adding entries in extension.json
, for example SkinTemplateNavigation:showView
. Inside the hook handler you can modify the navigation links or add custom links. Use OutputPage methods with caution, and ensure you return true to allow other handlers to run.
Let us build a combined use case. Suppose you want to add a feature that logs the length of content saved and displays a warning if a threshold is exceeded. Start by writing a parser function lengthWarning
that returns an empty string during rendering, but registers the value in parser cache. Then register a PageContentSaveComplete
hook. Inside the hook you access $content = ContentHandler::getContentText( $contentObj )
. Use strlen
to calculate length. If length exceeds a threshold (for example 20000 characters), write a log entry to a custom table or logging channel. Use MediaWikiServices::getInstance()->getDBLoadBalancer()->getConnectionRef( DB_PRIMARY )
to record length. You can also call $out->addHTML
or $out->addWarning
to surface a notice on the next page view.
You will need to define a database table for tracking saved lengths. Place SQL schema in sql/lengthlog.sql
and register in extension.json
under loadExtensionSchemaUpdates
. This ensures maintenance/update.php executes your table creation code.
Allow configuration by adding settings in extension.json
, such as "LengthWarningThreshold": 20000
. Use ConfigFactory to load with MediaWikiServices::getInstance()->getConfigFactory()->makeConfig( 'myextension' )->get('LengthWarningThreshold')
.
Testing is vital. Write unit tests for your parser function in tests/phpunit/ParserFunctionTest.php
, and simulate hook handling in tests/phpunit/LengthWarningHookTest.php
. Use mocks for database and content, and verify that logging occurs when threshold breached and does not occur otherwise.
Performance matters. Use wfDebugLog
to record debug messages only when needed. Wrap database writes in conditionals and avoid repeated writes during transactional saves. Use object cache to reduce redundant queries.
Register your extension with proper namespace in extension.json
, add i18n for messages, and document parser usage and hook behavior. Provide example markup in documentation pages.
Distribute your extension with version control, semantic version tags, and follow MediaWiki coding standards. Encourage community feedback and iterate on code.
This walkthrough gives you a robust foundation for building custom parser functions and hooks in MediaWiki. These patterns pave the way for powerful yet maintainable enhancements. For more advanced integration patterns, caching strategies, and best practices, check out Mastering MediaWiki Extensions: Beyond the Manual.
Creating High Performance Data APIs with MediaWiki and ServiceLayer Patterns
MediaWiki serves well as both a publishing platform and a backend content store. Exposing structured content via custom RESTful APIs allows external applications, mobile clients, or chatbots to access page data in structured JSON. Implementing a high performance API in MediaWiki requires adhering to ServiceLayer patterns, handling caching, routing, security, and output formatting carefully. This article covers building such APIs step by step.
Service classes in MediaWiki hold reusable logic separated from controllers. Define your service in src/ServicePageApi.php
. This class accepts a Title object and extracts data such as title text, summary, categories, revision ID, and metadata by calling PageProps::getProperties
. It returns an associative array. Register your service by adding an entry to extension.json under Services
, mapping an interface to your class.
Create a controller in src/PageApiController.php
. Extend ApiBase
. Implement execute()
to parse input via $params = $this->extractRequestParams()
, get a Title via Title::newFromText( $params['page'] )
, call the service layer via MediaWikiServices, and output JSON with $this->getResult()->addValue( null, $this->getModuleName(), $data )
. Register parameters and permission checks so only read permission is needed.
Add routing via $wgRestConfig['pageapi']
. Use '{page}' => 'MyExtension\\PageApiController::execute'
. This enables API endpoints like /api/rest_v1/pageapi/FrontPage
.
To handle reliability and performance integrate caching. Use FastFileCache or MediaWiki object cache. For GET requests, use $cache->getWithSetCallback('pageapi:'.sha1($title->getFullText()), function () use ($title, $service) { return $service->getPageData( $title ); }, 600 );
. This caches JSON output for 10 minutes.
Protect from unauthorized access by registering permissions such as api-read
. Apply $this->checkPermissions()
and use CSRF tokens for write endpoints. Configure API module rights in extension.json
.
Document your API using Swagger or OpenAPI. Register documentation entry by adding Swagger YAML in module
metadata with descriptions, parameter schemas, and examples. Provide reference docs on a wiki page.
Test your API with PHPUnit and integration tests. Use $this->runApi( 'get', [ 'page' => 'Main Page' ] )
and verify JSON structure. Simulate cache flows by clearing object cache or setting stale values.
For batch data extraction, implement pagination with continuations. Accept parameters such as limit
and from
. In caching logic, key results accordingly. Avoid stale data by purging cache when relevant changes occur—use Hook::register('PageContentSaveComplete', ...)
to clear cache for edited titles.
Monitor usage with logging. Add $wgDebugLog
entries for API hits, cache metrics, and errors. Use Prometheus exporter extension to expose metrics.
Extend your API to support embedded HTML or charts. For example, create a second endpoint that returns HTML rendered via Parsoid
by calling Rest API internally. Cache results separately.
Register ResourceLoader modules for any UI components in your API documentation. Provide interactive playground pages under Special:MyExtension
.
Deploy your extension with semantic versioning and provide clear upgrade instructions for new endpoint versions. Use hookLoadExtensionSchemaUpdates
for any schema changes.
By modularizing logic into service layers, handling cache, and creating robust controllers, your MediaWiki installation can expose clean, performant, secure JSON APIs. These APIs serve as reliable data sources for downstream systems. Explore Mastering MediaWiki Extensions: Beyond the Manual for deep dives into API versioning strategies, token revocation, and advanced caching methods.
Orchestrating MediaWiki Job Queues for Scalability and Background Processing
MediaWiki job queues enable delayed execution of expensive or asynchronous tasks. Using job queues effectively improves page response times and allows background processing at scale. This tutorial explains how to define custom jobs, configure queue runners, monitor performance, and integrate priority logic.
A job is defined by implementing MediaWiki’s Job interface or extending Job. Define your custom job in src/MyTaskJob.php
. Store payload in the constructor. Implement run()
to access necessary services, perform tasks such as sending emails, regenerating caches, or external API calls. Return true on success or false to retry.
Register the job in extension.json
under Jobs section. Use a unique label and PHP class mapping.
To enqueue a job, inject the job queue service via MediaWikiServices::getInstance()->getJobQueueGroup()
and call enqueue
with group name and job instance. The job will execute asynchronously.
Configure your cron or systemd to run runJobs.php
. To prioritize certain groups, configure $wgJobTypes\Tests
with weights. For example assign group ‘high’ weight 100 and ‘low’ weight 10.
Batch enqueueing is possible. Use enqueueBatch
with arrays of jobs to reduce lock contention.
Monitor job queue status via database table job. Inspect delays and backlog with queries. For long running jobs, consider partitioning work across multiple job instances and using continuation patterns.
Protect against DB contention by using transactions in run(), and respecting retry limits. Use runOnUpdatesOnly
for jobs triggered by update scripts to avoid race conditions.
Implement test suites under tests/phpunit/MyTaskJobTest.php
, mocking database and external services. Use InMemoryCache to simulate logic.
For real time error reporting, add $wgDebugLog['myextension'][] = 'MyTaskJob';
entries. Notify admins with email or webhook on job failures.
Scale job workers horizontally. Use multiple CLI workers. Ensure each node connects to same job table or Redis backend. Use $wgJobTypeConf
to distribute load.
Job queue integration improves user experience by deferring non critical work. Tasks like sitemap generation, file cleanup, or API ingest become manageable in background.
For advanced patterns such as long-running tasks, failure escalation, transactional retries, and cross site job coordination, see Mastering MediaWiki Extensions: Beyond the Manual.