diff --git a/extensions/organization/pom.xml b/extensions/organization/pom.xml
new file mode 100755
index 000000000000..999a5e81777c
--- /dev/null
+++ b/extensions/organization/pom.xml
@@ -0,0 +1,55 @@
+
+
+
+
+ keycloak-parent
+ org.keycloak
+ 999.0.0-SNAPSHOT
+ ../pom.xml
+
+
+ Keycloak Organization Extension
+ 4.0.0
+
+ keycloak-organization
+ jar
+
+
+
+ org.keycloak
+ keycloak-services
+
+
+ org.keycloak
+ keycloak-junit5
+
+
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+ org.keycloak
+ keycloak-admin-client
+ test
+
+
+
diff --git a/extensions/organization/src/main/java/org/keycloak/organization/OrganizationRepresentation.java b/extensions/organization/src/main/java/org/keycloak/organization/OrganizationRepresentation.java
new file mode 100644
index 000000000000..b18aa370f2bb
--- /dev/null
+++ b/extensions/organization/src/main/java/org/keycloak/organization/OrganizationRepresentation.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2024 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.organization;
+
+public class OrganizationRepresentation {
+
+ private String name;
+ private String id;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/extensions/organization/src/main/java/org/keycloak/organization/admin/resource/OrganizationAdminResource.java b/extensions/organization/src/main/java/org/keycloak/organization/admin/resource/OrganizationAdminResource.java
new file mode 100644
index 000000000000..64741d24daf3
--- /dev/null
+++ b/extensions/organization/src/main/java/org/keycloak/organization/admin/resource/OrganizationAdminResource.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2024 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.organization.admin.resource;
+
+import java.util.stream.Stream;
+
+import jakarta.ws.rs.BadRequestException;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+import org.keycloak.organization.OrganizationRepresentation;
+import org.keycloak.utils.StringUtil;
+
+@Path("/")
+public class OrganizationAdminResource {
+
+ public OrganizationAdminResource() {}
+
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response create(OrganizationRepresentation organizaiton) {
+ return Response.status(Status.CREATED).build();
+ }
+
+ @Path("{id}")
+ @DELETE
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response delete(@PathParam("id") String id) {
+ if (StringUtil.isBlank(id)) {
+ throw new BadRequestException();
+ }
+ return Response.noContent().build();
+ }
+
+ @GET
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Stream get(@PathParam("id") String id) {
+ OrganizationRepresentation organization = new OrganizationRepresentation();
+
+ organization.setId("1");
+ organization.setName("acme");
+
+ return Stream.of(organization);
+ }
+}
diff --git a/extensions/organization/src/main/java/org/keycloak/organization/admin/resource/OrganizationAdminResourceFactory.java b/extensions/organization/src/main/java/org/keycloak/organization/admin/resource/OrganizationAdminResourceFactory.java
new file mode 100644
index 000000000000..69f66209a587
--- /dev/null
+++ b/extensions/organization/src/main/java/org/keycloak/organization/admin/resource/OrganizationAdminResourceFactory.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2024 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.organization.admin.resource;
+
+import org.keycloak.Config.Scope;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.services.resources.admin.ext.AdminRealmResourceProvider;
+import org.keycloak.services.resources.admin.ext.AdminRealmResourceProviderFactory;
+
+public class OrganizationAdminResourceFactory implements AdminRealmResourceProviderFactory {
+
+ private OrganizationAdminResourceProvider PROVIDER_INSTANCE;
+
+ @Override
+ public AdminRealmResourceProvider create(KeycloakSession session) {
+ return PROVIDER_INSTANCE;
+ }
+
+ @Override
+ public void init(Scope config) {
+
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+ PROVIDER_INSTANCE = new OrganizationAdminResourceProvider();
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public String getId() {
+ return "organization";
+ }
+}
diff --git a/extensions/organization/src/main/java/org/keycloak/organization/admin/resource/OrganizationAdminResourceProvider.java b/extensions/organization/src/main/java/org/keycloak/organization/admin/resource/OrganizationAdminResourceProvider.java
new file mode 100644
index 000000000000..eb0358b627fb
--- /dev/null
+++ b/extensions/organization/src/main/java/org/keycloak/organization/admin/resource/OrganizationAdminResourceProvider.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.organization.admin.resource;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
+import org.keycloak.services.resources.admin.ext.AdminRealmResourceProvider;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+
+public class OrganizationAdminResourceProvider implements AdminRealmResourceProvider {
+
+ @Override
+ public Object getResource(KeycloakSession session, RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
+ return new OrganizationAdminResource();
+ }
+
+ @Override
+ public void close() {
+
+ }
+}
diff --git a/extensions/organization/src/main/resources/META-INF/beans.xml b/extensions/organization/src/main/resources/META-INF/beans.xml
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/extensions/organization/src/main/resources/META-INF/services/org.keycloak.services.resources.admin.ext.AdminRealmResourceProviderFactory b/extensions/organization/src/main/resources/META-INF/services/org.keycloak.services.resources.admin.ext.AdminRealmResourceProviderFactory
new file mode 100644
index 000000000000..c79290221fcf
--- /dev/null
+++ b/extensions/organization/src/main/resources/META-INF/services/org.keycloak.services.resources.admin.ext.AdminRealmResourceProviderFactory
@@ -0,0 +1,19 @@
+#
+# /*
+# * Copyright 2024 Red Hat, Inc. and/or its affiliates
+# * and other contributors as indicated by the @author tags.
+# *
+# * Licensed under the Apache License, Version 2.0 (the "License");
+# * you may not use this file except in compliance with the License.
+# * You may obtain a copy of the License at
+# *
+# * http://www.apache.org/licenses/LICENSE-2.0
+# *
+# * Unless required by applicable law or agreed to in writing, software
+# * distributed under the License is distributed on an "AS IS" BASIS,
+# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# * See the License for the specific language governing permissions and
+# * limitations under the License.
+# */
+#
+org.keycloak.organization.admin.resource.OrganizationAdminResourceFactory
\ No newline at end of file
diff --git a/extensions/organization/src/test/java/KeycloakTest.java b/extensions/organization/src/test/java/KeycloakTest.java
new file mode 100644
index 000000000000..cd7b2bad20ae
--- /dev/null
+++ b/extensions/organization/src/test/java/KeycloakTest.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2024 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.keycloak.Keycloak;
+
+public class KeycloakTest {
+
+ public static void main(String[] args) {
+ Keycloak.builder()
+ .addDependency("org.keycloak", "keycloak-organization", "999.0.0-SNAPSHOT")
+ .start(args);
+ }
+}
diff --git a/extensions/organization/src/test/java/org/keycloak/organization/OrganizationAdminRestApiTest.java b/extensions/organization/src/test/java/org/keycloak/organization/OrganizationAdminRestApiTest.java
new file mode 100644
index 000000000000..42eb72fa4397
--- /dev/null
+++ b/extensions/organization/src/test/java/org/keycloak/organization/OrganizationAdminRestApiTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2024 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.organization;
+
+import java.util.List;
+
+import io.restassured.RestAssured;
+import io.restassured.common.mapper.TypeRef;
+import io.restassured.specification.RequestSpecification;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response.Status;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.keycloak.admin.client.Keycloak;
+
+public class OrganizationAdminRestApiTest {
+
+ private static final TypeRef> ORGANIZATION_LIST_TYPE_DEF = new TypeRef>() {};
+ private static final String BASE_SERVER_URL = "http://localhost:8180";
+
+ private Keycloak kcClient;
+ private String accessToken;
+ private RequestSpecification restAssured;
+
+ @BeforeEach
+ public void onBefore() {
+ kcClient = Keycloak.getInstance(BASE_SERVER_URL, "master", "admin", "admin", "admin-cli");
+ accessToken = kcClient.tokenManager().getAccessTokenString();
+ restAssured = RestAssured.given().auth().oauth2(accessToken);
+ }
+
+ @AfterEach
+ public void onAfter() {
+ if (kcClient != null) {
+ kcClient.close();
+ }
+ }
+
+ @Test
+ public void testCreate() {
+ OrganizationRepresentation org = new OrganizationRepresentation();
+
+ org.setName("acme");
+
+ restAssured.body(org)
+ .contentType(MediaType.APPLICATION_JSON)
+ .post(BASE_SERVER_URL.concat("/admin/realms/master/organization"))
+ .then()
+ .statusCode(Status.CREATED.getStatusCode());
+ }
+
+ @Test
+ public void testDelete() {
+ OrganizationRepresentation org = new OrganizationRepresentation();
+
+ org.setId("1");
+
+ restAssured.delete(BASE_SERVER_URL.concat("/admin/realms/master/organization/{id}"), org.getId())
+ .then()
+ .statusCode(Status.NO_CONTENT.getStatusCode());
+ }
+
+ @Test
+ public void testGet() {
+ List organizations = restAssured
+ .get(BASE_SERVER_URL.concat("/admin/realms/master/organization"))
+ .as(ORGANIZATION_LIST_TYPE_DEF);
+ Assertions.assertFalse(organizations.isEmpty());
+ }
+}
diff --git a/extensions/organization/src/test/java/org/keycloak/organization/admin/resource/OrganizationTestProvider.java b/extensions/organization/src/test/java/org/keycloak/organization/admin/resource/OrganizationTestProvider.java
new file mode 100644
index 000000000000..f62367e3532d
--- /dev/null
+++ b/extensions/organization/src/test/java/org/keycloak/organization/admin/resource/OrganizationTestProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2024 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.organization.admin.resource;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.keycloak.it.TestProvider;
+import org.keycloak.organization.OrganizationRepresentation;
+
+public class OrganizationTestProvider implements TestProvider {
+
+ @Override
+ public Class[] getClasses() {
+ return new Class[] {
+ OrganizationAdminResource.class,
+ OrganizationRepresentation.class,
+ OrganizationAdminResourceProvider.class,
+ OrganizationAdminResourceFactory.class
+ };
+ }
+
+ @Override
+ public Map getManifestResources() {
+ return Collections.singletonMap("org.keycloak.services.resources.admin.ext.AdminRealmResourceProviderFactory", "services/org.keycloak.services.resources.admin.ext.AdminRealmResourceProviderFactory");
+ }
+}
diff --git a/extensions/organization/src/test/resources/org/keycloak/organization/admin/resource/org.keycloak.services.resources.admin.ext.AdminRealmResourceProviderFactory b/extensions/organization/src/test/resources/org/keycloak/organization/admin/resource/org.keycloak.services.resources.admin.ext.AdminRealmResourceProviderFactory
new file mode 100644
index 000000000000..c79290221fcf
--- /dev/null
+++ b/extensions/organization/src/test/resources/org/keycloak/organization/admin/resource/org.keycloak.services.resources.admin.ext.AdminRealmResourceProviderFactory
@@ -0,0 +1,19 @@
+#
+# /*
+# * Copyright 2024 Red Hat, Inc. and/or its affiliates
+# * and other contributors as indicated by the @author tags.
+# *
+# * Licensed under the Apache License, Version 2.0 (the "License");
+# * you may not use this file except in compliance with the License.
+# * You may obtain a copy of the License at
+# *
+# * http://www.apache.org/licenses/LICENSE-2.0
+# *
+# * Unless required by applicable law or agreed to in writing, software
+# * distributed under the License is distributed on an "AS IS" BASIS,
+# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# * See the License for the specific language governing permissions and
+# * limitations under the License.
+# */
+#
+org.keycloak.organization.admin.resource.OrganizationAdminResourceFactory
\ No newline at end of file
diff --git a/extensions/pom.xml b/extensions/pom.xml
new file mode 100755
index 000000000000..89e1f8ab6478
--- /dev/null
+++ b/extensions/pom.xml
@@ -0,0 +1,37 @@
+
+
+
+
+ keycloak-parent
+ org.keycloak
+ 999.0.0-SNAPSHOT
+ ../pom.xml
+
+
+ Keycloak Extension Parent
+
+ 4.0.0
+
+ keycloak-extension-parent
+ pom
+
+
+ organization
+
+
diff --git a/pom.xml b/pom.xml
index 42945c7691a9..b15b608d948d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2080,5 +2080,17 @@
+
+ extensions
+
+
+ extensions
+
+
+
+ extensions
+
+
+