Plaintext Root is an open-source application framework for rapidly building multi-tenant web applications with Jakarta Faces (JSF), PrimeFaces, and Spring Boot. It provides a complete foundation including security, navigation, admin panels, user management, and a pluggable template system — so you can focus on your business logic.
- Multi-Tenancy — Built-in mandate system for data isolation between tenants
- Security — Spring Security with role-based access control (User/Admin/Root), CSRF protection, session tracking
- Two-Factor Auth (TOTP) — Optional, opt-in authenticator-app 2FA for local password users with recovery codes (docs; default off via
plaintext.security.totp.enabled) - Page Access Guard — Per-view authorization derived from menu visibility (roles + mandate), enforced in a servlet filter before the FacesServlet, with an allowlist/alias mechanism and a build-time invariant test (docs;
plaintext.security.page-guard.mode) - Menu System — Annotation-driven menu builder with role-based visibility and badge support
- Admin Panels — Pre-built admin modules for settings, sessions, cron jobs, emails, and more
- Template System — Swappable UI templates (open-source Plaintext template included)
- Email — Complete email send/receive system with IMAP and SMTP support
- API Tokens — Token-based REST API authentication
- Cron Jobs — Annotation-driven scheduled task system with monitoring UI
- User Preferences — Persistent theme, layout, and UI preferences per user
graph TB
subgraph "Web Layer"
WEBAPP[plaintext-root-webapp<br/>Security, Login, Controllers]
TEMPLATE[plaintext-root-template<br/>UI Template, CSS, JS]
end
subgraph "Core Modules"
IFACE[plaintext-root-interfaces<br/>Shared Interfaces]
COMMON[plaintext-root-common<br/>Utilities]
JPA[plaintext-root-jpa<br/>Base Entities, Auditing]
MENU[plaintext-root-menu<br/>Menu Builder]
SECURITY[plaintext-root-menu-visibility<br/>Menu Visibility]
ROLES[plaintext-root-role-assignment<br/>Role Management]
FLYWAY[plaintext-root-flyway<br/>DB Migrations]
end
subgraph "Admin Modules"
ADMIN_SET[plaintext-admin-settings]
ADMIN_SES[plaintext-admin-sessions]
ADMIN_CRON[plaintext-admin-cron]
ADMIN_WL[plaintext-admin-value-lists]
ADMIN_ANF[plaintext-admin-requirements]
end
WEBAPP --> TEMPLATE
WEBAPP --> IFACE
WEBAPP --> JPA
WEBAPP --> MENU
WEBAPP --> SECURITY
WEBAPP --> ROLES
WEBAPP --> ADMIN_SET
WEBAPP --> ADMIN_SES
WEBAPP --> ADMIN_CRON
WEBAPP --> ADMIN_WL
MENU --> IFACE
SECURITY --> IFACE
ROLES --> IFACE
JPA --> COMMON
| Module | Description |
|---|---|
plaintext-root-interfaces |
Shared interfaces for security, settings, menu visibility |
plaintext-root-common |
Common utilities, XStream serialization, object storage |
plaintext-root-jpa |
Base JPA entities with audit fields, generic repositories |
plaintext-root-menu |
Annotation-driven menu system with hierarchical support |
plaintext-root-menu-visibility |
Mandate-based menu visibility control |
plaintext-root-role-assignment |
User role assignment and management |
plaintext-root-flyway |
Database migration management |
plaintext-root-webapp |
Main web application with security, login, and controllers |
plaintext-root-template |
UI Template |
plaintext-admin-settings |
Application settings management UI |
plaintext-admin-sessions |
Active session monitoring and insights |
plaintext-admin-cron |
Cron job monitoring and management UI |
plaintext-admin-apitoken |
API-token management for REST endpoints |
plaintext-admin-i18n |
Translatable resource bundles and UI |
plaintext-admin-oidc |
OIDC/OAuth2 provider configuration |
plaintext-admin-mailtemplate |
Editable mail templates |
plaintext-admin-modules |
Module registry and activation |
plaintext-admin-notifications |
In-app and push notifications |
plaintext-admin-secrets |
Secret store with pluggable backends |
plaintext-admin-webhooks |
Outgoing webhook configuration |
plaintext-admin-requirements |
Requirements management with AI integration |
plaintext-root-archtests |
Shared ArchUnit architecture/lint tests |
The authoritative list is the <modules> section of the root pom.xml.
| Technology | Version | Purpose |
|---|---|---|
| Java | 25 | Language |
| Spring Boot | 4.x | Application framework |
| Jakarta Faces | 4.1 | UI component framework |
| PrimeFaces | 15.x | JSF component library |
| JoinFaces | 5.x | Spring Boot + JSF integration |
| PostgreSQL | 18+ | Database |
| Flyway | — | Database migrations |
| Lombok | latest | Boilerplate reduction |
Exact versions live in the parent
pom.xml; the table above lists major lines so this README does not need to be touched on every dependency bump.
- Java 25+ (e.g., via SDKMAN:
sdk install java 25-open) - Maven 3.9+
- Docker or Podman (optional, only for PostgreSQL)
git clone https://github.com/Plaintext-Gmbh/plaintext-root.git
cd plaintext-root
# Build all modules (no database needed!)
mvn clean install -DskipTestsmvn spring-boot:run -pl plaintext-root-webappThe application starts at http://localhost:8080 with an in-memory H2 database (PostgreSQL compatibility mode). No external database setup needed!
Note: Data is lost on restart with H2. For persistent storage, switch to PostgreSQL (see below).
For production or persistent data, switch to PostgreSQL:
# Start PostgreSQL
docker compose up -d
# Run with PostgreSQL profile
mvn spring-boot:run -pl plaintext-root-webapp -Dspring-boot.run.profiles=postgresOr set the environment variable:
SPRING_PROFILES_ACTIVE=postgres mvn spring-boot:run -pl plaintext-root-webappIn dev mode, the H2 database console is available at http://localhost:8080/h2-console with:
- JDBC URL:
jdbc:h2:mem:plaintext_root - Username:
sa - Password: (empty)
Plaintext Root has built-in multi-tenancy support through the mandate system:
- Each user is assigned to a mandate (tenant)
- Data is isolated per mandate at the application level
- Menu visibility can be controlled per mandate
- Root users can switch between mandates at runtime
- The
SuperModelbase entity automatically tags records with the current mandate
Menus are defined as Spring beans using the MenuItemImpl class:
@Component
public class MyMenu extends MenuItemImpl {
public MyMenu() {
setTitle("My Feature");
setParent("Admin"); // Parent menu item
setCommand("myfeature.xhtml"); // Target page
setIcon("pi pi-star"); // PrimeIcons icon
setOrder(100); // Sort order
setRoles(List.of("ROLE_ADMIN")); // Required roles
}
}Menus are automatically discovered, sorted, and rendered with role-based visibility.
A topbar search field (⌘K / Ctrl+K) queries every module through the SearchProvider interface and
aggregates the results grouped by module. It mirrors the menu/dashboard registry pattern exactly: root
defines the interface, each module registers a @Component, root collects all beans automatically and
queries them. Each hit carries its own deep-link, so a click lands directly on the module's detail page —
root never needs to know about the target pages.
A module contributes hits by providing a single @Component that implements SearchProvider
(ch.plaintext.boot.search.SearchProvider, in plaintext-root-interfaces). No root change needed.
@Component
@RequiredArgsConstructor
public class KorrespondenzSearchProvider implements SearchProvider {
private final KorrespondenzRepository repo;
private final PlaintextSecurity security;
public String providerId() { return "korrespondenz"; }
public String moduleTitle() { return "Korrespondenz"; } // must match the module's menu title
public List<SearchHit> search(String q, int limit) {
return repo.searchByMandat(security.getMandat(), q, limit).stream()
.map(k -> new SearchHitDTO(
k.getTitel(), // title
k.getDatum().toString(), // subtitle
"korrespondenz.html?id=" + k.getId(),// deep-link (like a MenuAnnotation.link)
"pi pi-envelope", // icon
k.relevance(q))) // score (ranking within the group)
.collect(Collectors.toList());
}
}Key points:
getLink()is the deep-link. It is exactly aMenuAnnotation.link(relative to the context path, e.g.korrespondenz.html?id=42). The frontend navigates tocontextPath + "/" + link.- Visibility is coupled to the menu.
SearchServiceonly queries a provider when itsmoduleTitle()matches a visible menu title (MenuRegistry.getAllMenuItems()→isOn()), so hits never leak from modules the user/tenant cannot see. Each provider additionally scopes its own hits to the active tenant viaPlaintextSecurity.getMandat(). - Cross-cutting root providers (e.g. menu/page search, user search) are not tied to a single menu;
they return
isMenuScoped() == falseand enforce visibility/roles themselves. - Robust & timeboxed. A failing provider is caught and yields an empty list; queries under 2 chars are ignored and query length is capped. Results are grouped by module and capped per module.
GET /api/search?q=... (authenticated; runs behind the normal app auth — no security-config change)
returns JSON:
{ "groups": [ { "module": "Korrespondenz",
"hits": [ { "title": "...", "subtitle": "...", "link": "...", "icon": "..." } ] } ] }The topbar frontend debounces (~200 ms), supports ↑/↓/Enter/Esc, and navigates to the hit's deep-link on
click. Root ships two providers out of the box: page/navigation search (jump to any visible menu page)
and user search (ROOT/ADMIN only → useradmin.xhtml). Consumer-app modules add their own providers in
follow-up work.
The UI template is a separate Maven module that can be swapped:
<!-- Open-source template (default) -->
<dependency>
<groupId>ch.plaintext</groupId>
<artifactId>plaintext-root-template</artifactId>
</dependency>The template provides: layout CSS (light/dark), navigation JavaScript, XHTML templates (topbar, sidebar, config panel, footer), and theme color overrides.
- Light/Dark mode with persistent preference
- Three menu layouts: Sidebar, Horizontal, Slim
- Color themes: Blue, Green, Orange, Turquoise, Avocado, Purple, Red, Yellow
- Input styles: Outlined or Filled
- Responsive design with mobile sidebar
Flyway migrations use H2 (PostgreSQL mode) compatible SQL syntax and are located in each module's src/main/resources/db/migration/ directory. Migration file names follow the pattern:
V{timestamp}__description.sql
The timestamp is simply the number of seconds since 2000-01-01, which keeps new migrations strictly increasing and collision-free across modules:
echo $(( $(date +%s) - 946684800 ))See docs/FLYWAY_MIGRATIONS.md for the conventions.
| Role | Description |
|---|---|
ROLE_USER |
Standard user access |
ROLE_ADMIN |
User management, admin panels |
ROLE_ROOT |
Full access, mandate switching |
plaintext-root/
├── plaintext-root-interfaces/ # Shared interfaces
├── plaintext-root-common/ # Utilities
├── plaintext-root-jpa/ # Base JPA entities
├── plaintext-root-menu/ # Menu builder
├── plaintext-root-menu-visibility/ # Menu visibility
├── plaintext-root-role-assignment/ # Role management
├── plaintext-root-flyway/ # DB migrations
├── plaintext-root-template/ # UI template
├── plaintext-root-webapp/ # Main web application
├── plaintext-admin-settings/ # Settings admin
├── plaintext-admin-sessions/ # Session monitoring
├── plaintext-admin-cron/ # Cron job admin
├── plaintext-admin-apitoken/ # API tokens
├── plaintext-admin-i18n/ # Translations
├── plaintext-admin-oidc/ # OIDC configuration
├── plaintext-admin-mailtemplate/ # Mail templates
├── plaintext-admin-modules/ # Module registry
├── plaintext-admin-notifications/ # Notifications
├── plaintext-admin-secrets/ # Secret store
├── plaintext-admin-webhooks/ # Webhooks
├── plaintext-admin-requirements/ # Requirements + AI
├── plaintext-root-archtests/ # ArchUnit architecture tests
├── docs/ # Documentation
├── quality/ # Quality-gate configuration
├── scripts/ # Analysis helper scripts
├── compose.yaml # PostgreSQL dev setup
├── Dockerfile # Production container
├── LICENSE # MPL 2.0
└── NOTICE # Third-party components
The executable build and start scripts in the repository root are the
maintainers' release/deploy TUI. They pull in shared shell libraries from
plaintext-scripts and
assume a specific deployment environment. They are not needed to build or run
the project — use the plain Maven commands from the Quick Start instead.
See CONTRIBUTING.md for guidelines on how to contribute.
Coverage reports are generated with JaCoCo during mvn test. Reports are available in each module's target/site/jacoco/ directory.
# Run tests with coverage
mvn clean test
# Open report (example for webapp module)
open plaintext-root-webapp/target/site/jacoco/index.htmlCoverage reports are also uploaded as artifacts in the CI pipeline.
This project is licensed under the Mozilla Public License 2.0.
Every Java source file carries the MPL 2.0 header. Third-party files that are checked into this repository (PrimeFlex, PrimeIcons, marked.js — all MIT) and the notable licenses among the Maven dependencies are listed in NOTICE.
Please report security issues as described in SECURITY.md — not via public issues.