Keycloak 26.7 introduced an experimental Admin API v2 whose first resource is clients, and with it two Operator custom resources, KeycloakOIDCClient and KeycloakSAMLClient, that let you declare OIDC and SAML clients as Kubernetes manifests. Enable the client-admin-api:v2 feature on the Keycloak CR, create an admin credentials Secret the Operator can authenticate with, and apply a client CR in the same namespace as the Keycloak CR. The Operator reconciles the client on the server to match the CR, including deletion when you remove it. Three caveats decide whether this belongs in your pipeline today, and the first is not negotiable: the Keycloak guides for both the API and the Operator client CRs carry the standard experimental banner, which reads “Do not use this feature in production environments.” The Operator will also silently overwrite any change made to a managed client through the admin console, and the client CRs have to live in Keycloak’s own namespace, which constrains who can create them.
If you already run Keycloak on Kubernetes and configure clients with Terraform, this does not replace that. It gives you a second option with a different shape, and the interesting question is which parts of your configuration belong in each once the feature stabilises.
Why did Admin REST API v1 make GitOps awkward?
The original Admin REST API grew alongside the admin console rather than as a contract, and the Keycloak team says so directly. The 26.7 release notes describe the new API as providing “strict validation, support for declarative configuration, and an accurate OpenAPI specification that enables reliable client generation,” and state plainly that this “addresses long-standing limitations of the original Admin REST API” (Keycloak, 26.7.0 release notes, 2026).
Reading that against the v1 API as it exists, the specific friction is easy to locate. The ClientRepresentation object carries a large, loosely typed attributes map where meaningful configuration hides as stringly-typed keys, which is why so much Keycloak client config is documented as a magic string rather than a field. That shape is also what makes generated clients unreliable, since a map of strings tells a generator nothing. The release notes do not enumerate the individual complaints, so treat the specifics here as our reading of the v1 surface rather than upstream’s list.
Admin API v2 is the response, starting with clients as its first resource and more to follow in later releases.
How do you enable the Client Admin API v2 feature?
Add client-admin-api:v2 to the feature list on the Keycloak CR. Note that the Keycloak CR itself is v2beta1 while the client CRs are v2alpha1, which is not a typo, it reflects their different maturity.
apiVersion: k8s.keycloak.org/v2beta1
kind: Keycloak
metadata:
name: example-kc
spec:
features:
enabled:
- client-admin-api:v2
Running the server directly rather than through the Operator, it is the same flag on the command line:
bin/kc.sh start --features=client-admin-api:v2
Enabling the feature also switches on the OpenAPI specification endpoint on the management interface, which is what lets the CLI adapt its commands to whichever server version it is talking to.
The feature is registered in Keycloak’s source as experimental, alongside an experimental OpenAPI feature that depends on it (Keycloak source, Profile.Feature, release 26.7 branch). Both the Admin API v2 reference and the Operator client guide open with Keycloak’s standard experimental banner: “This feature is experimental and may introduce breaking changes in future versions of Keycloak. Do not use this feature in production environments.” That is upstream’s position, not a hedge, and it is the frame for everything below. Read the rest of this post as an evaluation guide for a development or staging cluster, with production adoption waiting on the feature graduating.
What does the Operator need before it can manage clients?
It needs one Secret created by hand, and optionally a TLS Secret. The Operator authenticates to Keycloak using the client credentials flow against the master realm, and it reads those credentials from a Kubernetes Secret named <keycloak-cr-name>-admin in the same namespace, containing client-id and client-secret keys (Keycloak, “Managing Keycloak Clients” Operator guide, 2026).
apiVersion: v1
kind: Secret
metadata:
name: example-kc-admin
stringData:
client-id: operator-admin
client-secret: "..."
The name is derived, not configurable, so a Keycloak CR called example-kc looks for example-kc-admin. The Keycloak guide notes this Secret must be created manually as part of the bootstrap admin setup, and that a future release may automate it.
If your Keycloak requires client certificates, spec.admin.tlsSecret on the Keycloak CR names a Kubernetes TLS Secret holding the Operator’s tls.crt and tls.key, which the server must trust.
The Operator’s Helm chart installs the client CRDs as part of the release, keycloakoidcclients and keycloaksamlclients alongside the existing keycloaks and keycloakrealmimports. If your CRDs are managed out of band, --set crds.enabled=false keeps the chart out of that. The chart itself is described upstream as experimental, and its cluster-wide install is a preview mode, so it carries the same caveat as the feature it installs.
One more prerequisite is structural rather than a resource. Because a client CR resolves its Keycloak CR by name within its own namespace, the client CRs have to live in the same namespace as the Keycloak CR they point at. Upstream is also explicit about what that implies for access: “Anyone with the ability to create or edit the CRs used by the operator (Keycloak, KeycloakRealmImport, KeycloakOIDCClient, etc.) should be a namespace-level admin.”
What does a KeycloakOIDCClient look like?
The manifest is compact, because v2 replaces the attribute map with typed fields.
apiVersion: k8s.keycloak.org/v2alpha1
kind: KeycloakOIDCClient
metadata:
name: my-oidc-app # becomes the clientId, immutable
spec:
keycloakCRName: example-kc # must match the Keycloak CR name, immutable
realm: my-realm # immutable
client:
loginFlows:
- STANDARD
redirectUris:
- "https://my-app.example.com/*"
Three fields are immutable after creation: the client ID derived from metadata.name, the keycloakCRName, and the realm. Changing any of them means deleting and recreating the client, so these are worth naming carefully the first time.
The SAML twin follows the same shape with protocol-specific fields:
apiVersion: k8s.keycloak.org/v2alpha1
kind: KeycloakSAMLClient
metadata:
name: my-saml-app
spec:
keycloakCRName: example-kc
realm: my-realm
client:
nameIdFormat: persistent
signDocuments: true
signAssertions: true
forcePostBinding: true
Confidential clients should not carry their secret inline. The auth block takes a secretRef pointing at a Kubernetes Secret, which keeps the credential out of the manifest you commit:
spec:
client:
auth:
method: client-secret
secretRef:
name: my-app-credentials
key: client-secret-key
When the value in that Secret changes, the Operator picks it up within its polling interval and updates the client on the server. If the Secret does not exist, the CR reports a HasErrors status condition rather than failing silently, which is a meaningful improvement over the v1 experience. Marking the reference optional: true suppresses that condition, which is useful when a secret operator populates the Kubernetes Secret asynchronously.
Checking status is a kubectl get away:
kubectl get keycloakoidcclients/my-oidc-app -o go-template='{{range .status.conditions}}CONDITION: {{.type}}{{"n"}} STATUS: {{.status}}{{"n"}} MESSAGE: {{.message}}{{"n"}}{{end}}'
A healthy client reports HasErrors: false. The common causes of a true are the client-admin-api:v2 feature not being enabled, a missing secret reference, or the Keycloak server not being ready yet. The Operator retries periodically, so a client CR applied before the server comes up settles on its own.
What happens when somebody edits the client in the admin console?
The Operator overwrites it, and the Keycloak documentation is blunt about this: “Do not modify operator-managed clients through the Admin UI or API, including server-side client secret rotation. The operator does not detect external changes and will overwrite them” (Keycloak, “Managing Keycloak Clients” Operator guide, 2026). A future release will add resource ownership controls that prevent those edits automatically, and nothing stops them today.
That single behavior does a lot to determine whether the feature suits your environment, and the answer depends less on the technology than on how your teams work. Where the same people own both the cluster and the realm, declarative clients fit naturally. Where a support engineer occasionally adjusts a redirect URI in the console during an incident, a silent overwrite on the next reconcile is a genuinely nasty failure mode, because the change works, the incident resolves, and the client breaks again hours later with no obvious cause.
Until ownership controls land, the mitigation is to restrict admin console permissions on Operator-managed clients using Keycloak’s fine-grained admin permissions, and to put a note in the client’s description field saying where the source of truth lives.
Two further behaviors are worth understanding before adoption. The CR must contain the complete desired configuration rather than a patch, so a field you leave out is a field you are asserting should be absent. Deleting the CR deletes the client on the server, with one ordering trap: if the Keycloak CR is deleted first, the Operator cannot reach the server and the client stays behind.
Should you use CRDs, Terraform, or the CLI?
All three now work, and they suit different parts of the same system.
| Approach | Best at | Weak at |
|---|---|---|
| Operator CRs | Clients managed alongside the Keycloak CR, in its namespace, on a cluster you control | Production today; anything off-cluster; realm-level configuration; the overwrite behavior above |
| Terraform | Whole-realm configuration, cross-environment promotion, existing state workflows | Reacting quickly to app lifecycle; needs credentials and network reach from the runner |
| Admin API v2 CLI | Scripts, one-off migrations, CI steps that are not full IaC | Long-term desired state; it is imperative by nature |
The namespace constraint shapes this more than the tooling does. Because a KeycloakOIDCClient resolves its Keycloak CR by name inside its own namespace, client CRs cannot ship in an application team’s namespace alongside the application. They live with Keycloak, applied by whoever holds namespace-admin there. That makes Operator CRs a platform-team tool for clients the platform team already owns, not a self-service delegation mechanism for application teams. If self-service is the goal, the Admin API v2 over HTTP behind your own thin approval layer gets closer than CRs do.
Terraform keeps the advantage for whole-realm configuration: realm settings, authentication flows, identity providers and groups change on a slower clock, and Terraform’s state model and plan output suit that. Advanced Terraform patterns for Keycloak covers the platform half, using Terraform to set up and configure Keycloak is the starting point if you are new to that provider, and the Skycloak Terraform provider compared to the community Keycloak one covers the difference between managing a cluster and managing what is inside it.
Pointing two systems at the same object is where this goes wrong. Two controllers both asserting desired state for one client produces a reconcile loop, and neither will tell you why.
The v2 API is also consumable outside Kubernetes entirely. There is a Java client, an auto-generated JavaScript client, and a CLI that takes a --v2 flag:
bin/kcadm.sh --v2 config credentials
--server http://localhost:8080 --realm master --user admin
The JavaScript client needs enableExperimentalApis: true when constructing it, which is a useful reminder of the maturity level. For the v1 surface that still covers everything except clients, the Keycloak Admin REST API guide remains the reference, and bulk pagination against the Admin API covers the part that bites at scale. If you want a starting point to hand-check a client’s shape before committing a CR, the Keycloak config generator produces realm and client configuration you can diff against.
Who owns desired state when somebody else runs the cluster?
The namespace constraint is worth thinking through before you design a workflow around these CRs, because it decides who can use them at all.
Client CRs live beside the Keycloak CR, and upstream expects anyone able to create or edit them to be a namespace-level admin in that namespace. On a cluster you run yourself, that is a platform-team workflow: the same people who own the Keycloak deployment also own the client manifests, and application teams reach them through pull requests into a platform repository rather than through their own namespaces. On a hosted Keycloak, where you do not own the Keycloak CR or its namespace, client CRs are not the interface available to you at all. The Admin API v2 over HTTP and the kcadm --v2 CLI are, once a provider exposes the feature.
That distinction matters for Skycloak specifically, and the honest answer today is that this is not yet a Skycloak workflow. Skycloak runs upstream Keycloak rather than a fork, so the API behaves as documented, but Skycloak’s per-cluster feature management exposes supported and preview features, and client-admin-api:v2 is neither. Anyone wanting to evaluate declarative clients right now should do it on a cluster they control. If you are weighing running the platform half yourself, is self-hosting Keycloak worth it in 2026 works through the cost, and Helm versus the Operator on Kubernetes covers the deployment decision that comes first. Teams already running ArgoCD will find the Keycloak on Kubernetes with ArgoCD walkthrough is where these CRs would eventually sit.
Frequently asked questions
Is Keycloak Admin API v2 production ready?
It is not, by Keycloak’s own labelling. The client-admin-api:v2 feature is registered as experimental in the 26.7 source, and both upstream guides carry the banner “Do not use this feature in production environments.” The Keycloak team is collecting feedback through GitHub discussion 50186, which is the appropriate way to engage with it for now.
What does Admin API v2 cover besides clients?
It covers clients only at this stage. The 26.7 release notes describe clients as “the first resource in the Admin API v2, with additional resources to follow in future releases.” Realms, users, groups, roles and identity providers are still v1 territory, so any complete automation story today is a mix of both.
Does the Operator overwrite manual changes to clients?
It does, and without warning. The Keycloak documentation states the Operator does not detect external changes and will overwrite them, including server-side client secret rotation. A future release plans resource ownership controls to prevent those edits. Until then, restricting console permissions on managed clients is the practical guard.
Can application teams manage their own client CRs?
Not directly. A client CR resolves its Keycloak CR by name within its own namespace, so client CRs have to live in Keycloak’s namespace, and upstream expects anyone who can create them there to be a namespace-level admin. The workable pattern is pull requests into a platform-owned repository rather than self-service in each team’s own namespace.
Can I use Terraform and the Operator together?
Yes, provided they own different objects. Terraform handling realm-level configuration and the Operator handling per-application clients works well, because each object has exactly one system asserting its desired state. Pointing both at the same client produces a reconcile loop with no useful error message.
What Kubernetes CRDs does the client feature add?
Two: keycloakoidcclients.k8s.keycloak.org and keycloaksamlclients.k8s.keycloak.org, both at v2alpha1. The Operator’s Helm chart installs them alongside the existing keycloaks and keycloakrealmimports CRDs, and --set crds.enabled=false skips that if you manage CRDs separately.
How do I store a client secret without committing it?
Use the auth.secretRef block to point at a Kubernetes Secret holding the value, rather than putting the secret inline in the CR. The Operator reads it from there and picks up changes within its polling interval, which keeps the manifest safe to commit and works with external secret operators that populate Kubernetes Secrets from a vault.
Sources
- Keycloak, “Keycloak 26.7.0 release notes,” declarative client management with Admin API v2, retrieved 2026-09-16, https://github.com/keycloak/keycloak/blob/main/docs/documentation/release_notes/topics/26_7_0.adoc
- Keycloak, “Managing Keycloak Clients,” Operator guide, retrieved 2026-09-16, https://github.com/keycloak/keycloak/blob/main/docs/guides/operator/managing-clients.adoc
- Keycloak, “Admin API v2: Reference,” retrieved 2026-09-16, https://github.com/keycloak/keycloak/blob/main/docs/guides/admin-api/admin-api-v2.adoc
- Keycloak,
Profile.Featuresource,client-admin-api:v2registered as experimental, release 26.7 branch, retrieved 2026-09-16, https://github.com/keycloak/keycloak/blob/release/26.7/common/src/main/java/org/keycloak/common/Profile.java - Keycloak, “Client Admin API v2” GitHub discussion 50186, retrieved 2026-09-16, https://github.com/keycloak/keycloak/discussions/50186
- Keycloak,
KeycloakClientBaseControllersource, client CRs resolving the Keycloak CR within their own namespace, retrieved 2026-09-16, https://github.com/keycloak/keycloak/blob/main/operator/src/main/java/org/keycloak/operator/controllers/KeycloakClientBaseController.java - Keycloak, “Basic Keycloak deployment,” Operator guide, on namespace-level admin expectations for operator CRs, retrieved 2026-09-16, https://github.com/keycloak/keycloak/blob/main/docs/guides/operator/basic-deployment.adoc
- Keycloak, experimental feature banner template, retrieved 2026-09-16, https://github.com/keycloak/keycloak/blob/main/docs/guides/templates/experimental-feature.adoc