BookStack Release v26.03

The March 2026 BookStack release arrives today, with a focus on powering-up some of the core customization & extension options to allow new possibilities and easier usage.

Upgrade Notices

  • Security Releases - There have been a number of security releases since v25.12. These can be found listed below, or on our updates page.
  • Email/SMTP - The way BookStack sends messages has changed slightly (Specifically, the SMTP HELO domain). This isn’t expected to be a breaking change but testing of emails (Using the test send action in Settings > Maintenance) is advised after updating to be sure there’s no impact.
  • Theme System - Within a theme directory, the modules/ folder is now dedicated to theme modules. If you happened to already have a folder of this name in your theme, it’s advised to use a different folder name instead.

Since v25.12.4:

  • Page Content - As of v25.12.4, extra layers of filtering have been applied to page content. While we have tried to ensure this has minimal impact on content, it’s possible this will lead to extra elements being filtered. See the “Page Content Filtering” section in our security docs for options to control filtering.
  • Option Change - As of v25.12.4, the ALLOW_CONTENT_SCRIPTS env option is now considered deprecated. It’s advised to use the APP_CONTENT_FILTERING option, as documented here, instead if needed.

Theme Module System

For significant customisations in BookStack we’ve had the theme system available for some time which, while developed over time, has always been focused on a single set of customizations. This has made it tricky to install multiple tweaks alongside each other, as you’d need to merge files.

In this release, we introduce theme modules. These use the same fundamental abilities as the logical/visual theme system while also being able to co-exist within an active theme, allowing much simpler modular management. This could be seen as a more traditional “plugin” or “extension” type approach.

Compared to the existing theme system, modules have the following additions & differences:

  • A bookstack-module.json file must exist in the module to provide some metadata.
  • View template files are placed into into their own views/ directory, instead of making a mess at the root level.
  • A head/ directory will be read in modules. The contents of any HTML files found in this folder will be inserted into the <head> of all standard BookStack views.

To make the distribution & use of customisations easier, we’ve also introduced a new command which can install modules from a ZIP:

1
2
3
4
5
# Example: Install from a local file
php artisan bookstack:install-module /home/barry/module.zip

# Example: Install from a URL
php artisan bookstack:install-module https://example.com/module.zip

As seen above, modules can be installed using the command from a local file, or from a URL. For this release, our hacks site has been updated so each can be installed as a module. Upon install, if an existing module of the same name is found, BookStack will offer options to replace that module or install alongside it.

In the future we may look to expand the use and capabilities of modules. This could include allowing viewing or managing via the UI, or defining an update mechanism. For now though I want to ensure the fundamental approach here works before expanding the scope further.

Full documentation on modules can be found in the dev/docs/ folder of the BookStack codebase.

New Logical Theme Events

In the theme of expanding our customization systems, the logical theme system has received a few new events in this release:

THEME_REGISTER_VIEWS

When listening to the THEME_REGISTER_VIEWS event, BookStack will provide a new ThemeViews class instance. This class provides methods for registration of custom views which can be rendered before or after existing views.

This allows a much more flexible way to insert custom content into pages, since it’s not overriding any existing views, but instead only adding content. This can help avoid conflicts with base templates, and with other customizations, making it ideal for use via the new module system.

As an example, the following functions.php content would register a custom welcome.blade.php view to render after the main header bar:

1
2
3
4
5
6
7
8
9
<?php

use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
use BookStack\Theming\ThemeViews;

Theme::listen(ThemeEvents::THEME_REGISTER_VIEWS, function (ThemeViews $themeViews) {
    $themeViews->renderAfter('layouts.parts.header', 'welcome', 10);
});

There’s also a renderBefore function in the ThemeViews class for inserting views before others. The third parameter to these functions is a priority, which defaults to 50 if not passed. The priority dictates the ordering when multiple views are inserted before/after a single other view.

More comprehensive guidance on this system can be found in the logical theme system documentation here.

PAGE_CONTENT_PRE_STORE and PAGE_CONTENT_POST_RENDER

These two new events allow access and modification possibilities for page content before it’s stored, and after it’s rendered (typically before display to an end-user). This provides some potentially powerful possibilities, like being able to define custom clean-up logic, or even implement new types of dynamic content.

These events are given the HTML page content being handled, as well as the relevant Page instance. If a string is returned from the event listener, that will be used as the content being stored/rendered instead. Here are some examples of using these events:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

use BookStack\Entities\Models\Page;
use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;

// When content is stored to the database, Change all 'dog' mentions in page content to 'cat'
Theme::listen(ThemeEvents::PAGE_CONTENT_PRE_STORE, function (string $html, Page $page) {
    return str_replace('dog', 'cat', $html);
});


// When content is rendered & shown, replace '[date]' instances with the current date
Theme::listen(ThemeEvents::PAGE_CONTENT_POST_RENDER, function (string $html, Page $page) {
    if (str_contains($html, '[date]')) {
        $date = date('Y-m-d');
        return str_replace('[date]', $date, $html);
    }
    // A null return will mean the original content is used
    return null;
});
OIDC_AUTH_PRE_REDIRECT

This is called during the OIDC login process just before the auth redirect, to the OIDC identity provider, takes place. Event listeners are passed the intended auth redirect URL. If the listener returns a string, that will be used as a redirect URL instead.

This allows a lot of customization & control over the URL used for OIDC auth. As an example of this in use:

1
2
3
4
5
6
7
8
9
<?php

use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;

// Add a 'prompt=select_account' query parameter to OIDC auth redirects
Theme::listen(ThemeEvents::OIDC_AUTH_PRE_REDIRECT, function (string $redirectUrl) {
    return $redirectUrl . '&prompt=select_account';
});

These kinds of customizations are useful when you need to use an awkward authentication system which may not strictly adhere to the OIDC specification, or if you need to use options beyond those which BookStack supports.

Updated Content Filtering

BookStack has long had page content filtering, to filter out potentially dangerous content. Unfortunately, filtering out leads to a wack-a-mole game of addressing new potential concerns as they become discovered. These concerns have had increasing importance as BookStack’s own use-cases have grown, requiring more consideration to different security scenarios.

In BookStack v25.12.4, while addressing a security report, we introduced an extra layer of page content filtering using HTML Purifier, which works more in an “allow-list” style manner, to filter in safe elements rather than filter out the bad. While I spent time to test and consider the impact, this did end up causing a little more trouble than hoped, leading to a range of follow-up patch releases. In hindsight, it may have been better to defer that for a feature release, where a larger compatibility impact would be more expected relative to a patch release.

In v25.12.4 we did also introduce a new level of control when it comes to content filtering via a new APP_CONTENT_FILTERING option, which allows toggling various filtering controls on/off. Full details of this option can be found in our security guidance here. The ALLOW_CONTENT_SCRIPTS env option is now considered deprecated, with APP_CONTENT_FILTERING taking its place.

REST API: Parent Shelves on Book Read

When you read a specific Book using the REST API, responses will now also include a list of all shelves which that Book is assigned to:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
  "id": 16,
  "name": "My own book",
  "slug": "my-own-book",
  /// ...
  "shelves": [
    {
      "id": 1,
      "name": "Great reads",
      "slug": "great-reads"
    },
    {
      "id": 5,
      "name": "Personal Books",
      "slug": "personal-books"
    }
  ]
}

Previously you’d have to search across all shelves, so getting this information is now much more efficient & convenient.

Translations

Of course, a big thanks to all the verbal vanguard volunteers below who have provided their time to help translate BookStack to other languages since the last feature release:

  • CriedHero - Chinese Simplified - 1100 words
  • MichaƂ Sadurski (wheeskeey) - Polish - 745 words
  • toras9000 - Japanese - 364 words
  • Jeff Huang (s8321414) - Chinese Traditional - 213 words
  • serinf-lauza - French - 204 words
  • Henrik (henrik2105) - Norwegian Bokmal - 195 words
  • HrCalmar - Danish - 156 words
  • FoW (fofwisdom) - Korean - 153 words
  • Honza Nagy (honza.nagy) - Czech - 139 words
  • matthias4217 - French - 109 words
  • Vitaliy (gviabcua) - Ukrainian - 104 words
  • Diyan Nikolaev (nikolaev.diyan) - Bulgarian - 96 words
  • JanDziaslo - Polish - 94 words
  • scureza - Italian - 89 words
  • cbridi - Portuguese, Brazilian - 87 words
  • m0uch0 - Spanish - 79 words
  • Tim (thegatesdev) - Dutch - 79 words
  • Shadluk Avan (quldosh) - Uzbek - 58 words
  • Tomas Darius Davainis (Tomasdd) - Lithuanian - 58 words
  • Indrek Haav (IndrekHaav) - Estonian - 56 words
  • Gabriel Silver (GabrielBSilver) - Hebrew - 51 words
  • Igor V Belousov (biv) - Russian - 41 words
  • Charllys Fernandes (CharllysFernandes) - Portuguese, Brazilian - 36 words
  • Marci (MartonPoto) - Hungarian - 24 words
  • Ilgiz Zigangirov (inov8) - Russian - 23 words
  • Irfan Hukama Arsyad (IrfanArsyad) - Indonesian - 11 words
  • Max Israelsson (Blezie) - Swedish - 6 words

Word counts are those tracked by Crowdin, indicating original EN words translated.

Next Steps

With the theme module system now released I want to clean up some work I started, to create an LLM/AI based query system for BookStack, which uses the BookStack theme systems. I was going to feature this as part of the release here, as an example of the module system, but the timing wasn’t right and it doesn’t particularly highlight the additions made in this release, so I’m deferring that into what will probably be its own blog post and video.

I haven’t fully decided what to focus on for the next release, but with this one being so developer-focused, I’m keen to work on something that’s more impactful to non-developer users.

We’ll be expecting to see the 26.04 LTS release of Ubuntu next month, so I’ll be aiming to have installation scripts at the ready, and maybe a new video guide too.

Full List of Changes

Released in v26.03

  • Added new module system to the theme system. (#5998)
  • Added logical theme events for page content render and pre-save. (#6049)
  • Added logical theme event and class to allow inserting custom views before/after others. (#5998)
  • Added logical theme event to allow customising the OIDC authentication URL. (#6014)
  • Updated book delete to return to the parent shelf in a shelf context. (#6029)
  • Updated book read API endpoint to provide parent shelf information. (#6006)
  • Updated cursor to pointer for drawio diagrams. Thanks to @lublak. (#5864)
  • Updated description for per-page display limits. (#6005)
  • Updated emails to use the domain from the APP_URL in the SMTP HELO. (#5990)
  • Updated translations with latest Crowdin changes. (#6007)
  • Fixed empty extra space showing for descriptions when the input is left empty. (#5724)

Released in v25.12.9

  • Updated page revision diffs to use content filtering.
  • Updated preference change redirect with stronger origin checks.
  • Updated application PHP dependencies.

Released in v25.12.8

  • Fixed content filtering removing link target attribute, which would impact “New Window” links. (#6034)
  • Fixed content filtering to not remove user references in comments.
  • Updated PHP package versions.

Released in v25.12.7

  • Updated page document handling to handle empty content instead of throwing an error. (#6026)

Released in v25.12.6

  • Updated content filter to allow required drawio diagram attributes. (#6026)

Released in v25.12.5

  • Updated filter caching folder handling to avoid server filesystem permission issues. (#6023)

Released in v25.12.4 - Security Release

  • Added new option for more granular page filter control.
  • Updated page content filtering to detect extra cases, and to apply a more aggressive allow-list style filter.
  • Updated application PHP dependencies.

Released in v25.12.3 - Security Release

  • Updated application PHP dependencies.
  • Updated session-based API authentication to only be active for GET requests.
  • Updated page content filtering to remove many common form elements & attributes.
  • Updated translations with latest Crowdin changes. (#5997)

Released in v25.12.2

  • Updated translations with latest Crowdin changes. (#5970)
  • Updated PHP dependency versions.

Released in v25.12.1 - Security Release

  • Updated application PHP dependencies.
  • Add some additional resource-based limits. (#5968)
  • Updated translations with latest Crowdin changes. (#5962)

Header Image Credits: Photo by Alec MacKinnon (CC-BY-SA-2) - Image Modified