Export the content page as it is
[TOC]
Overview
Customizations aren't appearing in PDF exports becuase BookStack's PDF engine (by default, dompdf) is a separate process that doesn't execute JavaScript and only recognizes a subset of CSS.
To ensure your styles are applied to PDF exports follows the steps
Steps
1. Install npm
apt update
apt install npm -y
2. Install Puppeteer
npm install puppeteer
Install Chrome for the Web User
# Define a local cache path and install
PUPPETEER_CACHE_DIR=/opt/bookstack/.cache npx puppeteer browsers install chrome
Set Cache Permissions
# Replace 'www-data' with your web user if different (e.g., 'nginx')
chown -R www-data:www-data /opt/bookstack/.cache
Update .env to manipulated export command
Edit .env adding:
ALLOW_UNTRUSTED_SERVER_FETCHING=true
EXPORT_PDF_COMMAND="node /opt/bookstack/puppeteer-pdf.js {input_html_path} {output_pdf_path}"
EXPORT_PDF_COMMAND_TIMEOUT=30
Create the Export Script
In BookStack root directory (/opt/bookstack) create a new file named puppeteer-pdf.js
const puppeteer = require('puppeteer');
const fs = require('fs');
(async () => {
const browser = await puppeteer.launch({
executablePath: '/opt/bookstack/.cache/chrome/linux-146.0.7680.76/chrome-linux64/chrome',
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
const page = await browser.newPage();
const htmlContent = fs.readFileSync(process.argv[2], 'utf8');
await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
// --- NEW TOC GENERATION LOGIC ---
await page.evaluate(() => {
const tocPlaceholder = document.body.innerHTML.match(/\[TOC\]/g);
if (!tocPlaceholder) return;
// Find all headings (h1 to h4)
const headings = Array.from(document.querySelectorAll('h1, h2, h3, h4'));
let tocHtml = '<div class="table-of-contents"><h2>Table of Contents</h2><ul>';
headings.forEach((heading, index) => {
const id = heading.id || `heading-${index}`;
heading.id = id; // Ensure heading has an ID to link to
const level = heading.tagName.toLowerCase();
tocHtml += `<li class="toc-${level}"><a href="#${id}">${heading.innerText}</a></li>`;
});
tocHtml += '</ul></div>';
// Replace the [TOC] text with our generated HTML
document.body.innerHTML = document.body.innerHTML.replace('[TOC]', tocHtml);
});
// --------------------------------
await page.pdf({
path: process.argv[3],
format: 'A4',
printBackground: true,
margin: { top: '1cm', right: '1cm', bottom: '1cm', left: '1cm' }
});
await browser.close();
})();
No comments to display
No comments to display