diff --git a/.changeset/three-peaches-move.md b/.changeset/three-peaches-move.md new file mode 100644 index 0000000000..f81580112a --- /dev/null +++ b/.changeset/three-peaches-move.md @@ -0,0 +1,5 @@ +--- +"@medusajs/medusa": patch +--- + +fix(medusa): Allow users to unset metadata fields in updates. diff --git a/integration-tests/api/__tests__/admin/product.js b/integration-tests/api/__tests__/admin/product.js index cf19a30054..1e3110478d 100644 --- a/integration-tests/api/__tests__/admin/product.js +++ b/integration-tests/api/__tests__/admin/product.js @@ -1334,6 +1334,37 @@ describe("/admin/products", () => { expect(response.data.product.images.length).toEqual(0) }) + it("updates a product by deleting a field from metadata", async () => { + const api = useApi() + + const product = await simpleProductFactory(dbConnection, { + metadata: { + "test-key": "test-value", + "test-key-2": "test-value-2", + "test-key-3": "test-value-3", + }, + }) + + const payload = { + metadata: { + "test-key": "", + "test-key-2": null, + }, + } + + const response = await api.post( + "/admin/products/" + product.id, + payload, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.product.metadata).toEqual({ + "test-key-2": null, + "test-key-3": "test-value-3", + }) + }) + it("fails to update product with invalid status", async () => { const api = useApi() diff --git a/integration-tests/api/factories/simple-product-factory.ts b/integration-tests/api/factories/simple-product-factory.ts index 39c6c1695b..aaece4f022 100644 --- a/integration-tests/api/factories/simple-product-factory.ts +++ b/integration-tests/api/factories/simple-product-factory.ts @@ -5,17 +5,17 @@ import { ProductType, ShippingProfile, ShippingProfileType, - Store + Store, } from "@medusajs/medusa" import faker from "faker" import { Connection } from "typeorm" import { ProductVariantFactoryData, - simpleProductVariantFactory + simpleProductVariantFactory, } from "./simple-product-variant-factory" import { SalesChannelFactoryData, - simpleSalesChannelFactory + simpleSalesChannelFactory, } from "./simple-sales-channel-factory" export type ProductFactoryData = { @@ -28,6 +28,7 @@ export type ProductFactoryData = { options?: { id: string; title: string }[] variants?: ProductVariantFactoryData[] sales_channels?: SalesChannelFactoryData[] + metadata?: Record } export const simpleProductFactory = async ( @@ -83,6 +84,7 @@ export const simpleProductFactory = async ( discountable: !data.is_giftcard, tags: [] as ProductTag[], profile_id: data.is_giftcard ? gcProfile?.id : defaultProfile?.id, + metadata: data.metadata || null, } as Product if (typeof data.tags !== "undefined") { diff --git a/packages/medusa/src/utils/set-metadata.ts b/packages/medusa/src/utils/set-metadata.ts index e56ca95050..1e01e179f5 100644 --- a/packages/medusa/src/utils/set-metadata.ts +++ b/packages/medusa/src/utils/set-metadata.ts @@ -12,6 +12,7 @@ export function setMetadata( ): Record { const existing = obj.metadata || {} const newData = {} + for (const [key, value] of Object.entries(metadata)) { if (typeof key !== "string") { throw new MedusaError( @@ -19,6 +20,21 @@ export function setMetadata( "Key type is invalid. Metadata keys must be strings" ) } + + /** + * We reserve the empty string as a way to delete a key. + * If the value is an empty string, we don't + * set it, and if it exists in the existing metadata, we + * unset the field. + */ + if (value === "") { + if (key in existing) { + delete existing[key] + } + + continue + } + newData[key] = value }