Spring Boot + Thymeleaf example
The plugwerk-springboot-thymeleaf-example project shows how to integrate Plugwerk into a Spring Boot web application so that PF4J plugins can contribute fully-formed pages (menu label + route + title + HTML body) at runtime. The host renders Thymeleaf templates as usual; plugin pages slot into the navigation bar without a restart and disappear cleanly when uninstalled.
Who it is for
Section titled “Who it is for”- Host-application developers integrating the Plugwerk client into an existing or new Spring Boot + Thymeleaf web application.
- Teams that want a controlled "marketplace inside the app" experience — end users browse a plugin catalog and click Install instead of running shell commands.
What it demonstrates
Section titled “What it demonstrates”- A custom extension-point interface (
PageContribution) where each plugin owns a route, a menu label, a page title, and the HTML body. - A web-UI plugin catalog at
/plugins/availablewith one-click Install / Uninstall buttons that drive the Plugwerk REST API behind the scenes. - Live navigation updates: install a plugin, the menu item appears immediately; uninstall, it disappears.
- A clean separation between host code and plugin code — host depends on
plugwerk-springboot-thymeleaf-example-api, plugins depend on the same artifact and contribute extensions through PF4J.
Project layout at a glance
Section titled “Project layout at a glance”| Module | Purpose |
| ----------------------------------------------- | ------------------------------------------------------------------------- |
| plugwerk-springboot-thymeleaf-example-api | Defines PageContribution, the PF4J ExtensionPoint for page plugins |
| Host (src/) | Spring Boot host: PF4J wiring, Plugwerk client config, catalog UI |
| plugwerk-example-plugin-sysinfo | Page plugin: JVM, OS, memory, uptime |
| plugwerk-example-plugin-env | Page plugin: environment variables (secrets masked) |
The interface a page plugin implements is small and self-explanatory:
public interface PageContribution extends ExtensionPoint { String getMenuLabel(); // label in the navigation bar String getRoute(); // URL path segment (e.g. "sysinfo") String getTitle(); // page <title> and heading String renderHtml(); // HTML fragment for the page body}A plugin only needs to implement these four methods (plus the standard PF4J Plugin subclass and a manifest entry). The host discovers extensions through PF4J, mounts them on /page/{route}, and re-renders the navigation bar.
Run it
Section titled “Run it”The full bootstrap (start the local Plugwerk server, create a default namespace, mint an API key into $PLUGWERK_API_KEY) lives in the Quick start section of the examples-repo root README. Once it is exported, two example-specific commands take you the rest of the way:
-
Build and start the host:
Terminal window cd plugwerk-springboot-thymeleaf-examplePLUGWERK_API_KEY=$PLUGWERK_API_KEY ./gradlew bootRunThe build automatically copies the Plugwerk client plugin ZIP into
plugins/, so the host can talk to the server on first start. -
Build the example plugin ZIPs and publish them to the server:
Terminal window ./gradlew :plugwerk-example-plugin-sysinfo:assemble \:plugwerk-example-plugin-env:assembleThe full upload + approve flow (
POST /plugin-releases, thenPOST /reviews/<release-id>/approve) is documented in the example's README. -
Open the host: http://localhost:8081
A user flow
Section titled “A user flow”This is what an end user of the host application sees, end to end:
-
Open the catalog at http://localhost:8081/plugins/available.
Both example plugins are listed with their metadata pulled from the Plugwerk server.
-
Click Install next to
plugwerk-example-plugin-sysinfo.Behind the scenes the host calls Plugwerk to download the ZIP, drops it into the local
plugins/directory, and asks PF4J to load it. The page reloads with a confirmation. -
Watch the navigation bar. A new entry —
System Info— appears. -
Click
System Info. You land on/page/sysinfo, which is the path the plugin chose. The plugin'srenderHtml()output (Java version, OS, heap, uptime) is rendered as the page body inside the host's standard layout. -
Repeat with the env plugin to see a second page mount at
/page/env. -
Click Uninstall on either plugin. PF4J stops and unloads it; the menu entry vanishes; visiting the route returns a 404.
The plugin flow is identical regardless of where the Plugwerk server runs — your local Docker Compose during development, a shared staging server, or production. Only the configured plugwerk.server-url and plugwerk.api-key change.
Authentication in one paragraph
Section titled “Authentication in one paragraph”The Spring host's Install action talks to the Plugwerk server (download the ZIP), so it sends the PLUGWERK_API_KEY it was started with whenever the namespace's catalog is not public. Anonymous read-only catalog browsing works for namespaces with publicCatalog = true without a key. Uninstall is local-only: the host removes the ZIP and extracted directory from the local plugins/ folder and asks PF4J to unload the plugin — no server call, no API key required. See Authentication for the full read/write split.
Configuration reference
Section titled “Configuration reference”| Property | Env | Default | Description |
| ----------------------- | ---------------------- | ------------------------- | -------------------------- |
| server.port | SERVER_PORT | 8081 | Web server port |
| plugwerk.server-url | PLUGWERK_SERVER_URL | http://localhost:8080 | Plugwerk server base URL |
| plugwerk.namespace | PLUGWERK_NAMESPACE | default | Namespace slug |
| plugwerk.api-key | PLUGWERK_API_KEY | (none) | Namespace-scoped API key |
| plugwerk.plugins-dir | PLUGWERK_PLUGINS_DIR | ./plugins | PF4J plugins directory |
Where the source lives
Section titled “Where the source lives”- Repository: plugwerk/examples
- This example:
plugwerk-springboot-thymeleaf-example/ - Per-example README — full setup, configuration reference, "Writing your own page plugin" walkthrough:
plugwerk-springboot-thymeleaf-example/README.md
Related pages
Section titled “Related pages”- First plugin upload — the minimal "upload a plugin" walkthrough that this example builds on
- Plugin lifecycle — the draft → published → archived flow you trigger when uploading the example plugins
- Java CLI example — the same install / use / uninstall loop, but for CLI subcommands instead of web pages