It's better when it's simple

User Tools

Site Tools


Templater Plugin

Compatible with DokuWiki

No compatibility info given!

plugin Allow wikipages to become templates for inclusion into other wikipages, like Wikipedia

Last updated on

This extension has not been updated in over 2 years. It may no longer be maintained or supported and may have compatibility issues.

Similar to include, pagetemplater, template

Tagged with !experimental, include, template, variables


This plugin is experimental, and its behavior subject to change in the future. It may also be folded into the include plugin


The Templater plugin is an extension of the include plugin. Like the include plugin, it allows you to embed one wikipage inside of another. Unlike the include plugin, the Templater also allows you to pass the included wikipage substitution strings to change the output of the included wikipage (also called infobox on MediaWiki).

Just like the include plugin you use it like this:


You may as well include pages from a different namespace: {{template>namespace:pagename}}. The namespaces shortcuts do also work: {{template>:pagename}} (top namespace) or {{template>.namespace:pagename}} (subnamespace).

Optionally you can limit the included page to a specific section (including its subsections):


The real power of this plugin comes in to play when you want to use the same general layout for an element inside of a wikipage, but with different pieces of data. You create a new template page, and instead of putting in the actual values, you give each value a name, and enclose them in at-signs like this:

=== This is the wikipage 'TemplateWikiPage' ===
== @subtitle@ ==
My name is @name@. I am a @value@.

Then on a page where you want to include this template, but with string substitutions, you would do this:

{{template>TemplateWikiPage|subtitle=Foo|name=Jonnay|value=Crazy Guy}}

Which outputs this:

This is the wikipage 'TemplateWikiPage'


My name is Jonnay. I am a Crazy Guy.


To install, you can use the URL given above.

Or you can use the following code and install manually in /lib/plugins/templater/syntax.php.

 * Templater Plugin: Based from the include plugin, like MediaWiki's template
 * Usage:
 * {{template>page}} for "page" in same namespace
 * {{template>:page}} for "page" in top namespace
 * {{template>namespace:page}} for "page" in namespace "namespace"
 * {{template>.namespace:page}} for "page" in subnamespace "namespace"
 * {{template>page#section}} for a section of "page"
 * Replacers are handled in a simple key/value pair method.
 * {{template>page|key=val|key2=val|key3=val}}
 * Templates are wiki pages, with replacers being delimited like
 * @key1@ @key2@ @key3@
 * @license    GPL 2 (
 * @author     Jonathan arkell <> based on code by Esther Brunner <> updated by Vincent de Lau <> with bugfix from Ximin Luo <>
 * @version             0.3.1
if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
 * All DokuWiki plugins to extend the parser/rendering mechanism
 * need to inherit from this class
class syntax_plugin_templater extends DokuWiki_Syntax_Plugin {
     * return some info
    function getInfo(){
        return array(
            'author' => 'Jonathan Arkell (updated by Vincent de Lau)',
            'email'  => '',
            'date'   => '2009-03-21',
            'name'   => 'Templater Plugin',
            'desc'   => 'Displays a wiki page (or a section thereof) within another, with user selectable replacements',
            'url'    => '',
     * What kind of syntax are we?
    function getType(){
        return 'container';
        function getAllowedTypes(){
                return array('container','substition','protected','disabled','formatting');
     * Where to sort in?
    function getSort(){
        return 302;
     * Paragraph Type
    function getPType(){
        return 'block';
     * Connect pattern to lexer
    function connectTo($mode) {
     * Handle the match
    function handle($match, $state, $pos, &$handler){
        global $ID;
        $match = substr($match,11,-2);                          // strip markup
        $replacers = preg_split('/(?<!\\\\)\|/', $match);       // Get the replacers
        $wikipage = array_shift($replacers);
        $replacers = $this->_massageReplacers($replacers);
        $wikipage = preg_split('/\#/u',$wikipage,2);            // split hash from filename
        $parentpage = empty(self::$pagestack)? $ID: end(self::$pagestack);  // get correct namespace
        resolve_pageid(getNS($parentpage),$wikipage[0],$exists);        // resolve shortcuts
        // check for perrmission
        if (auth_quickaclcheck($wikipage[0]) < 1) return false;
        return array($wikipage[0], $replacers, cleanID($wikipage[1]));
    private static $pagestack = array(); // keep track of recursing template renderings
     * Create output
     * This is a refactoring candidate. Needs to be a little clearer.
    function render($mode, &$renderer, $data) {
        if($mode == 'xhtml'){
            if ($data[0] === false) {
                // False means no permissions
                $renderer->doc .= '<div class="template"> No permissions to view the template </div>';
                $renderer->info['cache'] = FALSE;
                return true;
            $file = wikiFN($data[0]);
            if (!@file_exists($file))
                $renderer->doc .= '<div class="templater">';
                $renderer->doc .= "Template {$data[0]} not found. ";
                $renderer->internalLink($data[0], '[Click here to create it]');
                $renderer->doc .= '</div>';
                $renderer->info['cache'] = FALSE;
                return true;
            } elseif (array_search($data[0], self::$pagestack) !== false) {
                $renderer->doc .= '<div class="templater">';
                $renderer->doc .= "Processing of template {$data[0]} stopped due to recursion. ";
                $renderer->internalLink($data[0], '[Click here to edit it]');
                $renderer->doc .= '</div>';
                return true;
            self::$pagestack[] = $data[0]; // push this onto the stack
            // Get the raw file, and parse it into its instructions. This could be cached... maybe.
            $rawFile = io_readfile($file);
            $rawFile = str_replace($data[1]['keys'], $data[1]['vals'], $rawFile);
            // replace unmatched substitutions with "" or use DEFAULT_STR from data arguments if exists.
            $left_overs = '/'.BEGIN_REPLACE_DELIMITER.'.*'.END_REPLACE_DELIMITER.'/';
            if($def_key){ $DEFAULT_STR = $data[1]['vals'][$def_key];}
            else $DEFAULT_STR = "";                        
            $rawFile = preg_replace($left_overs,$DEFAULT_STR,$rawFile); 
            $instr = p_get_instructions($rawFile);
            // filter section if given
            if ($data[2]) $instr = $this->_getSection($data[2],$instr);
            // correct relative internal links and media
            $instr = $this->_correctRelNS($instr, $data[0]);
            // render the instructructions on the fly
            $text = p_render('xhtml',$instr,$info);
            // remove toc, section edit buttons and category tags
            $patterns = array('!<div class="toc">.*?(</div>\n</div>)!s',
                              '#<!-- SECTION \[(\d*-\d*)\] -->#e',
                              '!<div class="category">.*?</div>!s');
            $replace  = array('','','');
            $text = preg_replace($patterns,$replace,$text);
            // prevent caching to ensure the included page is always fresh
            $renderer->info['cache'] = FALSE;
            // embed the included page
            $renderer->doc .= '<div class="templater">';
            $renderer->doc .= $text;
            $renderer->doc .= '</div>';
            array_pop(self::$pagestack); // pop off the stack when done
            return true;
        return false;
     * Get a section including its subsections
    function _getSection($title,$instructions){
        foreach ($instructions as $instruction){
            if ($instruction[0] == 'header'){
                // found the right header
                if (cleanID($instruction[1][0]) == $title){
                    $level = $instruction[1][1];
                    $i[] = $instruction;
                // next header of the same level -> exit
                } elseif ($instruction[1][1] == $level){
                    return $i;
            // add instructions from our section
            } elseif (isset($level)){
                $i[] = $instruction;
        return $i;
     * Corrects relative internal links and media
    function _correctRelNS($instr,$incl){
        global $ID;
        // check if included page is in same namespace
        $iNS = getNS($incl);
        if (getNS($ID) == $iNS) return $instr;
        // convert internal links and media from relative to absolute
        $n = count($instr);
        for($i = 0; $i < $n; $i++){
            if (substr($instr[$i][0], 0, 8) == 'internal'){
                // relative subnamespace
                if ($instr[$i][1][0]{0} == '.'){
                    $instr[$i][1][0] = $iNS.':'.substr($instr[$i][1][0], 1);
                // relative link
                } elseif (strpos($instr[$i][1][0],':') === false) {
                    $instr[$i][1][0] = $iNS.':'.$instr[$i][1][0];
        return $instr;
     * Handles the replacement array
    function _massageReplacers($replacers)
                $r = array();
                if (is_null($replacers)) {
                        $r['keys'] = null;
            		$r['vals'] = null;
                } else if (is_string($replacers)) {
                        list ($k, $v) = explode('=', $replacers, 2);
                        $r['keys'] = BEGIN_REPLACE_DELIMITER.trim($k).END_REPLACE_DELIMITER;
                        $r['vals'] = trim(str_replace('\|','|',$v));
                } else if (is_array($replacers)) {
                        foreach($replacers as $rep) {
                                list ($k, $v) = explode('=', $rep, 2);
                                $r['keys'][] = BEGIN_REPLACE_DELIMITER.trim($k).END_REPLACE_DELIMITER;
                                $r['vals'][] = trim(str_replace('\|','|',$v));
                } else {
                        // This is an assertion failure. We should NEVER get here.
                        //die("FATAL ERROR!  Unknown type passed to syntax_plugin_templater::massageReplaceMentArray() can't massage syntax_plugin_templater::\$replacers!  Type is:".gettype($r)." Value is:".$r);
                        $r['keys'] = null;
            		$r['vals'] = null;
                return $r;
//Setup VIM: ex: et ts=4 enc=utf-8 :


The included page is set into a <div> tag of class templater.

.templater {
    padding: 0.5em;


  • 2005-10-26 v0.2
    • Changed the substitution delimiters from {} to @
    • Perform substitution on parsing rather then on output
    • When the template does not exist show a link to create the template
  • 2005-10-25 v0.1
    • First Version

To Do

  • Headings generated by templater are ignored when determining page title and building TOC!
    • Take page title (first heading) generated by templater in account. Currently dokuwiki takes first heading not generated by templater…
    • Also heading generated by templater are not listed in TOC!
  • Better error handling in _massageReplacers
  • Make templated (generated) pages indexable using idx_addPage() for fulltext search and backlink lookup using ft_backlinks()
  • Template caching of parser instructions.
  • Make the code a little clearer
  • Use > in template arguments to point to a wikipage containing a dataset.
  • Default template namespace.
  • Escaping of vertical bar.



  • Plugin manager can't seem to find the plugin with the URL given. It may be a problem with the .tar extension and the gz compression.
  • To use a Template with multiple lines e.g.:

is only possible in this form:

|name=Jonnay| }}

Bug or feature? .)

Several instances of the same template in one wiki page doesn't work

It would be fine, to call with this plugin the same template_page several times inside a wiki page. I want to create a page with dynamically created images from rrdtool. For this I use a CGI-script with many options, which I'd like to pack in a template-page. My page looks like this:

====== Performance data Production Line ======
++++ Load average |
__**SAP DB & CI - Cluster**__

__**Dev system**__

and my template page “template_performance” (in the same directory):


On the resulting page the first instance of templater is correct. But all other result in “Template not found. [Click here to create it]”.

The usage of different template pages inside one wiki page works correct. — Uwe Kirbach 2006-05-09

Hello Jonathan,

please could you look into this problem, because I'm not a PHP programmer to solve it.
This feature is very important for me: :!: Several includes of the same template page inside one wiki page.Uwe Kirbach 2006-09-02


To fix this, you just need to comment the following, which is around the 100th line.

if (in_array($wikipage[0], $filechain)) return false;

It seems that the include template specifically avoided including the same page multiple times. This might have to do with a template including itself, or other cyclic inclusion, so watch out when you try to include a template inside another, or you might end with an infinite loop.

Hugo Peixoto 2007-02-24

Full solution

This is not an ideal solution, but the original code was poor with regards to this area. The proper solution is to create a stack and push/pop the page name whenever rendering starts/finishes. This allows multiple uses of a template on a page, as well as prevent recursive templates or template-cycles. I have provided a fix in the code above to reflect this.

Ximin Luo 2009-03-21

Use with inline folding plugin

This text copied wholesale from the include plugin. It should work the same.

Use the block type of the folded text plugin, if you want to have another page available but hidden until the user clicks the inline folding icon. The syntax would be such as:

++++folder link title| {{templater>page to include}} ++++

Wikitext inside of template values

Wikitext can now be used inside of template values.

Suggestions, Questions, Commentary

Just a question - how is this plugin actually different from the older included plugin? Is there any reason to have both installed?

It's not. It is built upon the older include plugin, but allows you to do text substitution with the included wikipage. Jonathan Arkell 2005-10-25 19:43

Very neat plugin!

A few comments.

  • Isn't it a bit extreme to “die” if there is a problem with your string substitution? Wiki pages are usually able to be edited by anyone - a graceful fail with or without a message might be nicer.
  • It would be a good idea to filter the replacement through $renderer→_xmlEntities() (htmlspecialchars()) as part of the assignment, OR,
  • Carry out the replacement before passing the included page to the parser, you would automatically allow wiki markup in your replacements and also you wouldn't need to worry about filtering out special characters, the parser would do that for you.

Christopher Smith 2005-10-25 10:42

Thanks for the comments. I'll implement those suggestions in the next version — Jonathan Arkell 2005-10-25 19:43

Great! This was a long standing feature request for the include plugin, and if my day had 28 hours I would have done it already. What do you think, should we re-combine these plugins? – I tend to say yes because the functionality of the include plugin now is a subset of your new plugin. A darcs repository for the include plugin would be a good idea as well, so others can contribute more easily.

On a side note: I would have preferred the @ as BEGIN_ and END_REPLACE_DELIMITER to reach a higher coherence with the replacement patterns for signatures and namespace_templates. — Esther Brunner 2005-10-25 11:11

I am totally willing to re-combine these plugins. This is really my first foray into hacking DokuWiki, so I am not sure what is involved in setting up a darcs repository. Also, I have no problem with having @ as the BEGIN/END_REPLACE_DELIMITER. Especially if it gives the overall codebase a higher coherence. — Jonathan Arkell 2005-10-25 19:46

Looking at the way variables are specified now makes me think they'd better be placed into some other file instead of being literally specified every time. Something like {{templater>WikiTemplatePage@WikiDataPage}}. This will let you conveniently use the same data with different templates.

And if you allow using macros in data page name - it will be even more powerful. :-) E.g. you could put user data (or whatever) as 'name=value' pairs to some data file and render it according to common template with {{templater>UserProfileTemplate@users:@USER@:profile}} call.

This will make it similar to command/template plugin but does not require writing PHP code for simple substitution templates. :-)Dmitry Cherniachenko 2005-10-25 13:18

This is a really interesting idea, but a bit beyond the scope of what I had in mind for this plugin. This was built so that you can use the same presentation to layout different data, but it sounds like you're talking about using different presentations to layout the same data. How do you feel about a syntax like {{templater>UserProfileTemplate|>namespace:page#section}}? We'd use the > to point to a page that contains the key=val pairs. The overall idea of this plugin is to move away from the server admin having to deal with textfiles on the machine, and move all the template administration to competent WikiGnomes. — Jonathan Arkell 2005-10-25 19:55

This is obviously a great plugin. But I need some functionality, so I write a little bit about it :-) First, it could be great if we could put the character | as a part of a parameter value.

{{template>templatePage|param=^RowName\|Text\|Other Text}}

In this example, I make a table in DokuWiki syntax, escaping the pipe character by \. Currently (see $replacers = explode('|', $match); line 87), it is impossible to put | as a value, but preg_split could do it greatly and easily ;-)

After this first thing, I was wondering if it is possible to create template with optional arguments. Or maybe just not display @param@ if param is not provided. I didn't think about it deeper, so I am not sure of what could be implemented or not.

Conclusion : good work, the major part is done :-) Just 2 or 3 lines and it would be perfect — Adrien CLERC

I'll make the vertical bars escapeable in the next version. As for empty parameters: {{template>templatePage|blankParam=}} works just fine.

This could be very useful? Unfortunately it failed on my first attempt to use it

{{template>TemplaterWhois|name=Nadim Khemir||image=nadim.png}}

NKhemir [at] zicorp [dot] com 5th of April 2006

Dynamic Templating?

Great plugin! Is there a way to have it handle simple logic within the template itself? (Major Edit - I know know how to ask the question) - is there any way to include parser functions in a template?

Multiple copy of the same page?

Hello, since this version of templater is built upon an earlier version of include, it seems that the plugin won't allow more than one copy of the same page to be loaded, i.e. it's impossible to put a few templates in a page, make them sections and to include them all in the same page, since the plugin shows that it can't find templates from other sections. Can someone merge this plugin to be based on newer include plugin? — Roberto Ciang 2006-12-15 07:05

If a page can only be include once, then it could be a really big limitation of this plugin, because people might need to insert the same message more than once in the page. — Roberto Ciang 2006-12-15 07:17

Templater keeping unneeded whitespace/newlines in macros

I found a problem with the plugin.- When using a templater call like this


the templater plugin will also save the trailing newlines into field1, field2 and field3. This leads to problems when trying to insert these variables into an table. E.g. the following code:

| What: | @field1@ |
| When: | @field2@ |

is transformed into

| What: | foo
| When: | bar

which of course is not rendered correctly.

Fix: In the function _massageReplacers (i wonder if the function shouldn't be named messageReplacers?) change the two lines to $r['vals'] = $v; with $r['vals'] = trim($v);

Data entry and handling suggestions.

I very much needed this plugin, thanks. I think the data entry needs to be handled much easier than it currently is. Maybe something similar to the template plugin datafiles which uses a normal DokuWiki list of name:value pairs and is much easier to write. The template plugin allows the data to be culled from a data page or inline in the arguments. Also the template plugin data files support multiple records in one file, if would be very nice to see a module mode where it can loop the data set through the template for each one or for a specific field match.Oh that would also allow for entry of more markup that cant be normally added in {} –ShawnA

Feature Enhancement - Optional Substitution

If there is a space on the variable side of the = it fails. <code>key =value</code> a trim might make that doable. Also if you fail to declare or choose not to a enter a variable then the template still shows the @variable@ string. It would be nice if it was removed or used an a optional default var eg. “N/A”,“ - ”.

Nevermind, I am almost done with some modifications for these, will post solutions if anyone else find it of use. – ShawnA

Any undefined replacement values normally show up as @variable@ on the page if you failed to define them. This allows replacements to be optional now as it will allow you to replace all unspecified values with a default “”. You may also modify this behavior by defining a value for DEFAULT_STR in your data def. eg.


Any undefined variables will now show N/A for that template render.

file: /lib/plugins/templater/syntax.php function: render()

            // Get the raw file, and parse it into its instructions. This could be cached... maybe.
            $rawFile = io_readfile($file);
            $rawFile = str_replace($data[1]['keys'], $data[1]['vals'], $rawFile);                              
+			// ShawnA modify - replace unmatched substitutions with "" or use DEFAULT_STR from data arguments if exists.
+                       $left_overs = '/'.BEGIN_REPLACE_DELIMITER.'.*'.END_REPLACE_DELIMITER.'/';
+			$def_key=array_search(BEGIN_REPLACE_DELIMITER."DEFAULT_STR".END_REPLACE_DELIMITER,$data[1]['keys']);
+			if($def_key){ $DEFAULT_STR = $data[1]['vals'][$def_key];}
+			else $DEFAULT_STR = "";                        
+			$rawFile = preg_replace($left_overs,$DEFAULT_STR,$rawFile);	
+                      // end modify
            $instr = p_get_instructions($rawFile);


this code will produce a warning if $data[1]['keys'] is not defined. (JPta)

[FEATURE REQUEST] Make generated content indexable using idx_addPage()

Please make generated content of templated pages indexable using idx_addPage() for fulltext search and backlink lookup using ft_backlinks(). I think it would be very usefull!

Feature Enhancement - More forgiving syntax

:!: I failed to consider spaces are used in formatting in table alignment and such. So this is probably not a good idea unless you add a trim to the $v as well.

Variable key:value pairs can be assigned as such key=var or key= var.

Problem: Using key = var will fail. I prefer my operators and assignments be spaced for easier readability and this makes it forgivable if someone does so.

In the messageReplacers function add a trim() around the $k variable in the 2 locations like below.

file: /lib/plugins/templater/syntax.php function: messageReplacers()



The messageReplacers function is misspelled in the PHP code, so to perform the change listed above, search for massageReplacers. — Carl M

It would be nice to be able to include pictures in the page using a template. This seems to be a problem as they use {{<link>}}. A possible way to fix this would be using some kind of replacement for this (e.g. <pic>...</pic> or whatsoever). At the moment, my 'workaround' is using {{@picname@}} in the template, however the little “picture-button” adds a link with {{ and }}, so you have to modify it yourself.

Roll-up of Feature Enhancements

I've taken the liberty to update the piece of code above and integrate all the above suggestions (escaping | as \|, trimming keys and variables, allowing multiple instances of a template and default values (default default = '')). This is more because I needed these patches and I want to contribute back, then to appoint myself as maintainer. I also didn't make any effort to compare or integrate this with include. — Vincent de Lau 2008/04/16 14:45

Further Bugs / Feature Requests

Need escaping for = character. Fixed by adding the limit parameter (which is set to 2) to explode() calls. Another problem is, it's not possible to include image tags for values, since both image and template tags end with }}. (salviati 20080620)

With Adore Belle, an error, but it works

I am using this plugin with/ Adorde Belle. Everything works perfectly fine, as far as I'm using it, but I get this error “Warning: array_search() expects parameter 2 to be array, null given in /Users/Josh/Sites/dokuwiki/lib/plugins/templater/syntax.php on line 144” I set display_errors = Off in php.ini so I wouldn't have to see the error. Which is good for going live anyway. No harm no foul as far as I'm concerned.

Use sections of a document without headings

I added/changed a few lines in the render() function to filter out the headings and enclosing <div> so that I can have one :wiki:templates document with many different templates which are accessible by the {{templater>:wiki:templates#specifictemplate}} and doesn't show the heading for each template. — Markus Birth 2008-10-05 01:36:26

            // remove toc, section edit buttons and category tags
            $patterns = array('!<div class="toc">.*?(</div>\n</div>)!s',
                              '#<!-- SECTION \[(\d*-\d*)\] -->#e',
                              '!<div class="category">.*?</div>!s',
                              '!<div class="level[1-6]">!s');
            $replace  = array('','','','','');
            $text = preg_replace($patterns,$replace,$text);
            // strip last </div> (matches the <div class="level[1-6]"> removed in the previous command)
            for ($i=strlen($text)-6;$i>0;$i--) {
                if (substr($text, $i, 6) == '</div>') {
                    $text = substr($text, 0, $i);

Use template inline?

Hi. Great job with this plugin. One question:

Is there a way to use this plugin so that the included element can be placed inline? That is, not generating a new paragraph for every template. I'm asking because I'm trying to use this to templatize contents inside a list, with no good effect. Even though the syntax is correct and neither DokuWiki or validators do complain, the effect of using {{templater>something}} inside a list is that the included element is placed in a new paragraph, resulting in unwanted line breaks and empty paragraphs. The same happens if the plugin is used inside a normal paragraph.

I already tried using a CSS to force the div element as display: inline, as well as changing the <div> to a <span>, but the DokuWiki software responds by inserting unwanted paragraph terminators before the plugin output, something like:

...some text
<div class="templater">...
some more text

That would be my request: inline usage for templates. Thanks. — 'Ryan Chappelle' 2008/12/01 05:37

Problem with ODT plugin

Hello fine plugin, but, it appears to have a problem to export the page with ODT plugin. Page exported are empty.

can see here

'Bruno Teuile' 2010/04/14 12:30

Dokucms template breaks #section is included

If I don't include the section, it seems to work fine. But when I include the section:

1) I have my “Edit” buttons near the top of my sections, this plugin pushes those bottons to the bottom of the section.

2) If I use the top section, it will include all the sub-sections below that.

How do I use sections of a document without headings?

I suppose I need to edit this “render()” function. Where is that function?

Fix for array_search() warning

PHP generates a warning if a template page is used without any string substitutions. To suppress this, change the line:

$def_key = array_search(BEGIN_REPLACE_DELIMITER."DEFAULT_STR".END_REPLACE_DELIMITER, $data[1]['keys']);


if(!empty($data[1]['keys'])) $def_key = array_search(BEGIN_REPLACE_DELIMITER."DEFAULT_STR".END_REPLACE_DELIMITER, $data[1]['keys']);

Jack126Guy 2014-06-07 23:18 (UTC)

plugin/templater.txt · Last modified: 2017-02-28 03:04 by