Manual:Page content models

Other languages:
English • ‎Esperanto • ‎español • ‎français • ‎português • ‎日本語
Development Tag extensions Parser functions Hooks Special pages Skins Magic words API Content models

The ContentHandler introduced in MediaWiki 1.21 allows you to add new content models other than wikitext. It makes it possible for wiki pages to be composed of data other than wikitext, and represented in any way — for example: Markdown, reStructuredText, icalendar, or a custom XML format. The display and editing of these content models can be handled in custom ways (e.g. different syntax highlighting, or whole new data-entry forms).

This page steps through how to create a new content model in an extension. It assumes some familiarity with general extension development practices. For a brief summary of the requirements, see the Summary section at the bottom of this page.

A meaningless "Goat" content model will be used for the examples. You can also examine the DataPages extension, which is part of Extension:Example.


First of all, add the content model's name and handler class to your extension.json:

"ContentHandlers": {
	"goat": "MediaWiki\\Extension\\GoatExt\\GoatContentHandler"
  • The left-hand value here is the name of the content type, it can be any unique string you want, and it lives alongside the five built-in content types 'wikitext', 'JavaScript', 'CSS', 'plain text', and 'JSON'. This value is exposed to users in places such as Special:ChangeContentModel and page information.
  • The right-hand value is the fully-qualified name of a class that extends ContentHandler.

(Note that the GoatContent and GoatContentHandler classes must also be added to AutoloadClasses.)

Optional content model constantsEdit

The 'goat' string above is the content model's ID (generally called $modelId in code), and is usually also defined as a constant. These constants are defined for all built-in content models, and lots of documentation refers to the "CONTENT_MODEL_XXX" constants. If you have not defined them, this can be a bit confusing. The definition should be done via the callback item in extension.json. For example:

In extension.json:

"callback": "MediaWiki\\Extension\\GoatExt\\Hooks::registrationCallback"

In includes/Hooks.php:

namespace MediaWiki\Extension\GoatExt;
class Hooks {
    public static function registrationCallback() {
        // Must match the name used in the 'ContentHandlers' section of extension.json
        define( 'CONTENT_MODEL_GOAT', 'goat' );

You don't have to do it this way, and could just use the string.

Assigning content models to pagesEdit

If you want an entire wiki namespace to have a default content model, you can define it as such in extension.json:

"namespaces": [
		"id": 555,
		"constant": "NS_GOAT",
		"name": "Goat",
		"subpages": false,
		"content": true,
		"defaultcontentmodel": "goat"
		"id": 556,
		"constant": "NS_GOAT_TALK",
		"name": "Goat_talk",
		"subpages": true,
		"content": false,
		"defaultcontentmodel": "wikitext"

Or if you want to determine the content type by the addition of a quasi-file-type suffix on the wiki page name, you can use the ContentHandlerDefaultModelFor hook. For example:

namespace MediaWiki\Extension\GoatExt;
class Hooks {
	public static function onContentHandlerDefaultModelFor( Title $title, &$model ) {
		// Any page title (in any extension) ending in '.goat'.
		if ( substr( $title->getText(), -5) === '.goat' ) {
			// This is the constant you defined earlier.
			// If you change the content model, return false.
			return false;
		// If you don't change it, return true.
		return true;


The next thing to define is the GoatContentHandler class, which is where we also specify what format this content type will be stored as (in this case, text). ContentHandlers don't know anything about any particular page content, but determine the general structure and storage of the content.


namespace MediaWiki\Extension\GoatExt;

class GoatContentHandler extends \ContentHandler {

	public function __construct( $modelId = 'goat' ) {
		parent::__construct( $modelId, [ CONTENT_FORMAT_TEXT ] );

	protected function getContentClass() {
		return GoatContent::class;

The content handler will also implement the serializeContent() and unserializeContent() methods, but we'll get to those later.


The GoatContent class is the representation of the content's data, and does not know anything about pages, revisions, or how it is stored in the database.


namespace MediaWiki\Extension\GoatExt;

class GoatContent extends \AbstractContent {

	public function __construct( $modelId = 'goat' ) {
		parent::__construct( $modelId );


Edit formEdit

Now we've got the skeleton set up, we'll want to try editing a Goat. To do this, we create GoatContentHandler::getActionOverrides() and specify what actions we want to map to what classes. To start with, we'll just deal with 'edit' (which corresponds to '?action=edit' in the URL).

	public function getActionOverrides() {
		return [
			'edit' => EditAction::class,

And we'll create our new EditAction class, basically the same as the core EditAction but using our own EditPage:


namespace MediaWiki\Extension\GoatExt;

use EditAction as CoreEditAction;

class EditAction extends CoreEditAction {

	public function show() {
		$editPage = new EditPage( $this->page );
		$editPage->setContextTitle( $this->getTitle() );


Our new EditPage class is where the action happens (excuse the pun):


namespace MediaWiki\Extension\GoatExt;

use EditPage as CoreEditPage;
use OOUI\FieldLayout;
use OOUI\TextInputWidget;

class EditPage extends CoreEditPage {

	protected function showContentForm() {
		$out = $this->context->getOutput();

		// Get the data.
		$name = $this->getCurrentContent()->getGoatName();

		// Create the form.
		$nameField = new FieldLayout(
			new TextInputWidget( [ 'name' => 'goat_name', 'value' => $name ] ),
			[ 'label' => 'Name', 'align' => 'left' ]
		$out->addHTML( $nameField );


You should now be able to edit a page and see your form. But when you put data into it, and hit 'preview', you'll see that things are not yet working fully and that you get no output, nor is your submitted text shown again in the form.

So we must override the 'submit' action as well, with a new SubmitAction class and the addition of 'submit' => SubmitAction::class, to our GoatContentHandler::getActionOverrides() method. Our SubmitAction class should be the same as that of core, but inheriting from our EditAction.


A content model is responsible for producing any required output for display. This usually involves working with its data and producing HTML in some way, to add to the parser output.


namespace MediaWiki\Extension\GoatExt;

class GoatContent extends \AbstractContent {

	protected function fillParserOutput(
		Title $title, $revId, ParserOptions $options, $generateHtml, ParserOutput &$output
	) {
		// e.g. $output->setText( $html );


Display a description/documentationEdit

Sometimes you may want to display some informations or some documentation for an article that have a custom content model such as JSON. Actually there aren't system messages to display some text above such pages (with the exception of MediaWiki:Clearyourcache displayed above only JavaScript and CSS pages). You may want to see phab:T206395 for further details.

Comparing revisionsEdit



To implement a new content model with a custom editing form, create the following:


namespace MediaWiki\Extension\GoatExt;

class GoatContent extends \AbstractContent  {
	public function __construct( $modelId = 'goat' ) {
	protected function fillParserOutput( \Title $title, $revId, \ParserOptions $options, $generateHtml, \ParserOutput &$output) {}
	public function getTextForSearchIndex() {}
	public function getWikitextForTransclusion() {}
	public function getTextForSummary( $maxLength = 250 ) {}
	public function getNativeData() {}
	public function getSize() {}
	public function copy() {}
	public function isCountable( $hasLinks = null ) {}

namespace MediaWiki\Extension\GoatExt;

class GoatContentHandler extends \ContentHandler {	
	public function __construct( $modelId = CONTENT_MODEL_GOAT, $formats = ['text/x-goat'] ) {
		parent::__construct($modelId, $formats);
	protected function getContentClass() {}
	public function supportsDirectEditing() {}
	public function serializeContent( \Content $content, $format = null ) {}
	public function unserializeContent( $blob, $format = null ) {}
	public function makeEmptyContent() {}
	public function getActionOverrides() {}

See alsoEdit