Manage Products with the Admin API and JS SDK
In this guide, you'll learn how to create and manage products from a client application, such as a script or an external admin, using the JS SDK and the Admin API routes.
Authenticate Admin Requests#
All Admin API routes require an authenticated admin user. You can authenticate the JS SDK with a session, a JWT token, or a secret API key.
For server-side scripts, a secret API key is usually the simplest option:
Learn more about the authentication options in the JS SDK Authentication guide.
The rest of this guide uses the JS SDK methods and mentions the Admin API route that each method sends a request to.
Upload Product Images#
Product images are stored as URLs. So, before you create or update a product with new images, upload the image files using the sdk.admin.upload.create method. It sends a POST request to /admin/uploads as multipart/form-data.
For example:
The File Module Provider configured in your Medusa application stores the files and returns their URLs. You then pass these URLs in the product's images and thumbnail fields.
Create a Product#
To create a product, use the sdk.admin.product.create method. It sends a POST request to /admin/products.
For example, to create a draft product with one option, one variant, images, and associations to existing records:
1const { product } = await sdk.admin.product.create({2 title: "Medusa T-Shirt",3 handle: "medusa-t-shirt",4 description: "A comfortable cotton t-shirt.",5 status: "draft",6 images: imageUrls.map((url) => ({ url })),7 thumbnail: imageUrls[0],8 collection_id: "pcol_123",9 type_id: "ptyp_123",10 categories: [{ id: "pcat_123" }, { id: "pcat_456" }],11 tags: [{ id: "ptag_123" }],12 sales_channels: [{ id: "sc_123" }, { id: "sc_456" }],13 shipping_profile_id: "sp_123",14 options: [15 {16 title: "Size",17 values: ["S", "M"],18 },19 ],20 variants: [21 {22 title: "Small",23 sku: "SHIRT-S",24 ean: "1234567890123",25 manage_inventory: true,26 allow_backorder: false,27 options: {28 Size: "S",29 },30 prices: [31 {32 currency_code: "usd",33 amount: 20,34 },35 {36 currency_code: "eur",37 amount: 18,38 },39 ],40 },41 ],42 metadata: {43 source: "erp",44 },45})
Where:
titleis the only required field.statusdefaults todraft. Other statuses areproposed,published, andrejected.categories,tags, andsales_channelsare arrays of objects with anidproperty. There's nocategory_idsfield.collection_idandtype_idare single IDs, since a product belongs to at most one collection and one type. A product can belong to many categories.imagesis an array of objects with aurlproperty. Medusa stores the images in the order of the array.- If you don't pass a
thumbnail, Medusa uses the URL of the first image as the thumbnail. - If you don't pass a
handle, Medusa generates it from thetitle. - Each variant requires a
titleand apricesarray. The keys of a variant'soptionsobject are option titles, and the values are one of that option'svalues. - Amounts in
pricesare the actual amounts, such as20for $20. Learn more in the Pricing Module's documentation. manage_inventorydefaults totrue. Learn more in the Product Variant Inventory guide.
Refer to the Create Product API reference for all accepted fields.
Retrieve a Product with Its Relations#
To retrieve a product, use the sdk.admin.product.retrieve method. It sends a request to the Get Product API route.
Use the fields query parameter to select the relations to retrieve. A * prefix retrieves all fields of a relation, and a + prefix adds a field to the default fields:
The same fields syntax works with sdk.admin.product.list, which sends a request to the List Products API route. For example, to retrieve each product's category IDs so you can find products without categories:
Learn more about the fields query parameter in the API reference.
Update a Product#
To update a product, use the sdk.admin.product.update method. It sends a POST request to the Update Product API route. Only the fields that you pass are updated.
For example, to update a product's details, images, and thumbnail:
1const { product } = await sdk.admin.product.update(2 "prod_123",3 {4 title: "Medusa T-Shirt",5 description: "A soft cotton t-shirt.",6 thumbnail: "https://example.com/new-front.png",7 images: [8 { id: "img_123", url: "https://example.com/front.png" },9 { url: "https://example.com/new-back.png" },10 ],11 categories: [{ id: "pcat_123" }],12 tags: [{ id: "ptag_123" }],13 metadata: {14 color: "black",15 },16 }17)
Keep in mind the following about updates:
- Array fields such as
images,categories,tags, andsales_channelsreplace the product's existing values. For example, to add an image, pass the existing images along with the new one. - The order of the
imagesarray sets the order of the product's images. Pass an existing image'sidalong with itsurlto keep that image. - Updating
imagesdoesn't change thethumbnail. Passthumbnailto change it. metadatais merged with the product's existing metadata.- In the same request, you can update the product's
collection_id,type_id,shipping_profile_id,status, and other scalar fields.
To manage the product's variants and options, use the dedicated methods explained in the next sections.
options field. Use the sdk.admin.product.linkOptions method instead, which sends a request to the Add Options to Product API route.Manage Variants and Prices#
To create, update, or delete multiple variants of a product in one request, use the sdk.admin.product.batchVariants method. It sends a request to the Manage Variants API Route.
For example:
1const { created, updated, deleted } =2 await sdk.admin.product.batchVariants("prod_123", {3 create: [4 {5 title: "Medium",6 sku: "SHIRT-M",7 options: { Size: "M" },8 prices: [{ currency_code: "usd", amount: 20 }],9 },10 ],11 update: [12 {13 id: "variant_123",14 sku: "SHIRT-S-NEW",15 options: { Size: "S" },16 prices: [17 { id: "price_123", amount: 22 },18 { currency_code: "eur", amount: 19 },19 ],20 },21 ],22 delete: ["variant_456"],23 })
Each item in update requires the variant's id. The other fields are optional.
When you pass prices for a variant, they replace the variant's existing prices:
- Pass the
idof an existing price to update it. - Pass a price without an
idto add it. - Existing prices that you don't pass are removed.
To update a single variant, you can also use the sdk.admin.product.updateVariant method, which sends a request to the Update Variant API route.
Assign Products to Collections and Categories in Bulk#
To add or remove multiple products in a collection, use the sdk.admin.productCollection.updateProducts method. It sends a request to the Manage Products of a Collection API route:
Since a product belongs to at most one collection, adding a product to a collection sets its collection_id to that collection.
To add or remove multiple products in a category, use the sdk.admin.productCategory.updateProducts method. It sends a request to the Manage Products of a Category API route:
Adding a product to a category doesn't remove it from its other categories.
Publish a Product#
To publish a draft product, update its status to published using the Update Product API route.
Customers can view and purchase a published product in the sales channels that the product is available in.
Create, Update, or Delete Products in Bulk#
To create, update, and delete multiple products in one request, use the sdk.admin.product.batch method. It sends a request to the Manage Products API route:
1const { created, updated, deleted } =2 await sdk.admin.product.batch({3 create: [4 {5 title: "Medusa Hoodie",6 options: [{ title: "Size", values: ["M"] }],7 variants: [8 {9 title: "Medium",10 options: { Size: "M" },11 prices: [{ currency_code: "usd", amount: 40 }],12 },13 ],14 },15 ],16 update: [17 {18 id: "prod_123",19 status: "published",20 collection_id: "pcol_123",21 categories: [{ id: "pcat_123" }],22 },23 ],24 delete: ["prod_456"],25 })
Items in create accept the same fields as the create request. Items in update accept the same fields as the update request, along with the product's id.
To import many products from a CSV file, refer to the Import Products user guide instead.