Best practices for extensions

This page documents the current best practices for developing extensions to MediaWiki.

Each item is given a rating to reflect its relative importance. The rating uses keywords as defined by RFC 2119. Here is what they mean in context:
 * MUST – This item is required. Meeting only these means your extension will work, but it may be unsustainable.
 * SHOULD – This item is recommended. Meeting these as well means your extension works well and will likely continue to work well in the future.
 * OPTIONAL – This item is optional. We encourage you to consider it. Extensions that meet these can be considered as meeting the "gold standard" to which we all aspire, and may be used as an example to other developers.

Workflow

 * MUST: Use the standard issue tracker. Create or request creation of a project in Phabricator for your extension.
 * MUST: Use the standard code review system.
 * MUST: Before deployment on Wikimedia Foundation wikis – pass performance and security reviews.
 * SHOULD: The extension should be suitable for use on any wiki, avoid hardcoding specifics for Wikimedia Foundation or other organizations.
 * SHOULD: Before deployment on Wikimedia Foundation wikis – add the extension and your team to the Maintainers page.
 * SHOULD: Have co-maintainers! There should be two maintainers minimum. (Doesn't have to be Wikimedia Foundation staff.)
 * OPTIONAL: Create a MediaWiki-Vagrant role for your extension.

Architecture

 * MUST: Use, with a channel name specific to your extension and with appropiate use of message severity levels. This aids . For Wikimedia Foundation deployment, this also helps your team in monitoring the extension. See also: Logstash on Wikitech.
 * SHOULD: Provide the same functionality through the API (Action API or REST API) and the graphical interface (index.php).
 * OPTIONAL: The functionality should be implemented without significant code duplication (e.g. shared backend service class with light API and SpecialPage bindings.)
 * SHOULD: Use dependency injection. Avoid static calls for anything other than stateless utility methods or handlers.
 * SHOULD: Use service classes registered in service wiring. Avoid singletons.
 * SHOULD: Only throw exceptions for situations that callers should not handle and thus are meant to bubble up.
 * SHOULD: Don't hardcode wikicode / templates or stuff, especially in a way that's not configurable for other websites.
 * SHOULD: Code should be readable by someone who is familiar in that area.
 * SHOULD: Don't call out to static functions in an unrelated class because it wasn't refactored or thought out well.
 * SHOULD: Have a clear separation of concerns between what it actually does, and how its presented to the user.
 * SHOULD: Think twice before adding new public APIs that must remain for content compatibility, such as new wikitext syntax functionality.
 * SHOULD: Not tightly integrate skin functionality with extension functionality.
 * SHOULD: Not add new user preferences, unless you have a really good reason for doing so.
 * OPTIONAL: Expose JavaScript methods for use by user scripts and gadgets, and to enable easy debugging from the console.

Documentation

 * MUST: Have an Extension:… page on mediawiki.org.
 * MUST: Add it to the appropriate extension categories.
 * MUST: Explain briefly what it does.
 * MUST: Document available hooks under Extension:ExtensionName/Hooks/HookName using the "ExtensionHook" template (if any).
 * MUST: Complete list of extensions it depends on, and how to install and configure those.
 * SHOULD: Set a compatibility policy for your extension in the infobox.
 * SHOULD: Describe all configuration settings in one place, from most-used to most-obscure.
 * OPTIONAL: Mention any similar extensions and explain how it compares and differs to those.
 * OPTIONAL: Add notable you make use of in the infobox. This will automatically categorise the extension for discovery as good example of how to use a given hook.
 * OPTIONAL: Write instructions for how to uninstall the extension, or document the limited ability to uninstall.
 * MUST: Having a Help:Extension:… page on mediawiki.org if the extension has web-facing user interfaces.
 * OPTIONAL: Add screenshots in multiple languages, and ideally include one in a right-to-left language.
 * OPTIONAL: Mention some edge cases that were tested for – to prove due diligence of not just testing on simple/generic articles.
 * SHOULD: Consistently use, , , and in documentation as appropiate.
 * OPTIONAL: Add or update the extension's page on WikiApiary.

TODO: Update Documentation/Style guide.

File structure
Overall, the extension's file layout should be organized: consistent naming, directory structure that is logical and not messy.
 * MUST: Using the following standard directory layout:
 * (when using PSR4 namespaced, preferred) or : Contains all (and only) PHP classes.
 * i18n files for specialpage alias, magic words or namespaces located in root folder.
 * SHOULD: Use PSR-4 structure for classes and files (note that as of MediaWiki 1.31 you can use in extension.json instead of listing all classes).
 * SHOULD: Classes in  namespace.   is permissible if the extension name is a sufficiently  unique word and not something generic (e.g. not a verb or noun).
 * SHOULD: One class per file.
 * (or ): Contains JavaScript and CSS for ResourceLoader.
 * command-line maintenance scripts
 * : Contains localized messages in JSON files.
 * : SQL files for database modifications (e.g. called by )
 * : Contains parser test files.
 * : Contains test cases.
 * : Contains test cases.
 * : Contains Selenium browser test files.
 * or : Contains full copy of the relevant license the code is released under.
 * SHOULD: Avoid having many files in the root level directory.
 * SHOULD: Avoid having dozens of nested directories that all only contain one or two things.
 * SHOULD: Avoid having very large files, or very many tiny files (but keep following one class per file pattern – many tiny classes may be a sign of something else going wrong).
 * SHOULD: Write a README file that summarizes the docs and gives detailed installation instructions.
 * SHOULD: Write a README file that summarizes the docs and gives detailed installation instructions.

Database

 * MUST: If adding database tables, use the hook to ensure update.php works.
 * MUST: Uses the Wikimedia-Rdbms library for all database access. Doing so avoids most SQL injection attack vectors, takes care of ensuring transactional correctness, and follows performance best practices.
 * SHOULD: Work well in a distributed environment (concurrency, multiple databases, clustering).
 * SHOULD: If it needs persistence, create nice SQL (primary keys, indexes where needed) and uses some caching mechanism where/if necessary.
 * SHOULD: Never add fields to the core tables nor alter them in any way. To persist data associated with core tables, create a dedicated table for the extension and reference the core table's primary key. This makes it easier to remove an extension.
 * OPTIONAL: If the extension persists data and supports uninstalling, provide a maintainance script that automates this (e.g. drop tables, prune relevant log entries and page properties).

Coding conventions
Overall, follow the for, , , and any other languages that are in-use and have applicable code conventions.
 * SHOULD: Run MediaWiki-CodeSniffer to enforce PHP conventions (check CI Entry points).
 * SHOULD: Run Phan for PHP static analysis (check CI Entry points).
 * SHOULD: Run ESLint for JavaScript conventions and static analysis (check CI Entry points).
 * SHOULD: Run stylelint for CSS conventions.
 * SHOULD: Avoid writing all code into one large function (in JavaScript especially).
 * OPTIONAL: Use code comments generally to document why the code exists, not what the code does. In long blocks of code, adding comments stating what each paragraph does is nice for easy parsing, but generally, comments should focus on the questions that can't be answered by just reading the code.

Testing

 * SHOULD: Have and run and  tests.
 * OPTIONAL: Split out integration and unit tests (see T87781).
 * SHOULD: If there are parser functions or tags, have and run parser tests.
 * OPTIONAL: Have and run browser tests.
 * OPTIONAL: Test against right-to-left (RTL) languages! (how to verify?).
 * OPTIONAL: Test against language converter languages! (how to verify?).

i18n & Accessibility
Overall, your extension should be fully usable and compatible with non-English and non-left-to-right languages.
 * SHOULD: Don't have hardcoded non-translatable strings in your code, use the proper localisation functions.
 * MUST: Use the standard internationalization systems in MediaWiki.
 * MUST: Use a clear and unique prefix named after the extension for all interface mesages.
 * MUST: Use regularly submit and receive translations from translatewiki.net.
 * MUST: Use HTML elements for their intended purpose. Aka use a and not a  with a click handler. This is needed for accessibility.
 * SHOULD: ID's should only be for things you can navigate to. Everything else should be a class. Even if you expect to use it onlyh once, use a class name instead.
 * SHOULD: Escape parameters to localisation messages as close to output as possible. Document whether functions take/except wikitext vs. HTML.
 * OPTIONAL: Add QQQ message documentation for all messages that exist in, and lint them in CI using banana-checker.

Security

 * MUST: Shelling out should escape arguments.
 * MUST: All write actions must be protected against cross-site request forgery (CSRF).
 * MUST: Make sure privacy related issues (checkuser, oversight) are still covered when refactoring or writing new code.
 * SHOULD: Use the standard MediaWiki CSRF token system.
 * SHOULD: Don't modify HTML after it has been sanitized (common pattern is to use regex, but that's bad).
 * SHOULD: Don't load any resources from external domains. This is also needed for privacy and improves performance.
 * SHOULD: Discuss creation of new user rights or user groups with the community first. Adding user rights is easy in code, but must be carefully considered with respect to who may grant these rights, and who carries those rights by default.

Don't reinvent / abuse MediaWiki
As a general principle, do not re-implement or compete with functionality already provided by MediaWiki core.
 * MUST: Use MediaWiki functionality/wrappers for things like vs. , etc.
 * MUST: Use where possible as opposed to workarounds or novel ways of modifying, injecting, or extending functionality.
 * MUST Use MediaWiki's validation/sanitization methods e.g. those in the and  classes.
 * MUST: Don't disable parser cache unless you have a really good reason.
 * MUST: Use Composer for 3rd party PHP library management.
 * SHOULD: Don't reimplement the wheel. Prefer stable and well-maintained libraries when they exist.
 * SHOULD: Don't disable (ref T140664).
 * SHOULD: If an abstraction exists (e.g. ), use that instead of hooks.
 * SHOULD: Don't make things harder for yourself – use standard functionality like extension.json's tests/PHPUnit auto-discovery stuff.
 * SHOULD: Use global MediaWiki configuration such as read-only mode.

Uncategorized

 * Know when you should use ParserOutput methods vs. similar methods on OutputPage.
 * Should extensions be adding user groups in their default configuration?

TODO: Check on Extension:BoilerPlate – T97105 ("BoilerPlate extension should show latest coding practices")

Meta
This page was first drafted during the 2017 Wikimania Hackathon.