Recent Changes - Search:




Summary: use categories as tags
Download: ChangeLog - WebSvn
Prerequisites: PmWiki 2.x


Version: $Rev: 51 $
License: GPL
Maintainer: sts
Categories: Links CMS Blog PIM

Questions answered by this recipe

  • How do I use PmWiki's builtin categories like tags?
  • How do I list all referenced categories?
  • How do I make the HTML meta keywords tag to include all the tags of a wiki page?


PmWiki supports the [[!Category]] syntax to sort pages into categories. As Category.CategoryName pages do not need to exist (and in fact normally don't), pagelist cannot be used to list all referenced categories. Moreover pagelists have no way to list linked pages at the moment. So this recipe implements a simple custom markup to add this feature: (:listcategories simple (Main|Blog|Pics)\..*:).

To speed up tag listing the .pageindex file is used (which is created by pagelist.php anyway, so the speed penalty is nearly non-existant (in contrast to the tags recipe, which iterates over all pages all the time).

This recipe also alters the format of category links in the HTML output into something like <a class='categorylink' rel='tag' href='\$LinkUrl'>\$LinkText</a>. So Technorati and other sites will recognize PmWiki's categories as tags.



Just put

 (:listcategories simple (Main|Blog|Pics)\..*:) 

into your sidebar, footer or whereever. The regular expression tells the script which pages should be considered to look for categories. E.g. in the PmWiki documentation some categories are used which should be excluded in my setup.

The keyword "simple" is a key for the $ListCategories_MarkupFunctions array which is a list of possible markup functions. "simple" will give a simple space separated list, "sized" gives you the layout you see in the picture above with different font sizes. If you want another layout, just do something like that in your local config.php after including the recipe:

 $ListCategories_MarkupFunctions["yourlayout"] = YourLayoutFunction;
 function YourLayoutFunction( $categories ) {
   $out = "";
   $space = "";
   foreach( $categories as $pn => $count ) {
     $out .= $space . "[[Category.$pn|$pn]]";
     $space = " ";
   return PRR($out);

Then you can write in your wiki pages: (:listcategories yourlayout {$FullName}:).

You can also list the categories of a page by putting the following into the GroupFooter page:

 Tags: (:listcategories simple {$FullName}:)

There are the following further variables:

 SDV($ListCategories_CreatePages, true); // automatically create Category.Bla pages?                                                
 SDV($ListCategories_IncludeCategories, "/.*/");
 SDV($ListCategories_ExcludeCategories, "/^Blog$/");
 SDV($ListCategories_SizedlistNum, 30 ); // average number of item in the SizedList format
 SDV($ListCategories_SizedlistMinFontSize, 8 ); // minimal font size
 SDV($ListCategories_SizedlistMaxFontSize, 26 ); // maximal font size
 SDV($ListCategories_SortSizedList, false ); // sort tags in the tag cloud alphabetically?
 SDV($ListCategories_SortSimpleList, false ); // sort tags in the simple list alphabetically?  

The first one makes the script to create the references category pages. That's useful to avoid those ugly "?" at links to non-existant pages. The next two are regular expressions. Look here for an explanation. Basically ^ matches the beginning, $ the end of a line. The dot . matches any character, the * means that the previous character is repeated as often you want (also zero times). With the | you can define alternatives, e.g.

 SDV($ListCategories_IncludeCategories, "/^(Blog|Main)$/");

to match only those two categories.

It is possible that PmWiki's pageindex (used for caching page meta data) is incomplete. PmWiki updates it on every edit, but still it can be incomplete in certain circumstances. This recipe adds the action "pageindex" to create a complete new index (might take several seconds). Just go to your site and append "?action=pageindex" to the url. Do that everytime your category list is incomplete.


  • This recipe will not work if you disable the page index via $EnablePageIndex=0.

Release Notes

  • 2007-02-05 Added minimal and maximal font size for sized list
  • 2006-11-26 [[!foo|foo]] doesn't exist. Only [[!foo]]...
  • 2006-11-25 Added options to sort the category list alphetically
  • 2006-11-25 include rel='tag' as described in
  • 2006-11-13 $RecipeInfo line
  • 2006-10-15 $ListCategories_SizedlistNum variable. See above.
  • 2006-10-13 added "include" style to include all category pages. This is useful if your category pages only include (:keyword ...:) tags. So your tagged pages will get proper html meta tags for keywords, useful for search engines... Just include (:listcateories include {$FullName}:) into your PageFooter page and fill the categories pages with (:keywords Your,Own,Keywords:) lines. The auto-create feature of this recipe sets the pagename already as the first keyword. (To make use of this for an old installation of this recipe, delete your category pages. The recipe will recreate them. Then update your index with the ?action=pageindex action).

If the recipe has multiple releases, then release notes can be placed here. Note that it's often easier for people to work with "release dates" instead of "version numbers".


  • Nice recipe. Maybe this will work itself out with PmWiki 2.2, but I have a couple of comments. First of all, many users and even many administrators are unfamiliar with regular expressions; I wonder if you could modify $ListCategories_Include and ExcludeCategories as well as the "where" part of the markup such that somebody could just type the name of a category or separate categories with commas and it would work. Or, what would happen if you just wanted to get all categories? Maybe you could create a default to show all categories? - JonHaupt
    • Don't see the sense of inventing another syntax for that instead of regular expressions. It's not that complicated in my eyes. If there is another syntax you will certainly step into a case where you cannot express what you want. sts
      • Okay, fair enough; then perhaps a very short explanation of how to use regex to use this recipe? If you don't know how to use regex, then you won't know what some of this means and it will all seem very complex.
        • I tried guessing the reg expressions and getting them wrong resulted in buggy behaviour - php error messages - for example (:listcategories simple (*)\..):)
        • This recipe turned out to be very bad medicine on my wiki for reason of the unclear use of regex. I tried the example only editing the regex to something very simple (I want to list all of the available categories in a site rightbar), and managed to break my entire install. I fixed most functions by commenting out the recipe in the config.php file, but I now can no longer edit. It seems that a lock of some kind was left on some file or other, and no pages can be edited. I'd appreciate help if anyone has suggestions. John Paolillo.
  • It would be nice to have the option to sort the list in alphabetical order, both for the simple and sized modes. StephanPitois
    • Done. Update to the latest version an look at the SortSizeList and SortSimpleList variables. 2006-11-25 Sts
      • Thanks! I installed it and it's working beautifully! StephanPitois
  • Thanks for the great work. I wrote a custom function to emulate the 'simplename' format in Site.PageListTemplates. I also excluded a couple categories from within the function since setting ExcludeCategories didn't work (I'm assuming because they're 'real' pages). Here is the whole relevant section from my local/config.php file

    // Set this to true once everything is filtered right
    SDV($ListCategories_CreatePages, false);
    //didn't seem to exclude these pages doing it in the function
    SDV($ListCategories_ExcludeCategories, "/^(GroupFooter|RecentChanges)$/");
    $ListCategories_MarkupFunctions["simplename"] = ListCategories_simplename;
    function ListCategories_simplename( $categories ) {
            $out = "";
            ksort ($categories);
            foreach( $categories as $pn => $count ) {
                    if ($pn != 'GroupFooter' && $pn != 'RecentChanges') {
                            $out .= "\n* [[!$pn]]";
            return PRR($out);

    You can remove the if statement and it would work exactly as if you called simplename with pagelist. On my Category/HomePage I have the following:

    (:listcategories simplename (?!(PmWiki|Site)).*\..*:)

    It took me quite a while to find a way to negate the regexp. The (?! says to match things that do NOT contain the following. The (PmWiki|Site)) is just the standard this-or-that. If I just didn't want to match PmWiki I could have set it to (?!PmWiki). --Vrillusions February 04, 2007, at 01:53 AM
  • Another solution to the page links for non existent pages problem is to set $LinkPageCreateFmt = $LinkPageExistsFmt; in a local/Category.php file - This way, you can have SDV($ListCategories_CreatePages, false); but have no problems with the non-existent page links. See Also Test.NoEditLinks Francis
  • For example this file: ListCategoriesBullet.phpΔ incorporates a markup function "bullet" which creates a bulleted list. Francis
    • Why do you include that into a modified recipe file and not externally? That what the whole reason for the ListCategories_MarkupFunctions array? Sts
      • Because I wasn't aware of that array or how to use it - I just put the file up for anyone else (like me) who struggles with coding customisations and wanted another listing option without having to do the coding themselves. Would you just include everything I added in an external local php file? What does its name have to be? Francis
      • Exactly, just put your code after the include of the recipe. Look at the yourlayout example above. I hope it's a bit more clear now. Added some more explanation. Sts
  • Grr ... I can't get rid of a category from my tag cloud! I looked for all the places the tag might be listed, and can't find any. Why won't it go away? --David Bessler July 27, 2007, at 11:08 AM
    • You removed the category from your server and called a page with ?action=pageindex appended? Sts
      • Yep. Tried that. I think it has to do with the fact that the categories which won't go away, are portions of existing categories ... for example, I have one real category called "Restaurants" and a category which won't go away called "Rants". another similar pair are "CheesePages" and "Cheese" ... I can't get rid of the catgory "Cheese." even if it's deleted from the server and there are no links to Category.Cheese anywhere that I can find on the site. even if I run ?action=pageindex. My site is ... go see for yourself.
      • I think I figured it out. It was seeing links to the category within the Category Clouds themselves so I went through and deleted all the category clouds I had made, then ran ?action=pageindex, then put up the category cliuds again, and it got rid of them. Finally.
  • Hi sts, is there a way to exclude Category.HomePage from the category-list. I use this page for listing all categories, thanks ben Ben November 30, 2007, at 08:25 AM
  • There is a variable for the category group $CategoryGroup. Could you change the hardcoded "Category" to $CategoryGroup?
  • The download is an offsite link, blocked by corporate proxy. Would you please upload the recipe script to and amend the link? Thanks in advance. -- bwills
  • Good recipe. Especially after enabling it for my foreing german categories containing Umlauts. I changed function ListCategories() to make it use the global $NamePattern when preg-matching category pages:

    // line 135: added $NamePattern
    global $PageIndexFile, $NamePattern, $ListCategories_MarkupFunctions, $ListCategories_CreatePages,
    // commented out line 152 
    # $count = preg_match_all( "/Category\\.([A-Za-z0-9-]+) /", $line, $matches );
    // replaced it with
    $count = preg_match_all( "/Category\\.($NamePattern) /", $line, $matches );
    • Quite a good work-around. Could that be included in the standard distribution? Probably won't disturb anyone in the only-English-speaking part of the world, but it would be of much use for all others!
  • Posting a page that contains the (:listcategories:) markup adds all the listed categories to the page's targets field. Of course only the categories that are defined at the moment you post that page.

    When posting a page PmWiki calls (among others) the function SaveAttributes() which calls MarkupToHTML() to extract the page's $LinkTargets.
    The (:listcategories:) markup is evaluated and all the categroies it lists are added to $LinkTargets.

    I was wandering how (:pagelist:) manages that problem and found the
    Add the following code to your config.php or better bugfix listcategories.php:

    SDV($SaveAttrPatterns['/\\(:listcategories\\s+([A-Za-z0-9]+)\\s+([^:]*):\\)/'], ' ');

    SaveAttributes() applies the $SaveAttrPatterns before it extracts the links and the above code replaces the listcategories markup with a blank.

    Tontyna February 13, 2009, at 05:08 PM
  • While I've been using this recipe for years, right now this recipe is driving me nuts trying to delete categories. I did what was suggested above, it didn't work so I figured it was reading categories from -Drafts pages. I deleted them. The only thing left is that I'd have to delete the Site.AllRecentChanges page. Really, it shouldn't be reading that page! It makes this recipe hostile to deleting categories! I'll have to figure out a way to stop it from doing that. XES February 09, 2014, at 11:37 AM
I figured it out finally. I had to actually delete the pages (my sidebar & tag cloud pages) on which the cloud appeared -- not just delete the markup from them. There really should be a better way to do this! You don't want authors running around deleting major features of your website in order to clean up a typo'd link or to consolidate Category.Meeting and Category.Meetings. It might help if it didn't link in *-Drafts pages, since I usually tag my pages before publishing and this way I can catch category typos before going live. XES February 09, 2014, at 08:40 PM
  • Replace the markup line with this:
    Markup_e('(:listcategories format where:)','directives',"/\\(:listcategories\\s+([A-Za-z0-9]+)\\s+([^:]*):\\)/","ListCategories(\$m[1],\$m[2])");
    to remove the preg_replace error messages.

See Also


User notes? : If you use, used or reviewed this recipe, you can add your name. These statistics appear in the Cookbook listings and will help newcomers browsing through the wiki.

Edit - History - Print - Recent Changes - Search
Page last modified on August 29, 2014, at 02:07 PM