diff --git a/ui/litellm-dashboard/src/components/templates/key_info_view.test.tsx b/ui/litellm-dashboard/src/components/templates/key_info_view.test.tsx index 6b5732436a..724c961bbd 100644 --- a/ui/litellm-dashboard/src/components/templates/key_info_view.test.tsx +++ b/ui/litellm-dashboard/src/components/templates/key_info_view.test.tsx @@ -757,5 +757,24 @@ describe("KeyInfoView", () => { expect.objectContaining({ policies: [] }), ); }); + + it("should keep an empty policies field when the previous value lives only at the top level of keyData", async () => { + // Defensive: some premium fields may be present at the top level but not + // mirrored into metadata. A genuine clear must still be forwarded. + const keyData: KeyResponse = { + ...MOCK_KEY_DATA, + user_id: "proxy-admin-user", + metadata: {}, + policies: ["existing-policy"], + } as KeyResponse; + + await enterEditMode(keyData); + await editViewMocks.onSubmit!({ key: keyData.token, token: keyData.token, policies: [] }); + + expect(keyUpdateCall).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ policies: [] }), + ); + }); }); }); diff --git a/ui/litellm-dashboard/src/components/templates/key_info_view.tsx b/ui/litellm-dashboard/src/components/templates/key_info_view.tsx index bdb8254c72..492e43cbc8 100644 --- a/ui/litellm-dashboard/src/components/templates/key_info_view.tsx +++ b/ui/litellm-dashboard/src/components/templates/key_info_view.tsx @@ -166,7 +166,9 @@ export default function KeyInfoView({ // state; without this, the next save resends `[]` and trips the premium // gate in prepare_metadata_fields for non-premium users. for (const field of PREMIUM_METADATA_FIELDS) { - const previousValue = (currentKeyData.metadata as Record | undefined)?.[field]; + const previousValue = + (currentKeyData.metadata as Record | undefined)?.[field] ?? + (currentKeyData as unknown as Record)[field]; if (isEmptyValue(formValues[field]) && isEmptyValue(previousValue)) { delete formValues[field]; }