Skip to content

Add custom attributes support to organization invitations#48842

Open
kota65535 wants to merge 2 commits into
keycloak:mainfrom
kota65535:vk/e990-invitation-attri
Open

Add custom attributes support to organization invitations#48842
kota65535 wants to merge 2 commits into
keycloak:mainfrom
kota65535:vk/e990-invitation-attri

Conversation

@kota65535

@kota65535 kota65535 commented May 8, 2026

Copy link
Copy Markdown
Contributor

Closes: #48837

Summary

  • Add key-value custom attributes to organization invitations
  • Add application/json request body support to invite-user and invite-existing-user endpoints
  • Preserve attributes on invitation resend
  • Add Admin UI for managing invitation attributes with a 2-step modal flow

Changes

Backend

  • New ORG_INVITATION_ATTRIBUTE table (jpa-changelog-26.7.0.xml) with FK cascade delete
  • New OrganizationInvitationAttributeEntity JPA entity
  • Extend OrganizationInvitationModel SPI with getAttributes() / setAttribute() / removeAttribute()
  • Extend InvitationManager.createInvitation() with optional attributes parameter (backward-compatible default method)
  • Add JSON @Consumes variants for invite-user and invite-existing-user endpoints alongside existing application/x-www-form-urlencoded endpoints
  • New request types: OrganizationInvitationUserRequest, OrganizationInvitationExistingUserRequest
  • Add attributes field to OrganizationInvitationRepresentation

Admin UI

  • InviteMemberModal: switch from FormData to JSON body, add key-value attributes input (single-step flow for email invitations)
  • InvitationAttributesModal: reusable attributes modal shared across all invite flows
  • Invitations.tsx: 2-step flow using MemberModal (user selection) → InvitationAttributesModal (attributes) for existing user invitations
  • Organizations.tsx (user detail page): 2-step flow using OrganizationModal (org selection) → InvitationAttributesModal (attributes)
  • Invitations: show attributes count with tooltip displaying key-value details on hover

Organization details → Invite new user

Using InviteMemberModal:

image

Organization details → Invite existing user

Using OrganizationModal and InvitationAttributesModal (2-step flow):

image


スクリーンショット 2026-05-09 3 27 28

User details → Send invitation

Using MemberModal and InvitationAttributesModal (2-step flow):

image


スクリーンショット 2026-05-09 3 27 28

Attributes in an invitation list

Showing the number of the attributes:

image

Showing the attribute in a tooltip on hover:
image

Admin Client (JS)

  • Add InviteUserRequest / InviteExistingUserRequest type definitions
  • Add inviteUserJson / inviteExistingUserJson methods to organizations resource

Tests

  • Invitation creation and retrieval with attributes
  • Invitation resend preserves attributes
  • Backward compatibility without attributes
  • Existing user invitation with attributes
  • Tests written using the new test framework (tests/base/)

API

POST /admin/realms/{realm}/organizations/{id}/members/invite-user
Content-Type: application/json

{
  "email": "user@example.com",
  "firstName": "John",
  "lastName": "Doe",
  "attributes": {
    "department": ["engineering"],
    "role": ["developer", "reviewer"]
  }
}
POST /admin/realms/{realm}/organizations/{id}/members/invite-existing-user
Content-Type: application/json

{
  "id": "<user-id>",
  "attributes": {
    "department": ["engineering"]
  }
}

Migration

  • New ORG_INVITATION_ATTRIBUTE table added in jpa-changelog-26.7.0.xml
  • No impact on existing data

Backward Compatibility

  • Existing application/x-www-form-urlencoded endpoints remain unchanged
  • InvitationManager SPI maintains existing method signatures via default methods
  • Invitations without attributes continue to work as before

AI-assisted development was used in this PR with Claude Code. All changes have been reviewed
and are understood by the author.

@kota65535
kota65535 force-pushed the vk/e990-invitation-attri branch 2 times, most recently from 9691030 to c00970f Compare May 8, 2026 15:52
@kota65535 kota65535 changed the title Add custom attributes support to organization invitations feat: add custom attributes support to organization invitations May 8, 2026
@kota65535
kota65535 force-pushed the vk/e990-invitation-attri branch 4 times, most recently from 5c0f2b1 to c51e773 Compare May 8, 2026 18:24
@kota65535
kota65535 marked this pull request as ready for review May 8, 2026 19:46
@kota65535
kota65535 requested review from a team as code owners May 8, 2026 19:46
@kota65535 kota65535 changed the title feat: add custom attributes support to organization invitations Add custom attributes support to organization invitations May 9, 2026
edewit
edewit previously approved these changes May 11, 2026

@edewit edewit left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

other then some small nit picks looks good from a UI perspective

Comment thread js/apps/admin-ui/src/user/Organizations.tsx Outdated
Comment on lines +45 to +55
const attributes = keyValueToArray(data.attributes);
await adminClient.organizations.inviteUserJson(
{ orgId },
{
email: data.email,
firstName: data.firstName,
lastName: data.lastName,
attributes:
Object.keys(attributes).length > 0 ? attributes : undefined,
},
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bit of a nit pick, but this could be a lot shorter:

Suggested change
const attributes = keyValueToArray(data.attributes);
await adminClient.organizations.inviteUserJson(
{ orgId },
{
email: data.email,
firstName: data.firstName,
lastName: data.lastName,
attributes:
Object.keys(attributes).length > 0 ? attributes : undefined,
},
);
const { attr, ...rest } = data;
const attributes = keyValueToArray(attr) && undefined;
await adminClient.organizations.inviteUserJson({ orgId }, { ...rest, attributes })

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, attributes should be undefined if it is empty here, but with this code keyValueToArray(attr) && undefined doesn't return undefined because an empty object {} is truthy.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but it's an array not an object [] && undefined returns undefined

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for sharing the snippet, but the return type of keyValueToArray function appears to be Object.

const result: Record<string, string[]> = {};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ohh right but then my version is still a bit shorter:

const { attr, ...rest } = data;
const attributes = keyValueToArray(attr);
await adminClient.organizations.inviteUserJson({ orgId }, { ...rest, attributes: Object.keys(attributes).length ? attributes : undefined });

but couldn't we fix it on the server that when attributes is an empty object it would still work?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread js/libs/keycloak-admin-client/src/resources/organizations.ts Outdated
@kota65535
kota65535 force-pushed the vk/e990-invitation-attri branch 2 times, most recently from cf3d202 to 8c9ec01 Compare May 12, 2026 00:07
@kota65535
kota65535 force-pushed the vk/e990-invitation-attri branch from 8c9ec01 to 22dd211 Compare May 13, 2026 17:46
@kota65535
kota65535 requested a review from edewit May 13, 2026 19:12
@kota65535
kota65535 force-pushed the vk/e990-invitation-attri branch 2 times, most recently from 62c66a1 to 4878832 Compare May 13, 2026 19:41
@kota65535

Copy link
Copy Markdown
Contributor Author

@keycloak/maintainers Could someone also review the backend/model/API parts of this PR?
This PR changes the invitation model, JPA entity/changelog, admin resource, admin client API and tests, in addition to the Admin UI changes.

Copilot AI review requested due to automatic review settings June 25, 2026 12:19
@kota65535
kota65535 force-pushed the vk/e990-invitation-attri branch from 4878832 to 93fed69 Compare June 25, 2026 12:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds end-to-end support for custom key-value attributes on organization invitations, spanning persistence (new DB table + JPA entities), admin REST API (JSON request bodies + representation updates), admin client typings, Admin UI flows for entering attributes, and integration tests validating creation/resend behavior.

Changes:

  • Persist invitation attributes in a new ORG_INVITATION_ATTRIBUTE table and expose them via the invitation representation.
  • Add JSON application/json variants for invite-user / invite-existing-user endpoints while keeping form-encoded variants for backward compatibility.
  • Update Admin UI invite flows to collect attributes (single-step for email invites, 2-step modal for existing-user invites) and display attribute counts/details in the invitations list.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/base/src/test/java/org/keycloak/tests/organization/admin/OrganizationInvitationAttributeTest.java Adds integration tests covering invitation attributes creation/resend/empty/null behaviors.
services/src/main/java/org/keycloak/organization/admin/resource/OrganizationMemberResource.java Adds JSON-consuming invite endpoints delegating to invitation resource.
services/src/main/java/org/keycloak/organization/admin/resource/OrganizationInvitationResource.java Threads attributes through invite/create/resend and includes them in representations.
server-spi/src/main/java/org/keycloak/organization/InvitationManager.java Extends invitation creation SPI to accept optional attributes (with backward-compatible default).
server-spi/src/main/java/org/keycloak/models/OrganizationInvitationModel.java Adds invitation attributes accessor to the model SPI.
model/jpa/src/main/resources/META-INF/jpa-changelog-26.7.0.xml Adds Liquibase change set creating ORG_INVITATION_ATTRIBUTE with FK cascade delete + index.
model/jpa/src/main/resources/default-persistence.xml Registers the new JPA entity for persistence.
model/jpa/src/main/java/org/keycloak/organization/jpa/JpaInvitationManager.java Persists invitation attributes during invitation creation.
model/jpa/src/main/java/org/keycloak/models/jpa/entities/OrganizationInvitationEntity.java Adds @OneToMany relationship and exposes attributes as Map<String, List<String>>.
model/jpa/src/main/java/org/keycloak/models/jpa/entities/OrganizationInvitationAttributeEntity.java New JPA entity mapping for invitation attributes.
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/InfinispanInvitationManager.java Forwards attribute-aware create calls to delegate manager.
js/libs/keycloak-admin-client/src/resources/organizations.ts Expands invite request payload typing to allow JSON request bodies.
js/libs/keycloak-admin-client/src/defs/organizationInvitationRequest.ts Adds admin-client request type definitions for JSON invite endpoints.
js/libs/keycloak-admin-client/src/defs/organizationInvitationRepresentation.ts Adds attributes to JS invitation representation type.
js/apps/admin-ui/src/user/Organizations.tsx Implements 2-step “select orgs → set attributes” flow for inviting an existing user.
js/apps/admin-ui/src/organizations/OrganizationModal.tsx Updates confirm label to “next” for 2-step flow.
js/apps/admin-ui/src/organizations/InviteMemberModal.tsx Switches email-invite to JSON body and adds attribute input.
js/apps/admin-ui/src/organizations/Invitations.tsx Implements 2-step invite flow for existing users and displays attributes count/tooltip.
js/apps/admin-ui/src/organizations/InvitationAttributesModal.tsx New modal for entering invitation attributes.
js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties Adds i18n string for the attributes modal title.
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/OrganizationMembersResource.java Adds JSON @Consumes methods for invite endpoints in the Java admin client.
core/src/main/java/org/keycloak/representations/idm/OrganizationInvitationUserRequest.java New request DTO for JSON email-invite including attributes.
core/src/main/java/org/keycloak/representations/idm/OrganizationInvitationRepresentation.java Adds attributes to the invitation representation.
core/src/main/java/org/keycloak/representations/idm/OrganizationInvitationExistingUserRequest.java New request DTO for JSON existing-user invite including attributes.

Comment thread server-spi/src/main/java/org/keycloak/models/OrganizationInvitationModel.java Outdated
@kota65535

Copy link
Copy Markdown
Contributor Author

Hi @edewit, sorry for another ping.

This PR seems to touch a few areas beyond the Admin UI, so I’m not sure whether you’re the only reviewer needed here. Could you please help forward this to the right maintainers, or let me know who I should ask for review?

Thanks!

edewit
edewit previously approved these changes Jun 25, 2026

@edewit edewit left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good from a UI point of view

@kota65535

Copy link
Copy Markdown
Contributor Author

@edewit I'm so sorry, I pushed a commit to the wrong branch by mistake, which caused the approval to be dismissed. Could you please approve it again?

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 4 comments.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 5 comments.

Comment thread server-spi/src/main/java/org/keycloak/organization/InvitationManager.java Outdated
Comment thread model/jpa/src/main/resources/META-INF/jpa-changelog-26.7.0.xml Outdated
Comment thread js/apps/admin-ui/src/organizations/InvitationAttributesModal.tsx
Comment thread js/apps/admin-ui/src/organizations/Invitations.tsx
Comment thread js/apps/admin-ui/src/organizations/InvitationAttributesModal.tsx
@kota65535
kota65535 force-pushed the vk/e990-invitation-attri branch from 86d947f to 4f6eb59 Compare July 24, 2026 04:49
Signed-off-by: Tomohiko Ozawa <kota65535@gmail.com>

@keycloak-github-bot keycloak-github-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unreported flaky test detected, please review

@keycloak-github-bot

Copy link
Copy Markdown

Unreported flaky test detected

If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.

org.keycloak.testsuite.forms.MultipleTabsLoginTest#testLoginPageRefresh

Keycloak CI - Forms IT (chrome)

org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertTrue.failNotTrue(AssertTrue.java:63)
	at org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:36)
...

Report flaky test

org.keycloak.testsuite.forms.MultipleTabsLoginTest#multipleTabsParallelLoginTestWithAuthSessionExpiredAndRegisterClick

Keycloak CI - Forms IT (chrome)

org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertTrue.failNotTrue(AssertTrue.java:63)
	at org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:36)
...

Report flaky test

@kota65535

Copy link
Copy Markdown
Contributor Author

Hi @pedroigor, I am mentioning you because this feature is related to Keycloak Organizations. I have resolved all of Copilot's comments and confirmed that all tests pass. Could you please review this PR or assign an appropriate reviewer so that it can be merged?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add attributes support for Organization Invitations

4 participants