Content last modified Monday 6 March 2017
hop to #bottom
Current version:
About It:
Get it:
Rate it:
  • Helpful?
  • 4 Yes
  • 0 No
Flag it:

If you'd like to provide updated information and do not have access to directly edit, please contact the site admin; thanks!



For latest release info, see History, below.



Contains one tag, soo_toc, which will produce a linked and nested table of contents for an article, based on the article’s headings (h1, h2, etc. tags).


<txp:soo_toc />


The first five are standard Textpattern attributes; only the last two (level and full_url) are plugin-specific.

  • label="text"
    Label to precede the contents list. Optional; default is unset.
  • labeltag="tag name"
    (X)HTML element (without brackets) for the label. Optional; default is unset.
  • break="text"
    (X)HTML element (without brackets) or text for separating list items. Optional; default is li. If you change this, you should also change the wraptag.
  • wraptag="tag name"
    (X)HTML element for enclosing the list. Optional; default is ul. If you change this to something other than ol, you should also change the break.
  • class="class name"
    (X)HTML class attribute to be applied to wraptag. Optional; default is toc.
  • level="value"
    Number in the range 1–6, indicating the highest heading level to include in the table of contents (see Level, below). Required; default is 6 (include all headings).
  • full_url="boolean"
    If true, use absolute instead of relative URLs. Optional (but see Relative vs. Absolute URLs, below); default is unset (false).



Can be used in an article body, in a page, or in a form. If used with relative URLs (the default setting) it requires an individual article context. If used in an article list, or on a page in which the base element indicates anything other than current page, the full_url attribute must be set to true (or anything other than 0). (See Relative vs. Absolute URLs, below.)

The article headings

Only headings that have an id attribute will be included. This is to give the links in the table of contents something to point to. In Textile, you can apply the id attribute like this:

h3(#example). Example

to produce:

<h3 id="example">Example</h3>


The level attribute allows you to restrict which heading levels to include in the table of contents. For example, level="3" will restrict output to h1, h2, and h3 headings. The default value is 6, show all headings.

You might use this to have both a full and a concise table of contents, one in the sidebar and the other in the article body. Or combine with a bit of javascript to allow the user to expand or collapse the table of contents.

Relative vs. absolute URLs

The default output uses same-page relative URLs (e.g. href="#id_name"), which will only work correctly in an individual article context. To get absolute (full) URLs instead, set the full_url attribute to true (or any value other than 0).

If you use a base tag to point to the site root (or anything other than the current page), you will also need to use full URLs. (N. B., such usage of base is, strictly speaking, incorrect.)

Nested output

If the wraptag is ul (the default value) or ol, and if the article has different levels of headings, the table of contents will be a nested list. For example, given the following article headings (shown in Textile):

h2(#foo). Foo
h3(#bar). Bar

the plugin will produce, using default attributes:

<ul class="toc">
	<li><a href="#foo">Foo</a>
		<li><a href="#bar">Bar</a></li>

For this nested output to make sense, the article’s heading structure must also make sense. In particular:

  • do not jump upwards more than one heading level at a time (e.g., go directly from h2 to h4)
  • the first heading should be at the lowest heading level (e.g., if the first heading in the article body is h2, there should not be any h1s in the article body)

Because the plugin ignores headings with no id attribute, you could get unexpected results (and possibly XHTML validation errors) if you are inconsistent about assigning id to headings.



The plugin runs a preg_match_all search on the article’s HTML body, and another preg_match on each heading. Theoretically this could be a performance concern in some situations, particularly in an article list context.


This plugin is very similar to the cbs_article_index plugin (didn’t realize this when I wrote it), but with more options for output and control.

Version 0.1, 2008.1.26

  • Automatically generate a linked table of contents for an article
  • Nested output for (X)HTML lists
  • Option to filter results with the level attribute.
  • Option to use full URLs

Version 0.1.1, 2008.1.29

  • Fixed bug re headings containing HTML tags

Version 0.1.2, 2008.2.03

  • Fixed bug (design flaw, actually) re nested list output — thanks to Dejan for spotting this

Version 0.1.3, 2008.2.03

  • Code cleaning, plus valid XHTML output even for articles with unsemantic heading structure
Article Request Count:
Initially released:
Jan 28, 2008
Posted here:
28 Jan 2008
Article modified:
29 Sep 2009

If there is a comment form at the Information URL, you may want to leave your comments/questions there or at the Forum thread for quicker feedback. Otherwise, comment away:

Your comment will NOT be submitted until you click the 'Submit' button on the next pageload.

Commented (2)

Hey! I like the plugin so far, but I have trouble with nested lists.

If there is an, e.g. h1 followed by several h2’s, it should look like this:

<li>Heading level 1
<ul><li>Heading level 2</li>
<li>Heading level 2</li>

So, H2’s should be in the same <li></li> as Heading level 1, but with the title of H1 included.

Currently your plugin gives something like:

<li>Heading level 1</li>
<li><ul><li>Heading level 2</li>
<li>Heading level 2</li>

I.e. it creates a brand new <li></li> for the nested list. This is a problem because numbering or bullets are showed incorrectly. Here is a screenshot of what I get:

Thanks Dejan: this has been fixed in v0.1.2.

Subscribe to this article's comments RSS feed. [ ? ]   View Recent Comments across the site.

You know you want to visit the Archives.




There are also tag clouds, 'cause those are fun.
Published with Textpattern