Keycloak Theme Not Loading: Causes and Fixes

Guilliano Molaire Guilliano Molaire 9 min read

Last updated: June 2026

When a custom Keycloak theme does not show up, the cause is almost always theme caching (enabled by default in production) or an incorrect theme directory structure, not your CSS. Keycloak caches both rendered templates and static resources unless you explicitly disable caching, and it only discovers themes placed under themes/<name>/<type>/ with a valid theme.properties file present. After making changes, you must either restart Keycloak or run it with caching disabled before your updates will appear.

This guide walks through every layer of Keycloak’s theme system — how it discovers themes, how you select them per realm, what caching settings control visibility, and how the Quarkus-based distribution differs from older references you may find online. By the end you will have a repeatable checklist for diagnosing why a custom theme is not loading and exactly what to change.

How Keycloak discovers themes

Keycloak looks for themes in two locations at startup:

  1. The themes/ directory in the Keycloak installation root.
  2. Provider JARs dropped into the providers/ directory.

At startup, Keycloak scans both locations and builds an in-memory registry of available themes. If your theme folder or JAR is not present, correctly named, or is missing required files at scan time, Keycloak will not add it to the registry, and it will never appear in the admin console dropdown regardless of cache settings.

The themes/ directory structure

Every theme lives under its own named folder inside themes/. Within that folder, Keycloak expects one or more theme-type subdirectories:

themes/
└── my-company/
    ├── login/
    │   ├── theme.properties
    │   └── resources/
    │       ├── css/
    │       └── img/
    ├── account/
    │   └── theme.properties
    └── email/
        └── theme.properties

The four supported theme types are:

  • login — the login, registration, and error pages users see during authentication flows
  • account — the self-service account management console
  • email — email templates (password reset, verification, etc.)
  • admin — the Keycloak admin console itself (rarely customized)

You only need to include the types you actually want to customize. A theme named my-company with only a login/ subdirectory is valid; Keycloak falls back to the base theme for account and email.

The theme.properties file

Each theme-type directory must contain a theme.properties file. Without it, Keycloak ignores the directory entirely. A minimal file looks like this:

parent=keycloak
import=common/keycloak

The parent directive tells Keycloak which theme to inherit from when a template or resource is not found in your theme. For login themes, keycloak (or keycloak.v2 for the newer React-based account console) is the standard parent. Setting an incorrect or misspelled parent causes Keycloak to fall back to the base theme silently rather than throwing a visible error.

For a deeper walkthrough of building out theme templates and overriding specific pages, see the Keycloak theme customization guide.

Selecting the theme in Realm Settings

Even when Keycloak has discovered a theme correctly, it does not apply automatically. You must assign it to a realm:

  1. Open the Keycloak admin console and select your realm.
  2. Navigate to Realm Settings > Themes.
  3. Use the Login theme, Account theme, Email theme, or Admin console theme dropdowns to select your theme by name.
  4. Click Save.

If your theme name does not appear in the dropdown, the theme has not been discovered. Go back and verify directory structure and theme.properties before continuing.

The number-one cause: theme and template caching

This is where most developers lose time. Keycloak’s production configuration caches both the compiled theme files and the rendered Freemarker templates. The cache is populated at startup and is not invalidated when you modify files on disk. This means:

  • You deploy a corrected CSS file.
  • You reload the login page.
  • The old version appears.
  • You assume your change did not work.

The caching behavior is controlled by two SPI settings:

Setting Default (production) Effect
spi-theme-cache-themes true Caches the full theme registry built at startup
spi-theme-cache-templates true Caches compiled Freemarker templates
spi-theme-static-max-age 2592000 (30 days) Browser cache max-age for static resources (CSS, JS, images)

In production (using start), all three are active. In development mode (using start-dev), Keycloak automatically sets spi-theme-cache-themes=false, spi-theme-cache-templates=false, and spi-theme-static-max-age=-1, which disables all three layers of caching.

Disabling caching for development

To iterate on themes without restarting Keycloak, pass these flags when starting in dev mode:

bin/kc.sh start-dev 
  --spi-theme-static-max-age=-1 
  --spi-theme-cache-themes=false 
  --spi-theme-cache-templates=false

You can also set them as environment variables:

KC_SPI_THEME_STATIC_MAX_AGE=-1
KC_SPI_THEME_CACHE_THEMES=false
KC_SPI_THEME_CACHE_TEMPLATES=false

With caching disabled, changes to .ftl templates and CSS files reflect on the next browser reload. Changes to theme.properties (including the parent setting) still require a restart because they affect theme discovery, not template rendering.

Do not disable theme caching in production. The performance cost is significant — Freemarker template compilation runs on every page request. Use start-dev only on local machines or in a dedicated development environment.

For Quarkus-based Docker deployments, see Keycloak Docker Compose production setup for the correct environment variable structure.

Packaging themes as JAR providers

The alternative to the themes/ directory is to package your theme as a Keycloak provider JAR and drop it into the providers/ directory. This approach is common for organizations that distribute their theme as a reusable artifact across multiple Keycloak instances.

A provider JAR follows this internal layout:

my-theme-provider.jar
└── META-INF/
│   └── keycloak-themes.json
└── theme/
    └── my-company/
        └── login/
            ├── theme.properties
            └── resources/

The keycloak-themes.json manifest declares the theme names inside the JAR:

{
  "themes": [
    {
      "name": "my-company",
      "types": ["login", "account"]
    }
  ]
}

After placing the JAR in providers/, run bin/kc.sh build to reindex providers before starting Keycloak. Skipping the build step is a common reason JAR-packaged themes do not appear — Keycloak 26.x with Quarkus requires an explicit build phase when the providers/ directory contents change.

For SPI development patterns beyond theme packaging, the Keycloak custom SPI development guide covers the broader provider system.

Symptom, cause, and fix reference

Symptom Most likely cause Fix
Theme not in dropdown at all Missing theme.properties, wrong directory name, or JAR not built Verify directory structure; run kc.sh build after adding JARs
Theme in dropdown but old styles appear after edit Template or static resource cache active Restart Keycloak, or use --spi-theme-cache-themes=false in dev
CSS changes not visible even after restart Browser cache serving stale static assets Hard refresh (Ctrl+Shift+R) or set spi-theme-static-max-age=-1
Login page falls back to default Keycloak theme Realm setting not saved, or wrong theme assigned to wrong type Re-check Realm Settings > Themes and save
NullPointerException in Keycloak logs on theme load Misspelled parent= value or missing parent theme Check theme.properties parent value matches an existing theme name
Email templates still show default content Email theme type not customized or not assigned Add email/ subdirectory with theme.properties and assign in Realm Settings
Account console shows default theme Mixing old Account v1 and new Account v2 themes Use keycloak.v2 as parent for the account type

Quarkus distribution specifics

Keycloak 26.x runs exclusively on Quarkus. If you are reading older blog posts or Stack Overflow answers that reference standalone.xml, WildFly configuration, or the standalone/themes/ path, that information does not apply to current releases.

Key differences in Quarkus Keycloak:

  • Two startup commands: start-dev (development, caching off) vs start (production, caching on). These are not interchangeable — start will fail if hostname is not configured; start-dev accepts a loose configuration suitable for local work.
  • Build step: Configuration changes to providers/, and some SPI configurations, require bin/kc.sh build before starting. This pre-bakes configuration into the Quarkus application.
  • Environment variables: Keycloak SPI settings map to environment variables using the pattern KC_SPI_<SPI_NAME>_<SETTING_NAME> in uppercase with hyphens replaced by underscores.
  • themes/ path: Still valid and still the simplest option. Mount it as a volume in Docker, or include it in your custom Docker image built on top of quay.io/keycloak/keycloak.

A Docker Compose setup targeting the dev mode with volume-mounted themes looks like this:

services:
  keycloak:
    image: quay.io/keycloak/keycloak:26.0
    command: start-dev
    environment:
      KC_BOOTSTRAP_ADMIN_USERNAME: admin
      KC_BOOTSTRAP_ADMIN_PASSWORD: admin
      KC_SPI_THEME_STATIC_MAX_AGE: "-1"
      KC_SPI_THEME_CACHE_THEMES: "false"
      KC_SPI_THEME_CACHE_TEMPLATES: "false"
    volumes:
      - ./themes:/opt/keycloak/themes
    ports:
      - "8080:8080"

The volume mount means you can edit files in ./themes on your host and see changes immediately without rebuilding the container image.

Custom theme not appearing in the admin dropdown

If you have placed your theme correctly but it still does not appear in the Realm Settings dropdown, work through this checklist in order:

  1. Confirm the directory is at themes/<theme-name>/<type>/theme.properties — not nested further or at the wrong level.
  2. Confirm the theme name (the parent folder) uses only lowercase letters, digits, and hyphens. Spaces and uppercase letters can cause discovery failures.
  3. If using a JAR provider, confirm you ran bin/kc.sh build after adding it to providers/.
  4. Check the Keycloak startup logs for lines containing Loading theme or any WARN/ERROR entries related to themes. Discovery errors are logged at startup, not at request time.
  5. Restart Keycloak fully. Even with caching disabled, theme discovery only happens at startup.

CSS not applying after theme selection

Once a theme is selected and visible, CSS changes sometimes still do not appear. The layers to check:

Parent theme import in theme.properties: If you want to extend rather than replace the base styles, your stylesheet must import the parent theme’s CSS. Add this to resources/css/login.css:

@import url("../../../keycloak/login/resources/css/login.css");

The relative path traverses up three levels to reach the base theme. A missing or broken import causes the entire parent stylesheet to be skipped silently, leaving your page with raw HTML styling.

Static resource path in templates: If you have overridden a .ftl template and changed a resource path, confirm the URL resolves. Keycloak provides the ${url.resourcesPath} Freemarker variable for building correct resource URLs:

<link rel="stylesheet" href="${url.resourcesPath}/css/custom.css" />

Browser cache: Keycloak sets long cache headers on static resources in production. A hard refresh (Ctrl+Shift+R on most browsers) bypasses the disk cache. If you need to force re-validation across all users, change the resource filename or add a query string, then restart Keycloak.

For configuring email-related templates and customizing message content, the Keycloak email configuration guide covers the email theme type in detail.

Getting started with a clean theme baseline

If you are setting up themes for the first time rather than debugging an existing one, the Keycloak getting started 2026 guide covers the initial Keycloak setup, including realm creation and admin console navigation, before you reach the theme configuration steps.

Frequently asked questions

Why is my Keycloak custom theme not showing?

The most common reasons are a missing or malformed theme.properties file, placing theme files in the wrong directory depth, or theme discovery cache still holding the previous state. Keycloak only discovers themes at startup, so always restart after adding a new theme folder. If the theme appears in the dropdown but pages still show the old design, template caching is the cause — use --spi-theme-cache-templates=false during development or restart in production.

How do I disable theme caching in Keycloak?

Pass --spi-theme-cache-themes=false --spi-theme-cache-templates=false --spi-theme-static-max-age=-1 as arguments to bin/kc.sh start-dev. Alternatively, set the equivalent environment variables: KC_SPI_THEME_CACHE_THEMES=false, KC_SPI_THEME_CACHE_TEMPLATES=false, and KC_SPI_THEME_STATIC_MAX_AGE=-1. These settings are intended for development only. In production, disable the Keycloak process and apply changes with a restart rather than running with caching off.

Where do Keycloak themes go?

Themes belong in the themes/ directory at the root of the Keycloak installation — for example, /opt/keycloak/themes/ in the standard Docker image. Each theme is a subdirectory named after the theme, containing type subdirectories (login/, account/, email/) with a theme.properties file in each type folder. Alternatively, package your theme as a JAR and place it in the providers/ directory, then run kc.sh build.

Do I need to restart Keycloak after changing a theme file?

In production (using kc.sh start), yes — Keycloak caches templates and the theme registry at startup. In development mode (kc.sh start-dev) with cache flags disabled, .ftl template and CSS changes are picked up on the next request without a restart. Changes to theme.properties itself always require a restart because they affect theme discovery.

Can I use the same theme across multiple realms?

Yes. A theme registered in the themes/ directory is available to all realms in that Keycloak instance. Each realm independently selects which theme to use via Realm Settings > Themes. You can have five realms all pointing to the same theme, or each using a different one.


Managing theme deployments, cache configuration, and version consistency across environments is overhead that compounds as your Keycloak footprint grows. Skycloak handles Keycloak operations — including upgrades, restarts, and environment parity — so your team can focus on building the authentication experience rather than maintaining infrastructure. See Skycloak’s managed plans if you want a production-ready Keycloak instance without the operational burden.

Tired of running Keycloak yourself?

Skycloak runs real upstream Keycloak for you with a 99.99% SLA. No fork, no lock-in, just managed Keycloak that stays patched and on call so you don't have to.

Guilliano Molaire
Written by
Founder

Guilliano is the founder of Skycloak and a cloud infrastructure specialist with deep expertise in product development and scaling SaaS products. He discovered Keycloak while consulting on enterprise IAM and built Skycloak to make managed Keycloak accessible to teams of every size.

Start Free Trial Talk to Sales
© 2026 Skycloak. All Rights Reserved. Design by Yasser Soliman