Subchapter 2.11
checkpoints/checkpoint-workflow-hooks.mdMarkdown7 KBView on GitHub
This checkpoint verifies that you’ve successfully consumed the productsCreated workflow hook to link brands to products and configured additional_data validation.
Before proceeding, test your understanding:
What are workflow hooks and why are they useful?
Workflow hooks are injection points in Medusa’s core workflows where you can add custom logic. They allow you to extend core functionality (like product creation) without forking Medusa’s code. When a core workflow reaches a hook point, it executes all registered hook subscribers, allowing your custom code to run as part of the standard flow.
Why do we need both a step function AND a compensation function in the hook?
Hook subscribers are treated as workflow steps, which means they need compensation for rollback. If product creation succeeds and the link is created, but a later step fails (e.g., inventory allocation), the compensation function removes the link to maintain data consistency. This ensures links are only persisted when the entire product creation succeeds.
What is additional_data and why do we use it?
additional_data is a flexible object in Medusa’s core workflows that allows you to pass custom data without modifying core workflow types. For product creation, we use it to pass brand_id from the API request to our hook subscriber. This is the standard pattern for extending core workflows with custom parameters.
Why do we need to configure additional_data validation in middleware?
Without validation configuration, Medusa won’t allow brand_id in the request body - it would be filtered out or cause validation errors. The additionalDataValidator in middleware tells Medusa “it’s okay to accept brand_id in additional_data” and validates it against your schema before the request reaches the workflow.
Let me verify your implementation. Please share the following:
Show me your src/workflows/hooks/product-brand-link.ts file (or wherever you defined the hook).
Key things to check:
createProductsWorkflow from “@medusajs/medusa/core-flows”StepResponse from “@medusajs/framework/workflows-sdk”ContainerRegistrationKeys from “@medusajs/framework/utils”createProductsWorkflow.hooks.productsCreated()async ({ products, additional_data }, { container }) => { ... }async (links, { container }) => { ... }container.resolve(ContainerRegistrationKeys.LINK)link.create()new StepResponse(links, links)if (!links?.length) returnlink.dismiss(links)Show me your src/api/middlewares.ts file (specifically the POST /admin/products configuration).
Key things to check:
createFindParams, createOperatorMap from “@medusajs/medusa/api/utils/validators”CreateProductSchema or similar with Zodadditional_data field:
additional_data: z.object({
brand_id: z.string().optional(),
}).optional()"/admin/products""POST"validateAndTransformBody() with schema and additionalDataValidatorvalidateAndTransformBody(CreateProductSchema, {
additionalDataValidator: {
brand_id: z.string(),
},
})With dev server running, test creating a product with brand_id:
curl -X POST http://localhost:9000/admin/products \
-H "Content-Type: application/json" \
-d '{
"title": "Air Max 90",
"additional_data": {
"brand_id": "brand_..."
}
}'Replace brand_... with an actual brand ID from your database (use the Nike brand you created in Lesson 1).
Expected output: Product should be created successfully with a product ID.
Symptom: Product is created but link doesn’t exist
Causes and Fixes:
Cause 1: Hook file not in the right location
src/workflows/ directory (Medusa auto-discovers hooks here)Cause 2: brand_id not passed in request
additional_data: { brand_id: "..." } in POST bodyCause 3: additional_data validation not configured
Cause 4: Hook has syntax errors
Symptom: 400 error when posting with additional_data
Cause: Middleware not configured to accept additional_data
Fix:
In src/api/middlewares.ts, add configuration for POST /admin/products:
{
matcher: "/admin/products",
method: "POST",
middlewares: [
validateAndTransformBody(
CreateProductSchema,
{
additionalDataValidator: {
brand_id: z.string(),
},
}
),
],
}Verify each of these steps:
src/workflows/ directorynpm run buildAt this point, you should understand:
How hooks extend core workflows:
Core Workflow: createProductsWorkflow
┌─────────────────────────────────────┐
│ 1. Validate input │
│ 2. Create products │
│ 3. → HOOK: productsCreated ← │ ← Your custom logic runs here
│ ↳ Your hook: Link to brand │
│ 4. Handle inventory │
│ 5. Publish events │
└─────────────────────────────────────┘Why this matters:
Example: If inventory allocation (step 4) fails:
Once this checkpoint passes:
The link is now created automatically when products are created. Next, we’ll learn how to query linked data to retrieve brands with their products and vice versa.
Ready to continue? Let me know when all checks pass, and we’ll move on to querying linked records.