A lightweight local preview server for developing native Keycloak FreeMarker themes — without Docker, database, or running a full Keycloak instance.
Documentation | GitHub | Support on Patreon
Fitcloak is a local server that runs separately from Keycloak. Only Java 17+ is required.
- You create or edit a theme — standard
.ftltemplates, CSS, JS - Fitcloak assembles templates using the same inheritance chain as Keycloak (
Base → Parent → Child) - It substitutes test data and serves the rendered page to your browser
- When the theme is ready — copy it to Keycloak and select it in the admin console
- Optionally, you can use bundlers (Vite, Webpack, etc.) for HMR for faster development with any web framework
Customizing Keycloak's login/account/email pages normally means a painful feedback loop: rebuild the JAR, restart Keycloak, clear caches, refresh. Fitcloak eliminates all of that — just save your file and see the result.
Take any Keycloak template, point Fitcloak at it, and start hacking. The built-in dev server proxy means you can use any frontend toolchain — Vite, Webpack, Parcel — with any framework or preprocessor: React, Vue, Svelte, SCSS, Tailwind, whatever you prefer. FreeMarker renders the page structure, your tools handle the frontend, and HMR keeps the feedback loop instant.
This gives you the full flexibility of modern frontend development while staying within Keycloak's native theming system: no custom SPIs, no vendor lock-in — just standard .ftl templates that deploy to any Keycloak instance as-is.
- Instant feedback — edit
.ftl/.css/.properties, refresh browser - Full inheritance — emulates Keycloak's
Base -> Parent -> Childtheme chain - Vite/HMR integration — proxy to a dev server for hot module replacement
- Dynamic testing — override any template variable via URL query parameters
- Dashboard — template browser with inheritance visualization and QA links
- Zero infrastructure — just Java and Gradle, nothing else
- Java 17+ — the only required dependency. Gradle is downloaded automatically via the wrapper (
gradlew). - Node.js is not required for basic theme development. It is only needed if you want to use bundlers like Vite, Webpack, etc.
git clone https://github.com/msotnikov/fitcloak.git
cd fitcloak
cp config.example.json config.jsonchmod +x setup-keycloak-themes.sh
./setup-keycloak-themes.sh # Latest (main branch)
./setup-keycloak-themes.sh archive/release/23.0 # Or a specific versionThe script downloads Keycloak's FreeMarker templates and PatternFly CSS assets from the same commit, so versions always match.
./gradlew runOpen http://localhost:3030 — you'll see the dashboard with a list of all available templates. This is Fitcloak — a local server that renders your themes without Keycloak.
Point serverConfig.theme in config.json to your theme directory:
{
"serverConfig": {
"theme": "path/to/your-theme",
"keycloakThemesPath": "keycloak/base/themes/src/main/resources/theme"
}
}Your theme directory should follow the standard Keycloak theme structure:
your-theme/
login/
theme.properties # parent=keycloak, styles, scripts
login.ftl # Override login page
resources/
css/styles.css
messages/
messages_en.properties
Edit config.json and set:
"devResourcesUrl": "http://localhost:5173/"
# Start Vite dev server (terminal 1)
npm run dev
# Start Fitcloak (terminal 2)
./gradlew runWhen the theme is ready, deploy it to Keycloak:
-
Copy the theme directory to
<KEYCLOAK_HOME>/themes/:cp -r your-theme /opt/keycloak/themes/your-theme
-
Restart Keycloak (or rebuild if using Docker).
-
In Keycloak admin console, go to Realm Settings → Themes and select your theme.
If you used Vite/Webpack — build frontend resources first (npm run build) and make sure the compiled files are in the resources/ directory of your theme.
The project includes a demo theme with Vite/SCSS integration and a React password strength widget. The demo requires Node.js since it uses Vite.
# Install demo dependencies
cd demo && npm install && cd ..
# Edit config.json:
# "theme": "demo"
# "devResourcesUrl": "http://localhost:5173/"
# Start Vite dev server (terminal 1)
cd demo && npm run dev
# Start Fitcloak (terminal 2)
./gradlew runOpen http://localhost:3030. Edit demo/src/theme.scss — changes appear instantly via Vite HMR.
./gradlew run --args="--port 8080"
./gradlew run --args="--theme path/to/theme"
./gradlew run --args="--config my-config.json"
./gradlew run --args="--help"
./gradlew run --args="--version"
| Flag | Short | Description | Default |
|---|---|---|---|
--port |
-p |
Server port | 3030 (or from config) |
--config |
-c |
Config file path | config.json |
--theme |
-t |
Theme path (overrides config) | — |
--help |
-h |
Show help | — |
--version |
-v |
Show version | — |
See config.example.json for all options.
| Field | Description |
|---|---|
serverConfig.theme |
Path to your theme directory |
serverConfig.port |
Server port |
serverConfig.keycloakThemesPath |
Path to downloaded Keycloak base themes |
serverConfig.devResourcesUrl |
Vite/Webpack dev server URL for HMR |
serverConfig.qaRealms |
Quick-access links shown on the dashboard |
Data that Keycloak normally passes to templates is provided via JSON:
- Global:
config.json(root-level fields likerealm,url,locale) - Per-theme:
<theme-dir>/mock-data.json(overrides global) - Per-request: URL query parameters (highest priority)
Example: http://localhost:3030/login?realm.name=MyRealm&message.summary=Error&message.type=error
| URL pattern | Theme type |
|---|---|
/* (default) |
Login |
/account/* |
Account |
/email/* |
|
/admin/* |
Admin |
The .ftl extension is optional: /login and /login.ftl both work.
The included demo/ directory is a working example of Vite integration.
- Set
devResourcesUrlin config:"http://localhost:5173/" - In
theme.properties, reference source files directly:styles=css/login.css src/theme.scss - Fitcloak proxies
/resources/*,/src/*,/@*,/node_modules/*to the Vite dev server - Vite compiles SCSS (or other preprocessors) on the fly
- Falls back to local files if the dev server is unavailable
To use with your own theme: set up a Vite project in your theme directory with SCSS/PostCSS/etc., reference source files in theme.properties, and point devResourcesUrl to Vite.
Fitcloak provides mock implementations of Keycloak's FreeMarker objects:
| Helper | Behavior |
|---|---|
${msg("key")} |
Localized message from .properties files |
${kcSanitize(value)} |
Returns value as-is (mock) |
messagesPerField.existsError('field') |
Returns false |
messagesPerField.get('field') |
Returns "" |
auth.showUsername() |
Returns true |
auth.showResetCredentials() |
Returns true |
Template error in login.ftl: The following has evaluated to null or missing:
==> realm.password [in template "login.ftl" at line 2, column 109]
This means a FreeMarker template references a variable that doesn't exist in the mock data. In a real Keycloak instance these variables are populated by the server — in Fitcloak you provide them via JSON.
How to fix:
-
Add the missing variable to mock data. Open your theme's
mock-data.json(orconfig.json) and add the missing field. For the error above:{ "realm": { "name": "my-realm", "password": true } }Common
realmfields that Keycloak templates expect:Field Type Typical value Used by passwordboolean truelogin.ftl— controls whether the password form is shownregistrationAllowedboolean truelogin.ftl— "Register" linkresetPasswordAllowedboolean truelogin.ftl— "Forgot password" linkrememberMeboolean truelogin.ftl— "Remember me" checkboxloginWithEmailAllowedboolean truelogin.ftl— username field labelregistrationEmailAsUsernameboolean falselogin.ftl— username field labelinternationalizationEnabledboolean falsetemplate.ftl— language selector -
Override per-request via URL query parameters. Useful for quick testing without editing files:
http://localhost:3030/login?realm.password=true&realm.rememberMe=false -
Use FreeMarker defaults in your templates. If you're writing custom
.ftlfiles, use the!(default) operator to guard against missing values:<#-- Instead of: --> <#if realm.password> <#-- Use a default value: --> <#if (realm.password)!true>
The
!operator provides a fallback when the value is missing.(realm.password)!truemeans "userealm.passwordif it exists, otherwisetrue".
Keycloak templates reference many variables (realm, url, auth, login, social, properties, etc.). When you override or add a new .ftl file, you may need to provide additional mock values.
Approach: look at the .ftl file, find all ${...} expressions and <#if ...> conditions, then make sure each referenced object exists in your mock data. The demo's mock-data.json is a good starting point to copy from.
For comprehensive documentation on Keycloak's theming system, see the official guide: Keycloak Theme Development
If you find Fitcloak useful, consider supporting the project on Patreon.
./gradlew build # Build
./gradlew test # Run tests
./gradlew run # Start serverRequires Java 17+.

