Wikis are extensible. Plugins add features the core doesn't provide: custom rendering, integrations, workflows, analytics. Plugin systems vary by wiki platform.
This page covers the patterns for developing plugins that work and stay maintainable.
Convert custom syntax to HTML. [{Calendar}]() becomes a calendar widget.
Pre/post-process content. Strip HTML; apply transformations; insert metadata.
React to events: page saved, user logged in, comment posted.
Pluggable storage: page provider, attachment provider, user provider, search provider.
Custom UI elements: toolbar buttons, custom forms, dashboard widgets.
Connect to external systems: Slack notifications, JIRA links, GitHub commits.
Hook-based. Each extension registers for hooks and provides handlers.
Mature; many available; well-documented.
Atlassian Connect / Forge. Cloud-based; more constrained model than self-hosted plugins.
Action plugins, syntax plugins, helper plugins. Object-oriented.
Wikantik keeps JSPWiki's extension model, minus the JSP-era pieces:
com.wikantik.api.plugin.Plugin, invoked from a page as [{PluginName param=value}](). See "Writing a Wikantik plugin" below.com.wikantik.api.filters.PageFilter hooks (initialize, preTranslate, postTranslate, preSave, postSave, destroy).PageProvider and AttachmentProvider in com.wikantik.api.providers, selected by wikantik.pageProvider and wikantik.attachment.provider. SearchProvider lives in wikantik-main.wikantik-event bus (page saved, user logged in, and so on).McpTool implementations; see WritingANewMcpTool.wikantik-war.There is no template layer to customize any more: the JSP templates are gone and the UI is a React SPA.
A plugin is one class. The page syntax is [{HelloPlugin name=Ada}](); the Markdown pipeline rewrites [{Plugin}]() to [{Plugin}]() so Flexmark parses it as a link node, and PluginManager then runs the class.
package com.example.wiki;
import com.wikantik.api.core.Context;
import com.wikantik.api.exceptions.PluginException;
import com.wikantik.api.plugin.Plugin;
import com.wikantik.util.TextUtil;
import java.util.Map;
public class HelloPlugin implements Plugin {
@Override
public String execute( final Context context, final Map< String, String > params ) throws PluginException {
final String who = params.getOrDefault( "name", "world" );
// The return value is inserted into the page as HTML, so escape anything user-supplied.
return "<span>Hello, " + TextUtil.escapeHtml( who ) + "</span>";
}
}
params holds the key=value pairs from the page. Special keys are _body (the text after an empty line), _cmdline, _bounds and debug (constants on PluginManager).wikantik.plugin.searchPath (comma-separated; com.wikantik.plugin is always searched last, so you can override a built-in). Jars outside WEB-INF/lib go in wikantik.plugin.externalJars. To give a plugin a short alias, declare it in a wikantik_module.xml inside your jar, as the core plugins are in wikantik-main/src/main/resources/ini/wikantik_module.xml.InitializablePlugin.initialize( Engine ) for one-time setup. It runs once before the first execute: at startup if the plugin has a wikantik_module.xml declaration, otherwise on first use.Context.VAR_VIEWER_SENSITIVE), which keeps that render out of the shared, principal-less render caches. A plugin-heavy page therefore costs more to serve, so keep plugin code fast.Context; check permissions before doing anything privileged. JDBCPlugin shows the other end of the scale: it runs page-authored SQL and ships disabled behind wikantik.plugin.jdbc.enabled.Each plugin does one thing. Plugins that try to do many things become brittle.
Plugins should be configurable. Hard-coded behavior limits reuse.
plugin_name:
enabled: true
some_setting: value
Out of the box, plugin should work without configuration. Custom config for advanced needs.
What happens when the plugin fails? Wiki should continue; just the plugin's portion broken.
A plugin that takes the wiki down on error is a bad plugin.
Plugin uses only the platform's stable API. Doesn't reach into internals; doesn't depend on undocumented behavior.
Plugins typically have:
Handle each appropriately.
Some plugins have configuration UIs. The platform usually provides framework for this.
For complex config, separate page; simple config can be inline.
Each plugin needs:
Without this, users can't adopt.
Plugins evolve. Semantic versioning helps consumers know about breaking changes.
For widely-distributed plugins, version compatibility matrices.
Unit tests for plugin logic; integration tests against the wiki platform.
For complex plugins, automated testing is essential. For simple, smoke tests may suffice. In Wikantik, plugin tests in wikantik-main build an engine with TestEngine; see CurrentTimePluginTest and DefaultPluginManagerTest under wikantik-main/src/test/java/com/wikantik/plugin/, and run one with mvn test -pl wikantik-main -Dtest=CurrentTimePluginTest.
Plugins that render on every page view affect performance. See WikiPerformanceTuning.
Cache renderer output where possible.
Hooks that fire on every page save: keep them fast. Slow hooks slow saves.
Plugin makes API calls to external service: each call adds latency.
For non-critical: async (fire and forget). For required: timeout aggressively; handle failure.
Plugins that query the database: indexed queries; pooling; pagination.
Most plugin slowness comes from one of these.
Plugins age. Wiki platform updates; plugin may break. Maintain or deprecate.
For platform upgrades: test plugins. Update for new APIs.
When a plugin is no longer needed, remove cleanly. Deactivate; remove; verify wiki still works.
Some plugins ship with the wiki. Pre-installed.
Public plugins available for installation. Atlassian Marketplace for Confluence; Extension:Distribution for MediaWiki.
For organization-specific plugins, internal distribution.
Clone from source; build; install. Common for custom plugins.
com.wikantik.plugin: InsertPage, Search, Image, IndexPlugin, RecentChangesPlugin, RecentArticles, ReferringPagesPlugin, ReferredPagesPlugin, ReferringUndefinedPagesPlugin, UndefinedPagesPlugin, UnusedPagesPlugin, CurrentTimePlugin, IfPlugin, AliasPlugin, RelationshipsPlugin, HubSetPlugin and JDBCPlugin (disabled by default).[{TableOfContents}]() is not a Plugin class. The Flexmark link post-processor (PluginLinkNodePostProcessorState) renders it directly.StructuralSpinePageFilter enforces the structural-spine rules (canonical IDs, clusters) at save time; SchemaValidationPageFilter validates frontmatter; MathValidationPageFilter flags suspect math; FrontmatterDefaultsFilter adds a default block to a page saved without one; SpamFilter and ProfanityFilter are the classic content filters. Core filters are registered in code with FilterManager.addPageFilter( filter, priority ); add-on filters are listed in WEB-INF/filters.xml (or the file named by wikantik.filterConfig).FileSystemProvider (plain files), VersioningFileProvider (files with history, the default) and CachingProvider (a decorator) for page storage.Unhandled exceptions in hooks. Catch; log; continue.
Reaches into internals not in the public API. Breaks on platform updates.
Users can't install or configure.
Plugin is fast in isolation; slow at scale.
New version incompatible with old; users can't upgrade gracefully.
Plugin requires extensive configuration; nobody understands the options.
For new plugin development:
Cluster Hub: Wikantik Platform Development Hub: Architecture, MCP Protocols, and Agent Briefings (wikantik-development)