- Get Started
- Product
- Resources
- Tools & SDKs
- Framework
- Reference
- Get Started
- Product
- Resources
- Tools & SDKs
- Framework
- Reference
2.2.2. Guide: Extend Create Product Flow
After linking the custom Brand data model and Medusa's Product Module in the previous chapter, you'll extend the create product workflow and API route to allow associating a brand with a product.
Some API routes, including the Create Product API route, accept an additional_data
request body parameter. This parameter can hold custom data that's passed to the hooks of the workflow executed in the API route, allowing you to consume those hooks and perform actions with the custom data.
So, in this chapter, to extend the create product flow and associate a brand with a product, you will:
- Consume the productsCreated hook of the createProductsWorkflow, which is executed within the workflow after the product is created. You'll link the product with the brand passed in the
additional_data
parameter. - Extend the Create Product API route to allow passing a brand ID in
additional_data
.
additional_data
property and the API routes that accept additional data, refer to this chapter.1. Consume the productCreated Hook#
A workflow hook is a point in a workflow where you can inject a step to perform a custom functionality. Consuming a workflow hook allows you to extend the features of a workflow and, consequently, the API route that uses it.
The createProductsWorkflow used in the Create Product API route has a productsCreated
hook that runs after the product is created. You'll consume this hook to link the created product with the brand specified in the request parameters.
To consume the productsCreated
hook, create the file src/workflows/hooks/created-product.ts
with the following content:
1import { createProductsWorkflow } from "@medusajs/medusa/core-flows"2import { StepResponse } from "@medusajs/framework/workflows-sdk"3import { Modules } from "@medusajs/framework/utils"4import { LinkDefinition } from "@medusajs/framework/types"5import { BRAND_MODULE } from "../../modules/brand"6import BrandModuleService from "../../modules/brand/service"7 8createProductsWorkflow.hooks.productsCreated(9 (async ({ products, additional_data }, { container }) => {10 if (!additional_data?.brand_id) {11 return new StepResponse([], [])12 }13 14 const brandModuleService: BrandModuleService = container.resolve(15 BRAND_MODULE16 )17 // if the brand doesn't exist, an error is thrown.18 await brandModuleService.retrieveBrand(additional_data.brand_id as string)19 20 // TODO link brand to product21 })22)
Workflows have a special hooks
property to access its hooks and consume them. Each hook, such as productCreated
, accepts a step function as a parameter. The step function accepts the following parameters:
- An object having an
additional_data
property, which is the custom data passed in the request body underadditional_data
. The object will also have properties passed from the workflow to the hook, which in this case is theproducts
property that holds an array of the created products. - An object of properties related to the step's context. It has a
container
property whose value is the Medusa container to resolve framework and commerce tools.
In the step, if a brand ID is passed in additional_data
, you resolve the Brand Module's service and use its generated retrieveBrand
method to retrieve the brand by its ID. The retrieveBrand
method will throw an error if the brand doesn't exist.
Link Brand to Product#
Next, you want to create a link between the created products and the brand. To do so, you use Remote Link, which is a class from the Modules SDK that provides methods to manage linked records.
To use the remote link in the productCreated
hook, replace the TODO
with the following:
1const remoteLink = container.resolve("remoteLink")2const logger = container.resolve("logger")3 4const links: LinkDefinition[] = []5 6for (const product of products) {7 links.push({8 [Modules.PRODUCT]: {9 product_id: product.id,10 },11 [BRAND_MODULE]: {12 brand_id: additional_data.brand_id,13 },14 })15}16 17await remoteLink.create(links)18 19logger.info("Linked brand to products")20 21return new StepResponse(links, links)
You resolve the remote link from the container. Then you loop over the created products to assemble an array of links to be created. After that, you pass the array of links to remote link's create
method, which will link the product and brand records.
Each property in the link object is the name of a module, and its value is an object having a {model_name}_id
property, where {model_name}
is the snake-case name of the module's data model. Its value is the ID of the record to be linked. The link object's properties must be set in the same order as the link configurations passed to defineLink
.
Finally, you return an instance of StepResponse
returning the created links.
Dismiss Links in Compensation#
You can pass as a second parameter of the hook a compensation function that undoes what the step did. It receives as a first parameter the returned StepResponse
's second parameter, and the step context object as a second parameter.
To undo creating the links in the hook, pass the following compensation function as a second parameter to productsCreated
:
In the compensation function, if the links
parameter isn't empty, you resolve remote link from the container and use its dismiss
method. This method removes a link between two records. It accepts the same parameter as the create
method.
2. Configure Additional Data Validation#
Now that you've consumed the productCreated
hook, you want to configure the /admin/products
API route that creates a new product to accept a brand ID in its additional_data
parameter.
You configure the properties accepted in additional_data
in the src/api/middlewares.ts
that exports middleware configurations. So, create the file (or, if already existing, add to the file) src/api/middlewares.ts
the following content:
1import { defineMiddlewares } from "@medusajs/framework/http"2import { z } from "zod"3 4// ...5 6export default defineMiddlewares({7 routes: [8 // ...9 {10 matcher: "/admin/products",11 method: ["POST"],12 additionalDataValidator: {13 brand_id: z.string().optional(),14 },15 },16 ],17})
Objects in routes
accept an additionalDataValidator
property that configures the validation rules for custom properties passed in the additional_data
request parameter. It accepts an object whose keys are custom property names, and their values are validation rules created using Zod.
So, POST
requests sent to /admin/products
can now pass the ID of a brand in the brand_id
property of additional_data
.
Test it Out#
To test it out, first, retrieve the authentication token of your admin user by sending a POST
request to /auth/user/emailpass
:
Make sure to replace the email and password in the request body with your user's credentials.
Then, send a POST
request to /admin/products
to create a product, and pass in the additional_data
parameter a brand's ID:
1curl -X POST 'http://localhost:9000/admin/products' \2-H 'Content-Type: application/json' \3-H 'Authorization: Bearer {token}' \4--data '{5 "title": "Product 1",6 "options": [7 {8 "title": "Default option",9 "values": ["Default option value"]10 }11 ],12 "additional_data": {13 "brand_id": "{brand_id}"14 }15}'
Make sure to replace {token}
with the token you received from the previous request, and {brand_id}
with the ID of a brand in your application.
The request creates a product and returns it.
In the Medusa application's logs, you'll find the message Linked brand to products
, indicating that the workflow hook handler ran and linked the brand to the products.
Next Steps: Query Linked Brands and Products#
Now that you've extending the create-product flow to link a brand to it, you want to retrieve the brand details of a product. You'll learn how to do so in the next chapter.