# Mediawiki Extension Development

> Guide developers through creating MediaWiki extensions from setup to publication. Covers extension architecture, implementing hooks/special pages/APIs, database integration, localization, testing, and Wikimedia deployment. Use when developers want to create new extensions, add functionality to MediaWiki, or prepare extensions for production deployment.

- **Type:** Skill
- **Install:** `agentstack add skill-santhoshtr-wiki-skills-mediawiki-extension-development`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [santhoshtr](https://agentstack.voostack.com/s/santhoshtr)
- **Installs:** 0
- **Category:** [Databases](https://agentstack.voostack.com/c/databases)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [santhoshtr](https://github.com/santhoshtr)
- **Source:** https://github.com/santhoshtr/wiki-skills/tree/master/skills/mediawiki-extension-development

## Install

```sh
agentstack add skill-santhoshtr-wiki-skills-mediawiki-extension-development
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# MediaWiki Extension Development

## Overview

MediaWiki extensions add custom functionality, modify behavior, and integrate new features into MediaWiki wikis. This skill provides comprehensive guidance for developing extensions following MediaWiki best practices, from initial setup through production deployment.

Extensions can add special pages, modify wiki behavior through hooks, provide APIs, extend wiki markup, manage content, and integrate with external services. The development process involves setup, implementation, localization, testing, and publication.

## When to Use This Skill

Use this skill when developers request help with:

- Creating a new MediaWiki extension from scratch
- Setting up extension structure and registration
- Implementing hooks to modify MediaWiki behavior
- Adding special pages for custom functionality
- Creating API modules (Action API or REST API)
- Extending wiki markup with parser functions or tags
- Adding database tables for extension data
- Implementing localization (i18n) for messages
- Preparing extensions for Wikimedia deployment
- Understanding MediaWiki extension architecture
- Debugging extension issues
- Following MediaWiki coding conventions

## Quick Start

### Minimal Extension Setup

Create a functional extension in 5 minutes:

1. **Create directory structure**:
```bash
cd extensions/
mkdir -p MyExtension/includes
cd MyExtension
```

2. **Create `extension.json`**:
```json
{
	"name": "MyExtension",
	"author": "Your Name",
	"url": "https://www.mediawiki.org/wiki/Extension:MyExtension",
	"description": "Brief description of what your extension does",
	"version": "1.0.0",
	"license-name": "GPL-2.0-or-later",
	"type": "other",
	"manifest_version": 2,
	"AutoloadNamespaces": {
		"MediaWiki\\Extension\\MyExtension\\": "includes/"
	}
}
```

3. **Enable in `LocalSettings.php`**:
```php
wfLoadExtension( 'MyExtension' );
```

4. **Verify**: Visit `Special:Version` to see your extension listed.

This creates a valid, registered extension. Now add functionality through extension points.

## Core Workflows

### Workflow 1: Creating a New Extension

Follow this complete workflow for new extensions:

#### Step 1: Set Up Development Environment

Ensure a working MediaWiki installation:

```bash
# Clone MediaWiki if needed
git clone https://gerrit.wikimedia.org/r/mediawiki/core.git
cd core
composer install
php maintenance/install.php --dbtype=sqlite --dbpath=$(pwd)/data \
    --pass=AdminPassword "My Wiki" "Admin"
```

**Development settings** - Add to `LocalSettings.php`:
```php
// Disable caching during development
$wgMainCacheType = CACHE_NONE;
$wgCacheDirectory = false;

// Enable error reporting
error_reporting( -1 );
ini_set( 'display_errors', 1 );
$wgShowExceptionDetails = true;
$wgShowDBErrorBacktrace = true;
$wgShowSQLErrors = true;

// Enable debugging
$wgDebugToolbar = true;
$wgShowDebug = true;
```

#### Step 2: Use BoilerPlate as Starting Point

MediaWiki provides the BoilerPlate extension as a template:

```bash
cd extensions/
git clone https://gerrit.wikimedia.org/r/mediawiki/extensions/BoilerPlate.git MyExtension
cd MyExtension
rm -rf .git
```

**Customize the extension**:
1. Rename namespace in files from `MediaWiki\Extension\BoilerPlate` to `MediaWiki\Extension\MyExtension`
2. Update `extension.json` with your details
3. Update `i18n/*.json` files with your messages
4. Modify or remove example code

#### Step 3: Define Extension Metadata

Complete the `extension.json` file with all relevant fields:

**Essential fields**:
```json
{
	"name": "MyExtension",
	"author": ["Your Name", "Contributor Name"],
	"url": "https://www.mediawiki.org/wiki/Extension:MyExtension",
	"description": "One-sentence description",
	"version": "1.0.0",
	"license-name": "GPL-2.0-or-later",
	"type": "other",
	"manifest_version": 2
}
```

**Type field** - Choose appropriate type:
- `specialpage` - Adds special pages
- `parserhook` - Extends wiki markup
- `media` - Media handlers
- `semantic` - Semantic extensions
- `skin` - Skins (for skin development)
- `api` - API extensions
- `other` - General extensions

**Additional useful fields**:
```json
{
	"requires": {
		"MediaWiki": ">= 1.39.0"
	},
	"AutoloadNamespaces": {
		"MediaWiki\\Extension\\MyExtension\\": "includes/"
	},
	"config": {
		"MyExtensionSomeSetting": {
			"value": true,
			"description": "Description of this configuration option"
		}
	},
	"MessagesDirs": {
		"MyExtension": ["i18n"]
	}
}
```

See `assets/templates/extension.json` for a complete template.

#### Step 4: Implement Core Functionality

Choose appropriate extension points based on your needs. See **Workflow 2-6** for specific implementations.

#### Step 5: Add Localization

Implement internationalization for all user-facing text:

1. Create `i18n/en.json`:
```json
{
	"myextension-desc": "Extension description for Special:Version",
	"myextension-hello": "Hello, world!",
	"myextension-greeting": "Hello, $1! Welcome to $2."
}
```

2. Create `i18n/qqq.json` for documentation:
```json
{
	"myextension-desc": "{{desc|name=MyExtension|url=https://...}}",
	"myextension-hello": "Simple greeting message shown on example page",
	"myextension-greeting": "Personalized greeting. Parameters:\n* $1 - User name\n* $2 - Wiki name"
}
```

3. Register in `extension.json`:
```json
{
	"MessagesDirs": {
		"MyExtension": ["i18n"]
	}
}
```

**For API-specific messages**, use separate directory:
```json
{
	"MessagesDirs": {
		"MyExtension": ["i18n", "i18n/api"]
	}
}
```

Create `i18n/api/en.json` for API help messages with `apihelp-` prefix:
```json
{
	"apihelp-myaction-summary": "Performs a custom action",
	"apihelp-myaction-param-text": "Text to process",
	"apihelp-myaction-example-1": "Process the text 'hello'"
}
```

**For special page aliases**, use `ExtensionMessagesFiles`:
```json
{
	"ExtensionMessagesFiles": {
		"MyExtensionAlias": "MyExtension.i18n.alias.php"
	}
}
```

Create `MyExtension.i18n.alias.php`:
```php
 [ 'MyPage', 'My Page' ],
];

$specialPageAliases['nl'] = [
	'MyPage' => [ 'MijnPagina' ],
];
```

See `assets/templates/MyExtension.i18n.alias.php` for complete template.

4. Use in code:
```php
// In special pages or contexts with IContextSource
$output->addHTML( $this->msg( 'myextension-hello' )->escaped() );

// With parameters
$greeting = $this->msg( 'myextension-greeting', $userName, $wikiName )->text();

// Standalone (not context-aware)
$message = wfMessage( 'myextension-hello' )->inLanguage( 'de' )->text();
```

See `references/localization-guide.md` for complete i18n patterns.

#### Step 6: Test and Debug

**Testing approaches**:
1. **Manual testing**: Enable extension, test features in browser
2. **PHPUnit tests**: Create tests in `tests/phpunit/`
3. **Integration tests**: Test with other extensions enabled
4. **Parser tests**: For parser hooks, create `.txt` files in `tests/parser/`

**Debugging tools**:
```php
// Use MediaWiki's debug log
wfDebugLog( 'MyExtension', 'Debug message here' );

// Check variables
wfDebug( print_r( $variable, true ) );

// Use MediaWiki's logger
LoggerFactory::getInstance( 'MyExtension' )->info( 'Log message' );
```

**Common issues**:
- Extension not appearing: Check `wfLoadExtension()` in LocalSettings.php
- Hooks not firing: Verify hook name and signature
- Namespace errors: Check autoloading configuration
- Messages not showing: Verify MessagesDirs and file location

### Workflow 2: Implementing Hooks

Hooks allow extensions to modify MediaWiki behavior at specific points.

#### Understanding Hooks

MediaWiki core and extensions emit hooks at key execution points. Extensions register handler functions to run when hooks fire.

**Hook types**:
1. **Run hooks**: Execute handler, continue regardless of return value
2. **Abort hooks**: Handler can stop execution by returning false
3. **Processing hooks**: Handler modifies data passed by reference

**Finding hooks**: See `references/hooks-reference.md` for common hooks or search [MediaWiki documentation](https://www.mediawiki.org/wiki/Manual:Hooks).

#### Registering Hooks

**Method 1: Function-based (simple)**:

In `extension.json`:
```json
{
	"Hooks": {
		"BeforePageDisplay": "MyExtensionHooks::onBeforePageDisplay"
	}
}
```

Create handler class:
```php
addModuleStyles( 'ext.myextension.styles' );
	}
}
```

**Method 2: HookHandler (recommended for MW 1.35+)**:

In `extension.json`:
```json
{
	"HookHandlers": {
		"main": {
			"class": "MediaWiki\\Extension\\MyExtension\\Hooks\\MainHookHandler"
		}
	},
	"Hooks": {
		"BeforePageDisplay": "main",
		"ParserFirstCallInit": "main"
	}
}
```

Create handler:
```php
addModuleStyles( 'ext.myextension.styles' );
	}

	public function onParserFirstCallInit( $parser ) {
		$parser->setHook( 'myextension', [ $this, 'renderTag' ] );
	}

	public function renderTag( $input, array $args, $parser, $frame ) {
		return '' . htmlspecialchars( $input ) . '';
	}
}
```

**Hook handler advantages**:
- Type safety through interfaces
- Better IDE support and autocomplete
- Dependency injection support
- Cleaner separation of concerns

#### Organizing Multiple Hook Handlers

For larger extensions, organize hooks into multiple handler classes by domain or purpose:

**extension.json**:
```json
{
	"HookHandlers": {
		"ui": {
			"class": "MediaWiki\\Extension\\MyExtension\\Hooks\\UIHooks",
			"services": [ "PermissionManager" ]
		},
		"parser": {
			"class": "MediaWiki\\Extension\\MyExtension\\Hooks\\ParserHooks"
		},
		"database": {
			"class": "MediaWiki\\Extension\\MyExtension\\Hooks\\DatabaseHooks"
		}
	},
	"Hooks": {
		"BeforePageDisplay": "ui",
		"SkinTemplateNavigation::Universal": "ui",
		"ParserFirstCallInit": "parser",
		"ParserGetVariableValueSwitch": "parser",
		"LoadExtensionSchemaUpdates": "database"
	}
}
```

**Handle deprecated hooks**:
```json
{
	"Hooks": {
		"SomeOldHook": {
			"handler": "main",
			"deprecated": true
		},
		"NewReplacementHook": "main"
	}
}
```

Marking hooks as deprecated prevents deprecation warnings when MediaWiki phases out old hooks.

#### Common Hook Patterns

**Modify page content before display**:
```php
// Hook: BeforePageDisplay
public function onBeforePageDisplay( $out, $skin ): void {
	$out->addJsConfigVars( 'myExtensionConfig', [
		'setting' => true
	] );
	$out->addModules( 'ext.myextension.init' );
}
```

**Add custom user rights**:
```php
// Hook: UserGetRights
public function onUserGetRights( $user, &$rights ) {
	if ( $user->isRegistered() ) {
		$rights[] = 'myextension-use-feature';
	}
}
```

**Validate page saves**:
```php
// Hook: EditFilterMergedContent
public function onEditFilterMergedContent( $context, $content, $status, $summary, $user, $minoredit ) {
	if ( strpos( $content->getText(), 'forbidden-word' ) !== false ) {
		$status->fatal( 'myextension-forbidden-word-error' );
		return false; // Abort save
	}
	return true;
}
```

**Track custom actions**:
```php
// Hook: PageSaveComplete
public function onPageSaveComplete( $wikiPage, $user, $summary, $flags, $revisionRecord, $editResult ) {
	// Log to custom table or external service
	$dbw = wfGetDB( DB_PRIMARY );
	$dbw->insert(
		'myextension_edits',
		[
			'page_id' => $wikiPage->getId(),
			'user_id' => $user->getId(),
			'timestamp' => $dbw->timestamp()
		],
		__METHOD__
	);
}
```

See `references/hooks-reference.md` for 50+ common hooks with examples and `assets/templates/HookHandlerExample.php` for complete implementation.

### Workflow 3: Creating Special Pages

Special pages provide custom functionality accessible via `Special:PageName`.

#### Basic Special Page

1. **Create special page class**:

Create `includes/Specials/SpecialMyPage.php`:
```php
setHeaders();
		$this->outputHeader();

		$out = $this->getOutput();
		$out->setPageTitle( $this->msg( 'myextension-mypage-title' ) );
		$out->addWikiMsg( 'myextension-mypage-intro' );

		$out->addHTML( 'Hello from MyPage!' );
	}

	protected function getGroupName() {
		return 'other';
	}
}
```

2. **Register in `extension.json`**:
```json
{
	"SpecialPages": {
		"MyPage": "MediaWiki\\Extension\\MyExtension\\Specials\\SpecialMyPage"
	},
	"MessagesDirs": {
		"MyExtension": ["i18n"]
	}
}
```

3. **Add messages** to `i18n/en.json`:
```json
{
	"mypage": "My Page",
	"myextension-mypage-title": "My Custom Page",
	"myextension-mypage-intro": "This is an example special page."
}
```

4. **Access**: Navigate to `Special:MyPage` on your wiki.

#### Special Page with Form

For pages accepting user input:

```php
setHeaders();
		$this->checkPermissions();

		$formDescriptor = [
			'username' => [
				'type' => 'text',
				'label-message' => 'myextension-form-username',
				'required' => true,
			],
			'message' => [
				'type' => 'textarea',
				'label-message' => 'myextension-form-message',
				'rows' => 5,
			],
			'sendcopy' => [
				'type' => 'check',
				'label-message' => 'myextension-form-sendcopy',
			],
		];

		$htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() );
		$htmlForm
			->setSubmitTextMsg( 'myextension-form-submit' )
			->setSubmitCallback( [ $this, 'onSubmit' ] )
			->show();
	}

	public function onSubmit( array $data ) {
		// Process form submission
		$username = $data['username'];
		$message = $data['message'];

		// Do something with the data
		$this->getOutput()->addWikiMsg( 'myextension-form-success', $username );

		return true; // Or return Status object
	}

	protected function getGroupName() {
		return 'users';
	}
}
```

#### Restricted Special Page

Require specific permissions:

```php
public function __construct() {
	parent::__construct(
		'MyAdminPage',
		'myextension-admin' // Required user right
	);
}

public function execute( $subPage ) {
	$this->setHeaders();
	$this->checkPermissions(); // Verifies user has required right

	// Page content for authorized users only
}
```

Define the user right in `extension.json`:
```json
{
	"AvailableRights": [
		"myextension-admin"
	],
	"GroupPermissions": {
		"sysop": {
			"myextension-admin": true
		}
	}
}
```

See `assets/templates/SpecialPageExample.php` for complete implementations.

#### Includable Special Pages

Includable special pages can be transcluded into wiki pages using `{{Special:PageName}}` syntax:

**Create includable special page**:
```php
including() ) {
			// Content when transcluded: {{Special:MyIncludable}}
			$this->getOutput()->addWikiTextAsInterface(
				$this->msg( 'myextension-transcluded', $par )->text()
			);
		} else {
			// Content when viewed directly at Special:MyIncludable
			$this->setHeaders();
			$this->getOutput()->addWikiTextAsInterface(
				$this->msg( 'myextension-direct-view' )->text()
			);
		}
	}
}
```

**Register in extension.json**:
```json
{
	"SpecialPages": {
		"MyIncludable": "MediaWiki\\Extension\\MyExtension\\Specials\\SpecialMyIncludable"
	}
}
```

**Usage in wiki pages**:
- Direct link: `[[Special:MyIncludable]]`
- Transclusion: `{{Special:MyIncludable}}`
- With parameter: `{{Special:MyIncludable/Item123}}`

See `assets/templates/SpecialIncludableExample.php` for complete implementation.

### Workflow 4: Creating API Modules

Extensions can provide API endpoints for programmatic access.

#### Action API Module

The Action API follows MediaWiki's `api.php` pattern.

1. **Create API module**:

Create `includes/Api/ApiMyAction.php`:
```php
extractRequestParams();

		$result = [
			'success' => true,
			'input' => $params['text'],
			'output' => strtoupper( $params['text'] )
		];

		$this->getResult()->addValue( null, $this->getModuleName(), $result );
	}

	public function getAllowedParams() {
		return [
			'text' => [
				ApiBase::PARAM_TYPE => 'string',
				ApiBase::PARAM_REQUIRED => true,
			],
		];
	}

	public function getExamplesMessages() {
		return [
			'action=myaction&text=hello'
				=> 'apihelp-myaction-example-1',
		];
	}
}
```

2. **Register in `extension.json`**:
```json
{
	"APIModules": {
		"myaction": "MediaWiki\\Extension\\MyExtension\\Api\\ApiMyAction"
	}
}
```

3. **Use the API**:
```
GET /api.php?action=myaction&text=hello&format=json
```

Respons

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [santhoshtr](https://github.com/santhoshtr)
- **Source:** [santhoshtr/wiki-skills](https://github.com/santhoshtr/wiki-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-santhoshtr-wiki-skills-mediawiki-extension-development
- Seller: https://agentstack.voostack.com/s/santhoshtr
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
