Get markdown headline from $page->text() to genereate list of content

I wonder if it’s possible to have some kind of automatic headline list from my markdown text…

for example what i am trying to accomplish is, when writing a very long article with multiple h2, h3, h4 markdown tags to have an automated way to list some sort of index based on headlines so a viewer can jump right into the topic of choice.

so basically the script is reading the $page->text() field and identifies each




tag and generates a list with id hopping links towards each headline.

can anyone pinpoint in a direction ?

Yes, that is possible. The Kirby site uses this to display a table of contents above the docs pages.
The code that does this is the toc plugin. Every occurrence of (toc) inside your Kirbytext content will then be replaced dynamically. You can extend the plugin to work with h3 and h4.


i tried that. looks like what i am searching for. can this also work in form of implementing it somewhere within my template rather than using kirbytext (toc)?

The Kirbytext post filter needs access to the Kirbytext content. What you can do is to prepend the table of contents instead of replacing the placeholder inside the filter. Then you wouldn’t need the placeholder in the content.

If the table of contents needs to be somewhere else on the page, you can modify the function to be not a filter but a normal function and pass the rendered Kirbytext content as an argument manually.

got it into my template, just how to add the correct “id” tag to the each h2 headline so it’ll actually jump to each #link

That’s a good question that came to my mind too. I couldn’t figure it out although does have the id-attributes. They are not within the textfile, anyone an idea how they are generated? @sashtown?

1 Like

You can achieve that with a few lines of JS. :slight_smile:

can’t get the js to worK :frowning:

Works perfectly for me, thank you @sashtown. @carstengrimm: Did you change the jquery selectors to match your html code? My headings are in .main-content not in .text so I changed the code. I added German Umlauts too:

$('.main-content h2, 
   .main-content h3, 
   .main-content h4, 
   .main-content h5, 
   .main-content h6').each(function() {
          var anchor = $(this).text()
                                   .replace(/[\-\[\]\/\{\}\(\)\*\+\?\!\.\,\:\=\\\^\$\'\&\%\|]/g, '')
                                   .replace(/\u00c4/g, "Ae")
                                   .replace(/\u00d6/g, "Oe")
                                   .replace(/\u00dc/g, "Ue")
                                   .replace(/\u00e4/g, "ae")
                                   .replace(/\u00f6/g, "oe")
                                   .replace(/\u00fc/g, "ue")
                                   .replace(/\u00df/g, "ss");

If you wanted, you could also add the IDs directly in the Kirbytext post filter. That would require a bit more of string parsing though.

Template File:

<?php echo $page->text()->kt() ?>

End of Footer before Body Tag:


No ID tags showing… and no jumping.

Do you have a live version of your example where we can check the running code?

still can’t get it to work :frowning:

I can look into your code, if you want to, but you would have to send it completely or give me a link to a live version on your server.

i sent you via pm the other day.

Thanks for this. How can I use this plugin when using the kirby starterkit 2.4.1?

Copy the code and save it in /site/plugins/toc/toc.php.

Use in a field, that you output using the kirbytext() method, usually the main textarea field.

Thanks a lot. Sorry for asking again but: what do you mean with “field”, the .txt containing the headlines I want to generate the TOC of?

Maybe an example works best:


And well, the field in the text file, that contains the headlines, in this case the text field. A .txt file usually contains several fields, like the title, a text field, maybe a date field etc.

1 Like

Thx, unfortunately providing


like in just displays “(toc)” on my page (followed by the text content).

EDIT: sorry, works now. Forgot to provide the toc.php again. :grimacing: