diff --git a/.changeset/nextjs-organization-profile-update.md b/.changeset/nextjs-organization-profile-update.md
new file mode 100644
index 000000000..abb72c243
--- /dev/null
+++ b/.changeset/nextjs-organization-profile-update.md
@@ -0,0 +1,5 @@
+---
+'@asgardeo/nextjs': patch
+---
+
+Editing an organization through `` works. The component called the Organizations API from the browser without an access token (the token lives in the HttpOnly session cookie), so every save was rejected. Updates now go through a server action (`updateOrganizationAction`, backed by `AsgardeoNextClient.updateOrganization()`) that attaches the token on the server, and a failed save surfaces its reason instead of a bare request error.
diff --git a/packages/nextjs/src/AsgardeoNextClient.ts b/packages/nextjs/src/AsgardeoNextClient.ts
index 3d4426a3c..5b2097be6 100644
--- a/packages/nextjs/src/AsgardeoNextClient.ts
+++ b/packages/nextjs/src/AsgardeoNextClient.ts
@@ -39,6 +39,7 @@ import {
Storage,
TokenExchangeRequestConfig,
TokenResponse,
+ UpdateOrganizationConfig,
User,
UserProfile,
createOrganization,
@@ -56,6 +57,7 @@ import {
getSchemas,
initializeEmbeddedSignInFlow,
updateMeProfile,
+ updateOrganization,
} from '@asgardeo/node';
import {AsgardeoNextConfig} from './models/config';
import getClientOrigin from './server/actions/getClientOrigin';
@@ -342,6 +344,45 @@ class AsgardeoNextClient exte
}
}
+ /**
+ * Updates an organization with a set of patch operations, using the access token of the session.
+ *
+ * @param organizationId - The ID of the organization to update.
+ * @param operations - The patch operations to apply.
+ * @param userId - Optional session ID.
+ * @returns The updated organization.
+ */
+ async updateOrganization(
+ organizationId: string,
+ operations: UpdateOrganizationConfig['operations'],
+ userId?: string,
+ ): Promise {
+ try {
+ const configData: AuthClientConfig = await this.asgardeo.getConfigData();
+ const baseUrl: string = configData?.baseUrl as string;
+
+ const organization: OrganizationDetails = await updateOrganization({
+ baseUrl,
+ headers: {
+ Authorization: `Bearer ${await this.getAccessToken(userId)}`,
+ },
+ operations,
+ organizationId,
+ });
+
+ return organization;
+ } catch (error) {
+ throw new AsgardeoRuntimeError(
+ `Failed to update the organization ${organizationId}: ${
+ error instanceof Error ? error.message : String(error)
+ }`,
+ 'AsgardeoNextClient-updateOrganization-RuntimeError-001',
+ 'nextjs',
+ `An error occurred while updating the organization with the id: ${organizationId}.`,
+ );
+ }
+ }
+
override async getMyOrganizations(options?: any, userId?: string): Promise {
try {
const configData: AuthClientConfig = await this.asgardeo.getConfigData();
diff --git a/packages/nextjs/src/client/components/presentation/OrganizationProfile/OrganizationProfile.tsx b/packages/nextjs/src/client/components/presentation/OrganizationProfile/OrganizationProfile.tsx
index ef441fe95..591ef617a 100644
--- a/packages/nextjs/src/client/components/presentation/OrganizationProfile/OrganizationProfile.tsx
+++ b/packages/nextjs/src/client/components/presentation/OrganizationProfile/OrganizationProfile.tsx
@@ -18,11 +18,12 @@
'use client';
-import {OrganizationDetails, updateOrganization, createPatchOperations} from '@asgardeo/node';
+import {OrganizationDetails, createPatchOperations} from '@asgardeo/node';
import {BaseOrganizationProfile, BaseOrganizationProfileProps, useTranslation} from '@asgardeo/react';
import {FC, ReactElement, useEffect, useState} from 'react';
import getOrganizationAction from '../../../../server/actions/getOrganizationAction';
import getSessionId from '../../../../server/actions/getSessionId';
+import updateOrganizationAction from '../../../../server/actions/updateOrganizationAction';
import logger from '../../../../utils/logger';
import useAsgardeo from '../../../contexts/Asgardeo/useAsgardeo';
@@ -188,11 +189,15 @@ const OrganizationProfile: FC = ({
const operations: Array<{operation: 'REPLACE' | 'REMOVE'; path: string; value?: any}> =
createPatchOperations(payload);
- await updateOrganization({
- baseUrl,
- operations,
- organizationId,
- });
+ // The access token only exists on the server (HttpOnly session cookie), so the update goes through
+ // a server action rather than calling the Organizations API from the browser.
+ const result: {data: {organization?: OrganizationDetails}; error: string | null; success: boolean} =
+ await updateOrganizationAction(organizationId, operations, (await getSessionId()) as string);
+
+ if (!result.success) {
+ throw new Error(result.error ?? 'Failed to update organization');
+ }
+
// Refetch organization data after update
await fetchOrganization();
diff --git a/packages/nextjs/src/server/actions/__tests__/updateOrganizationAction.test.ts b/packages/nextjs/src/server/actions/__tests__/updateOrganizationAction.test.ts
new file mode 100644
index 000000000..93262275b
--- /dev/null
+++ b/packages/nextjs/src/server/actions/__tests__/updateOrganizationAction.test.ts
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com).
+ *
+ * WSO2 LLC. licenses this file to you 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 {beforeEach, describe, expect, it, vi, Mock} from 'vitest';
+import AsgardeoNextClient from '../../../AsgardeoNextClient';
+import updateOrganizationAction from '../updateOrganizationAction';
+
+vi.mock('../../../AsgardeoNextClient', () => ({
+ default: {
+ getInstance: vi.fn(),
+ },
+}));
+
+describe('updateOrganizationAction', () => {
+ type ActionResult = Awaited>;
+
+ const client: {updateOrganization: Mock} = {updateOrganization: vi.fn()};
+ const operations: Array<{operation: 'REPLACE'; path: string; value: string}> = [
+ {operation: 'REPLACE', path: '/name', value: 'Acme Inc.'},
+ ];
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ (AsgardeoNextClient.getInstance as unknown as Mock).mockReturnValue(client);
+ });
+
+ it('returns the updated organization when the update succeeds', async () => {
+ const organization: Record = {id: 'org-1', name: 'Acme Inc.'};
+
+ client.updateOrganization.mockResolvedValue(organization);
+
+ const result: ActionResult = await updateOrganizationAction('org-1', operations, 'session-1');
+
+ expect(client.updateOrganization).toHaveBeenCalledWith('org-1', operations, 'session-1');
+ expect(result).toEqual({data: {organization}, error: null, success: true});
+ });
+
+ it('reports the failure reason instead of throwing when the update fails', async () => {
+ client.updateOrganization.mockRejectedValue(new Error('Failed to update the organization org-1: forbidden'));
+
+ const result: ActionResult = await updateOrganizationAction('org-1', operations);
+
+ expect(result.success).toBe(false);
+ expect(result.error).toBe('Failed to update the organization org-1: forbidden');
+ expect(result.data.organization).toBeUndefined();
+ });
+});
diff --git a/packages/nextjs/src/server/actions/updateOrganizationAction.ts b/packages/nextjs/src/server/actions/updateOrganizationAction.ts
new file mode 100644
index 000000000..9ad0d11f1
--- /dev/null
+++ b/packages/nextjs/src/server/actions/updateOrganizationAction.ts
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
+ *
+ * WSO2 LLC. licenses this file to you 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.
+ */
+
+'use server';
+
+import {OrganizationDetails, UpdateOrganizationConfig} from '@asgardeo/node';
+import AsgardeoNextClient from '../../AsgardeoNextClient';
+
+/**
+ * Server action to update an organization with a set of patch operations.
+ *
+ * The access token stays on the server: it is read from the session cookie and attached to the request,
+ * which is why the browser cannot call the Organizations API directly.
+ *
+ * @param organizationId - The ID of the organization to update.
+ * @param operations - The patch operations to apply (see `createPatchOperations`).
+ * @param sessionId - Optional session ID; resolved from the session cookie when omitted.
+ */
+const updateOrganizationAction = async (
+ organizationId: string,
+ operations: UpdateOrganizationConfig['operations'],
+ sessionId?: string,
+): Promise<{
+ data: {organization?: OrganizationDetails};
+ error: string | null;
+ success: boolean;
+}> => {
+ try {
+ const client: AsgardeoNextClient = AsgardeoNextClient.getInstance();
+ const organization: OrganizationDetails = await client.updateOrganization(organizationId, operations, sessionId);
+
+ return {data: {organization}, error: null, success: true};
+ } catch (error) {
+ return {
+ data: {},
+ error: error instanceof Error ? error.message : String(error),
+ success: false,
+ };
+ }
+};
+
+export default updateOrganizationAction;