ssddanbrown/bookstack
Free, open-source self-hosted knowledge base built on Laravel. Organize content in books, chapters, and pages with a simple WYSIWYG editor, powerful search, role-based permissions, and integrations—ideal for team wikis, documentation, and internal knowledge management.
BookStack is not a Composer package you pull into an existing Laravel app — it's a full Laravel application (its own app/, routes/, resources/views/, database/migrations/) that you self-host, extend, or integrate with over HTTP. Set expectations accordingly before you start.
Minimal setup:
git clone https://codeberg.org/bookstack/bookstack.git
cd bookstack
composer install --no-dev
cp .env.example .env
php artisan key:generate
# set DB_* and APP_URL in .env
php artisan migrate
php artisan db:seed # optional demo content
php artisan serve
Docker is the faster path for evaluation: run the linuxserver/bookstack image with a MariaDB/MySQL sidecar, then complete setup at /.
Where to look first:
routes/web.php and routes/api.php — the whole surface area, and the fastest way to understand what you can integrate with.app/Entities/ — BookStack's own model layer (Book, Chapter, Page, Bookshelf). Note it does not map cleanly onto Eloquent conventions; read it before writing observers.app/Http/Controllers/Api/ — the REST API implementations; treat this as the contract. Permission checks live in app/Permissions/ and are the source of truth for what the API will return.themes/ and app/Theme/ — the supported extension point for UI changes.lang/ — translations, relevant because every release ships Crowdin updates.First use case: don't fork. Create an API token (Profile → API Tokens) and drive content from your own Laravel app:
Http::withToken("{$tokenId}:{$tokenSecret}")
->baseUrl('https://docs.example.com/api')
->get('pages', ['count' => 100, 'sort' => '-updated_at']);
That covers 80% of "leverage this package" needs — syncing docs into a product, generating pages from CI, or pulling a knowledge base into a customer portal.
API-first integration. Authenticate with Authorization: Token <id>:<secret>. Paginate with count/offset/sort; keep count well under the 500 cap to stay inside the API rate limit (api_rate_limit, default 60/min — raise it deliberately in .env, don't disable it). Endpoints cover books, chapters, pages, bookshelves, users, roles, attachments, comments, and search. Page payloads return both html and markdown, so pick one and normalize rather than re-rendering.
Treat list endpoints as permission-filtered views, not tables. Two consecutive security releases have tightened what the API returns for entities sitting in the recycle bin or in draft state (/api/attachments in v26.09.1, /api/comments in v26.09.2). Design sync jobs to reconcile against the list endpoint's current output rather than assuming monotonic row counts — see Gotchas.
Webhooks instead of polling. Settings → Webhooks posts JSON for events like page_create, page_update, page_delete, book_update. Point these at a Laravel route in your own app to keep a mirror index, invalidate caches, or trigger a search reindex. Verify the payload's event field and treat delivery as at-least-once — make your handler idempotent on the entity id. Note that recycle-bin transitions (delete/restore) are the operations most likely to desync a mirror built on webhooks alone; pair them with a periodic full reconcile.
Custom theming (the supported fork-lite path). Drop a folder under themes/<name>/ mirroring resources/views, set APP_THEME=<name>, and use themes/<name>/functions.php with BookStack's own hook system:
Theme::listen(ThemeEvents::APP_BODY_OPEN, function () {
// inject analytics, banners, custom CSS
});
This is not Laravel's event dispatcher — ThemeEvents is BookStack-specific and only fires for view rendering. Don't expect Event::listen() to catch it.
Content automation jobs. For bulk page creation/moves, go through the API rather than raw SQL: permissions in BookStack are denormalized into join tables, and writing rows directly leaves them inconsistent. After any bulk move, run php artisan bookstack:regenerate-permissions and php artisan bookstack:regenerate-search. The v26.09.1 chapter-promotion permission fix means child page permissions are now correctly regenerated on promote — if you built a workaround (re-saving child pages via the API to force a rebuild), remove it or you'll double the permission writes.
Deployment/update flow. Per the linked update instructions, patch updates are in-place: git pull (or swap the release archive), composer install --no-dev -o, php artisan migrate --force, then clear caches. Back up the database and the files/ uploads directory first — migrations here are one-way. Security releases like this one are drop-in; schedule them on the short cadence rather than waiting for a maintenance window.
Domain changes and exports. php artisan bookstack:update-url rewrites stored links after a hostname change; PDF export depends on wkhtmltopdf being present in the image, which is a common "works locally, blank in Docker" cause.
This release (v26.09.2) is a security patch — no breaking changes, no deprecations, no new configuration or schema requirements. Standard git pull + composer install + php artisan migrate path. But it does change API output and permission behaviour, so read the next three items before deploying if you have automations.
Prioritise it if you expose comments or draft pages. Two fixed vulnerabilities are directly relevant to integrations:
/api/comments now filters them out. If you mirror comments, your last full sync may have cached comments for deleted pages — treat this as a data-cleanup task, not just an upgrade.Attachments in the recycle bin are also gone from /api/attachments (since v26.09.1, issue #6194). Combined with the comment change, any orphan-cleanup or reconciliation logic that assumed a stable row count will now see fewer rows. Make cleanup idempotent and keyed on entity IDs, never on totals.
Password changes now drop remember-me cookies. If a runbook rotates a service-account password through the UI, existing browser sessions and remember-me tokens for that account are invalidated — expect a forced re-login, and don't be surprised if a headless flow relying on a persisted session breaks. API tokens are unaffected.
SAML2 routes were hardened against nuisance cross-site requests causing logouts. If you front BookStack with an IdP and had users randomly logged out, that should stop. If you have custom SP-side redirect or logout handling layered on top, re-test the flow — the routes are more restrictive about how they're invoked.
ZIP import validation got another layer (total uncompressed size, on top of v26.09.1's actual-size check before extraction). Still worth capping import size at the web-server/proxy layer as defence in depth — application-level checks are the last line, not the only one.
Audit log timestamps use the configured display timezone (since v26.09.1, PR #6170). If anything downstream parses audit log times — a SIEM shipper, a compliance export, your own reporting query — normalize to UTC. A stored-vs-displayed offset assumption may now be wrong in the other direction.
Debugging the app itself:
php artisan bookstack:clear-views after theme changes — BookStack caches compiled views aggressively and stale Blade output is the #1 theme-dev confusion.php artisan config:clear and php artisan cache:clear after .env edits; settings are cached separately and can mask changes.APP_DEBUG=true locally only — BookStack leaks a lot of internal state in debug pages.storage/logs/laravel.log; failed imports and webhook deliveries surface there, not in the UI. Permission denials on attachment/comment actions are silent from the API's perspective — check the log, not the response body.Things that will bite you:
bookstack:regenerate-permissions is slow on large instances; schedule it, don't run it inline in a request.bookstack:regenerate-search rather than assuming a bug.composer require into your own app) is not supported. If you need deep behaviour changes, fork and maintain the fork — budget for merge conflicts on every minor release, since this project ships frequently.database/migrations/, app/, and — for security releases — app/Http/Controllers/Api/ and app/Permissions/.How can I help you explore Laravel packages today?