Creating custom content models with ContentHandler for non-wikitext pages.
The ContentHandler facility, introduced in MediaWiki 1.21, allows wiki pages to store and render arbitrary content types instead of treating all pages as wikitext. Each content type is identified by a content model — a unique string ID. MediaWiki ships with built-in models and allows extensions to register their own.
The ContentHandler system uses two class hierarchies:
Content interface
Content (and AbstractContent base class) — represents the actual content of a specific page revision. Provides methods like getParserOutput(), diff(), isValid().
ContentHandler class
ContentHandler — a singleton that provides functionality for a content model without acting on specific content. Acts as a factory for Content objects.
The ContentHandler for a given model can be retrieved via:
$handler = ContentHandlerFactory::getContentHandler( $modelId );// Or via Title/WikiPage:$handler = $title->getContentHandler();$handler = $wikiPage->getContentHandler();
ContentHandler objects are singletons. Use ContentHandler::makeEmptyContent() and ContentHandler::unserializeContent() to create Content objects. However, the preferred approach is:
// Preferred: get content from a WikiPage or RevisionRecord$content = $wikiPage->getContent();$content = $revisionRecord->getContent( SlotRecord::MAIN );
// Serialize to a specific format$serialized = $handler->serializeContent( $content, CONTENT_FORMAT_JSON );// Get all formats supported by a model$formats = $handler->getSupportedFormats();// Deserialize from stored data$content = $handler->unserializeContent( $data, $format );
When using the Action API (action=edit, action=parse, action=query&prop=revisions) to access page content, always specify both the content model and format explicitly. Without this information, interpretation of returned content is unreliable.
To add support for a new content type, subclass ContentHandler and implement the required methods:
namespace MyExtension;use MediaWiki\Content\AbstractContent;use MediaWiki\Content\ContentHandler;use MediaWiki\Content\JsonContent;use MediaWiki\Parser\ParserOutput;use MediaWiki\Parser\ParserOptions;use MediaWiki\Page\PageReference;class MyDataContentHandler extends ContentHandler { public function __construct() { // Register the model ID and supported serialization formats parent::__construct( 'myextension-mydata', [ CONTENT_FORMAT_JSON ] ); } /** * Deserialize stored content into a Content object. */ public function unserializeContent( string $text, ?string $format = null ): MyDataContent { $this->checkFormat( $format ); return new MyDataContent( $text ); } /** * Create an empty Content object for this model. */ public function makeEmptyContent(): MyDataContent { return new MyDataContent( '{}' ); }}
Then implement the Content class:
class MyDataContent extends AbstractContent { private array $data; public function __construct( string $text ) { parent::__construct( 'myextension-mydata' ); $this->data = json_decode( $text, true ) ?? []; } public function serialize( ?string $format = null ): string { return json_encode( $this->data, JSON_PRETTY_PRINT ); } public function isValid(): bool { return is_array( $this->data ); } public function isEmpty(): bool { return $this->data === []; } public function equals( ?Content $that = null ): bool { if ( !( $that instanceof MyDataContent ) ) { return false; } return $this->data === $that->data; } public function copy(): self { return new self( $this->serialize() ); } public function getSize(): int { return strlen( $this->serialize() ); } /** * Render this content to a ParserOutput. * Use Content::getParserOutput() — do NOT access the parser directly. */ public function getParserOutput( PageReference $page, int $revId = 0, ?ParserOptions $options = null, bool $generateHtml = true ): ParserOutput { $output = new ParserOutput(); if ( $generateHtml ) { $html = '<pre class="mydata-content">'; $html .= htmlspecialchars( json_encode( $this->data, JSON_PRETTY_PRINT ) ); $html .= '</pre>'; $output->setRawText( $html ); } return $output; }}
For rendering page content, always use Content::getParserOutput() rather than accessing the parser directly. Use WikiPage::makeParserOptions() to construct appropriate ParserOptions.
ContentHandler::getActionOverrides() allows a content handler to replace the default edit, view, or history actions with custom implementations. This is similar to WikiPage::getActionOverrides():
public function getActionOverrides(): array { return [ 'edit' => MyDataEditAction::class, 'view' => MyDataViewAction::class, ];}
Content is stored using the same mechanism as wikitext. Non-text content is serialized first. Each revision records its content_model and content_format in the revision table — but only if they differ from the page’s default (the defaults are stored as NULL to save space).
JavaScript and CSS pages are no longer parsed as wikitext. Links and category tags inside .js and .css pages do not create actual page links or category memberships.