TYPO3 as a Wiki Platform: How I Built a Full Wiki Extension from Scratch
Typo3

TYPO3 as a Wiki Platform: How I Built a Full Wiki Extension from Scratch

Yannick Aister 5 min read

The starting question: buy a wiki or build one?

There is no shortage of wiki tools. MediaWiki, DokuWiki, Confluence, Notion, BookStack – the list goes on. So why build a custom wiki extension for TYPO3 instead of just picking a ready-made solution?

The answer lies in context. When a project already runs on TYPO3, when unified user management via fe_users is a requirement, and when the wiki needs to be part of an existing infrastructure rather than a standalone tool, a custom extension is the cleanest approach. No second system, no second login, no data privacy concerns with an external service.

That was my starting point. The result: AisteaWiki – a fully featured wiki frontend extension for TYPO3 v14, built with PHP 8.3 and MySQL 8.

What the extension does

The feature set is deliberately practical – not maximised for features, but designed around real usage scenarios:

  • Articles with Draft, Published and Archived status
  • Hierarchical page tree via parent/child relationships
  • Categories, tags and manual tree sorting
  • Revisions with diff view and restore functionality
  • Wiki-style links using [[Article Name]] syntax
  • Article templates for recurring content types
  • Attachments and inline images
  • Full-text search with MySQL FULLTEXT and a LIKE fallback
  • Team context with roles, token-based invitations and an admin approval flow
  • Profile pages with avatar, password change and account deletion

All of this runs as a frontend plugin. Users register in the frontend, collaborate in teams and only see what they are permitted to see – controlled via visibility rules (private or internal).

Architecture decisions

Extbase as the foundation

The extension is built on Extbase and Fluid, TYPO3's MVC framework. This was a deliberate choice: Extbase integrates deeply with the TYPO3 core, uses established mechanisms for persistence, routing and caching – and is immediately readable for any experienced TYPO3 developer.

The plugin configuration with its permitted controller–action combinations is correspondingly comprehensive:

 

# Excerpt from the plugin registration
Article: list, show, dashboard, new, create, edit,
         update, delete, search, myArticles,
         history, restore, templates
Profile: show, updateProfile, updatePassword, deleteAccount
Team:    switch, show, members, invite,
         removeMember, promote, createTeam

 

Intentional request parsing outside Extbase

One notable architectural decision concerns request parsing. Several flows – including file uploads, frontend registration and token-based invitations – intentionally read data from the PSR-7 main request rather than from the Extbase request object:

 

// File uploads
$uploadedFiles = $GLOBALS['TYPO3_REQUEST']->getUploadedFiles();
 
// Registration fields
$body = $GLOBALS['TYPO3_REQUEST']->getParsedBody();
 
// Invite token from URL
$queryParams = $GLOBALS['TYPO3_REQUEST']->getQueryParams();

 

The reason: Extbase normalises and filters certain data before it reaches the controller. For file uploads and flat field names this caused problems that were cleanly resolved by accessing the PSR-7 request directly. This is intentional and should not be quietly reverted during refactoring.

Flat field names to avoid Fluid parsing errors

Nested field names like tx_aisteawiki[key] inside regular HTML attributes within Fluid templates caused parse errors – Fluid interprets the square brackets as its own syntax. The solution was pragmatic: flat field names like reg_firstName in the template, with manual mapping in the controller. Not a hack, but a deliberate trade-off worth documenting.

Full-text search: MySQL FULLTEXT with a graceful fallback

The search runs on MySQL FULLTEXT indexes on title, teaser, content and tags. What sounds straightforward has an important fallback mechanism baked in: if the database structure does not match the expected index – for instance after a database import without rebuilding indexes – the search automatically falls back to a LIKE query instead of throwing a SQL exception.

 

// Simplified repository logic
try {
    // FULLTEXT search
    $result = $this->searchFulltext($searchTerm);
} catch (\Exception $e) {
    // Graceful fallback to LIKE
    $result = $this->searchLike($searchTerm);
}

 

Building robustly does not just mean "it works when everything is in place" – it also means "it does not break when something is not".

The cHash problem with GET-based search

TYPO3 validates a cHash parameter on cached pages to verify the integrity of query parameters. With a GET-based search – where the search term is passed as a URL parameter – this reliably produces 404 errors because the cHash does not match the dynamic search term.

The fix is to explicitly exclude the search parameters from cHash validation:

 

# config/system/settings.php
$GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters'] = [
    'tx_aisteawiki_wiki[searchTerm]',
    'tx_aisteawiki_wiki[action]',
    'tx_aisteawiki_wiki[controller]',
];

 

Forgetting this – or removing it quietly during a refactor – leads to mysterious 404s on the search page. These exclusions belong in the documentation and must be treated as load-bearing configuration.

Deployment with Deployer

Deployment runs via Deployer, a PHP-based deployment tool that works well with TYPO3 projects. The initial setup on a fresh production server follows this sequence:

 

# 1. Deploy the code
vendor/bin/dep deploy production
 
# 2. Push the local database to production (one-time only)
vendor/bin/dep typo3:database_push production
 
# 3. Set up extensions and flush the cache
vendor/bin/dep typo3:extension_setup production
vendor/bin/dep typo3:cache_flush production

 

From the second deployment onwards a single command handles everything: vendor/bin/dep deploy production. Dedicated pull tasks allow syncing the production database and fileadmin back into the local DDEV environment when needed.

When does this approach make sense?

A custom TYPO3 wiki extension is not the right call for every project. It makes sense when:

  • the project already runs on TYPO3 and unified user management matters
  • the wiki needs to be part of an existing website, not a separate tool alongside it
  • data privacy and on-premise data storage are non-negotiable
  • the requirements are stable and well-defined (no real-time collaborative editing à la Notion)

For teams that need real-time collaborative writing, large plugin ecosystems or out-of-the-box mobile apps, specialised tools like BookStack or Confluence remain the better choice.

Conclusion

Using TYPO3 as a wiki platform is not a workaround – it is a genuine architectural decision that makes more sense than any off-the-shelf tool in certain contexts. The technical challenges along the way – cHash handling, PSR-7 request parsing, Fluid edge cases, robust search – are all solvable and instructive.

If you are already running TYPO3, do not dismiss the option of a custom extension too quickly. Sometimes the cleanest system is the one you have full control over.

Have you built something similar, or are there specific parts of this architecture you would like to explore further? I would love to hear from you in the comments.

Leave a comment