Extensions and skins
Overview
Canasta bundles over 170 extensions and skins. The CLI provides commands to enable and disable them without manually editing PHP files, and to add and remove extensions and skins that are not bundled:
canasta extension listβ list available extensionscanasta extension enableβ enable one or more extensionscanasta extension disableβ disable one or more extensionscanasta extension addβ add one or more extensions, cloning them from their git repositories if needed, and enable themcanasta extension removeβ remove extensions that were added to the instance'sextensions/directorycanasta skin listβ list available skinscanasta skin enableβ enable one or more skinscanasta skin disableβ disable one or more skinscanasta skin addβ add one or more skins, cloning them from their git repositories if needed, and enable themcanasta skin removeβ remove skins that were added to the instance'sskins/directory
See the CLI Reference for the full list of flags and usage.
Bundled vs. user extensions
Canasta distinguishes between two types of extensions and skins:
Bundled extensions/skins are included in the Canasta Docker image. Inside the container, they live in canasta-extensions/ and canasta-skins/. These are the extensions listed by canasta extension list and canasta skin list, and are what the enable/disable commands manage. (Note that "bundled" here has a different meaning from the set of extensions and skins that are bundled with MediaWiki; all extensions and skins bundled with MediaWiki are also included in Canasta, but Canasta additionally bundles another 120 or so extensions and skins.)
User extensions/skins are extensions you add yourself. On the host, they go in the extensions/ and skins/ directories of your instance. On Docker Compose, canasta extension add and canasta skin add can clone them there for you, and canasta extension remove and canasta skin remove take them out again (see below). You can also place them there yourself and load them with canasta extension enable / canasta skin enable, or with wfLoadExtension() or wfLoadSkin() calls in a settings file under config/settings/.
Listing available extensions and skins
To see what bundled extensions are available:
canasta extension list
To see available skins:
canasta skin list
These commands list the names of all bundled extensions or skins shipped with the Canasta image, along with any user extensions or skins in the instance's extensions/ or skins/ directory. The instance must be running.
Enabling extensions and skins
canasta extension enable VisualEditor
canasta skin enable Vector
Global vs. per-wiki
By default, enabling an extension applies to all wikis in the instance. To enable an extension for a specific wiki only, use the -w flag:
# Enable for all wikis
canasta extension enable VisualEditor
# Enable for a specific wiki
canasta extension enable SemanticMediaWiki -w docs
Disabling works the same way β without -w it affects the global setting, with -w it affects that wiki only.
The wiki given with -w must be one of the instance's wikis in config/wikis.yaml. enable and disable refuse an unknown wiki ID before changing anything, so a typo can't create a stray settings file.
Enabling multiple at once
Pass a comma-separated list of names:
canasta extension enable VisualEditor,Cite,Scribunto
canasta skin enable Vector,CologneBlue
Every name must already be installed in the container. If any is not, the command fails and lists the missing names, and nothing is enabled β use canasta extension add or canasta skin add (see below) for one that is not installed yet.
After enabling, update.php runs automatically to apply any database changes the extension needs. Pass --skip-update to skip it.
On a Kubernetes instance managed through gitops, enable and disable (as of Canasta CLI 4.25.1) save and commit the change without deploying it, and end with the commands that deploy it: canasta gitops push, then canasta gitops sync. enable does not run update.php there; the web pods run it for the affected wikis when the sync restarts them, even with --skip-update. See Commands that stage instead of deploying.
Disabling extensions and skins
canasta extension disable VisualEditor
canasta skin disable CologneBlue -w docs
The CLI can only disable extensions that were enabled using the CLI. If you manually created a settings file to load an extension, the CLI will not remove it.
Adding extensions not bundled with Canasta
There are two ways to use an extension or skin that is not included in the Canasta image: let canasta extension add / canasta skin add fetch and enable it, or place it in the instance yourself.
Adding with canasta extension add
canasta extension add and canasta skin add (Canasta CLI 4.21.0 or later) put an extension or skin into the instance and enable it in one step:
canasta extension add DarkMode --clone
canasta skin add Mirage --clone
These commands work on Docker Compose instances, with or without gitops; they are not supported on Kubernetes, where they refuse before changing anything. The instance must be running.
How a name is resolved
Names are matched case-insensitively, and each name is handled in this order:
- Bundled with Canasta β nothing is cloned; the extension or skin is only enabled.
--repositoryand--branchare refused, because the image sets its version. - Already in the instance's
extensions/(orskins/) directory β the existing checkout is kept, and its branch is checked against the expected branch (see Branch selection). - Otherwise β it is cloned into
extensions/(orskins/). This requires--clone, or an explicit--repository.
Without --clone, a name that would need cloning makes the command fail before anything is changed. The error names the URL the extension would be cloned from and where that URL came from, so you can check it first:
Extension 'DarkMode' is not bundled and not in extensions/. It would be cloned from https://gerrit.wikimedia.org/r/mediawiki/extensions/DarkMode (ExtensionJson.json). Pass --clone to clone it.
When cloning, the git URL comes from the first of these that applies:
--repository, if you pass it. It must be a single name per command.- For extensions only: the ExtensionJson.json dataset, a catalog of extension manifests. A copy ships with the CLI; the live dataset is used only if that copy cannot be read. An entry whose repository is not an
https://git URL is skipped. - Wikimedia Gerrit β
mediawiki/extensions/<Name>ormediawiki/skins/<Name>.
URLs taken from ExtensionJson.json or Gerrit must be https://. The CLI identifies itself to Gerrit with a Canasta-CLI/<version> user agent, since Wikimedia refuses requests with a generic one. When Wikimedia Gerrit does not answer, a Gerrit project is cloned from Wikimedia's GitHub mirror instead (https://github.com/wikimedia/mediawiki-extensions-<Name> or mediawiki-skins-<Name>), and the printed source says so. A URL passed with --repository is never replaced. The URL and its source are printed for every clone, and flagged when the repository is not hosted on Wikimedia Gerrit. A project that is archived on Gerrit is refused when the CLI looks the name up on Gerrit β that check cannot be made when Gerrit does not answer and the mirror is used β and so is a name that is found nowhere; pass --repository with its git URL instead. Skins are not in ExtensionJson.json, so a skin hosted anywhere other than Gerrit always needs --repository:
canasta extension add MyExtension --repository https://example.com/MyExtension.git --branch main
Branch selection
Unless you pass --branch, the branch is REL1_YY matching the instance's MediaWiki version β for example, REL1_43 on MediaWiki 1.43. This is tried on every remote, since GitHub and GitLab mirrors of MediaWiki extensions carry the same release branches. If the remote has no such branch, the repository's default branch is used and the output says so.
The MediaWiki version is read from the running web container. To override it, pass --mw-version:
canasta extension add DarkMode --clone --mw-version 1.43.2
--branch applies to a single name per command.
If the extension is already in extensions/ but on a different branch than expected, add switches it to the expected branch. A checkout that is detached at a commit on the expected branch β such as one pinned with canasta extension set-version (see below) β is left alone.
Composer dependencies, gitops, and enabling
If the extension or skin has a composer.json, add registers it in config/composer.local.json and installs its requirements in the web container, keeping the packages of the bundled extensions and skins (see Composer dependencies). If composer fails β usually because of a version conflict with packages already installed β the code is still added, but the registration is undone so the instance's composer configuration stays valid, and a warning explains what to fix.
On a gitops instance, the extension or skin is added as a git submodule and committed locally, along with config/composer.local.json when composer succeeded. Run canasta gitops push to distribute it to other hosts. On an instance without gitops, it is a plain git clone.
Finally, the extension or skin is enabled the same way as canasta extension enable: it is added to settings.yaml and update.php runs. Use -w to enable it for one wiki only (the code itself is shared by every wiki in the instance), --skip-update to skip update.php, or --skip-enable to add the code without enabling it.
See CLI:canasta extension add and CLI:canasta skin add for all flags.
Removing an added extension or skin
canasta extension remove and canasta skin remove undo add:
canasta extension remove MyExtension
canasta skin remove Mirage
For each name present in extensions/ (or skins/), the command:
- disables it in the global
settings.yamland in every wiki'ssettings.yaml - deletes its directory β on a gitops instance, it also unregisters the submodule
- unregisters its
composer.jsonfromconfig/composer.local.jsonand updates the web container's Composer packages, keeping those of the bundled extensions and skins β on a stopped instance, the container does this when it starts - on a gitops instance, commits the removal; run
canasta gitops pushto distribute it - starts the containers if they are stopped
Names that are not present in extensions/ or skins/ are reported and skipped, so remove never touches a bundled extension. Like add, it is supported on Docker Compose only.
ℹ️ Note: Database tables created by a removed extension are left in place. MediaWiki does not reverse schema updates.
Adding an extension by hand
To place an extension in the instance yourself:
- Place the extension in the
extensions/directory of your instance (orskins/for skins) - Create a PHP settings file to load it β either in
config/settings/global/(for all wikis) orconfig/settings/wikis/{wiki-id}/(for a specific wiki):<?php wfLoadExtension( 'MyCustomExtension' );
- Restart the instance:
canasta restart
Instead of writing a settings file in step 2, you can run canasta extension enable MyCustomExtension (or canasta skin enable) once the extension is in place β user extensions in extensions/ are shown by canasta extension list and can be enabled and disabled like bundled ones. An extension loaded from a settings file you wrote yourself is not managed by enable/disable.
Overriding a bundled extension
If you need a different version of an extension than the one bundled with Canasta, you can override it by placing your version in the extensions/ (or skins/) directory with the same name. This is useful when you need a newer release, a patched fork, or a development branch of a bundled extension.
This works because the host's extensions/ directory is mounted to user-extensions/ inside the container, and user extensions are symlinked into the final extensions/ directory after bundled extensions, so they take precedence.
To override a bundled extension:
- Place your version of the extension in
extensions/{ExtensionName}/ - Restart the instance:
canasta restart -i myinstance - The bundled version will be ignored in favor of yours
No changes to settings files are needed β the existing wfLoadExtension() call (whether CLI-managed or manual) will load from your copy automatically.
To revert to the bundled version, remove your copy from extensions/ and restart.
⚠️ Warning: Do not use canasta extension remove to revert an override. It disables the extension in the global and every wiki's settings.yaml before deleting your copy, so the bundled version would be left disabled too.
Pinning a user extension to a version
Once a user extension is vendored as a submodule in a gitops-managed instance β for example, by canasta extension add β canasta extension set-version moves it to a specific version and stages the change:
canasta extension set-version UserPageEditProtection --ref REL1_43
--ref takes a branch, tag, or commit SHA. The change is staged but not distributed β run canasta gitops push to roll it out to every host:
canasta gitops push
This replaces the raw git checkout inside the submodule that changing an extension's version otherwise requires, which is easy to get wrong and easy to leave un-staged.
Pass the release branch that matches your MediaWiki version β REL1_43, not master. Pointing an extension at a development branch is the most common cause of version-mismatch regressions after an otherwise clean upgrade.
If the new version changes the database schema, apply it with --run-update:
canasta extension set-version UserPageEditProtection --ref REL1_43 --run-update
Otherwise run canasta maintenance update afterward.
Running canasta extension add again for a pinned extension leaves the pin in place as long as the pinned commit is on the expected branch (REL1_YY, or the --branch you pass). If it is not, add switches the checkout to that branch.
Only user extensions can be pinned this way. A bundled extension has no per-instance submodule, so the command refuses it β to run a different version of a bundled extension, add your copy to extensions/ first as described above, and the user copy takes precedence.
Composer dependencies
Canasta uses a unified composer autoloader. At build time, bundled extensions and skins that need composer dependencies are merged into MediaWiki's root vendor/autoload.php via the composer merge plugin. This means extensions like SemanticMediaWiki, Maps, and Bootstrap all share a single autoloader with MediaWiki core.
The image keeps its own list of the bundled extensions and skins that need Composer, in composer.canasta.json in the MediaWiki directory, and builds its vendor/ directory from that list. As of Canasta 3.5.28, the instance's config/composer.local.json holds only your additions to that list; the container merges the two when it starts. An instance with nothing to add needs no config/composer.local.json at all.
canasta extension add and canasta skin add register an added extension's or skin's composer.json in this file automatically, as extensions/MyExtension/composer.json (or skins/MySkin/composer.json), and remove unregisters it. If you add a user extension with Composer dependencies by hand, add its composer.json to the include list yourself:
{
"extra": {
"merge-plugin": {
"include": [
"extensions/MyExtension/composer.json"
]
}
}
}
Alongside those includes, the file can hold:
requireandrepositories, which are added to the image's own;- any other Composer key, merged over the image's file, with your value winning;
extra.canasta.exclude-bundled, a list of the image's include paths to leave out β for example"extensions/Maps/composer.json"β which removes those packages fromvendor/.
An include counts as yours only when it resolves to an extension or skin in the instance's extensions/ or skins/ directory, or to a path outside MediaWiki. Includes for extensions and skins the image already provides, and includes that match nothing, are ignored. A bundled extension you override with your own copy has its dependencies installed from your copy's composer.json without any entry here. To see the image's own list:
canasta maintenance exec -- cat /var/www/mediawiki/w/composer.canasta.json
When the container starts, composer update runs only if your additions β the content of config/composer.local.json, or a composer.json it includes β have changed since the container's vendor/ was built. With nothing of yours in the file, or no file, the build-time packages are used as-is and Composer does not run.
When the startup update fails, the container usually still starts on the image's bundled packages, but yours are not installed. The failure is recorded in config/persistent/composer-update.failed, which the next successful update removes. canasta start, canasta restart and canasta upgrade print a warning with its first line and the command that shows the whole record, and canasta doctor reports it as well.
Instances created before Canasta 3.5.28 list the bundled extensions and skins in config/composer.local.json. They are no longer needed there. canasta upgrade removes the entries for bundled extensions and skins from the file, along with entries for extensions that used to be bundled but no longer are. It keeps everything else, and deletes the file if nothing of yours is left; see Upgrading.
Settings file loading order
Settings directories support both YAML (*.yaml) and PHP (*.php) files. YAML files are loaded first, then PHP files, each in lexicographic order. Global settings load before per-wiki settings. Changes take effect on the next page load β no restart is required.
The CLI manages a YAML file called settings.yaml for extensions and skins enabled via canasta extension enable and canasta skin enable (or by add).
See Settings for full details on loading order, YAML vs. PHP formats, and the SettingsBuilder API.
Semantic MediaWiki
Semantic MediaWiki (SMW) is bundled with Canasta and lets you store and query structured data within your wiki.
Enabling Semantic MediaWiki
- Add the following to a settings file (e.g.,
config/settings/global/SemanticMediaWiki.php):<?php wfLoadExtension( 'SemanticMediaWiki' ); enableSemantics( 'example.org' );
Replace
example.orgwith your wiki's domain name. - Restart Canasta:
canasta restart
On the first startup after enabling SMW, Canasta automatically runs setupStore.php to initialize the SMW database tables. This creates a smw.json configuration file in config/smw/ on the persistent volume, so the setup only runs once.
Rebuilding SMW data
After initial setup, you need to run rebuildData.php to populate the SMW store. Canasta does not run this automatically because it can take a long time on large wikis. Use the extension maintenance command:
canasta maintenance extension SemanticMediaWiki:rebuildData
For large wikis, pass options to run in segments:
canasta maintenance extension SemanticMediaWiki:rebuildData -s 1000 -e 2000
See Running extension maintenance scripts for more examples.
Notes
- The
enableSemantics()function is provided by SMW's composer autoloader. Canasta's unified autoloader ensures it is available without any additional steps. - SMW's
smw.jsonis stored persistently inconfig/smw/so it survives container recreations. - If you need to reinitialize the SMW store (e.g., after a major upgrade), delete
config/smw/smw.jsonand restart Canasta.
CirrusSearch / Elasticsearch
CirrusSearch replaces MediaWiki's default search with Elasticsearch. Canasta bundles both, with Elasticsearch as a built-in service that is disabled by default. Enabling, indexing, and operating search is covered on Help:Search. What stays on this page is building a custom Elasticsearch image with extra plugins.
Custom Elasticsearch plugins
If you need additional Elasticsearch plugins (for example, analysis-icu for ICU-based text analysis and collation), you build a custom Elasticsearch image and point the elasticsearch service at it. How you deliver that image differs by orchestrator:
- Docker Compose can build the image for you from a
docker-compose.override.yml, or run any image you name inCANASTA_ELASTICSEARCH_IMAGE. - Kubernetes has no override file β pods pull from a registry, so you build the image, push it to a registry the cluster can reach, and name it in
CANASTA_ELASTICSEARCH_IMAGE.
The custom image takes effect only while Elasticsearch is enabled (CANASTA_ENABLE_ELASTICSEARCH=true, see above). The image setting and the enable/disable toggle are independent β setting one never disturbs the other.
Docker Compose
Building from a docker-compose.override.yml is the self-contained option: the build inputs live in the instance directory and are captured by canasta backup, so a restore can rebuild the image.
1. Create a Dockerfile for your custom Elasticsearch image in a build directory (e.g., build/), called Dockerfile.elasticsearch:
FROM docker.elastic.co/elasticsearch/elasticsearch:7.10.2
RUN elasticsearch-plugin install analysis-icu
2. Create or edit docker-compose.override.yml in your instance directory:
services:
elasticsearch:
build:
context: ./build
dockerfile: Dockerfile.elasticsearch
image: canasta-elasticsearch-icu:7.10.2
Always give your custom build a unique image: tag (here, canasta-elasticsearch-icu:7.10.2) β never reuse the base image's name, docker.elastic.co/elasticsearch/elasticsearch:7.10.2. A service that has both image: and build: only builds when no image of that name already exists locally. If you point image: at the base image's tag, Docker Compose finds the already-pulled base image and silently runs it instead of your build, and a later docker compose pull can overwrite your build from the registry. A unique tag is written only by your build, so nothing else shadows or clobbers it.
3. Rebuild and restart:
docker compose build elasticsearch
canasta restart
Rebuild whenever you change the Dockerfile. canasta restart runs docker compose up -d, which never rebuilds an image that already exists locally β it reuses the cached tag. After editing Dockerfile.elasticsearch, always run docker compose build elasticsearch (add --no-cache if a cached layer is hiding your change) before canasta restart, or the old image keeps running.
The override file is automatically picked up by Docker Compose alongside the main docker-compose.yml, and is included in backups along with the build inputs it references β the Dockerfile.elasticsearch and the build/ context β so a backup restored onto a fresh host can rebuild the custom image. See Help:Backup and restore.
If you would rather run a ready-made image than have Compose build one, skip the override file: build or pull the image so its tag exists locally, then set CANASTA_ELASTICSEARCH_IMAGE as shown for Kubernetes below (it works the same on Compose). The override-with-build approach above remains the better choice when you want the build to be self-contained and rebuilt automatically on restore.
Kubernetes
Override files do not apply on Kubernetes. Build the image, push it to a registry the cluster can pull from, and name it in CANASTA_ELASTICSEARCH_IMAGE.
1. Build and push the image. The simplest path is canasta image push (CLI 4.9.0 or later), which builds a directory as a Docker context on the cluster's control-plane node, pushes the result to Canasta's in-cluster registry, and prints the reference pods can pull:
canasta image push <build-dir> --name elasticsearch-icu -H <host>
The Dockerfile must be named Dockerfile and sit at the top of the build directory. Repeated pushes to the same name orphan the previous layers; run canasta image prune occasionally to reclaim them.
Alternatively, build the image yourself and push it to any registry the cluster can pull from (Docker Hub, GHCR, a private registry):
docker build -t <registry>/<repo>:<tag> -f Dockerfile.elasticsearch .
docker push <registry>/<repo>:<tag>
2. Point the Elasticsearch service at it and restart, using the reference canasta image push printed (or your registry's ref):
canasta config set CANASTA_ELASTICSEARCH_IMAGE=<image-ref> --id <your-instance-id>
canasta restart --id <your-instance-id>
Canasta writes the image into the instance's Helm values, and the next deploy runs Elasticsearch on it. For a private registry, make sure the cluster nodes are configured to authenticate to it.
How it works under the hood
When you run canasta extension enable VisualEditor, the CLI adds the extension name to a YAML file called settings.yaml on the host filesystem:
- Global enable updates
config/settings/global/settings.yaml:
extensions:
- VisualEditor
- Per-wiki enable (
-w docs) updatesconfig/settings/wikis/docs/settings.yamlwith the same format.
Extensions and skins are sorted alphabetically within the file. Disabling removes the entry. If the file becomes empty, it is deleted. The CLI never modifies any other files in the settings directory.
On a gitops instance, enable and disable also commit the changed settings.yaml to the instance's local repository, so that canasta gitops push distributes it. Nothing is pushed until you run canasta gitops push. On a gitops-managed Kubernetes instance, the change also reaches the cluster only through that push and a sync (see GitOps).
For details on how these files are processed at runtime, see Settings.
Important notes
- Extension and skin names are case-sensitive for
enable,disable, andremove. Use the exact name as shown bycanasta extension listorcanasta skin list.addmatches names case-insensitively and uses the canonical spelling. enable,add, andlistrequire running containers. Theenablecommand checks that the extension exists in the container β bundled, or in the instance'sextensions/directory β before adding it to the config.addqueries the container for the bundled extensions and the MediaWiki version. Thelistcommand queries the container.disabledoes not require running containers. It operates on host-side YAML files only.- Enabling is idempotent. Running
enableon an already-enabled extension leavessettings.yamlunchanged. - Disable only works on CLI-managed extensions. Only entries in
settings.yamlare affected; other files are left alone. addandremoveare Docker Compose only. On Kubernetes,enableanddisablestill work for bundled extensions and skins.- No restart required. Enabling and disabling take effect on the next page load since PHP reads the settings files on each request. On a gitops-managed Kubernetes instance, that is once the push and sync have deployed the change.