Skip to content

Writing Custom DokuWiki Plugins — Syntax, Action, and Helper Plugin Development

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you'll learn how to write custom DokuWiki plugins, including syntax plugins for custom markup, action plugins for event handling, and helper plugins for shared functionality.

What You'll Learn

  • Planning a custom plugin
  • Writing a syntax plugin (custom markup)
  • Writing an action plugin (event handling)
  • Writing a helper plugin (shared functions)
  • Plugin testing and debugging
  • Packaging and distributing plugins

Why It Matters

Pre-built plugins cover common use cases, but every wiki has unique requirements. A custom syntax tag for your internal tools, an action plugin that integrates with your company's API, or a helper plugin that provides reusable functions — these are tasks that only you can implement. Learning to write DokuWiki plugins means you never hit a "DokuWiki cannot do that" wall.

Real-World Use

A company runs its internal tools dashboard on DokuWiki. They write a custom syntax plugin that renders [[jira:PROJ-123]] as a link to their Jira ticket, complete with the ticket status icon fetched from the Jira API. The plugin makes documentation more useful because every Jira reference is a live link. The entire plugin is 50 lines of PHP.

Learning Path

flowchart LR
  A[Essential Plugins] --> B[Custom Plugins]
  B --> C[Plugin Configuration]
  C --> D[Plugin Security]
  D --> E[Template Anatomy]
  E --> F[Template Variables]

Planning a Plugin

Before writing code, answer these questions:

  1. What type of plugin? Syntax (new markup), Action (event hook), Admin (admin page), or Helper (shared functions)?
  2. What is the plugin name? Short, lowercase, no spaces. Example: jiralink
  3. What events or syntax does it need? For syntax plugins, define the markup pattern. For action plugins, identify the events.
  4. Does a similar plugin already exist? Check the plugin Repository to avoid duplication.

Plugin File Structure

lib/plugins/myplugin/
├── plugin.info.txt      # Manifest (required)
├── syntax.php           # Syntax plugin class
├── action.php           # Action plugin class
├── helper.php           # Helper plugin class
├── admin.php            # Admin plugin class
├── conf/
│   ├── default.php     # Default configuration
│   └── metadata.php    # Configuration metadata
├── lang/
│   └── en/
│       └── lang.php    # Language strings
└── style.css           # Plugin styles

Writing a Syntax Plugin

Syntax plugins add custom markup to DokuWiki's parser. They define a pattern to match and a replacement to render.

This plugin converts [[jira:PROJ-123]] into a link to a Jira ticket.

plugin.info.txt:

base    jiralink
author  Your Name
email   your@email.com
date    2026-06-28
name    Jira Link Plugin
desc    Converts [[jira:TICKET-ID]] to Jira links
url     https://example.com/jiralink

syntax.php:

<?php
class syntax_plugin_jiralink extends DokuWiki_Syntax_Plugin {

    public function getType() {
        return 'substition';
    }

    public function getPType() {
        return 'normal';
    }

    public function getSort() {
        return 150;
    }

    public function connectTo($mode) {
        $this->Lexer->addSpecialPattern('\[\[jira:[A-Z]+-\d+\]\]', $mode, 'plugin_jiralink');
    }

    public function handle($match, $state, $pos, Doku_Handler $handler) {
        $ticketId = substr($match, 6, -2);
        return array($ticketId);
    }

    public function render($mode, Doku_Renderer $renderer, $data) {
        if ($mode != 'xhtml') return false;
        $ticketId = $data[0];
        $jiraUrl = 'https://your-jira-instance.atlassian.net/browse/' . $ticketId;
        $renderer->doc .= '<a href="' . $jiraUrl . '" class="jira-link">' . $ticketId . '</a>';
        return true;
    }
}

Syntax Plugin Methods

  • getType(): Returns the plugin type (substition, container, baseonly, etc.)
  • getPType(): Paragraph handling (normal, block, stack)
  • getSort(): Priority in Parsing order (lower numbers = earlier)
  • connectTo(): Register patterns with the lexer
  • handle(): Process the matched pattern
  • render(): Generate output (XHTML, metadata, etc.)

Pattern Types

  • addSpecialPattern(): Matches specific pattern, replaces entire string
  • addEntryPattern(): Matches opening and closing patterns (container)
  • addPattern(): General pattern matching

Writing an Action Plugin

Action plugins hook into events to modify behavior.

Example: Page Save Logger Plugin

This plugin logs every page save to a file.

plugin.info.txt:

base    pagesavelog
author  Your Name
email   your@email.com
date    2026-06-28
name    Page Save Log
desc    Logs all page saves to data/log/pagesaves.log
url     https://example.com/pagesavelog

action.php:

<?php
class action_plugin_pagesavelog extends DokuWiki_Action_Plugin {

    public function register(Doku_Event_Handler $controller) {
        $controller->register_hook(
            'COMMON_WIKIPAGE_SAVE',
            'AFTER',
            $this,
            'log_page_save'
        );
    }

    public function log_page_save($event, $param) {
        $data = $event->data;
        $logFile = DOKU_CONF . '../data/log/pagesaves.log';
        $logEntry = date('Y-m-d H:i:s') . "\t" .
                    $data['id'] . "\t" .
                    $data['oldRevision'] . "\t" .
                    $data['newRevision'] . "\t" .
                    $_SERVER['REMOTE_ADDR'] . "\n";
        file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
    }
}

Common Events for Action Plugins

Event Timing Use Case
COMMON_WIKIPAGE_SAVE AFTER Logging, notifications
COMMON_PAGE_SAVE BEFORE Content filtering, validation
TPL_ACT_RENDER BEFORE Modifying page output
HTML_EDITFORM_OUTPUT BEFORE Adding form fields
ACTION_ACT_PREPROCESS BEFORE Custom action handling

Writing a Helper Plugin

Helper plugins provide reusable functions for other plugins.

Example: API Client Helper

<?php
class helper_plugin_apiclient extends DokuWiki_Plugin {

    public function get($url, $headers = array()) {
        $http = new DokuHTTPClient();
        $http->headers = $headers;
        $response = $http->get($url);
        return $response;
    }

    public function post($url, $data, $headers = array()) {
        $http = new DokuHTTPClient();
        $http->headers = $headers;
        $response = $http->post($url, $data);
        return $response;
    }

    public function jsonGet($url) {
        $response = $this->get($url);
        return json_decode($response, true);
    }
}

Other plugins access the helper:

<?php
$helper = $this->loadHelper('apiclient');
$data = $helper->jsonGet('https://api.example.com/data');

Testing Plugins

Enable Debug Mode

<?php
// conf/local.php
$conf['plugin']['myplugin']['debug'] = 1;

Check PHP Error Logs

tail -f /var/log/apache2/error.log

Test with Minimal Pages

Create a test page with only the plugin feature to isolate issues.

Test on a Staging Wiki

Never test new plugins on a production wiki. Use a development copy.

Plugin Packaging and Distribution

Naming Convention

  • Plugin directory: lowercase, no spaces, no underscores
  • Plugin base name: matches directory name

Distribution File

# Create a distributable ZIP file
cd lib/plugins/
zip -r myplugin.zip myplugin/

README

Include a README with:

  • Plugin name and description
  • Requirements (PHP version, DokuWiki version)
  • Installation instructions
  • Configuration options
  • Usage examples
  • Changelog

Common Mistakes

  1. Not checking the event data structure: Each event passes different data. Read the DokuWiki source or documentation to understand what $event->data contains.
  2. Hard-coding paths: Use DokuWiki constants like DOKU_INC, DOKU_CONF, and DOKU_PLUGIN instead of hard-coded paths.
  3. Forgetting to return false from render() for unsupported modes: If your plugin cannot render in the requested mode (e.g., text instead of xhtml), return false to let other renderers handle it.
  4. Not escaping output: Plugin output should be escaped to prevent XSS. Use hsc() or $renderer->doc .= hsc($data).
  5. Making plugins too complex: A plugin should do one thing well. If you need multiple features, create multiple small plugins.

Practice Questions

  1. What are the three main plugin types in DokuWiki, and when would you use each?
  2. How does a syntax plugin register a custom pattern with the DokuWiki lexer?
  3. What event would you hook into to modify page content before it is saved?
  4. Challenge: Write a custom syntax plugin that implements a [[weather:city]] tag. The plugin should: fetch the current temperature for the given city from a free weather API, cache the result for 1 hour (to avoid excessive API calls), display the temperature with a weather icon, and gracefully fall back to a link if the API is unavailable. Include plugin.info.txt, syntax.php, and conf/default.php files. Test the plugin with at least 3 cities.

FAQ

Can I use Composer libraries in my plugins?

Yes. Add your Composer dependencies to the plugin directory. DokuWiki's autoloader does not automatically load Composer dependencies, so you may need to include the vendor/autoload.php file in your plugin.

How do I add plugin configuration options?

Create conf/default.php with default values and conf/metadata.php with type definitions. Configuration options appear in the Configuration Manager automatically when the plugin is enabled.

Can I override existing syntax with a plugin?

Yes, by registering a pattern that matches before the existing syntax. Set a lower sort value in getSort() to process your pattern earlier in the parsing sequence.

How do I include JavaScript in my plugin?

Place JavaScript files in the plugin directory and load them using the $this->addScript() method in your plugin class, or use the TPL_METAHEADER_OUTPUT event to add script tags.

How do I ensure backward compatibility when updating my plugin?

Use semantic versioning. Document breaking changes in the changelog. Deprecate features gradually, keeping backward compatibility for at least one major version cycle.

Mini Project

Goal: Write and test a complete syntax plugin.

  1. Plan a syntax plugin that adds a [[server:hostname]] tag linking to a server monitoring dashboard
  2. Create the plugin.info.txt manifest
  3. Write the syntax.php file with pattern matching and rendering
  4. Add configuration (conf/default.php) for the monitoring dashboard URL
  5. Install the plugin on your testing wiki
  6. Create a test page using the syntax tag
  7. Verify the link renders correctly
  8. Add error handling for invalid server names

What's Next

Custom plugins extend functionality. Learn about plugin configuration options and how to make your plugins configurable.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro