Back to Medusa

{metadata.title}

www/apps/book/app/learn/fundamentals/api-routes/additional-data/page.mdx

2.14.27.7 KB
Original Source

import { Details } from "docs-ui"

export const metadata = { title: ${pageNumber} Pass Additional Data to Medusa's API Route, }

{metadata.title}

In this chapter, you'll learn how to pass additional data in requests to Medusa's API Route.

Why Pass Additional Data?

Some of Medusa's API Routes accept an additional_data parameter whose type is an object. The API Route passes the additional_data to the workflow, which in turn passes it to its hooks.

This is useful when you have a link from your custom module to a Commerce Module, and you want to perform an additional action when a request is sent to an existing API route.

For example, the Create Product API Route accepts an additional_data parameter. If you have a data model linked to it, you consume the productsCreated hook to create a record of the data model using the custom data and link it to the product.

API Routes Accepting Additional Data

<Details summaryContent="API Routes List"> </Details>

How to Pass Additional Data

1. Specify Validation of Additional Data

Before passing custom data in the additional_data object parameter, you must specify validation rules for the allowed properties in the object.

To do that, use the middleware route object defined in src/api/middlewares.ts.

For example, create the file src/api/middlewares.ts with the following content:

<Note>

As of Medusa v2.13.0, Zod should be imported from @medusajs/framework/zod.

</Note>
ts
import { defineMiddlewares } from "@medusajs/framework/http"
import { z } from "@medusajs/framework/zod"

export default defineMiddlewares({
  routes: [
    {
      method: "POST",
      matcher: "/admin/products",
      additionalDataValidator: {
        brand: z.string().optional(),
      },
    },
  ],
})

The middleware route object accepts an optional parameter additionalDataValidator whose value is an object of key-value pairs. The keys indicate the name of accepted properties in the additional_data parameter, and the value is Zod validation rules of the property.

In this example, you indicate that the additional_data parameter accepts a brand property whose value is an optional string.

<Note>

Refer to Zod's documentation for all available validation rules.

</Note>

2. Pass the Additional Data in a Request

You can now pass a brand property in the additional_data parameter of a request to the Create Product API Route.

For example:

bash
curl -X POST 'http://localhost:9000/admin/products' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {token}' \
--data '{
    "title": "Product 1",
    "options": [
      {
        "title": "Default option",
        "values": ["Default option value"]
      }
    ],
    "shipping_profile_id": "{shipping_profile_id}",
    "additional_data": {
        "brand": "Acme"
    }
}'
<Note title="Tip">

Make sure to replace the {token} in the authorization header with an admin user's authentication token, and {shipping_profile_id} with an existing shipping profile's ID.

</Note>

In this request, you pass in the additional_data parameter a brand property and set its value to Acme.

The additional_data is then passed to hooks in the createProductsWorkflow used by the API route.


Use Additional Data in a Hook

<Note>

Learn about workflow hooks in this guide.

</Note>

Step functions consuming the workflow hook can access the additional_data in the first parameter.

For example, consider you want to store the data passed in additional_data in the product's metadata property.

To do that, create the file src/workflows/hooks/product-created.ts with the following content:

ts
import { StepResponse } from "@medusajs/framework/workflows-sdk"
import { createProductsWorkflow } from "@medusajs/medusa/core-flows"
import { Modules } from "@medusajs/framework/utils"

createProductsWorkflow.hooks.productsCreated(
  async ({ products, additional_data }, { container }) => {
    if (!additional_data?.brand) {
      return
    }

    const productModuleService = container.resolve(
      Modules.PRODUCT
    )

    await productModuleService.upsertProducts(
      products.map((product) => ({
        ...product,
        metadata: {
          ...product.metadata,
          brand: additional_data.brand,
        },
      }))
    )

    return new StepResponse(products, {
      products,
      additional_data,
    })
  }
)

This consumes the productsCreated hook, which runs after the products are created.

If brand is passed in additional_data, you resolve the Product Module's main service and use its upsertProducts method to update the products, adding the brand to the metadata property.

Compensation Function

Hooks also accept a compensation function as a second parameter to undo the actions made by the step function.

For example, pass the following second parameter to the productsCreated hook:

ts
createProductsWorkflow.hooks.productsCreated(
  async ({ products, additional_data }, { container }) => {
    // ...
  },
  async ({ products, additional_data }, { container }) => {
    if (!additional_data.brand) {
      return
    }

    const productModuleService = container.resolve(
      Modules.PRODUCT
    )

    await productModuleService.upsertProducts(
      products
    )
  }
)

This updates the products to their original state before adding the brand to their metadata property.