diff --git a/www/apps/book/app/learn/fundamentals/admin/page.mdx b/www/apps/book/app/learn/fundamentals/admin/page.mdx
index 2149f1ed47..4807c16112 100644
--- a/www/apps/book/app/learn/fundamentals/admin/page.mdx
+++ b/www/apps/book/app/learn/fundamentals/admin/page.mdx
@@ -11,6 +11,8 @@ You can customize the admin dashboard by:
- Adding new sections to existing pages using Widgets.
- Adding new pages using UI Routes.
+However, you can't customize the admin dashboard's layout, design, or the content of the existing pages (aside from injecting widgets).
+
---
## Medusa UI Package
diff --git a/www/apps/book/app/learn/fundamentals/module-links/query/page.mdx b/www/apps/book/app/learn/fundamentals/module-links/query/page.mdx
index cd3bab1170..05607334af 100644
--- a/www/apps/book/app/learn/fundamentals/module-links/query/page.mdx
+++ b/www/apps/book/app/learn/fundamentals/module-links/query/page.mdx
@@ -42,12 +42,12 @@ export const GET = async (
) => {
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
- const { data: myCustoms } = await query.graph({
- entity: "my_custom",
- fields: ["id", "name"],
+ const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
})
- res.json({ my_customs: myCustoms })
+ res.json({ posts })
}
```
@@ -65,7 +65,7 @@ The method returns an object that has a `data` property, which holds an array of
"data": [
{
"id": "123",
- "name": "test"
+ "title": "My Post"
}
]
}
@@ -88,21 +88,33 @@ Retrieve the records of a linked data model by passing in `fields` the data mode
For example:
```ts highlights={[["6"]]}
-const { data: myCustoms } = await query.graph({
- entity: "my_custom",
+const { data: posts } = await query.graph({
+ entity: "post",
fields: [
"id",
- "name",
+ "title",
"product.*",
],
})
```
-
+`.*` means that all of data model's properties should be retrieved. You can also retrieve specific properties by replacing the `*` with the property name, for each property.
-`.*` means that all of data model's properties should be retrieved. To retrieve a specific property, replace the `*` with the property's name. For example, `product.title`.
+For example:
-
+```ts
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: [
+ "id",
+ "title",
+ "product.id",
+ "product.title",
+ ],
+})
+```
+
+In the example above, you retrieve only the `id` and `title` properties of the `product` linked to a `post`.
### Retrieve List Link Records
@@ -111,19 +123,21 @@ If the linked data model has `isList` enabled in the link definition, pass in `f
For example:
```ts highlights={[["6"]]}
-const { data: myCustoms } = await query.graph({
- entity: "my_custom",
+const { data: posts } = await query.graph({
+ entity: "post",
fields: [
"id",
- "name",
+ "title",
"products.*",
],
})
```
+In the example above, you retrieve all products linked to a post.
+
### Apply Filters and Pagination on Linked Records
-Consider you want to apply filters or pagination configurations on the product(s) linked to `my_custom`. To do that, you must query the module link's table instead.
+Consider you want to apply filters or pagination configurations on the product(s) linked to `post`. To do that, you must query the module link's table instead.
As mentioned in the [Module Link](../page.mdx) documentation, Medusa creates a table for your module link. So, not only can you retrieve linked records, but you can also retrieve the records in a module link's table.
@@ -133,19 +147,19 @@ For example:
export const queryLinkTableHighlights = [
["1", "", "Import the module link."],
- ["6", "productBrandLink.entryPoint", "Pass the `entryPoint` property of the link to Query"],
- ["7", `"product.*"`, "Retrieve the fields of a product record linked to a `MyCustom` record."],
- ["7", `"brand.*"`, "Retrieve the fields of a `MyCustom` record linked to a product record."]
+ ["6", "ProductPostLink.entryPoint", "Pass the `entryPoint` property of the link to Query"],
+ ["7", `"product.*"`, "Retrieve the fields of a product record linked to a `Post` record."],
+ ["7", `"post.*"`, "Retrieve the fields of a `Post` record linked to a product record."]
]
```ts highlights={queryLinkTableHighlights}
-import productCustomLink from "../../../links/product-custom"
+import ProductPostLink from "../../../links/product-post"
// ...
const { data: productCustoms } = await query.graph({
- entity: productCustomLink.entryPoint,
- fields: ["*", "product.*", "my_custom.*"],
+ entity: ProductPostLink.entryPoint,
+ fields: ["*", "product.*", "post.*"],
pagination: {
take: 5,
skip: 0,
@@ -158,8 +172,8 @@ In the object passed to the `graph` method:
- You pass the `entryPoint` property of the link definition as the value for `entity`. So, Query will retrieve records from the module link's table.
- You pass three items to the `field` property:
- `*` to retrieve the link table's fields. This is useful if the link table has [custom columns](../custom-columns/page.mdx).
- - `product.*` to retrieve the fields of a product record linked to a `MyCustom` record.
- - `my_custom.*` to retrieve the fields of a `MyCustom` record linked to a product record.
+ - `product.*` to retrieve the fields of a product record linked to a `Post` record.
+ - `post.*` to retrieve the fields of a `Post` record linked to a product record.
You can then apply any [filters](#apply-filters) or [pagination configurations](#apply-pagination).
@@ -169,58 +183,173 @@ The returned `data` is similar to the following:
[{
"id": "123",
"product_id": "prod_123",
- "my_custom_id": "123",
+ "post_id": "123",
"product": {
"id": "prod_123",
// other product fields...
},
- "my_custom": {
+ "post": {
"id": "123",
- // other my_custom fields...
+ // other post fields...
}
}]
```
-
-
---
## Apply Filters
-```ts highlights={[["6"], ["7"], ["8"], ["9"]]}
-const { data: myCustoms } = await query.graph({
- entity: "my_custom",
- fields: ["id", "name"],
+```ts highlights={[["4"], ["5"], ["6"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
filters: {
- id: [
- "mc_01HWSVWR4D2XVPQ06DQ8X9K7AX",
- "mc_01HWSVWK3KYHKQEE6QGS2JC3FX",
- ],
+ id: "post_123",
},
})
```
The `query.graph` function accepts a `filters` property. You can use this property to filter retrieved records.
-In the example above, you filter the `my_custom` records by multiple IDs.
+In the example above, you filter the `post` records by the ID `post_123`.
+
+You can also filter by multiple values of a property. For example:
+
+```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"], ["9"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ id: [
+ "post_123",
+ "post_321",
+ ],
+ },
+})
+```
+
+In the example above, you filter the `post` records by multiple IDs.
-Filters don't apply on fields of linked data models from other modules.
+Filters don't apply on fields of linked data models from other modules. Refer to the [Retrieve Linked Records](#retrieve-linked-records) section for an alternative solution.
+### Advanced Query Filters
+
+Under the hood, Query uses the `listX` (`listPosts`) method of the data model's module's service to retrieve records. This method accepts a filter object that can be used to filter records.
+
+Those filters don't just allow you to filter by exact values. You can also filter by properties that don't match a value, match multiple values, and other filter types.
+
+Refer to the [Service Factory Reference](!resources!/service-factory-reference/tips/filtering) for examples of advanced filters. The following sections provide some quick examples.
+
+#### Filter by Not Matching a Value
+
+```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ title: {
+ $ne: null,
+ },
+ },
+})
+```
+
+In the example above, only posts that have a title are retrieved.
+
+#### Filter by Not Matching Multiple Values
+
+```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ title: {
+ $nin: ["My Post", "Another Post"],
+ },
+ },
+})
+```
+
+In the example above, only posts that don't have the title `My Post` or `Another Post` are retrieved.
+
+#### Filter by a Range
+
+```ts highlights={[["10"], ["11"], ["12"], ["13"], ["14"], ["15"]]}
+const startToday = new Date()
+startToday.setHours(0, 0, 0, 0)
+
+const endToday = new Date()
+endToday.setHours(23, 59, 59, 59)
+
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ published_at: {
+ $gt: startToday,
+ $lt: endToday,
+ },
+ },
+})
+```
+
+In the example above, only posts that were published today are retrieved.
+
+#### Filter Text by Like Value
+
+
+
+This filter only applies to text-like properties, including `text`, `id`, and `enum` properties.
+
+
+
+```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ title: {
+ $like: "%My%",
+ },
+ },
+})
+```
+
+In the example above, only posts that have the word `My` in their title are retrieved.
+
+#### Filter a Relation's Property
+
+```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ author: {
+ name: "John",
+ },
+ },
+})
+```
+
+While it's not possible to filter by a linked data model's property, you can filter by a relation's property (that is, the property of a related data model that is defined in the same module).
+
+In the example above, only posts that have an author with the name `John` are retrieved.
+
---
## Apply Pagination
```ts highlights={[["8", "skip", "The number of records to skip before fetching the results."], ["9", "take", "The number of records to fetch."]]}
const {
- data: myCustoms,
+ data: posts,
metadata: { count, take, skip } = {},
} = await query.graph({
- entity: "my_custom",
- fields: ["id", "name"],
+ entity: "post",
+ fields: ["id", "title"],
pagination: {
skip: 0,
take: 10,
@@ -258,9 +387,9 @@ When you provide the pagination fields, the `query.graph` method's returned obje
### Sort Records
```ts highlights={[["5"], ["6"], ["7"]]}
-const { data: myCustoms } = await query.graph({
- entity: "my_custom",
- fields: ["id", "name"],
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
pagination: {
order: {
name: "DESC",
@@ -284,6 +413,72 @@ The `order` property is an object whose keys are property names, and values are
---
+## Configure Query to Throw Errors
+
+By default, if Query doesn't find records matching your query, it returns an empty array. You can add option to configure Query to throw an error when no records are found.
+
+The `query.graph` method accepts as a second parameter an object that can have a `throwIfKeyNotFound` property. Its value is a boolean indicating whether to throw an error if no record is found when filtering by IDs. By default, it's `false`.
+
+For example:
+
+```ts
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ id: "post_123",
+ },
+}, {
+ throwIfKeyNotFound: true,
+})
+```
+
+In the example above, if no post is found with the ID `post_123`, Query will throw an error. This is useful to stop execution when a record is expected to exist.
+
+### Throw Error on Related Data Model
+
+The `throwIfKeyNotFound` option can also be used to throw an error if the ID of a related data model's record (in the same module) is passed in the filters, and the related record doesn't exist.
+
+For example:
+
+```ts
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title", "author.*"],
+ filters: {
+ id: "post_123",
+ author_id: "author_123",
+ },
+}, {
+ throwIfKeyNotFound: true,
+})
+```
+
+In the example above, Query throws an error either if no post is found with the ID `post_123` or if its found but its author ID isn't `author_123`.
+
+In the above example, it's assumed that a post belongs to an author, so it has an `author_id` property. However, this also works in the opposite case, where an author has many posts.
+
+For example:
+
+```ts
+const { data: posts } = await query.graph({
+ entity: "author",
+ fields: ["id", "name", "posts.*"],
+ filters: {
+ id: "author_123",
+ posts: {
+ id: "post_123",
+ },
+ },
+}, {
+ throwIfKeyNotFound: true,
+})
+```
+
+In the example above, Query throws an error if no author is found with the ID `author_123` or if the author is found but doesn't have a post with the ID `post_123`.
+
+---
+
## Request Query Configurations
For API routes that retrieve a single or list of resources, Medusa provides a `validateAndTransformQuery` middleware that:
@@ -317,7 +512,7 @@ export default defineMiddlewares({
{
defaults: [
"id",
- "name",
+ "title",
"products.*",
],
isList: true,
@@ -375,12 +570,12 @@ export const GET = async (
) => {
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
- const { data: myCustoms } = await query.graph({
- entity: "my_custom",
+ const { data: posts } = await query.graph({
+ entity: "post",
...req.queryConfig,
})
- res.json({ my_customs: myCustoms })
+ res.json({ posts: posts })
}
```
@@ -394,10 +589,10 @@ To test it out, start your Medusa application and send a `GET` request to the `/
```json title="Returned Data"
{
- "my_customs": [
+ "posts": [
{
"id": "123",
- "name": "test"
+ "title": "test"
}
]
}
diff --git a/www/apps/book/app/learn/fundamentals/module-links/read-only/page.mdx b/www/apps/book/app/learn/fundamentals/module-links/read-only/page.mdx
index 652cdbd8b5..62e0f929cd 100644
--- a/www/apps/book/app/learn/fundamentals/module-links/read-only/page.mdx
+++ b/www/apps/book/app/learn/fundamentals/module-links/read-only/page.mdx
@@ -51,7 +51,7 @@ export default defineLink(
},
ProductModule.linkable.product,
{
- readOnly: true
+ readOnly: true,
}
)
```
@@ -76,8 +76,8 @@ const { result } = await query.graph({
entity: "post",
fields: ["id", "product.*"],
filters: {
- id: "post_123"
- }
+ id: "post_123",
+ },
})
```
@@ -114,7 +114,7 @@ export default defineLink(
primaryKey: "product_id",
},
{
- readOnly: true
+ readOnly: true,
}
)
```
@@ -133,8 +133,8 @@ const { result } = await query.graph({
entity: "product",
fields: ["id", "post.*"],
filters: {
- id: "prod_123"
- }
+ id: "prod_123",
+ },
})
```
@@ -194,7 +194,7 @@ export default defineLink(
},
ProductModule.linkable.product,
{
- readOnly: true
+ readOnly: true,
}
)
```
@@ -229,7 +229,7 @@ export default defineLink(
},
ProductModule.linkable.product,
{
- readOnly: true
+ readOnly: true,
}
)
```
@@ -276,7 +276,7 @@ export default defineLink(
primaryKey: "product_id",
},
{
- readOnly: true
+ readOnly: true,
}
)
```
@@ -343,14 +343,14 @@ export default defineLink(
{
linkable: ProductModule.linkable.product,
field: "id",
- isList: true
+ isList: true,
},
{
...BlogModule.linkable.post.id,
- primaryKey: "product_id"
+ primaryKey: "product_id",
},
{
- readOnly: true
+ readOnly: true,
}
)
```
@@ -444,14 +444,14 @@ import { CMS_MODULE } from "../modules/cms"
export default defineLink(
{
linkable: ProductModule.linkable.product,
- field: "id"
+ field: "id",
},
{
linkable: {
serviceName: CMS_MODULE,
alias: "cms_post",
primaryKey: "product_id",
- }
+ },
},
{
readOnly: true,
diff --git a/www/apps/book/app/learn/fundamentals/modules/db-operations/page.mdx b/www/apps/book/app/learn/fundamentals/modules/db-operations/page.mdx
index 56c365f9db..b2278bcd0f 100644
--- a/www/apps/book/app/learn/fundamentals/modules/db-operations/page.mdx
+++ b/www/apps/book/app/learn/fundamentals/modules/db-operations/page.mdx
@@ -28,8 +28,8 @@ So, to run database queries in a service:
For example, in your service, add the following methods:
export const methodsHighlight = [
- ["12", "getCount", "Retrieves the number of records in `my_custom` using the `count` method."],
- ["19", "getCountSql", "Retrieves the number of records in `my_custom` using the `execute` method."]
+ ["13", "getCount", "Retrieves the number of records in `my_custom` using the `count` method."],
+ ["20", "getCountSql", "Retrieves the number of records in `my_custom` using the `execute` method."]
]
```ts highlights={methodsHighlight}
@@ -38,7 +38,8 @@ import {
InjectManager,
MedusaContext,
} from "@medusajs/framework/utils"
-import { SqlEntityManager } from "@mikro-orm/knex"
+import { Context } from "@medusajs/framework/types"
+import { EntityManager } from "@mikro-orm/knex"
class BlogModuleService {
// ...
@@ -46,19 +47,19 @@ class BlogModuleService {
@InjectManager()
async getCount(
@MedusaContext() sharedContext?: Context
- ): Promise {
- return await sharedContext.manager.count("my_custom")
+ ): Promise {
+ return await sharedContext?.manager?.count("my_custom")
}
@InjectManager()
async getCountSql(
@MedusaContext() sharedContext?: Context
): Promise {
- const data = await sharedContext.manager.execute(
+ const data = await sharedContext?.manager?.execute(
"SELECT COUNT(*) as num FROM my_custom"
)
- return parseInt(data[0].num)
+ return parseInt(data?.[0].num || 0)
}
}
```
@@ -115,8 +116,8 @@ class BlogModuleService {
},
@MedusaContext() sharedContext?: Context
): Promise {
- const transactionManager = sharedContext.transactionManager
- await transactionManager.nativeUpdate(
+ const transactionManager = sharedContext?.transactionManager
+ await transactionManager?.nativeUpdate(
"my_custom",
{
id: input.id,
@@ -127,7 +128,7 @@ class BlogModuleService {
)
// retrieve again
- const updatedRecord = await transactionManager.execute(
+ const updatedRecord = await transactionManager?.execute(
`SELECT * FROM my_custom WHERE id = '${input.id}'`
)
@@ -178,10 +179,22 @@ For example, the `update` method could be changed to the following:
```ts
// other imports...
+import {
+ InjectManager,
+ InjectTransactionManager,
+ MedusaContext,
+} from "@medusajs/framework/utils"
+import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
class BlogModuleService {
// ...
+ @InjectTransactionManager()
+ protected async update_(
+ // ...
+ ): Promise {
+ // ...
+ }
@InjectManager()
async update(
input: {
@@ -192,12 +205,14 @@ class BlogModuleService {
) {
const newData = await this.update_(input, sharedContext)
- await sendNewDataToSystem(newData)
+ // example method that sends data to another system
+ await this.sendNewDataToSystem(newData)
return newData
}
}
```
+
In this case, only the `update_` method is wrapped in a transaction. The returned value `newData` holds the committed result, which can be used for other operations, such as passed to a `sendNewDataToSystem` method.
### Using Methods in Transactional Methods
@@ -208,6 +223,11 @@ For example:
```ts
// other imports...
+import {
+ InjectTransactionManager,
+ MedusaContext,
+} from "@medusajs/framework/utils"
+import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
class BlogModuleService {
@@ -343,7 +363,7 @@ class BlogModuleService {
return updatedRecord
},
{
- transaction: sharedContext.transactionManager,
+ transaction: sharedContext?.transactionManager,
}
)
}
@@ -382,6 +402,12 @@ The second parameter of the `baseRepository_.transaction` method is an object of
```ts highlights={[["16"]]}
// other imports...
import { EntityManager } from "@mikro-orm/knex"
+import {
+ InjectTransactionManager,
+ MedusaContext,
+} from "@medusajs/framework/utils"
+import { Context } from "@medusajs/framework/types"
+import { EntityManager } from "@mikro-orm/knex"
class BlogModuleService {
// ...
@@ -398,7 +424,7 @@ class BlogModuleService {
// ...
},
{
- transaction: sharedContext.transactionManager,
+ transaction: sharedContext?.transactionManager,
}
)
}
@@ -414,6 +440,12 @@ class BlogModuleService {
```ts highlights={[["19"]]}
// other imports...
+import {
+ InjectTransactionManager,
+ MedusaContext,
+} from "@medusajs/framework/utils"
+import { Context } from "@medusajs/framework/types"
+import { EntityManager } from "@mikro-orm/knex"
import { IsolationLevel } from "@mikro-orm/core"
class BlogModuleService {
@@ -442,6 +474,14 @@ class BlogModuleService {
- If `transaction` is provided and this is disabled, the manager in `transaction` is re-used.
```ts highlights={[["16"]]}
+// other imports...
+import {
+ InjectTransactionManager,
+ MedusaContext,
+} from "@medusajs/framework/utils"
+import { Context } from "@medusajs/framework/types"
+import { EntityManager } from "@mikro-orm/knex"
+
class BlogModuleService {
// ...
@InjectTransactionManager()
diff --git a/www/apps/book/generated/edit-dates.mjs b/www/apps/book/generated/edit-dates.mjs
index f71327e8d0..876498e1a9 100644
--- a/www/apps/book/generated/edit-dates.mjs
+++ b/www/apps/book/generated/edit-dates.mjs
@@ -31,7 +31,7 @@ export const generatedEditDates = {
"app/learn/fundamentals/events-and-subscribers/emit-event/page.mdx": "2025-03-18T15:09:40.243Z",
"app/learn/fundamentals/workflows/conditions/page.mdx": "2025-01-27T08:45:19.027Z",
"app/learn/fundamentals/modules/module-link-directions/page.mdx": "2024-07-24T09:16:01+02:00",
- "app/learn/fundamentals/admin/page.mdx": "2024-10-23T07:08:55.898Z",
+ "app/learn/fundamentals/admin/page.mdx": "2025-03-21T08:25:13.754Z",
"app/learn/fundamentals/workflows/long-running-workflow/page.mdx": "2025-03-18T08:02:14.085Z",
"app/learn/fundamentals/workflows/constructor-constraints/page.mdx": "2025-02-12T13:55:33.437Z",
"app/learn/fundamentals/data-models/write-migration/page.mdx": "2025-03-18T08:00:44.980Z",
@@ -68,8 +68,8 @@ export const generatedEditDates = {
"app/learn/fundamentals/module-links/custom-columns/page.mdx": "2025-03-11T13:29:54.752Z",
"app/learn/fundamentals/module-links/directions/page.mdx": "2025-03-17T12:52:06.161Z",
"app/learn/fundamentals/module-links/page.mdx": "2025-03-11T13:39:14.345Z",
- "app/learn/fundamentals/module-links/query/page.mdx": "2025-03-11T15:35:10.605Z",
- "app/learn/fundamentals/modules/db-operations/page.mdx": "2025-03-18T15:10:57.351Z",
+ "app/learn/fundamentals/module-links/query/page.mdx": "2025-03-21T09:14:54.943Z",
+ "app/learn/fundamentals/modules/db-operations/page.mdx": "2025-03-21T09:21:46.901Z",
"app/learn/fundamentals/modules/multiple-services/page.mdx": "2025-03-18T15:11:44.632Z",
"app/learn/fundamentals/modules/page.mdx": "2025-03-18T07:51:09.049Z",
"app/learn/debugging-and-testing/instrumentation/page.mdx": "2025-02-24T08:12:53.132Z",
@@ -117,6 +117,6 @@ export const generatedEditDates = {
"app/learn/configurations/medusa-config/page.mdx": "2025-03-11T14:27:04.528Z",
"app/learn/configurations/ts-aliases/page.mdx": "2025-02-11T16:57:46.683Z",
"app/learn/production/worker-mode/page.mdx": "2025-03-11T15:21:50.906Z",
- "app/learn/fundamentals/module-links/read-only/page.mdx": "2025-03-17T14:18:29.924Z",
+ "app/learn/fundamentals/module-links/read-only/page.mdx": "2025-03-21T09:14:54.944Z",
"app/learn/fundamentals/data-models/properties/page.mdx": "2025-03-18T07:57:17.826Z"
}
\ No newline at end of file
diff --git a/www/apps/book/public/llms-full.txt b/www/apps/book/public/llms-full.txt
index d9fc48f9bd..419f32f108 100644
--- a/www/apps/book/public/llms-full.txt
+++ b/www/apps/book/public/llms-full.txt
@@ -136,76 +136,6 @@ The next chapter covers how you generally deploy the production build.
You can also refer to the [deployment how-to guides](https://docs.medusajs.com/resources/deployment/index.html.md) for platform-specific how-to guides.
-# Medusa Deployment Overview
-
-In this chapter, you’ll learn the general approach to deploying the Medusa application.
-
-## Medusa Project Components
-
-A standard Medusa project is made up of:
-
-- Medusa application: The Medusa server and the Medusa Admin.
-- One or more storefronts
-
-
-
-You deploy the Medusa application, with the server and admin, separately from the storefront.
-
-***
-
-## Deploying the Medusa Application
-
-You must deploy the Medusa application before the storefront, as it connects to the server and won’t work without a deployed Medusa server URL.
-
-The Medusa application must be deployed to a hosting provider supporting Node.js server deployments, such as Railway, DigitalOcean, AWS, Heroku, etc…
-
-
-
-Your server connects to a PostgreSQL database, Redis, and other services relevant for your setup. Most hosting providers support deploying and managing these databases along with your Medusa server (such as Railway and DigitalOcean).
-
-When you deploy your Medusa application, you also deploy the Medusa Admin. For optimal experience, your hosting provider and plan must offer at least 2GB of RAM.
-
-### Deploy Server and Worker Instances
-
-By default, Medusa runs all processes in a single instance. This includes the server that handles incoming requests and the worker that processes background tasks. While this works for development, it’s not optimal for production environments as many background tasks can be long-running or resource-heavy.
-
-Instead, you should deploy two instances:
-
-- A server instance, which handles incoming requests to the application’s API routes.
-- A worker instance, which processes background tasks, including scheduled jobs and subscribers.
-
-You don’t need to set up different projects for each instance. Instead, you can configure the Medusa application to run in different modes based on environment variables.
-
-Learn more about worker modes and how to configure them in the [Worker Mode chapter](https://docs.medusajs.com/learn/production/worker-mode/index.html.md).
-
-### How to Deploy Medusa?
-
-Medusa Cloud is our managed services offering that makes deploying and operating Medusa applications possible without having to worry about configuring, scaling, and maintaining infrastructure. Medusa Cloud hosts your server, Admin dashboard, database, and Redis instance.
-
-With Medusa Cloud, you maintain full customization control as you deploy your own modules and customizations directly from GitHub:
-
-- Push to deploy.
-- Multiple testing environments.
-- Preview environments for new PRs.
-- Test on production-like data.
-
-[Sign up and learn more about Medusa Cloud](https://medusajs.com/pricing)
-
-To self-host Medusa, the [next chapter](https://docs.medusajs.com/learn/deployment/general/index.html.md) explains the general steps to deploy your Medusa application. Refer to [this reference](https://docs.medusajs.com/resources/deployment/index.html.md) to find how-to deployment guides for general and specific hosting providers.
-
-***
-
-## Deploying the Storefront
-
-The storefront is deployed separately from the Medusa application, and the hosting options depend on the tools and frameworks you use to create the storefront.
-
-If you’re using the Next.js Starter storefront, you may deploy the storefront to any hosting provider that supports frontend frameworks, such as Vercel.
-
-Per Vercel’s [license and plans](https://vercel.com/pricing), their free plan can only be used for personal, non-commercial projects. So, you can deploy the storefront on the free plan for development purposes, but for commercial projects, you must update your Vercel plan.
-
-Refer to [this reference](https://docs.medusajs.com/resources/deployment/index.html.md) to find how-to deployment guides for specific hosting providers.
-
-
# Install Medusa
In this chapter, you'll learn how to install and run a Medusa application.
@@ -330,6 +260,76 @@ Refer to [this documentation](https://docs.medusajs.com/learn/update/index.html.
In the next chapters, you'll learn about the architecture of your Medusa application, then learn how to customize your application to build custom features.
+# Medusa Deployment Overview
+
+In this chapter, you’ll learn the general approach to deploying the Medusa application.
+
+## Medusa Project Components
+
+A standard Medusa project is made up of:
+
+- Medusa application: The Medusa server and the Medusa Admin.
+- One or more storefronts
+
+
+
+You deploy the Medusa application, with the server and admin, separately from the storefront.
+
+***
+
+## Deploying the Medusa Application
+
+You must deploy the Medusa application before the storefront, as it connects to the server and won’t work without a deployed Medusa server URL.
+
+The Medusa application must be deployed to a hosting provider supporting Node.js server deployments, such as Railway, DigitalOcean, AWS, Heroku, etc…
+
+
+
+Your server connects to a PostgreSQL database, Redis, and other services relevant for your setup. Most hosting providers support deploying and managing these databases along with your Medusa server (such as Railway and DigitalOcean).
+
+When you deploy your Medusa application, you also deploy the Medusa Admin. For optimal experience, your hosting provider and plan must offer at least 2GB of RAM.
+
+### Deploy Server and Worker Instances
+
+By default, Medusa runs all processes in a single instance. This includes the server that handles incoming requests and the worker that processes background tasks. While this works for development, it’s not optimal for production environments as many background tasks can be long-running or resource-heavy.
+
+Instead, you should deploy two instances:
+
+- A server instance, which handles incoming requests to the application’s API routes.
+- A worker instance, which processes background tasks, including scheduled jobs and subscribers.
+
+You don’t need to set up different projects for each instance. Instead, you can configure the Medusa application to run in different modes based on environment variables.
+
+Learn more about worker modes and how to configure them in the [Worker Mode chapter](https://docs.medusajs.com/learn/production/worker-mode/index.html.md).
+
+### How to Deploy Medusa?
+
+Medusa Cloud is our managed services offering that makes deploying and operating Medusa applications possible without having to worry about configuring, scaling, and maintaining infrastructure. Medusa Cloud hosts your server, Admin dashboard, database, and Redis instance.
+
+With Medusa Cloud, you maintain full customization control as you deploy your own modules and customizations directly from GitHub:
+
+- Push to deploy.
+- Multiple testing environments.
+- Preview environments for new PRs.
+- Test on production-like data.
+
+[Sign up and learn more about Medusa Cloud](https://medusajs.com/pricing)
+
+To self-host Medusa, the [next chapter](https://docs.medusajs.com/learn/deployment/general/index.html.md) explains the general steps to deploy your Medusa application. Refer to [this reference](https://docs.medusajs.com/resources/deployment/index.html.md) to find how-to deployment guides for general and specific hosting providers.
+
+***
+
+## Deploying the Storefront
+
+The storefront is deployed separately from the Medusa application, and the hosting options depend on the tools and frameworks you use to create the storefront.
+
+If you’re using the Next.js Starter storefront, you may deploy the storefront to any hosting provider that supports frontend frameworks, such as Vercel.
+
+Per Vercel’s [license and plans](https://vercel.com/pricing), their free plan can only be used for personal, non-commercial projects. So, you can deploy the storefront on the free plan for development purposes, but for commercial projects, you must update your Vercel plan.
+
+Refer to [this reference](https://docs.medusajs.com/resources/deployment/index.html.md) to find how-to deployment guides for specific hosting providers.
+
+
# Storefront Development
The Medusa application is made up of a Node.js server and an admin dashboard. Storefronts are installed, built, and hosted separately from the Medusa application, giving you the flexibility to choose the frontend tech stack that you and your team are proficient in, and implement unique design systems and user experience.
@@ -458,113 +458,6 @@ npm install
```
-# Build Custom Features
-
-In the upcoming chapters, you'll follow step-by-step guides to build custom features in Medusa. These guides gradually introduce Medusa's concepts to help you understand what they are and how to use them.
-
-By following these guides, you'll add brands to the Medusa application that you can associate with products.
-
-To build a custom feature in Medusa, you need three main tools:
-
-- [Module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md): a package with commerce logic for a single domain. It defines new tables to add to the database, and a class of methods to manage these tables.
-- [Workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md): a tool to perform an operation comprising multiple steps with built-in rollback and retry mechanisms.
-- [API route](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md): a REST endpoint that exposes commerce features to clients, such as the admin dashboard or a storefront. The API route executes a workflow that implements the commerce feature using modules.
-
-
-
-***
-
-## Next Chapters: Brand Module Example
-
-The next chapters will guide you to:
-
-1. Build a Brand Module that creates a `Brand` data model and provides data-management features.
-2. Add a workflow to create a brand.
-3. Expose an API route that allows admin users to create a brand using the workflow.
-
-
-# Customize Medusa Admin Dashboard
-
-In the previous chapters, you've customized your Medusa application to [add brands](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md), [expose an API route to create brands](https://docs.medusajs.com/learn/customization/custom-features/api-route/index.html.md), and [linked brands to products](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md).
-
-After customizing and extending your application with new features, you may need to provide an interface for admin users to utilize these features. The Medusa Admin dashboard is extendable, allowing you to:
-
-- Insert components, called [widgets](https://docs.medusajs.com/learn/fundamentals/admin/widgets/index.html.md), on existing pages.
-- Add new pages, called [UI Routes](https://docs.medusajs.com/learn/fundamentals/admin/ui-routes/index.html.md).
-
-From these customizations, you can send requests to custom API routes, allowing admin users to manage custom resources on the dashboard
-
-***
-
-## Next Chapters: View Brands in Dashboard
-
-In the next chapters, you'll continue with the brands example to:
-
-- Add a new section to the product details page that shows the product's brand.
-- Add a new page in the dashboard that shows all brands in the store.
-
-
-# Integrate Third-Party Systems
-
-Commerce applications often connect to third-party systems that provide additional or specialized features. For example, you may integrate a Content-Management System (CMS) for rich content features, a payment provider to process credit-card payments, and a notification service to send emails.
-
-Medusa's framework facilitates integrating these systems and orchestrating operations across them, saving you the effort of managing them yourself. You won't find those capabilities in other commerce platforms that in these scenarios become a bottleneck to building customizations and iterating quickly.
-
-In Medusa, you integrate a third-party system by:
-
-1. Creating a module whose service provides the methods to connect to and perform operations in the third-party system.
-2. Building workflows that complete tasks spanning across systems. You use the module that integrates a third-party system in the workflow's steps.
-3. Executing the workflows you built in an [API route](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md), at a scheduled time, or when an event is emitted.
-
-***
-
-## Next Chapters: Sync Brands Example
-
-In the previous chapters, you've [added brands](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) to your Medusa application. In the next chapters, you will:
-
-1. Integrate a dummy third-party CMS in the Brand Module.
-2. Sync brands to the CMS when a brand is created.
-3. Sync brands from the CMS at a daily schedule.
-
-
-# Extend Core Commerce Features
-
-In the upcoming chapters, you'll learn about the concepts and tools to extend Medusa's core commerce features.
-
-In other commerce platforms, you extend core features and models through hacky workarounds that can introduce unexpected issues and side effects across the platform. It also makes your application difficult to maintain and upgrade in the long run.
-
-Medusa's framework and orchestration tools mitigate these issues while supporting all your customization needs:
-
-- [Module Links](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md): Link data models of different modules without building direct dependencies, ensuring that the Medusa application integrates your modules without side effects.
-- [Workflow Hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md): inject custom functionalities into a workflow at predefined points, called hooks. This allows you to perform custom actions as a part of a core workflow without hacky workarounds.
-- [Additional Data in API Routes](https://docs.medusajs.com/learn/fundamentals/api-routes/additional-data/index.html.md): Configure core API routes to accept request parameters relevant to your customizations. These parameters are passed to the underlying workflow's hooks, where you can manage your custom data as part of an existing flow.
-
-***
-
-## Next Chapters: Link Brands to Products Example
-
-The next chapters explain how to use the tools mentioned above with step-by-step guides. You'll continue with the [brands example from the previous chapters](https://docs.medusajs.com/learn/customization/custom-features/index.html.md) to:
-
-- Link brands from the custom [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) to products from Medusa's [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md).
-- Extend the core product-creation workflow and the API route that uses it to allow setting the brand of a newly created product.
-- Retrieve a product's associated brand's details.
-
-
-# Re-Use Customizations with Plugins
-
-In the previous chapters, you've learned important concepts related to creating modules, implementing commerce features in workflows, exposing those features in API routes, customizing the Medusa Admin dashboard with Admin Extensions, and integrating third-party systems.
-
-You've implemented the brands example within a single Medusa application. However, this approach is not scalable when you want to reuse your customizations across multiple projects.
-
-To reuse your customizations across multiple Medusa applications, such as implementing brands in different projects, you can create a plugin. A plugin is an NPM package that encapsulates your customizations and can be installed in any Medusa application. Plugins can include modules, workflows, API routes, Admin Extensions, and more.
-
-
-
-Medusa provides the tooling to create a plugin package, test it in a local Medusa application, and publish it to NPM.
-
-To learn more about plugins and how to create them, refer to [this chapter](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
-
-
# Medusa Application Configuration
In this chapter, you'll learn available configurations in the Medusa application. You can change the application's configurations to customize the behavior of the application, its integrated modules and plugins, and more.
@@ -1468,28 +1361,6 @@ npx medusa db:migrate
```
-# Customizations Next Steps: Learn the Fundamentals
-
-The previous guides introduced Medusa's different concepts and how you can use them to customize Medusa for a realistic use case, You added brands to your application, linked them to products, customized the admin dashboard, and integrated a third-party CMS.
-
-The next chapters will cover each of these concepts in depth, with the different ways you can use them, their options or configurations, and more advanced features that weren't covered in the previous guides. While you can start building with Medusa, it's highly recommended to follow the next chapters for a better understanding of Medusa's fundamentals.
-
-## Useful Guides
-
-The following guides and references are useful for your development journey:
-
-3. [Commerce Modules](https://docs.medusajs.com/resources/commerce-modules/index.html.md): Browse the list of commerce modules in Medusa and their references to learn how to use them.
-4. [Service Factory Reference](https://docs.medusajs.com/resources/service-factory-reference/index.html.md): Learn about the methods generated by `MedusaService` with examples.
-5. [Workflows Reference](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md): Browse the list of core workflows and their hooks that are useful for your customizations.
-6. [Admin Injection Zones](https://docs.medusajs.com/resources/admin-widget-injection-zones/index.html.md): Browse the injection zones in the Medusa Admin to learn where you can inject widgets.
-
-***
-
-## More Examples in Recipes
-
-In the [Recipes](https://docs.medusajs.com/resources/recipes/index.html.md) documentation, you'll also find step-by-step guides for different use cases, such as building a marketplace, digital products, and more.
-
-
# Using TypeScript Aliases
By default, Medusa doesn't support TypeScript aliases in production.
@@ -1534,1867 +1405,6 @@ import { BrandModuleService } from "@/modules/brand/service"
```
-# General Medusa Application Deployment Guide
-
-In this document, you'll learn the general steps to deploy your Medusa application. How you apply these steps depend on your chosen hosting provider or platform.
-
-Find how-to guides for specific platforms in [this documentation](https://docs.medusajs.com/resources/deployment/index.html.md).
-
-Want Medusa to manage and maintain your infrastructure? [Sign up and learn more about Medusa Cloud](https://medusajs.com/pricing)
-
-Medusa Cloud is our managed services offering that makes deploying and operating Medusa applications possible without having to worry about configuring, scaling, and maintaining infrastructure. Medusa Cloud hosts your server, Admin dashboard, database, and Redis instance.
-
-With Medusa Cloud, you maintain full customization control as you deploy your own modules and customizations directly from GitHub:
-
-- Push to deploy.
-- Multiple testing environments.
-- Preview environments for new PRs.
-- Test on production-like data.
-
-### Prerequisites
-
-- [Medusa application’s codebase hosted on GitHub repository.](https://docs.medusajs.com/learn/index.html.md)
-
-## Hosting Provider Requirements
-
-When you deploy your Medusa application, make sure your chosen hosting provider supports deploying the following resources:
-
-1. PostgreSQL database: If your hosting provider doesn't support database hosting, you must find another hosting provider for the PostgreSQL database.
-2. Redis database: If your hosting provider doesn't support database hosting, you must find another hosting provider for the Redis database.
-3. Medusa application in server and worker mode. This means your hosting provider should support deploying two applications or instances from the same codebase.
-4. For optimal experience, the hosting provider and plan must offer at least 2GB of RAM.
-
-***
-
-## 1. Configure Medusa Application
-
-### Worker Mode
-
-The `workerMode` configuration determines which mode the Medusa application runs in. When you deploy the Medusa application, you deploy two instances: one in server mode, and one in worker mode.
-
-Learn more about worker mode in the [Worker Module chapter](https://docs.medusajs.com/learn/production/worker-mode/index.html.md).
-
-So, add the following configuration in `medusa-config.ts`:
-
-```ts title="medusa-config.ts"
-module.exports = defineConfig({
- projectConfig: {
- // ...
- workerMode: process.env.MEDUSA_WORKER_MODE as "shared" | "worker" | "server",
- },
-})
-```
-
-Later, you’ll set different values of the `MEDUSA_WORKER_MODE` environment variable for each Medusa application deployment or instance.
-
-### Configure Medusa Admin
-
-You need to disable the Medusa Admin in the worker Medusa application, while keeping it enabled in the server Medusa application. So, add the following configuration in `medusa-config.ts`:
-
-```ts title="medusa-config.ts"
-module.exports = defineConfig({
- // ...
- admin: {
- disable: process.env.DISABLE_MEDUSA_ADMIN === "true",
- },
-})
-```
-
-Later, you’ll set different values of the `DISABLE_MEDUSA_ADMIN` environment variable.
-
-### Configure Redis URL
-
-The `redisUrl` configuration specifies the connection URL to Redis to store the Medusa server's session.
-
-Learn more in the [Medusa Configuration documentation](https://docs.medusajs.com/learn/configurations/medusa-config#redisurl/index.html.md).
-
-So, add the following configuration in `medusa-config.ts` :
-
-```ts title="medusa-config.ts"
-module.exports = defineConfig({
- projectConfig: {
- // ...
- redisUrl: process.env.REDIS_URL,
- },
-})
-```
-
-***
-
-## 2. Add predeploy Script
-
-Before you start the Medusa application in production, you should always run migrations and sync links.
-
-So, add the following script in `package.json`:
-
-```json
-"scripts": {
- // ...
- "predeploy": "medusa db:migrate"
-},
-```
-
-***
-
-## 3. Install Production Modules and Providers
-
-By default, your Medusa application uses modules and providers useful for development, such as the In-Memory Cache Module or the Local File Module Provider.
-
-It’s highly recommended to instead use modules and providers suitable for production, including:
-
-- [Redis Cache Module](https://docs.medusajs.com/resources/architectural-modules/cache/redis/index.html.md)
-- [Redis Event Bus Module](https://docs.medusajs.com/resources/architectural-modules/event/redis/index.html.md)
-- [Workflow Engine Redis Module](https://docs.medusajs.com/resources/architectural-modules/workflow-engine/redis/index.html.md)
-- [S3 File Module Provider](https://docs.medusajs.com/resources/architectural-modules/file/s3/index.html.md) (or other file module providers production-ready).
-- [SendGrid Notification Module Provider](https://docs.medusajs.com/resources/architectural-modules/notification/sendgrid/index.html.md) (or other notification module providers production-ready).
-
-Then, add these modules in `medusa-config.ts`:
-
-```ts title="medusa-config.ts"
-import { Modules } from "@medusajs/framework/utils"
-
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "@medusajs/medusa/cache-redis",
- options: {
- redisUrl: process.env.REDIS_URL,
- },
- },
- {
- resolve: "@medusajs/medusa/event-bus-redis",
- options: {
- redisUrl: process.env.REDIS_URL,
- },
- },
- {
- resolve: "@medusajs/medusa/workflow-engine-redis",
- options: {
- redis: {
- url: process.env.REDIS_URL,
- },
- },
- },
- ],
-})
-```
-
-Check out the [Integrations](https://docs.medusajs.com/resources/integrations/index.html.md) and [Architectural Modules](https://docs.medusajs.com/resources/architectural-modules/index.html.md) documentation for other modules and providers to use.
-
-***
-
-## 4. Create PostgreSQL and Redis Databases
-
-Your Medusa application must connect to PostgreSQL and Redis databases. So, before you deploy it, create production PostgreSQL and Redis databases.
-
-If your hosting provider doesn't support databases, you can use [Neon for PostgreSQL database hosting](https://neon.tech/), and [Redis Cloud for the Redis database hosting](https://redis.io/cloud/).
-
-After hosting both databases, keep their connection URLs for the next steps.
-
-***
-
-## 5. Deploy Medusa Application in Server Mode
-
-As mentioned earlier, you'll deploy two instances or create two deployments of your Medusa application: one in server mode, and the other in worker mode.
-
-The deployment steps depend on your hosting provider. This section provides the general steps to perform during the deployment.
-
-### Set Environment Variables
-
-When setting the environment variables of the Medusa application, set the following variables:
-
-```bash
-COOKIE_SECRET=supersecret # TODO GENERATE SECURE SECRET
-JWT_SECRET=supersecret # TODO GENERATE SECURE SECRET
-STORE_CORS= # STOREFRONT URL
-ADMIN_CORS= # ADMIN URL
-AUTH_CORS= # STOREFRONT AND ADMIN URLS, SEPARATED BY COMMAS
-DISABLE_MEDUSA_ADMIN=false
-MEDUSA_WORKER_MODE=server
-PORT=9000
-DATABASE_URL # POSTGRES DATABASE URL
-REDIS_URL= # REDIS DATABASE URL
-```
-
-Where:
-
-- The value of `COOKIE_SECRET` and `JWT_SECRET` must be a randomly generated secret.
-- `STORE_CORS`'s value is the URL of your storefront. If you don’t have it yet, you can skip adding it for now.
-- `ADMIN_CORS`'s value is the URL of the admin dashboard, which is the same as the server Medusa application. You can add it later if you don't currently have it.
-- `AUTH_CORS`'s value is the URLs of any application authenticating users, customers, or other actor types, such as the storefront and admin URLs. The URLs are separated by commas. If you don’t have the URLs yet, you can set its value later.
-- Set `DISABLE_MEDUSA_ADMIN`'s value to `false` so that the admin is built with the server application.
-- Set the PostgreSQL database's connection URL as the value of `DATABASE_URL`
-- Set the Redis database's connection URL as the value of `REDIS_URL`.
-
-Feel free to add any other relevant environment variables, such as for integrations and architectural modules.
-
-### Set Start Command
-
-The Medusa application's production build, which is created using the `build` command, outputs the Medusa application to `.medusa/server`. So, you must install the dependencies in the `.medusa/server` directory, then run the `start` command in it.
-
-If your hosting provider doesn't support setting a current-working directory, set the start command to the following:
-
-```bash npm2yarn
-cd .medusa/server && npm install && npm run predeploy && npm run start
-```
-
-Notice that you run the `predeploy` command before starting the Medusa application to run migrations and sync links whenever there's an update.
-
-### Set Backend URL in Admin Configuration
-
-After you’ve obtained the Medusa application’s URL, add the following configuration to `medusa-config.ts`:
-
-```ts title="medusa-config.ts"
-module.exports = defineConfig({
- // ...
- admin: {
- // ...
- backendUrl: process.env.MEDUSA_BACKEND_URL,
- },
-})
-```
-
-Then, push the changes to the GitHub repository or deployed application.
-
-In your hosting provider, add or modify the following environment variables for the Medusa application in server mode:
-
-```bash
-ADMIN_CORS= # MEDUSA APPLICATION URL
-AUTH_CORS= # ADD MEDUSA APPLICATION URL
-MEDUSA_BACKEND_URL= # URL TO DEPLOYED MEDUSA APPLICATION
-```
-
-Where you set the value of `ADMIN_CORS` and `MEDUSA_BACKEND_URL` to the Medusa application’s URL, and you add the URL to `AUTH_CORS`.
-
-Remember to separate URLs in `AUTH_CORS` by commas.
-
-***
-
-## 6. Deploy Medusa Application in Worker Mode
-
-Next, you'll deploy the Medusa application in worker mode.
-
-As explained in the previous section, the deployment steps depend on your hosting provider. This section provides the general steps to perform during the deployment.
-
-### Set Environment Variables
-
-When setting the environment variables of the Medusa application, set the following variables:
-
-```bash
-COOKIE_SECRET=supersecret # TODO GENERATE SECURE SECRET
-JWT_SECRET=supersecret # TODO GENERATE SECURE SECRET
-DISABLE_MEDUSA_ADMIN=true
-MEDUSA_WORKER_MODE=worker
-PORT=9000
-DATABASE_URL # POSTGRES DATABASE URL
-REDIS_URL= # REDIS DATABASE URL
-```
-
-Where:
-
-- The value of `COOKIE_SECRET` and `JWT_SECRET` must be a randomly generated secret.
-- Set `DISABLE_MEDUSA_ADMIN`'s value to `true` so that the admin isn't built with the worker application.
-- Set the PostgreSQL database's connection URL as the value of `DATABASE_URL`
-- Set the Redis database's connection URL as the value of `REDIS_URL`.
-
-Feel free to add any other relevant environment variables, such as for integrations and architectural modules.
-
-### Set Start Command
-
-The Medusa application's production build, which is created using the `build` command, outputs the Medusa application to `.medusa/server`. So, you must install the dependencies in the `.medusa/server` directory, then run the `start` command in it.
-
-If your hosting provider doesn't support setting a current-working directory, set the start command to the following:
-
-```bash npm2yarn
-cd .medusa/server && npm run install && npm run start
-```
-
-***
-
-## 7. Test Deployed Application
-
-Once the application is deployed and live, go to `/health`, where `` is the URL of the Medusa application in server mode. If the deployment was successful, you’ll see the `OK` response.
-
-The Medusa Admin is also available at `/app`.
-
-***
-
-## Create Admin User
-
-If your hosting provider supports running commands in your Medusa application's directory, run the following command to create an admin user:
-
-```bash
-npx medusa user -e admin-medusa@test.com -p supersecret
-```
-
-Replace the email `admin-medusa@test.com` and password `supersecret` with the credentials you want.
-
-You can use these credentials to log into the Medusa Admin dashboard.
-
-
-# Admin Development
-
-In the next chapters, you'll learn more about possible admin customizations.
-
-You can customize the admin dashboard by:
-
-- Adding new sections to existing pages using Widgets.
-- Adding new pages using UI Routes.
-
-***
-
-## Medusa UI Package
-
-Medusa provides a Medusa UI package to facilitate your admin development through ready-made components and ensure a consistent design between your customizations and the dashboard’s design.
-
-Refer to the [Medusa UI documentation](https://docs.medusajs.com/ui/index.html.md) to learn how to install it and use its components.
-
-***
-
-## Admin Components List
-
-To build admin customizations that match the Medusa Admin's designs and layouts, refer to [this guide](https://docs.medusajs.com/resources/admin-components/index.html.md) to find common components.
-
-
-# Custom CLI Scripts
-
-In this chapter, you'll learn how to create and execute custom scripts from Medusa's CLI tool.
-
-## What is a Custom CLI Script?
-
-A custom CLI script is a function to execute through Medusa's CLI tool. This is useful when creating custom Medusa tooling to run through the CLI.
-
-***
-
-## How to Create a Custom CLI Script?
-
-To create a custom CLI script, create a TypeScript or JavaScript file under the `src/scripts` directory. The file must default export a function.
-
-For example, create the file `src/scripts/my-script.ts` with the following content:
-
-```ts title="src/scripts/my-script.ts"
-import {
- ExecArgs,
- IProductModuleService,
-} from "@medusajs/framework/types"
-import { Modules } from "@medusajs/framework/utils"
-
-export default async function myScript({ container }: ExecArgs) {
- const productModuleService: IProductModuleService = container.resolve(
- Modules.PRODUCT
- )
-
- const [, count] = await productModuleService
- .listAndCountProducts()
-
- console.log(`You have ${count} product(s)`)
-}
-```
-
-The function receives as a parameter an object having a `container` property, which is an instance of the Medusa Container. Use it to resolve resources in your Medusa application.
-
-***
-
-## How to Run Custom CLI Script?
-
-To run the custom CLI script, run the Medusa CLI's `exec` command:
-
-```bash
-npx medusa exec ./src/scripts/my-script.ts
-```
-
-***
-
-## Custom CLI Script Arguments
-
-Your script can accept arguments from the command line. Arguments are passed to the function's object parameter in the `args` property.
-
-For example:
-
-```ts
-import { ExecArgs } from "@medusajs/framework/types"
-
-export default async function myScript({ args }: ExecArgs) {
- console.log(`The arguments you passed: ${args}`)
-}
-```
-
-Then, pass the arguments in the `exec` command after the file path:
-
-```bash
-npx medusa exec ./src/scripts/my-script.ts arg1 arg2
-```
-
-
-# API Routes
-
-In this chapter, you’ll learn what API Routes are and how to create them.
-
-## What is an API Route?
-
-An API Route is an endpoint. It exposes commerce features to external applications, such as storefronts, the admin dashboard, or third-party systems.
-
-The Medusa core application provides a set of admin and store API routes out-of-the-box. You can also create custom API routes to expose your custom functionalities.
-
-***
-
-## How to Create an API Route?
-
-An API Route is created in a TypeScript or JavaScript file under the `src/api` directory of your Medusa application. The file’s name must be `route.ts` or `route.js`.
-
-
-
-Each file exports API Route handler functions for at least one HTTP method (`GET`, `POST`, `DELETE`, etc…).
-
-For example, to create a `GET` API Route at `/hello-world`, create the file `src/api/hello-world/route.ts` with the following content:
-
-```ts title="src/api/hello-world/route.ts"
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-
-export const GET = (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- res.json({
- message: "[GET] Hello world!",
- })
-}
-```
-
-### Test API Route
-
-To test the API route above, start the Medusa application:
-
-```bash npm2yarn
-npm run dev
-```
-
-Then, send a `GET` request to the `/hello-world` API Route:
-
-```bash
-curl http://localhost:9000/hello-world
-```
-
-***
-
-## When to Use API Routes
-
-You're exposing custom functionality to be used by a storefront, admin dashboard, or any external application.
-
-
-# Environment Variables
-
-In this chapter, you'll learn how environment variables are loaded in Medusa.
-
-## System Environment Variables
-
-The Medusa application loads and uses system environment variables.
-
-For example, if you set the `PORT` environment variable to `8000`, the Medusa application runs on that port instead of `9000`.
-
-In production, you should always use system environment variables that you set through your hosting provider.
-
-***
-
-## Environment Variables in .env Files
-
-During development, it's easier to set environment variables in a `.env` file in your repository.
-
-Based on your `NODE_ENV` system environment variable, Medusa will try to load environment variables from the following `.env` files:
-
-As of [Medusa v2.5.0](https://github.com/medusajs/medusa/releases/tag/v2.5.0), `NODE_ENV` defaults to `production` when using `medusa start`. Otherwise, it defaults to `development`.
-
-|\`.env\`|
-|---|---|
-|\`NODE\_ENV\`|\`.env\`|
-|\`NODE\_ENV\`|\`.env.production\`|
-|\`NODE\_ENV\`|\`.env.staging\`|
-|\`NODE\_ENV\`|\`.env.test\`|
-
-### Set Environment in `loadEnv`
-
-In the `medusa-config.ts` file of your Medusa application, you'll find a `loadEnv` function used that accepts `process.env.NODE_ENV` as a first parameter.
-
-This function is responsible for loading the correct `.env` file based on the value of `process.env.NODE_ENV`.
-
-To ensure that the correct `.env` file is loaded as shown in the table above, only specify `development`, `production`, `staging` or `test` as the value of `process.env.NODE_ENV` or as the parameter of `loadEnv`.
-
-***
-
-## Environment Variables for Admin Customizations
-
-Since the Medusa Admin is built on top of [Vite](https://vite.dev/), you prefix the environment variables you want to use in a widget or UI route with `VITE_`. Then, you can access or use them with the `import.meta.env` object.
-
-Learn more in [this documentation](https://docs.medusajs.com/learn/fundamentals/admin/environment-variables/index.html.md).
-
-***
-
-## Predefined Medusa Environment Variables
-
-The Medusa application uses the following predefined environment variables that you can set:
-
-You should opt for setting configurations in `medusa-config.ts` where possible. For a full list of Medusa configurations, refer to the [Medusa Configurations chapter](https://docs.medusajs.com/learn/configurations/medusa-config/index.html.md).
-
-|Environment Variable|Description|Default|
-|---|---|---|---|---|
-|
-|
-|
-|
-||The URL to connect to the PostgreSQL database. Only used if ||
-||URLs of storefronts that can access the Medusa backend's Store APIs. Only used if ||
-||URLs of admin dashboards that can access the Medusa backend's Admin APIs. Only used if ||
-||URLs of clients that can access the Medusa backend's authentication routes. Only used if ||
-||A random string used to create authentication tokens in the http layer. Only used if ||
-||A random string used to create cookie tokens in the http layer. Only used if ||
-||The URL to the Medusa backend. Only used if ||
-|
-|
-|
-|
-|
-|
-|
-|
-||The allowed levels to log. Learn more in ||
-||The file to save logs in. By default, logs aren't saved in any file. Learn more in ||
-||Whether to disable analytics data collection. Learn more in ||
-
-
-# Medusa Container
-
-In this chapter, you’ll learn about the Medusa container and how to use it.
-
-## What is the Medusa Container?
-
-The Medusa container is a registry of framework and commerce tools that's accessible across your application. Medusa automatically registers these tools in the container, including custom ones that you've built, so that you can use them in your customizations.
-
-In other platforms, if you have a resource A (for example, a class) that depends on a resource B, you have to manually add resource B to the container or specify it beforehand as A's dependency, which is often done in a file separate from A's code. This becomes difficult to manage as you maintain larger applications with many changing dependencies.
-
-Medusa simplifies this process by giving you access to the container, with the tools or resources already registered, at all times in your customizations. When you reach a point in your code where you need a tool, you resolve it from the container and use it.
-
-For example, consider you're creating an API route that retrieves products based on filters using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md), a tool that fetches data across the application. In the API route's function, you can resolve Query from the container passed to the API route and use it:
-
-```ts highlights={highlights}
-import { MedusaRequest, MedusaResponse } from "@medusajs/framework"
-import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const query = req.scope.resolve("query")
-
- const { data: products } = await query.graph({
- entity: "product",
- fields: ["*"],
- filters: {
- id: "prod_123",
- },
- })
-
- res.json({
- products,
- })
-}
-```
-
-The API route accepts as a first parameter a request object that has a `scope` property, which is the Medusa container. It has a `resolve` method that resolves a resource from the container by the key it's registered with.
-
-You can learn more about how Query works in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md).
-
-***
-
-## List of Resources Registered in the Medusa Container
-
-Find a full list of the registered resources and their registration key in [this reference](https://docs.medusajs.com/resources/medusa-container-resources/index.html.md)
-
-***
-
-## How to Resolve From the Medusa Container
-
-This section gives quick examples of how to resolve resources from the Medusa container in customizations other than an API route, which is covered in the section above.
-
-### Subscriber
-
-A [subscriber](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) function, which is executed when an event is emitted, accepts as a parameter an object with a `container` property, whose value is the Medusa container. Use its `resolve` method to resolve a resource by its registration key:
-
-```ts highlights={subscriberHighlights}
-import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework"
-import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
-
-export default async function productCreateHandler({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- const query = container.resolve(ContainerRegistrationKeys.QUERY)
-
- const { data: products } = await query.graph({
- entity: "product",
- fields: ["*"],
- filters: {
- id: data.id,
- },
- })
-
- console.log(`You created a product with the title ${products[0].title}`)
-}
-
-export const config: SubscriberConfig = {
- event: `product.created`,
-}
-```
-
-### Scheduled Job
-
-A [scheduled job](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md) function, which is executed at a specified interval, accepts the Medusa container as a parameter. Use its `resolve` method to resolve a resource by its registration key:
-
-```ts highlights={scheduledJobHighlights}
-import { MedusaContainer } from "@medusajs/framework/types"
-import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
-
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const query = container.resolve(ContainerRegistrationKeys.QUERY)
-
- const { data: products } = await query.graph({
- entity: "product",
- fields: ["*"],
- filters: {
- id: "prod_123",
- },
- })
-
- console.log(
- `You have ${products.length} matching your filters.`
- )
-}
-
-export const config = {
- name: "every-minute-message",
- // execute every minute
- schedule: "* * * * *",
-}
-```
-
-### Workflow Step
-
-A [step in a workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), which is a special function where you build durable execution logic across multiple modules, accepts in its second parameter a `container` property, whose value is the Medusa container. Use its `resolve` method to resolve a resource by its registration key:
-
-```ts highlights={workflowStepsHighlight}
-import {
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
-
-const step1 = createStep("step-1", async (_, { container }) => {
- const query = container.resolve(ContainerRegistrationKeys.QUERY)
-
- const { data: products } = await query.graph({
- entity: "product",
- fields: ["*"],
- filters: {
- id: "prod_123",
- },
- })
-
- return new StepResponse(products)
-})
-```
-
-### Module Services and Loaders
-
-A [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), which is a package of functionalities for a single feature or domain, has its own container, so it can't resolve resources from the Medusa container.
-
-Learn more about the module's container in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/container/index.html.md).
-
-
-# Events and Subscribers
-
-In this chapter, you’ll learn about Medusa's event system, and how to handle events with subscribers.
-
-## Handle Core Commerce Flows with Events
-
-When building commerce digital applications, you'll often need to perform an action after a commerce operation is performed. For example, sending an order confirmation email when the customer places an order, or syncing data that's updated in Medusa to a third-party system.
-
-Medusa emits events when core commerce features are performed, and you can listen to and handle these events in asynchronous functions. You can think of Medusa's events like you'd think about webhooks in other commerce platforms, but instead of having to setup separate applications to handle webhooks, your efforts only go into writing the logic right in your Medusa codebase.
-
-You listen to an event in a subscriber, which is an asynchronous function that's executed when its associated event is emitted.
-
-
-
-Subscribers are useful to perform actions that aren't integral to the original flow. For example, you can handle the `order.placed` event in a subscriber that sends a confirmation email to the customer. The subscriber has no impact on the original order-placement flow, as it's executed outside of it.
-
-If the action you're performing is integral to the main flow of the core commerce feature, use [workflow hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md) instead.
-
-### List of Emitted Events
-
-Find a list of all emitted events in [this reference](https://docs.medusajs.com/resources/events-reference/index.html.md).
-
-***
-
-## How to Create a Subscriber?
-
-You create a subscriber in a TypeScript or JavaScript file under the `src/subscribers` directory. The file exports the function to execute and the subscriber's configuration that indicate what event(s) it listens to.
-
-For example, create the file `src/subscribers/order-placed.ts` with the following content:
-
-
-
-```ts title="src/subscribers/product-created.ts"
-import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework"
-import { sendOrderConfirmationWorkflow } from "../workflows/send-order-confirmation"
-
-export default async function orderPlacedHandler({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- const logger = container.resolve("logger")
-
- logger.info("Sending confirmation email...")
-
- await sendOrderConfirmationWorkflow(container)
- .run({
- input: {
- id: data.id,
- },
- })
-}
-
-export const config: SubscriberConfig = {
- event: `order.placed`,
-}
-```
-
-This subscriber file exports:
-
-- An asynchronous subscriber function that's executed whenever the associated event, which is `order.placed` is triggered.
-- A configuration object with an `event` property whose value is the event the subscriber is listening to. You can also pass an array of event names to listen to multiple events in the same subscriber.
-
-The subscriber function receives an object as a parameter that has the following properties:
-
-- `event`: An object with the event's details. The `data` property contains the data payload of the event emitted, which is the order's ID in this case.
-- `container`: The [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) that you can use to resolve registered resources.
-
-In the subscriber function, you use the container to resolve the Logger utility and log a message in the console. Also, assuming you have a [workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md) that sends an order confirmation email, you execute it in the subscriber.
-
-***
-
-## Test the Subscriber
-
-To test the subscriber, start the Medusa application:
-
-```bash npm2yarn
-npm run dev
-```
-
-Then, try placing an order either using Medusa's API routes or the [Next.js Storefront](https://docs.medusajs.com/learn/storefront-development/nextjs-starter/index.html.md). You'll see the following message in the terminal:
-
-```bash
-info: Processing order.placed which has 1 subscribers
-Sending confirmation email...
-```
-
-The first message indicates that the `order.placed` event was emitted, and the second one is the message logged from the subscriber.
-
-***
-
-## Event Module
-
-The subscription and emitting of events is handled by an Event Module, an architectural module that implements the pub/sub functionalities of Medusa's event system.
-
-Medusa provides two Event Modules out of the box:
-
-- [Local Event Module](https://docs.medusajs.com/resources/architectural-modules/event/local/index.html.md), used by default. It's useful for development, as you don't need additional setup to use it.
-- [Redis Event Module](https://docs.medusajs.com/resources/architectural-modules/event/redis/index.html.md), which is useful in production. It uses [Redis](https://redis.io/) to implement Medusa's pub/sub events system.
-
-Medusa's [architecture](https://docs.medusajs.com/learn/introduction/architecture/index.html.md) also allows you to build a custom Event Module that uses a different service or logic to implement the pub/sub system. Learn how to build an Event Module in [this guide](https://docs.medusajs.com/resources/architectural-modules/event/create/index.html.md).
-
-
-# Module Link
-
-In this chapter, you’ll learn what a module link is and how to define one.
-
-## What is a Module Link?
-
-Medusa's modular architecture isolates modules from one another to ensure they can be integrated into your application without side effects. Module isolation has other benefits, which you can learn about in the [Module Isolation chapter](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md). Since modules are isolated, you can't access another module's data models to add a relation to it or extend it. Instead, you use a module link.
-
-A module link forms an association between two data models of different modules while maintaining module isolation. Using module links, you can build virtual relations between your custom data models and data models in the commerce modules, which is useful as you extend the features provided by the commerce modules. Then, Medusa creates a link table in the database to store the IDs of the linked records. You'll learn more about link tables later in this chapter.
-
-For example, the [Brand Customizations Tutorial](https://docs.medusajs.com/learn/customization/extend-features/index.html.md) shows how to create a Brand Module that adds the concept of brands to your application, then link those brands to a product.
-
-***
-
-## How to Define a Module Link?
-
-### 1. Create Link File
-
-Module links are defined in a TypeScript or JavaScript file under the `src/links` directory. The file defines the link using `defineLink` from the Modules SDK and exports it.
-
-For example:
-
-```ts title="src/links/blog-product.ts" highlights={highlights}
-import BlogModule from "../modules/blog"
-import ProductModule from "@medusajs/medusa/product"
-import { defineLink } from "@medusajs/framework/utils"
-
-export default defineLink(
- ProductModule.linkable.product,
- BlogModule.linkable.post
-)
-```
-
-The `defineLink` function accepts as parameters the link configurations of each module's data model. A module has a special `linkable` property that holds these configurations for its data models.
-
-In this example, you define a module link between the `blog` module's `post` data model and the Product Module's `Product` data model.
-
-### 2. Sync Links
-
-After defining the link, run the `db:sync-links` command:
-
-```bash
-npx medusa db:sync-links
-```
-
-The Medusa application creates a new table for your module link to store the IDs of linked records.
-
-You can also use the `db:migrate` command, which runs both the migrations and syncs the links.
-
-Use either of these commands whenever you make changes to your link definitions. For example, run this command if you remove your link definition file.
-
-***
-
-### Module Link's Database Table
-
-When you define a module link, the Medusa application creates a table in the database for that module link. The table's name is a combination of the names of the two data models linked in the format `module1_table1_module2_table2`, where:
-
-- `module1` and `module2` are the names of the modules.
-- `table1` and `table2` are the table names of the data models.
-
-For example, if you define a link between the `Product` data model from the [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md) and a `Post` data model from a Blog Module, the table name would be `product_product_blog_post`.
-
-The table has two columns, each storing the ID of a record from the linked data models. For example, the `product_product_blog_post` table would have columns `product_id` and `post_id`. These columns store only the IDs of the linked records and do not hold a foreign key constraint.
-
-Then, when you create links between records of the data models, the IDs of these data models are stored as a new record in the link's table.
-
-You can also add custom columns in the link table as explained in the [Add Columns to Link Table chapter](https://docs.medusajs.com/learn/fundamentals/module-links/custom-columns/index.html.md).
-
-
-
-***
-
-## When to Use Module Links
-
-- You want to create a relation between data models from different modules.
-- You want to extend the data model of another module.
-
-You want to create a relationship between data models in the same module. Use data model relationships instead.
-
-***
-
-## Define a List Module Link
-
-By default, a module link establishes a one-to-one relation: a record of a data model is linked to one record of the other data model.
-
-To specify that a data model can have multiple of its records linked to the other data model's record, use the `isList` option.
-
-For example:
-
-```ts
-import BlogModule from "../modules/blog"
-import ProductModule from "@medusajs/medusa/product"
-import { defineLink } from "@medusajs/framework/utils"
-
-export default defineLink(
- ProductModule.linkable.product,
- {
- linkable: BlogModule.linkable.post,
- isList: true,
- }
-)
-```
-
-In this case, you pass an object of configuration as a parameter instead. The object accepts the following properties:
-
-- `linkable`: The data model's link configuration.
-- `isList`: Whether multiple records can be linked to one record of the other data model.
-
-In this example, a record of `product` can be linked to more than one record of `post`.
-
-### Many-to-Many Module Link
-
-Your module link can also establish a many-to-many relation between the linked data models. To do this, enable `isList` on both sides of the link.
-
-For example:
-
-```ts
-import BlogModule from "../modules/blog"
-import ProductModule from "@medusajs/medusa/product"
-import { defineLink } from "@medusajs/framework/utils"
-
-export default defineLink(
- {
- linkable: ProductModule.linkable.product,
- isList: true,
- },
- {
- linkable: BlogModule.linkable.post,
- isList: true,
- }
-)
-```
-
-***
-
-## Set Delete Cascades on Link
-
-To enable delete cascade on a link so that when a record is deleted, its linked records are also deleted, pass the `deleteCascade` property in the object passed to `defineLink`.
-
-For example:
-
-```ts
-import BlogModule from "../modules/blog"
-import ProductModule from "@medusajs/medusa/product"
-import { defineLink } from "@medusajs/framework/utils"
-
-export default defineLink(
- ProductModule.linkable.product,
- {
- linkable: BlogModule.linkable.post,
- deleteCascade: true,
- }
-)
-```
-
-In this example, when a product is deleted, its linked `post` record is also deleted.
-
-***
-
-## Renaming Participants in a Module Link
-
-As mentioned in the [Module Link's Database Table](#module-links-database-table) section, the name of a link's table consists of the names of the modules and the data models' table names.
-
-So, if you rename a module or a data model's table, then run the `db:sync-links` or `db:migrate` commands, you'll be asked to delete the old link table and create a new one.
-
-A data model's table name is passed in the first parameter of `model.define`, and a module's name is passed in the first parameter of `Module` in the module's `index.ts` file.
-
-For example, if you have the link table `product_product_blog_post` and you rename the Blog Module from `blog` to `article`, Medusa considers the old link definition deleted. Then, when you run the `db:sync-links` or `db:migrate` command, Medusa will ask if you want to delete the old link table, and will create a new one with the new name `product_product_article_post`.
-
-To resolve this, you can rename the link table in the link definition.
-
-### Rename Link Table
-
-If you need to rename a module or its data model's table, you can persist the old name by passing a third parameter to `defineLink`. This parameter is an object of additional configurations. It accepts a `database` property that allows you to configure the link's table name.
-
-For example, after renaming the Blog Module to `article`, you can persist the old name `blog` in the link table name:
-
-```ts highlights={renameHighlights}
-import ArticleModule from "../modules/article"
-import ProductModule from "@medusajs/medusa/product"
-import { defineLink } from "@medusajs/framework/utils"
-
-export default defineLink(
- ProductModule.linkable.product,
- {
- linkable: ArticleModule.linkable.post,
- isList: true,
- },
- {
- database: {
- table: "product_product_blog_post",
- },
- }
-)
-```
-
-In this example, you set the `table` property in the `database` object to the old link table name `product_product_blog_post`, ensuring that the old link table is not deleted.
-
-This is enough to rename the link table when you rename a module. If you renamed a data model's table, you need to also run the `db:sync-links` or `db:migrate` commands, which will update the column names in the link table automatically:
-
-```bash
-npx medusa db:migrate
-```
-
-***
-
-## Delete Module Link Definition
-
-To delete a module link definition, remove the link file from the `src/links` directory. Then, run the `db:sync-links` or `db:migrate` command to delete the link table from the database:
-
-```bash
-npx medusa db:migrate
-```
-
-
-# Plugins
-
-In this chapter, you'll learn what a plugin is in Medusa.
-
-Plugins are available starting from [Medusa v2.3.0](https://github.com/medusajs/medusa/releases/tag/v2.3.0).
-
-## What is a Plugin?
-
-A plugin is a package of reusable Medusa customizations that you can install in any Medusa application. The supported customizations are [Modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), [API Routes](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md), [Workflows](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), [Workflow Hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md), [Links](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md), [Subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md), [Scheduled Jobs](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md), and [Admin Extensions](https://docs.medusajs.com/learn/fundamentals/admin/index.html.md).
-
-Plugins allow you to reuse your Medusa customizations across multiple projects or share them with the community. They can be published to npm and installed in any Medusa project.
-
-
-
-Learn how to create a wishlist plugin in [this guide](https://docs.medusajs.com/resources/plugins/guides/wishlist/index.html.md).
-
-***
-
-## Plugin vs Module
-
-A [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md) is an isolated package related to a single domain or functionality, such as product reviews or integrating a Content Management System. A module can't access any resources in the Medusa application that are outside its codebase.
-
-A plugin, on the other hand, can contain multiple Medusa customizations, including modules. Your plugin can define a module, then build flows around it.
-
-For example, in a plugin, you can define a module that integrates a third-party service, then add a workflow that uses the module when a certain event occurs to sync data to that service.
-
-- You want to reuse your Medusa customizations across multiple projects.
-- You want to share your Medusa customizations with the community.
-
-- You want to build a custom feature related to a single domain or integrate a third-party service. Instead, use a [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md). You can wrap that module in a plugin if it's used in other customizations, such as if it has a module link or it's used in a workflow.
-
-***
-
-## How to Create a Plugin?
-
-The next chapter explains how you can create and publish a plugin.
-
-
-# Data Models
-
-In this chapter, you'll learn what a data model is and how to create a data model.
-
-## What is a Data Model?
-
-A data model represents a table in the database. You create data models using Medusa's data modeling language (DML). It simplifies defining a table's columns, relations, and indexes with straightforward methods and configurations.
-
-You create a data model in a [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md). The module's service provides the methods to store and manage those data models. Then, you can resolve the module's service in other customizations, such as a [workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), to manage the data models' records.
-
-***
-
-## How to Create a Data Model
-
-In a module, you can create a data model in a TypeScript or JavaScript file under the module's `models` directory.
-
-So, for example, assuming you have a Blog Module at `src/modules/blog`, you can create a `Post` data model by creating the `src/modules/blog/models/post.ts` file with the following content:
-
-
-
-```ts title="src/modules/blog/models/post.ts"
-import { model } from "@medusajs/framework/utils"
-
-const Post = model.define("post", {
- id: model.id().primaryKey(),
- title: model.text(),
-})
-
-export default Post
-```
-
-You define the data model using the `define` method of the DML. It accepts two parameters:
-
-1. The first one is the name of the data model's table in the database. Use snake-case names.
-2. The second is an object, which is the data model's schema. The schema's properties are defined using the `model`'s methods, such as `text` and `id`.
- - Data models automatically have the date properties `created_at`, `updated_at`, and `deleted_at`, so you don't need to add them manually.
-
-The code snippet above defines a `Post` data model with `id` and `title` properties.
-
-***
-
-## Generate Migrations
-
-After you create a data model in a module, then [register that module in your Medusa configurations](https://docs.medusajs.com/learn/fundamentals/modules#4-add-module-to-medusas-configurations/index.html.md), you must generate a migration to create the data model's table in the database.
-
-A migration is a TypeScript or JavaScript file that defines database changes made by a module. Migrations are useful when you re-use a module or you're working in a team, so that when one member of a team makes a database change, everyone else can reflect it on their side by running the migrations.
-
-For example, to generate a migration for the Blog Module, run the following command in your Medusa application's directory:
-
-If you're creating the module in a plugin, use the [plugin:db:generate command](https://docs.medusajs.com/resources/medusa-cli/commands/plugin#plugindbgenerate/index.html.md) instead.
-
-```bash
-npx medusa db:generate blog
-```
-
-The `db:generate` command of the Medusa CLI accepts one or more module names to generate the migration for. It will create a migration file for the Blog Module in the directory `src/modules/blog/migrations` similar to the following:
-
-```ts
-import { Migration } from "@mikro-orm/migrations"
-
-export class Migration20241121103722 extends Migration {
-
- async up(): Promise {
- this.addSql("create table if not exists \"post\" (\"id\" text not null, \"title\" text not null, \"created_at\" timestamptz not null default now(), \"updated_at\" timestamptz not null default now(), \"deleted_at\" timestamptz null, constraint \"post_pkey\" primary key (\"id\"));")
- }
-
- async down(): Promise {
- this.addSql("drop table if exists \"post\" cascade;")
- }
-
-}
-```
-
-In the migration class, the `up` method creates the table `post` and defines its columns using PostgreSQL syntax. The `down` method drops the table.
-
-### Run Migrations
-
-To reflect the changes in the generated migration file on the database, run the `db:migrate` command:
-
-If you're creating the module in a plugin, run this command on the Medusa application that the plugin is installed in.
-
-```bash
-npx medusa db:migrate
-```
-
-This creates the `post` table in the database.
-
-### Migrations on Data Model Changes
-
-Whenever you make a change to a data model, you must generate and run the migrations.
-
-For example, if you add a new column to the `Post` data model, you must generate a new migration and run it.
-
-***
-
-## Manage Data Models
-
-Your module's service should extend the [service factory](https://docs.medusajs.com/learn/fundamentals/modules/service-factory/index.html.md), which generates data-management methods for your module's data models.
-
-For example, the Blog Module's service would have methods like `retrievePost` and `createPosts`.
-
-Refer to the [Service Factory](https://docs.medusajs.com/learn/fundamentals/modules/service-factory/index.html.md) chapter to learn more about how to extend the service factory and manage data models, and refer to the [Service Factory Reference](https://docs.medusajs.com/resources/service-factory-reference/index.html.md) for the full list of generated methods and how to use them.
-
-
-# Modules
-
-In this chapter, you’ll learn about modules and how to create them.
-
-## What is a Module?
-
-A module is a reusable package of functionalities related to a single domain or integration. Medusa comes with multiple pre-built modules for core commerce needs, such as the [Cart Module](https://docs.medusajs.com/resources/commerce-modules/cart/index.html.md) that holds the data models and business logic for cart operations.
-
-When building a commerce application, you often need to introduce custom behavior specific to your products, tech stack, or your general ways of working. In other commerce platforms, introducing custom business logic and data models requires setting up separate applications to manage these customizations.
-
-Medusa removes this overhead by allowing you to easily write custom modules that integrate into the Medusa application without affecting the existing setup. You can also re-use your modules across Medusa projects.
-
-As you learn more about Medusa, you will see that modules are central to customizations and integrations. With modules, your Medusa application can turn into a middleware solution for your commerce ecosystem.
-
-- You want to build a custom feature related to a single domain or integrate a third-party service.
-
-- You want to create a reusable package of customizations that include not only modules, but also API routes, workflows, and other customizations. Instead, use a [plugin](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
-
-***
-
-## How to Create a Module?
-
-In a module, you define data models that represent new tables in the database, and you manage these models in a class called a service. Then, the Medusa application registers the module's service in the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) so that you can build commerce flows and features around the functionalities provided by the module.
-
-In this section, you'll build a Blog Module that has a `Post` data model and a service to manage that data model. You'll also expose an API endpoint to create a blog post.
-
-Modules are created in a sub-directory of `src/modules`. So, start by creating the directory `src/modules/blog`.
-
-### 1. Create Data Model
-
-A data model represents a table in the database. You create data models using Medusa's data modeling language (DML). It simplifies defining a table's columns, relations, and indexes with straightforward methods and configurations.
-
-You create a data model in a TypeScript or JavaScript file under the `models` directory of a module. So, to create a `Post` data model in the Blog Module, create the file `src/modules/blog/models/post.ts` with the following content:
-
-
-
-```ts title="src/modules/blog/models/post.ts"
-import { model } from "@medusajs/framework/utils"
-
-const Post = model.define("post", {
- id: model.id().primaryKey(),
- title: model.text(),
-})
-
-export default Post
-```
-
-You define the data model using the `define` method of the DML. It accepts two parameters:
-
-1. The first one is the name of the data model's table in the database. Use snake-case names.
-2. The second is an object, which is the data model's schema. The schema's properties are defined using the `model`'s methods, such as `text` and `id`.
- - Data models automatically have the date properties `created_at`, `updated_at`, and `deleted_at`, so you don't need to add them manually.
-
-Learn about other property types in [this chapter](https://docs.medusajs.com/learn/fundamentals/data-models/properties#property-types/index.html.md).
-
-The code snippet above defines a `Post` data model with `id` and `title` properties.
-
-### 2. Create Service
-
-You perform database operations on your data models in a service, which is a class exported by the module and acts like an interface to its functionalities. Medusa registers the service in its [container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md), allowing you to resolve and use it when building custom commerce flows.
-
-You define a service in a `service.ts` or `service.js` file at the root of your module's directory. So, to create the Blog Module's service, create the file `src/modules/blog/service.ts` with the following content:
-
-
-
-```ts title="src/modules/blog/service.ts" highlights={highlights}
-import { MedusaService } from "@medusajs/framework/utils"
-import Post from "./models/post"
-
-class BlogModuleService extends MedusaService({
- Post,
-}){
-}
-
-export default BlogModuleService
-```
-
-Your module's service extends a class generated by `MedusaService` from the Modules SDK. This class comes with generated methods for data-management Create, Read, Update, and Delete (CRUD) operations on each of your modules, saving you time that can be spent on building custom business logic.
-
-The `MedusaService` function accepts an object of data models to generate methods for. You can pass all data models in your module in this object.
-
-For example, the `BlogModuleService` now has a `createPosts` method to create post records, and a `retrievePost` method to retrieve a post record. The suffix of each method (except for `retrieve`) is the pluralized name of the data model.
-
-Find all methods generated by the `MedusaService` in [this reference](https://docs.medusajs.com/resources/service-factory-reference/index.html.md)
-
-If a module doesn't have data models, such as when it's integrating a third-party service, it doesn't need to extend `MedusaService`.
-
-### 3. Export Module Definition
-
-The final piece to a module is its definition, which is exported in an `index.ts` file at its root directory. This definition tells Medusa the name of the module and its main service. Medusa will then register the main service in the container under the module's name.
-
-So, to export the definition of the Blog Module, create the file `src/modules/blog/index.ts` with the following content:
-
-
-
-```ts title="src/modules/blog/index.ts" highlights={moduleDefinitionHighlights}
-import BlogModuleService from "./service"
-import { Module } from "@medusajs/framework/utils"
-
-export const BLOG_MODULE = "blog"
-
-export default Module(BLOG_MODULE, {
- service: BlogModuleService,
-})
-```
-
-You use `Module` from the Modules SDK to create the module's definition. It accepts two parameters:
-
-1. The name that the module's main service is registered under (`blog`). The module name can contain only alphanumeric characters and underscores.
-2. An object with a required property `service` indicating the module's main service.
-
-You export `BLOG_MODULE` to reference the module's name more reliably when resolving its service in other customizations.
-
-### 4. Add Module to Medusa's Configurations
-
-If you're creating the module in a plugin, this step isn't required as the module is registered when the plugin is registered. Learn more about plugins in [this documentation](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
-
-Once you finish building the module, add it to Medusa's configurations to start using it. Medusa will then register the module's main service in the Medusa container, allowing you to resolve and use it in other customizations.
-
-In `medusa-config.ts`, add a `modules` property and pass an array with your custom module:
-
-```ts title="medusa-config.ts" highlights={[["7"]]}
-module.exports = defineConfig({
- projectConfig: {
- // ...
- },
- modules: [
- {
- resolve: "./src/modules/blog",
- },
- ],
-})
-```
-
-Each object in the `modules` array has a `resolve` property, whose value is either a path to the module's directory, or an `npm` package’s name.
-
-### 5. Generate Migrations
-
-Since data models represent tables in the database, you define how they're created in the database with migrations. A migration is a TypeScript or JavaScript file that defines database changes made by a module.
-
-Migrations are useful when you re-use a module or you're working in a team, so that when one member of a team makes a database change, everyone else can reflect it on their side by running the migrations.
-
-You don't have to write migrations yourself. Medusa's CLI tool has a command that generates the migrations for you. You can also use this command again when you make changes to the module at a later point, and it will generate new migrations for that change.
-
-To generate a migration for the Blog Module, run the following command in your Medusa application's directory:
-
-If you're creating the module in a plugin, use the [plugin:db:generate command](https://docs.medusajs.com/resources/medusa-cli/commands/plugin#plugindbgenerate/index.html.md) instead.
-
-```bash
-npx medusa db:generate blog
-```
-
-The `db:generate` command of the Medusa CLI accepts one or more module names to generate the migration for. It will create a migration file for the Blog Module in the directory `src/modules/blog/migrations` similar to the following:
-
-```ts
-import { Migration } from "@mikro-orm/migrations"
-
-export class Migration20241121103722 extends Migration {
-
- async up(): Promise {
- this.addSql("create table if not exists \"post\" (\"id\" text not null, \"title\" text not null, \"created_at\" timestamptz not null default now(), \"updated_at\" timestamptz not null default now(), \"deleted_at\" timestamptz null, constraint \"post_pkey\" primary key (\"id\"));")
- }
-
- async down(): Promise {
- this.addSql("drop table if exists \"post\" cascade;")
- }
-
-}
-```
-
-In the migration class, the `up` method creates the table `post` and defines its columns using PostgreSQL syntax. The `down` method drops the table.
-
-### 6. Run Migrations
-
-To reflect the changes in the generated migration file on the database, run the `db:migrate` command:
-
-If you're creating the module in a plugin, run this command on the Medusa application that the plugin is installed in.
-
-```bash
-npx medusa db:migrate
-```
-
-This creates the `post` table in the database.
-
-***
-
-## Test the Module
-
-Since the module's main service is registered in the Medusa container, you can resolve it in other customizations to use its methods.
-
-To test out the Blog Module, you'll add the functionality to create a post in a [workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), which is a special function that performs a task in a series of steps with rollback logic. Then, you'll expose an [API route](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md) that creates a blog post by executing the workflow.
-
-By building a commerce feature in a workflow, you can execute it in other customizations while ensuring data consistency across systems. If an error occurs during execution, every step has its own rollback logic to undo its actions. Workflows have other special features which you can learn about in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md).
-
-To create the workflow, create the file `src/workflows/create-post.ts` with the following content:
-
-```ts title="src/workflows/create-post.ts" highlights={workflowHighlights}
-import {
- createStep,
- createWorkflow,
- StepResponse,
- WorkflowResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { BLOG_MODULE } from "../modules/blog"
-import BlogModuleService from "../modules/blog/service"
-
-type CreatePostWorkflowInput = {
- title: string
-}
-
-const createPostStep = createStep(
- "create-post",
- async ({ title }: CreatePostWorkflowInput, { container }) => {
- const blogModuleService: BlogModuleService = container.resolve(BLOG_MODULE)
-
- const post = await blogModuleService.createPosts({
- title,
- })
-
- return new StepResponse(post, post)
- },
- async (post, { container }) => {
- const blogModuleService: BlogModuleService = container.resolve(BLOG_MODULE)
-
- await blogModuleService.deletePosts(post.id)
- }
-)
-
-export const createPostWorkflow = createWorkflow(
- "create-post",
- (postInput: CreatePostWorkflowInput) => {
- const post = createPostStep(postInput)
-
- return new WorkflowResponse(post)
- }
-)
-```
-
-The workflow has a single step `createPostStep` that creates a post. In the step, you resolve the Blog Module's service from the Medusa container, which the step receives as a parameter. Then, you create the post using the method `createPosts` of the service, which was generated by `MedusaService`.
-
-The step also has a compensation function, which is a function passed as a third-parameter to `createStep` that implements the logic to rollback the change made by a step in case an error occurs during the workflow's execution.
-
-You'll now execute that workflow in an API route to expose the feature of creating blog posts to clients. To create an API route, create the file `src/api/blog/posts/route.ts` with the following content:
-
-```ts
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import {
- createPostWorkflow,
-} from "../../../workflows/create-post"
-
-export async function POST(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result: post } = await createPostWorkflow(req.scope)
- .run({
- input: {
- title: "My Post",
- },
- })
-
- res.json({
- post,
- })
-}
-```
-
-This adds a `POST` API route at `/blog/posts`. In the API route, you execute the `createPostWorkflow` by invoking it, passing it the Medusa container in `req.scope`, then invoking the `run` method. In the `run` method, you pass the workflow's input in the `input` property.
-
-To test this out, start the Medusa application:
-
-```bash npm2yarn
-npm run dev
-```
-
-Then, send a `POST` request to `/blog/posts`:
-
-```bash
-curl -X POST http://localhost:9000/blog/posts
-```
-
-This will create a post and return it in the response:
-
-```json
-{
- "post": {
- "id": "123...",
- "title": "My Post",
- "created_at": "...",
- "updated_at": "..."
- }
-}
-```
-
-You can also execute the workflow from a [subscriber](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) when an event occurs, or from a [scheduled job](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md) to run it at a specified interval.
-
-
-# Scheduled Jobs
-
-In this chapter, you’ll learn about scheduled jobs and how to use them.
-
-## What is a Scheduled Job?
-
-When building your commerce application, you may need to automate tasks and run them repeatedly at a specific schedule. For example, you need to automatically sync products to a third-party service once a day.
-
-In other commerce platforms, this feature isn't natively supported. Instead, you have to setup a separate application to execute cron jobs, which adds complexity as to how you expose this task to be executed in a cron job, or how do you debug it when it's not running within the platform's tooling.
-
-Medusa removes this overhead by supporting this feature natively with scheduled jobs. A scheduled job is an asynchronous function that the Medusa application runs at the interval you specify during the Medusa application's runtime. Your efforts are only spent on implementing the functionality performed by the job, such as syncing products to an ERP.
-
-- You want the action to execute at a specified schedule while the Medusa application **isn't** running. Instead, use the operating system's equivalent of a cron job.
-- You want to execute the action once when the application loads. Use [loaders](https://docs.medusajs.com/learn/fundamentals/modules/loaders/index.html.md) instead.
-- You want to execute the action if an event occurs. Use [subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) instead.
-
-***
-
-## How to Create a Scheduled Job?
-
-You create a scheduled job in a TypeScript or JavaScript file under the `src/jobs` directory. The file exports the asynchronous function to run, and the configurations indicating the schedule to run the function.
-
-For example, create the file `src/jobs/hello-world.ts` with the following content:
-
-
-
-```ts title="src/jobs/hello-world.ts" highlights={highlights}
-import { MedusaContainer } from "@medusajs/framework/types"
-
-export default async function greetingJob(container: MedusaContainer) {
- const logger = container.resolve("logger")
-
- logger.info("Greeting!")
-}
-
-export const config = {
- name: "greeting-every-minute",
- schedule: "* * * * *",
-}
-```
-
-You export an asynchronous function that receives the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) as a parameter. In the function, you resolve the [Logger utility](https://docs.medusajs.com/learn/debugging-and-testing/logging/index.html.md) from the Medusa container and log a message.
-
-You also export a `config` object that has the following properties:
-
-- `name`: A unique name for the job.
-- `schedule`: A string that holds a [cron expression](https://crontab.guru/) indicating the schedule to run the job.
-
-This scheduled job executes every minute and logs into the terminal `Greeting!`.
-
-### Test the Scheduled Job
-
-To test out your scheduled job, start the Medusa application:
-
-```bash npm2yarn
-npm run dev
-```
-
-After a minute, the following message will be logged to the terminal:
-
-```bash
-info: Greeting!
-```
-
-***
-
-## Example: Sync Products Once a Day
-
-In this section, you'll find a brief example of how you use a scheduled job to sync products to a third-party service.
-
-When implementing flows spanning across systems or [modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), you use [workflows](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md). A workflow is a task made up of a series of steps, and you construct it like you would a regular function, but it's a special function that supports rollback mechanism in case of errors, background execution, and more.
-
-You can learn how to create a workflow in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), but this example assumes you already have a `syncProductToErpWorkflow` implemented. To execute this workflow once a day, create a scheduled job at `src/jobs/sync-products.ts` with the following content:
-
-```ts title="src/jobs/sync-products.ts"
-import { MedusaContainer } from "@medusajs/framework/types"
-import { syncProductToErpWorkflow } from "../workflows/sync-products-to-erp"
-
-export default async function syncProductsJob(container: MedusaContainer) {
- await syncProductToErpWorkflow(container)
- .run()
-}
-
-export const config = {
- name: "sync-products-job",
- schedule: "0 0 * * *",
-}
-```
-
-In the scheduled job function, you execute the `syncProductToErpWorkflow` by invoking it and passing it the container, then invoking the `run` method. You also specify in the exported configurations the schedule `0 0 * * *` which indicates midnight time of every day.
-
-The next time you start the Medusa application, it will run this job every day at midnight.
-
-
-# Workflows
-
-In this chapter, you’ll learn about workflows and how to define and execute them.
-
-## What is a Workflow?
-
-In digital commerce you typically have many systems involved in your operations. For example, you may have an ERP system that holds product master data and accounting reports, a CMS system for content, a CRM system for managing customer campaigns, a payment service to process credit cards, and so on. Sometimes you may even have custom built applications that need to participate in the commerce stack. One of the biggest challenges when operating a stack like this is ensuring consistency in the data spread across systems.
-
-Medusa has a built-in durable execution engine to help complete tasks that span multiple systems. You orchestrate your operations across systems in Medusa instead of having to manage it yourself. Other commerce platforms don't have this capability, which makes them a bottleneck to building customizations and iterating quickly.
-
-A workflow is a series of queries and actions, called steps, that complete a task. You construct a workflow similar to how you create a JavaScript function.
-
-However, unlike regular functions, workflows:
-
-- Create an internal representation of your steps, allowing you to track them and their progress.
-- Support defining roll-back logic for each step, so that you can handle errors gracefully and your data remain consistent across systems.
-- Perform long actions asynchronously, giving you control over when a step starts and finishes.
-
-You implement all custom flows within workflows, then execute them from [API routes](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md), [subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md), and [scheduled jobs](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md).
-
-***
-
-## How to Create and Execute a Workflow?
-
-### 1. Create the Steps
-
-A workflow is made of a series of steps. A step is created using `createStep` from the Workflows SDK.
-
-Create the file `src/workflows/hello-world.ts` with the following content:
-
-
-
-```ts title="src/workflows/hello-world.ts" highlights={step1Highlights}
-import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
-
-const step1 = createStep(
- "step-1",
- async () => {
- return new StepResponse(`Hello from step one!`)
- }
-)
-```
-
-The `createStep` function accepts the step's unique name as a first parameter, and the step's function as a second parameter.
-
-Steps must return an instance of `StepResponse`, whose parameter is the data to return to the workflow executing the step.
-
-Steps can accept input parameters. For example, add the following to `src/workflows/hello-world.ts`:
-
-```ts title="src/workflows/hello-world.ts" highlights={step2Highlights}
-type WorkflowInput = {
- name: string
-}
-
-const step2 = createStep(
- "step-2",
- async ({ name }: WorkflowInput) => {
- return new StepResponse(`Hello ${name} from step two!`)
- }
-)
-```
-
-This adds another step whose function accepts as a parameter an object with a `name` property.
-
-### 2. Create a Workflow
-
-Next, add the following to the same file to create the workflow using the `createWorkflow` function:
-
-```ts title="src/workflows/hello-world.ts" highlights={workflowHighlights}
-import {
- // other imports...
- createWorkflow,
- WorkflowResponse,
-} from "@medusajs/framework/workflows-sdk"
-
-// ...
-
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input: WorkflowInput) {
- const str1 = step1()
- // to pass input
- const str2 = step2(input)
-
- return new WorkflowResponse({
- message: str2,
- })
- }
-)
-
-export default myWorkflow
-```
-
-The `createWorkflow` function accepts the workflow's unique name as a first parameter, and the workflow's function as a second parameter. The workflow can accept input which is passed as a parameter to the function.
-
-The workflow must return an instance of `WorkflowResponse`, whose parameter is returned to workflow executors.
-
-### 3. Execute the Workflow
-
-You can execute a workflow from different customizations:
-
-- Execute in an API route to expose the workflow's functionalities to clients.
-- Execute in a subscriber to use the workflow's functionalities when a commerce operation is performed.
-- Execute in a scheduled job to run the workflow's functionalities automatically at a specified repeated interval.
-
-To execute the workflow, invoke it passing the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) as a parameter. Then, use its `run` method:
-
-### API Route
-
-```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"], ["13"], ["14"], ["15"], ["16"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import myWorkflow from "../../workflows/hello-world"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await myWorkflow(req.scope)
- .run({
- input: {
- name: "John",
- },
- })
-
- res.send(result)
-}
-```
-
-### Subscriber
-
-```ts title="src/subscribers/order-placed.ts" highlights={[["11"], ["12"], ["13"], ["14"], ["15"], ["16"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import {
- type SubscriberConfig,
- type SubscriberArgs,
-} from "@medusajs/framework"
-import myWorkflow from "../workflows/hello-world"
-
-export default async function handleOrderPlaced({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- const { result } = await myWorkflow(container)
- .run({
- input: {
- name: "John",
- },
- })
-
- console.log(result)
-}
-
-export const config: SubscriberConfig = {
- event: "order.placed",
-}
-```
-
-### Scheduled Job
-
-```ts title="src/jobs/message-daily.ts" highlights={[["7"], ["8"], ["9"], ["10"], ["11"], ["12"]]}
-import { MedusaContainer } from "@medusajs/framework/types"
-import myWorkflow from "../workflows/hello-world"
-
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const { result } = await myWorkflow(container)
- .run({
- input: {
- name: "John",
- },
- })
-
- console.log(result.message)
-}
-
-export const config = {
- name: "run-once-a-day",
- schedule: `0 0 * * *`,
-};
-```
-
-### 4. Test Workflow
-
-To test out your workflow, start your Medusa application:
-
-```bash npm2yarn
-npm run dev
-```
-
-Then, if you added the API route above, send a `GET` request to `/workflow`:
-
-```bash
-curl http://localhost:9000/workflow
-```
-
-You’ll receive the following response:
-
-```json title="Example Response"
-{
- "message": "Hello John from step two!"
-}
-```
-
-***
-
-## Access Medusa Container in Workflow Steps
-
-A step receives an object as a second parameter with configurations and context-related properties. One of these properties is the `container` property, which is the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) to allow you to resolve framework and commerce tools in your application.
-
-For example, consider you want to implement a workflow that returns the total products in your application. Create the file `src/workflows/product-count.ts` with the following content:
-
-```ts title="src/workflows/product-count.ts" highlights={highlights} collapsibleLines="1-7" expandButtonLabel="Show Imports"
-import {
- createStep,
- StepResponse,
- createWorkflow,
- WorkflowResponse,
-} from "@medusajs/framework/workflows-sdk"
-
-const getProductCountStep = createStep(
- "get-product-count",
- async (_, { container }) => {
- const productModuleService = container.resolve("product")
-
- const [, count] = await productModuleService.listAndCountProducts()
-
- return new StepResponse(count)
- }
-)
-
-const productCountWorkflow = createWorkflow(
- "product-count",
- function () {
- const count = getProductCountStep()
-
- return new WorkflowResponse({
- count,
- })
- }
-)
-
-export default productCountWorkflow
-```
-
-In `getProductCountStep`, you use the `container` to resolve the Product Module's main service. Then, you use its `listAndCountProducts` method to retrieve the total count of products and return it in the step's response. You then execute this step in the `productCountWorkflow`.
-
-You can now execute this workflow in a custom API route, scheduled job, or subscriber to get the total count of products.
-
-Find a full list of the registered resources in the Medusa container and their registration key in [this reference](https://docs.medusajs.com/resources/medusa-container-resources/index.html.md). You can use these resources in your custom workflows.
-
-
-# Medusa's Architecture
-
-In this chapter, you'll learn about the architectural layers in Medusa.
-
-Find the full architectural diagram at the [end of this chapter](#full-diagram-of-medusas-architecture).
-
-## HTTP, Workflow, and Module Layers
-
-Medusa is a headless commerce platform. So, storefronts, admin dashboards, and other clients consume Medusa's functionalities through its API routes.
-
-In a common Medusa application, requests go through four layers in the stack. In order of entry, those are:
-
-1. API Routes (HTTP): Our API Routes are the typical entry point. The Medusa server is based on Express.js, which handles incoming requests. It can also connect to a Redis database that stores the server session data.
-2. Workflows: API Routes consume workflows that hold the opinionated business logic of your application.
-3. Modules: Workflows use domain-specific modules for resource management.
-4. Data store: Modules query the underlying datastore, which is a PostgreSQL database in common cases.
-
-These layers of stack can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
-
-
-
-***
-
-## Database Layer
-
-The Medusa application injects into each module, including your [custom modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), a connection to the configured PostgreSQL database. Modules use that connection to read and write data to the database.
-
-Modules can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
-
-
-
-***
-
-## Third-Party Integrations Layer
-
-Third-party services and systems are integrated through Medusa's Commerce and Architectural modules. You also create custom third-party integrations through a [custom module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
-
-Modules can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
-
-### Commerce Modules
-
-[Commerce modules](https://docs.medusajs.com/resources/commerce-modules/index.html.md) integrate third-party services relevant for commerce or user-facing features. For example, you can integrate [Stripe](https://docs.medusajs.com/resources/commerce-modules/payment/payment-provider/stripe/index.html.md) through a Payment Module Provider, or [ShipStation](https://docs.medusajs.com/resources/integrations/guides/shipstation/index.html.md) through a Fulfillment Module Provider.
-
-You can also integrate third-party services for custom functionalities. For example, you can integrate [Sanity](https://docs.medusajs.com/resources/integrations/guides/sanity/index.html.md) for rich CMS capabilities, or [Odoo](https://docs.medusajs.com/resources/recipes/erp/odoo/index.html.md) to sync your Medusa application with your ERP system.
-
-You can replace any of the third-party services mentioned above to build your preferred commerce ecosystem.
-
-
-
-### Architectural Modules
-
-[Architectural modules](https://docs.medusajs.com/resources/architectural-modules/index.html.md) integrate third-party services and systems for architectural features. Medusa has the following Architectural modules:
-
-- [Cache Module](https://docs.medusajs.com/resources/architectural-modules/cache/index.html.md): Caches data that require heavy computation. You can integrate a custom module to handle the caching with services like Memcached, or use the existing [Redis Cache Module](https://docs.medusajs.com/resources/architectural-modules/cache/redis/index.html.md).
-- [Event Module](https://docs.medusajs.com/resources/architectural-modules/event/index.html.md): A pub/sub system that allows you to subscribe to events and trigger them. You can integrate [Redis](https://docs.medusajs.com/resources/architectural-modules/event/redis/index.html.md) as the pub/sub system.
-- [File Module](https://docs.medusajs.com/resources/architectural-modules/file/index.html.md): Manages file uploads and storage, such as upload of product images. You can integrate [AWS S3](https://docs.medusajs.com/resources/architectural-modules/file/s3/index.html.md) for file storage.
-- [Locking Module](https://docs.medusajs.com/resources/architectural-modules/locking/index.html.md): Manages access to shared resources by multiple processes or threads, preventing conflict between processes and ensuring data consistency. You can integrate [Redis](https://docs.medusajs.com/resources/architectural-modules/locking/redis/index.html.md) for locking.
-- [Notification Module](https://docs.medusajs.com/resources/architectural-modules/notification/index.html.md): Sends notifications to customers and users, such as for order updates or newsletters. You can integrate [SendGrid](https://docs.medusajs.com/resources/architectural-modules/notification/sendgrid/index.html.md) for sending emails.
-- [Workflow Engine Module](https://docs.medusajs.com/resources/architectural-modules/workflow-engine/index.html.md): Orchestrates workflows that hold the business logic of your application. You can integrate [Redis](https://docs.medusajs.com/resources/architectural-modules/workflow-engine/redis/index.html.md) to orchestrate workflows.
-
-All of the third-party services mentioned above can be replaced to help you build your preferred architecture and ecosystem.
-
-
-
-***
-
-## Full Diagram of Medusa's Architecture
-
-The following diagram illustrates Medusa's architecture including all its layers.
-
-
-
-
# Logging
In this chapter, you’ll learn how to use Medusa’s logging utility.
@@ -3741,6 +1751,1998 @@ Medusa's Testing Framework works for integration tests only. You can write unit
The next chapters explain how to use the testing tools provided by `@medusajs/test-utils` to write tests.
+# General Medusa Application Deployment Guide
+
+In this document, you'll learn the general steps to deploy your Medusa application. How you apply these steps depend on your chosen hosting provider or platform.
+
+Find how-to guides for specific platforms in [this documentation](https://docs.medusajs.com/resources/deployment/index.html.md).
+
+Want Medusa to manage and maintain your infrastructure? [Sign up and learn more about Medusa Cloud](https://medusajs.com/pricing)
+
+Medusa Cloud is our managed services offering that makes deploying and operating Medusa applications possible without having to worry about configuring, scaling, and maintaining infrastructure. Medusa Cloud hosts your server, Admin dashboard, database, and Redis instance.
+
+With Medusa Cloud, you maintain full customization control as you deploy your own modules and customizations directly from GitHub:
+
+- Push to deploy.
+- Multiple testing environments.
+- Preview environments for new PRs.
+- Test on production-like data.
+
+### Prerequisites
+
+- [Medusa application’s codebase hosted on GitHub repository.](https://docs.medusajs.com/learn/index.html.md)
+
+## Hosting Provider Requirements
+
+When you deploy your Medusa application, make sure your chosen hosting provider supports deploying the following resources:
+
+1. PostgreSQL database: If your hosting provider doesn't support database hosting, you must find another hosting provider for the PostgreSQL database.
+2. Redis database: If your hosting provider doesn't support database hosting, you must find another hosting provider for the Redis database.
+3. Medusa application in server and worker mode. This means your hosting provider should support deploying two applications or instances from the same codebase.
+4. For optimal experience, the hosting provider and plan must offer at least 2GB of RAM.
+
+***
+
+## 1. Configure Medusa Application
+
+### Worker Mode
+
+The `workerMode` configuration determines which mode the Medusa application runs in. When you deploy the Medusa application, you deploy two instances: one in server mode, and one in worker mode.
+
+Learn more about worker mode in the [Worker Module chapter](https://docs.medusajs.com/learn/production/worker-mode/index.html.md).
+
+So, add the following configuration in `medusa-config.ts`:
+
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ projectConfig: {
+ // ...
+ workerMode: process.env.MEDUSA_WORKER_MODE as "shared" | "worker" | "server",
+ },
+})
+```
+
+Later, you’ll set different values of the `MEDUSA_WORKER_MODE` environment variable for each Medusa application deployment or instance.
+
+### Configure Medusa Admin
+
+You need to disable the Medusa Admin in the worker Medusa application, while keeping it enabled in the server Medusa application. So, add the following configuration in `medusa-config.ts`:
+
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ // ...
+ admin: {
+ disable: process.env.DISABLE_MEDUSA_ADMIN === "true",
+ },
+})
+```
+
+Later, you’ll set different values of the `DISABLE_MEDUSA_ADMIN` environment variable.
+
+### Configure Redis URL
+
+The `redisUrl` configuration specifies the connection URL to Redis to store the Medusa server's session.
+
+Learn more in the [Medusa Configuration documentation](https://docs.medusajs.com/learn/configurations/medusa-config#redisurl/index.html.md).
+
+So, add the following configuration in `medusa-config.ts` :
+
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ projectConfig: {
+ // ...
+ redisUrl: process.env.REDIS_URL,
+ },
+})
+```
+
+***
+
+## 2. Add predeploy Script
+
+Before you start the Medusa application in production, you should always run migrations and sync links.
+
+So, add the following script in `package.json`:
+
+```json
+"scripts": {
+ // ...
+ "predeploy": "medusa db:migrate"
+},
+```
+
+***
+
+## 3. Install Production Modules and Providers
+
+By default, your Medusa application uses modules and providers useful for development, such as the In-Memory Cache Module or the Local File Module Provider.
+
+It’s highly recommended to instead use modules and providers suitable for production, including:
+
+- [Redis Cache Module](https://docs.medusajs.com/resources/architectural-modules/cache/redis/index.html.md)
+- [Redis Event Bus Module](https://docs.medusajs.com/resources/architectural-modules/event/redis/index.html.md)
+- [Workflow Engine Redis Module](https://docs.medusajs.com/resources/architectural-modules/workflow-engine/redis/index.html.md)
+- [S3 File Module Provider](https://docs.medusajs.com/resources/architectural-modules/file/s3/index.html.md) (or other file module providers production-ready).
+- [SendGrid Notification Module Provider](https://docs.medusajs.com/resources/architectural-modules/notification/sendgrid/index.html.md) (or other notification module providers production-ready).
+
+Then, add these modules in `medusa-config.ts`:
+
+```ts title="medusa-config.ts"
+import { Modules } from "@medusajs/framework/utils"
+
+module.exports = defineConfig({
+ // ...
+ modules: [
+ {
+ resolve: "@medusajs/medusa/cache-redis",
+ options: {
+ redisUrl: process.env.REDIS_URL,
+ },
+ },
+ {
+ resolve: "@medusajs/medusa/event-bus-redis",
+ options: {
+ redisUrl: process.env.REDIS_URL,
+ },
+ },
+ {
+ resolve: "@medusajs/medusa/workflow-engine-redis",
+ options: {
+ redis: {
+ url: process.env.REDIS_URL,
+ },
+ },
+ },
+ ],
+})
+```
+
+Check out the [Integrations](https://docs.medusajs.com/resources/integrations/index.html.md) and [Architectural Modules](https://docs.medusajs.com/resources/architectural-modules/index.html.md) documentation for other modules and providers to use.
+
+***
+
+## 4. Create PostgreSQL and Redis Databases
+
+Your Medusa application must connect to PostgreSQL and Redis databases. So, before you deploy it, create production PostgreSQL and Redis databases.
+
+If your hosting provider doesn't support databases, you can use [Neon for PostgreSQL database hosting](https://neon.tech/), and [Redis Cloud for the Redis database hosting](https://redis.io/cloud/).
+
+After hosting both databases, keep their connection URLs for the next steps.
+
+***
+
+## 5. Deploy Medusa Application in Server Mode
+
+As mentioned earlier, you'll deploy two instances or create two deployments of your Medusa application: one in server mode, and the other in worker mode.
+
+The deployment steps depend on your hosting provider. This section provides the general steps to perform during the deployment.
+
+### Set Environment Variables
+
+When setting the environment variables of the Medusa application, set the following variables:
+
+```bash
+COOKIE_SECRET=supersecret # TODO GENERATE SECURE SECRET
+JWT_SECRET=supersecret # TODO GENERATE SECURE SECRET
+STORE_CORS= # STOREFRONT URL
+ADMIN_CORS= # ADMIN URL
+AUTH_CORS= # STOREFRONT AND ADMIN URLS, SEPARATED BY COMMAS
+DISABLE_MEDUSA_ADMIN=false
+MEDUSA_WORKER_MODE=server
+PORT=9000
+DATABASE_URL # POSTGRES DATABASE URL
+REDIS_URL= # REDIS DATABASE URL
+```
+
+Where:
+
+- The value of `COOKIE_SECRET` and `JWT_SECRET` must be a randomly generated secret.
+- `STORE_CORS`'s value is the URL of your storefront. If you don’t have it yet, you can skip adding it for now.
+- `ADMIN_CORS`'s value is the URL of the admin dashboard, which is the same as the server Medusa application. You can add it later if you don't currently have it.
+- `AUTH_CORS`'s value is the URLs of any application authenticating users, customers, or other actor types, such as the storefront and admin URLs. The URLs are separated by commas. If you don’t have the URLs yet, you can set its value later.
+- Set `DISABLE_MEDUSA_ADMIN`'s value to `false` so that the admin is built with the server application.
+- Set the PostgreSQL database's connection URL as the value of `DATABASE_URL`
+- Set the Redis database's connection URL as the value of `REDIS_URL`.
+
+Feel free to add any other relevant environment variables, such as for integrations and architectural modules.
+
+### Set Start Command
+
+The Medusa application's production build, which is created using the `build` command, outputs the Medusa application to `.medusa/server`. So, you must install the dependencies in the `.medusa/server` directory, then run the `start` command in it.
+
+If your hosting provider doesn't support setting a current-working directory, set the start command to the following:
+
+```bash npm2yarn
+cd .medusa/server && npm install && npm run predeploy && npm run start
+```
+
+Notice that you run the `predeploy` command before starting the Medusa application to run migrations and sync links whenever there's an update.
+
+### Set Backend URL in Admin Configuration
+
+After you’ve obtained the Medusa application’s URL, add the following configuration to `medusa-config.ts`:
+
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ // ...
+ admin: {
+ // ...
+ backendUrl: process.env.MEDUSA_BACKEND_URL,
+ },
+})
+```
+
+Then, push the changes to the GitHub repository or deployed application.
+
+In your hosting provider, add or modify the following environment variables for the Medusa application in server mode:
+
+```bash
+ADMIN_CORS= # MEDUSA APPLICATION URL
+AUTH_CORS= # ADD MEDUSA APPLICATION URL
+MEDUSA_BACKEND_URL= # URL TO DEPLOYED MEDUSA APPLICATION
+```
+
+Where you set the value of `ADMIN_CORS` and `MEDUSA_BACKEND_URL` to the Medusa application’s URL, and you add the URL to `AUTH_CORS`.
+
+Remember to separate URLs in `AUTH_CORS` by commas.
+
+***
+
+## 6. Deploy Medusa Application in Worker Mode
+
+Next, you'll deploy the Medusa application in worker mode.
+
+As explained in the previous section, the deployment steps depend on your hosting provider. This section provides the general steps to perform during the deployment.
+
+### Set Environment Variables
+
+When setting the environment variables of the Medusa application, set the following variables:
+
+```bash
+COOKIE_SECRET=supersecret # TODO GENERATE SECURE SECRET
+JWT_SECRET=supersecret # TODO GENERATE SECURE SECRET
+DISABLE_MEDUSA_ADMIN=true
+MEDUSA_WORKER_MODE=worker
+PORT=9000
+DATABASE_URL # POSTGRES DATABASE URL
+REDIS_URL= # REDIS DATABASE URL
+```
+
+Where:
+
+- The value of `COOKIE_SECRET` and `JWT_SECRET` must be a randomly generated secret.
+- Set `DISABLE_MEDUSA_ADMIN`'s value to `true` so that the admin isn't built with the worker application.
+- Set the PostgreSQL database's connection URL as the value of `DATABASE_URL`
+- Set the Redis database's connection URL as the value of `REDIS_URL`.
+
+Feel free to add any other relevant environment variables, such as for integrations and architectural modules.
+
+### Set Start Command
+
+The Medusa application's production build, which is created using the `build` command, outputs the Medusa application to `.medusa/server`. So, you must install the dependencies in the `.medusa/server` directory, then run the `start` command in it.
+
+If your hosting provider doesn't support setting a current-working directory, set the start command to the following:
+
+```bash npm2yarn
+cd .medusa/server && npm run install && npm run start
+```
+
+***
+
+## 7. Test Deployed Application
+
+Once the application is deployed and live, go to `/health`, where `` is the URL of the Medusa application in server mode. If the deployment was successful, you’ll see the `OK` response.
+
+The Medusa Admin is also available at `/app`.
+
+***
+
+## Create Admin User
+
+If your hosting provider supports running commands in your Medusa application's directory, run the following command to create an admin user:
+
+```bash
+npx medusa user -e admin-medusa@test.com -p supersecret
+```
+
+Replace the email `admin-medusa@test.com` and password `supersecret` with the credentials you want.
+
+You can use these credentials to log into the Medusa Admin dashboard.
+
+
+# Build Custom Features
+
+In the upcoming chapters, you'll follow step-by-step guides to build custom features in Medusa. These guides gradually introduce Medusa's concepts to help you understand what they are and how to use them.
+
+By following these guides, you'll add brands to the Medusa application that you can associate with products.
+
+To build a custom feature in Medusa, you need three main tools:
+
+- [Module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md): a package with commerce logic for a single domain. It defines new tables to add to the database, and a class of methods to manage these tables.
+- [Workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md): a tool to perform an operation comprising multiple steps with built-in rollback and retry mechanisms.
+- [API route](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md): a REST endpoint that exposes commerce features to clients, such as the admin dashboard or a storefront. The API route executes a workflow that implements the commerce feature using modules.
+
+
+
+***
+
+## Next Chapters: Brand Module Example
+
+The next chapters will guide you to:
+
+1. Build a Brand Module that creates a `Brand` data model and provides data-management features.
+2. Add a workflow to create a brand.
+3. Expose an API route that allows admin users to create a brand using the workflow.
+
+
+# Customizations Next Steps: Learn the Fundamentals
+
+The previous guides introduced Medusa's different concepts and how you can use them to customize Medusa for a realistic use case, You added brands to your application, linked them to products, customized the admin dashboard, and integrated a third-party CMS.
+
+The next chapters will cover each of these concepts in depth, with the different ways you can use them, their options or configurations, and more advanced features that weren't covered in the previous guides. While you can start building with Medusa, it's highly recommended to follow the next chapters for a better understanding of Medusa's fundamentals.
+
+## Useful Guides
+
+The following guides and references are useful for your development journey:
+
+3. [Commerce Modules](https://docs.medusajs.com/resources/commerce-modules/index.html.md): Browse the list of commerce modules in Medusa and their references to learn how to use them.
+4. [Service Factory Reference](https://docs.medusajs.com/resources/service-factory-reference/index.html.md): Learn about the methods generated by `MedusaService` with examples.
+5. [Workflows Reference](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md): Browse the list of core workflows and their hooks that are useful for your customizations.
+6. [Admin Injection Zones](https://docs.medusajs.com/resources/admin-widget-injection-zones/index.html.md): Browse the injection zones in the Medusa Admin to learn where you can inject widgets.
+
+***
+
+## More Examples in Recipes
+
+In the [Recipes](https://docs.medusajs.com/resources/recipes/index.html.md) documentation, you'll also find step-by-step guides for different use cases, such as building a marketplace, digital products, and more.
+
+
+# Extend Core Commerce Features
+
+In the upcoming chapters, you'll learn about the concepts and tools to extend Medusa's core commerce features.
+
+In other commerce platforms, you extend core features and models through hacky workarounds that can introduce unexpected issues and side effects across the platform. It also makes your application difficult to maintain and upgrade in the long run.
+
+Medusa's framework and orchestration tools mitigate these issues while supporting all your customization needs:
+
+- [Module Links](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md): Link data models of different modules without building direct dependencies, ensuring that the Medusa application integrates your modules without side effects.
+- [Workflow Hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md): inject custom functionalities into a workflow at predefined points, called hooks. This allows you to perform custom actions as a part of a core workflow without hacky workarounds.
+- [Additional Data in API Routes](https://docs.medusajs.com/learn/fundamentals/api-routes/additional-data/index.html.md): Configure core API routes to accept request parameters relevant to your customizations. These parameters are passed to the underlying workflow's hooks, where you can manage your custom data as part of an existing flow.
+
+***
+
+## Next Chapters: Link Brands to Products Example
+
+The next chapters explain how to use the tools mentioned above with step-by-step guides. You'll continue with the [brands example from the previous chapters](https://docs.medusajs.com/learn/customization/custom-features/index.html.md) to:
+
+- Link brands from the custom [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) to products from Medusa's [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md).
+- Extend the core product-creation workflow and the API route that uses it to allow setting the brand of a newly created product.
+- Retrieve a product's associated brand's details.
+
+
+# Customize Medusa Admin Dashboard
+
+In the previous chapters, you've customized your Medusa application to [add brands](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md), [expose an API route to create brands](https://docs.medusajs.com/learn/customization/custom-features/api-route/index.html.md), and [linked brands to products](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md).
+
+After customizing and extending your application with new features, you may need to provide an interface for admin users to utilize these features. The Medusa Admin dashboard is extendable, allowing you to:
+
+- Insert components, called [widgets](https://docs.medusajs.com/learn/fundamentals/admin/widgets/index.html.md), on existing pages.
+- Add new pages, called [UI Routes](https://docs.medusajs.com/learn/fundamentals/admin/ui-routes/index.html.md).
+
+From these customizations, you can send requests to custom API routes, allowing admin users to manage custom resources on the dashboard
+
+***
+
+## Next Chapters: View Brands in Dashboard
+
+In the next chapters, you'll continue with the brands example to:
+
+- Add a new section to the product details page that shows the product's brand.
+- Add a new page in the dashboard that shows all brands in the store.
+
+
+# Integrate Third-Party Systems
+
+Commerce applications often connect to third-party systems that provide additional or specialized features. For example, you may integrate a Content-Management System (CMS) for rich content features, a payment provider to process credit-card payments, and a notification service to send emails.
+
+Medusa's framework facilitates integrating these systems and orchestrating operations across them, saving you the effort of managing them yourself. You won't find those capabilities in other commerce platforms that in these scenarios become a bottleneck to building customizations and iterating quickly.
+
+In Medusa, you integrate a third-party system by:
+
+1. Creating a module whose service provides the methods to connect to and perform operations in the third-party system.
+2. Building workflows that complete tasks spanning across systems. You use the module that integrates a third-party system in the workflow's steps.
+3. Executing the workflows you built in an [API route](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md), at a scheduled time, or when an event is emitted.
+
+***
+
+## Next Chapters: Sync Brands Example
+
+In the previous chapters, you've [added brands](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) to your Medusa application. In the next chapters, you will:
+
+1. Integrate a dummy third-party CMS in the Brand Module.
+2. Sync brands to the CMS when a brand is created.
+3. Sync brands from the CMS at a daily schedule.
+
+
+# Re-Use Customizations with Plugins
+
+In the previous chapters, you've learned important concepts related to creating modules, implementing commerce features in workflows, exposing those features in API routes, customizing the Medusa Admin dashboard with Admin Extensions, and integrating third-party systems.
+
+You've implemented the brands example within a single Medusa application. However, this approach is not scalable when you want to reuse your customizations across multiple projects.
+
+To reuse your customizations across multiple Medusa applications, such as implementing brands in different projects, you can create a plugin. A plugin is an NPM package that encapsulates your customizations and can be installed in any Medusa application. Plugins can include modules, workflows, API routes, Admin Extensions, and more.
+
+
+
+Medusa provides the tooling to create a plugin package, test it in a local Medusa application, and publish it to NPM.
+
+To learn more about plugins and how to create them, refer to [this chapter](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
+
+
+# Admin Development
+
+In the next chapters, you'll learn more about possible admin customizations.
+
+You can customize the admin dashboard by:
+
+- Adding new sections to existing pages using Widgets.
+- Adding new pages using UI Routes.
+
+However, you can't customize the admin dashboard's layout, design, or the content of the existing pages (aside from injecting widgets).
+
+***
+
+## Medusa UI Package
+
+Medusa provides a Medusa UI package to facilitate your admin development through ready-made components and ensure a consistent design between your customizations and the dashboard’s design.
+
+Refer to the [Medusa UI documentation](https://docs.medusajs.com/ui/index.html.md) to learn how to install it and use its components.
+
+***
+
+## Admin Components List
+
+To build admin customizations that match the Medusa Admin's designs and layouts, refer to [this guide](https://docs.medusajs.com/resources/admin-components/index.html.md) to find common components.
+
+
+# API Routes
+
+In this chapter, you’ll learn what API Routes are and how to create them.
+
+## What is an API Route?
+
+An API Route is an endpoint. It exposes commerce features to external applications, such as storefronts, the admin dashboard, or third-party systems.
+
+The Medusa core application provides a set of admin and store API routes out-of-the-box. You can also create custom API routes to expose your custom functionalities.
+
+***
+
+## How to Create an API Route?
+
+An API Route is created in a TypeScript or JavaScript file under the `src/api` directory of your Medusa application. The file’s name must be `route.ts` or `route.js`.
+
+
+
+Each file exports API Route handler functions for at least one HTTP method (`GET`, `POST`, `DELETE`, etc…).
+
+For example, to create a `GET` API Route at `/hello-world`, create the file `src/api/hello-world/route.ts` with the following content:
+
+```ts title="src/api/hello-world/route.ts"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+
+export const GET = (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ res.json({
+ message: "[GET] Hello world!",
+ })
+}
+```
+
+### Test API Route
+
+To test the API route above, start the Medusa application:
+
+```bash npm2yarn
+npm run dev
+```
+
+Then, send a `GET` request to the `/hello-world` API Route:
+
+```bash
+curl http://localhost:9000/hello-world
+```
+
+***
+
+## When to Use API Routes
+
+You're exposing custom functionality to be used by a storefront, admin dashboard, or any external application.
+
+
+# Environment Variables
+
+In this chapter, you'll learn how environment variables are loaded in Medusa.
+
+## System Environment Variables
+
+The Medusa application loads and uses system environment variables.
+
+For example, if you set the `PORT` environment variable to `8000`, the Medusa application runs on that port instead of `9000`.
+
+In production, you should always use system environment variables that you set through your hosting provider.
+
+***
+
+## Environment Variables in .env Files
+
+During development, it's easier to set environment variables in a `.env` file in your repository.
+
+Based on your `NODE_ENV` system environment variable, Medusa will try to load environment variables from the following `.env` files:
+
+As of [Medusa v2.5.0](https://github.com/medusajs/medusa/releases/tag/v2.5.0), `NODE_ENV` defaults to `production` when using `medusa start`. Otherwise, it defaults to `development`.
+
+|\`.env\`|
+|---|---|
+|\`NODE\_ENV\`|\`.env\`|
+|\`NODE\_ENV\`|\`.env.production\`|
+|\`NODE\_ENV\`|\`.env.staging\`|
+|\`NODE\_ENV\`|\`.env.test\`|
+
+### Set Environment in `loadEnv`
+
+In the `medusa-config.ts` file of your Medusa application, you'll find a `loadEnv` function used that accepts `process.env.NODE_ENV` as a first parameter.
+
+This function is responsible for loading the correct `.env` file based on the value of `process.env.NODE_ENV`.
+
+To ensure that the correct `.env` file is loaded as shown in the table above, only specify `development`, `production`, `staging` or `test` as the value of `process.env.NODE_ENV` or as the parameter of `loadEnv`.
+
+***
+
+## Environment Variables for Admin Customizations
+
+Since the Medusa Admin is built on top of [Vite](https://vite.dev/), you prefix the environment variables you want to use in a widget or UI route with `VITE_`. Then, you can access or use them with the `import.meta.env` object.
+
+Learn more in [this documentation](https://docs.medusajs.com/learn/fundamentals/admin/environment-variables/index.html.md).
+
+***
+
+## Predefined Medusa Environment Variables
+
+The Medusa application uses the following predefined environment variables that you can set:
+
+You should opt for setting configurations in `medusa-config.ts` where possible. For a full list of Medusa configurations, refer to the [Medusa Configurations chapter](https://docs.medusajs.com/learn/configurations/medusa-config/index.html.md).
+
+|Environment Variable|Description|Default|
+|---|---|---|---|---|
+|
+|
+|
+|
+||The URL to connect to the PostgreSQL database. Only used if ||
+||URLs of storefronts that can access the Medusa backend's Store APIs. Only used if ||
+||URLs of admin dashboards that can access the Medusa backend's Admin APIs. Only used if ||
+||URLs of clients that can access the Medusa backend's authentication routes. Only used if ||
+||A random string used to create authentication tokens in the http layer. Only used if ||
+||A random string used to create cookie tokens in the http layer. Only used if ||
+||The URL to the Medusa backend. Only used if ||
+|
+|
+|
+|
+|
+|
+|
+|
+||The allowed levels to log. Learn more in ||
+||The file to save logs in. By default, logs aren't saved in any file. Learn more in ||
+||Whether to disable analytics data collection. Learn more in ||
+
+
+# Custom CLI Scripts
+
+In this chapter, you'll learn how to create and execute custom scripts from Medusa's CLI tool.
+
+## What is a Custom CLI Script?
+
+A custom CLI script is a function to execute through Medusa's CLI tool. This is useful when creating custom Medusa tooling to run through the CLI.
+
+***
+
+## How to Create a Custom CLI Script?
+
+To create a custom CLI script, create a TypeScript or JavaScript file under the `src/scripts` directory. The file must default export a function.
+
+For example, create the file `src/scripts/my-script.ts` with the following content:
+
+```ts title="src/scripts/my-script.ts"
+import {
+ ExecArgs,
+ IProductModuleService,
+} from "@medusajs/framework/types"
+import { Modules } from "@medusajs/framework/utils"
+
+export default async function myScript({ container }: ExecArgs) {
+ const productModuleService: IProductModuleService = container.resolve(
+ Modules.PRODUCT
+ )
+
+ const [, count] = await productModuleService
+ .listAndCountProducts()
+
+ console.log(`You have ${count} product(s)`)
+}
+```
+
+The function receives as a parameter an object having a `container` property, which is an instance of the Medusa Container. Use it to resolve resources in your Medusa application.
+
+***
+
+## How to Run Custom CLI Script?
+
+To run the custom CLI script, run the Medusa CLI's `exec` command:
+
+```bash
+npx medusa exec ./src/scripts/my-script.ts
+```
+
+***
+
+## Custom CLI Script Arguments
+
+Your script can accept arguments from the command line. Arguments are passed to the function's object parameter in the `args` property.
+
+For example:
+
+```ts
+import { ExecArgs } from "@medusajs/framework/types"
+
+export default async function myScript({ args }: ExecArgs) {
+ console.log(`The arguments you passed: ${args}`)
+}
+```
+
+Then, pass the arguments in the `exec` command after the file path:
+
+```bash
+npx medusa exec ./src/scripts/my-script.ts arg1 arg2
+```
+
+
+# Events and Subscribers
+
+In this chapter, you’ll learn about Medusa's event system, and how to handle events with subscribers.
+
+## Handle Core Commerce Flows with Events
+
+When building commerce digital applications, you'll often need to perform an action after a commerce operation is performed. For example, sending an order confirmation email when the customer places an order, or syncing data that's updated in Medusa to a third-party system.
+
+Medusa emits events when core commerce features are performed, and you can listen to and handle these events in asynchronous functions. You can think of Medusa's events like you'd think about webhooks in other commerce platforms, but instead of having to setup separate applications to handle webhooks, your efforts only go into writing the logic right in your Medusa codebase.
+
+You listen to an event in a subscriber, which is an asynchronous function that's executed when its associated event is emitted.
+
+
+
+Subscribers are useful to perform actions that aren't integral to the original flow. For example, you can handle the `order.placed` event in a subscriber that sends a confirmation email to the customer. The subscriber has no impact on the original order-placement flow, as it's executed outside of it.
+
+If the action you're performing is integral to the main flow of the core commerce feature, use [workflow hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md) instead.
+
+### List of Emitted Events
+
+Find a list of all emitted events in [this reference](https://docs.medusajs.com/resources/events-reference/index.html.md).
+
+***
+
+## How to Create a Subscriber?
+
+You create a subscriber in a TypeScript or JavaScript file under the `src/subscribers` directory. The file exports the function to execute and the subscriber's configuration that indicate what event(s) it listens to.
+
+For example, create the file `src/subscribers/order-placed.ts` with the following content:
+
+
+
+```ts title="src/subscribers/product-created.ts"
+import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework"
+import { sendOrderConfirmationWorkflow } from "../workflows/send-order-confirmation"
+
+export default async function orderPlacedHandler({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const logger = container.resolve("logger")
+
+ logger.info("Sending confirmation email...")
+
+ await sendOrderConfirmationWorkflow(container)
+ .run({
+ input: {
+ id: data.id,
+ },
+ })
+}
+
+export const config: SubscriberConfig = {
+ event: `order.placed`,
+}
+```
+
+This subscriber file exports:
+
+- An asynchronous subscriber function that's executed whenever the associated event, which is `order.placed` is triggered.
+- A configuration object with an `event` property whose value is the event the subscriber is listening to. You can also pass an array of event names to listen to multiple events in the same subscriber.
+
+The subscriber function receives an object as a parameter that has the following properties:
+
+- `event`: An object with the event's details. The `data` property contains the data payload of the event emitted, which is the order's ID in this case.
+- `container`: The [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) that you can use to resolve registered resources.
+
+In the subscriber function, you use the container to resolve the Logger utility and log a message in the console. Also, assuming you have a [workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md) that sends an order confirmation email, you execute it in the subscriber.
+
+***
+
+## Test the Subscriber
+
+To test the subscriber, start the Medusa application:
+
+```bash npm2yarn
+npm run dev
+```
+
+Then, try placing an order either using Medusa's API routes or the [Next.js Storefront](https://docs.medusajs.com/learn/storefront-development/nextjs-starter/index.html.md). You'll see the following message in the terminal:
+
+```bash
+info: Processing order.placed which has 1 subscribers
+Sending confirmation email...
+```
+
+The first message indicates that the `order.placed` event was emitted, and the second one is the message logged from the subscriber.
+
+***
+
+## Event Module
+
+The subscription and emitting of events is handled by an Event Module, an architectural module that implements the pub/sub functionalities of Medusa's event system.
+
+Medusa provides two Event Modules out of the box:
+
+- [Local Event Module](https://docs.medusajs.com/resources/architectural-modules/event/local/index.html.md), used by default. It's useful for development, as you don't need additional setup to use it.
+- [Redis Event Module](https://docs.medusajs.com/resources/architectural-modules/event/redis/index.html.md), which is useful in production. It uses [Redis](https://redis.io/) to implement Medusa's pub/sub events system.
+
+Medusa's [architecture](https://docs.medusajs.com/learn/introduction/architecture/index.html.md) also allows you to build a custom Event Module that uses a different service or logic to implement the pub/sub system. Learn how to build an Event Module in [this guide](https://docs.medusajs.com/resources/architectural-modules/event/create/index.html.md).
+
+
+# Data Models
+
+In this chapter, you'll learn what a data model is and how to create a data model.
+
+## What is a Data Model?
+
+A data model represents a table in the database. You create data models using Medusa's data modeling language (DML). It simplifies defining a table's columns, relations, and indexes with straightforward methods and configurations.
+
+You create a data model in a [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md). The module's service provides the methods to store and manage those data models. Then, you can resolve the module's service in other customizations, such as a [workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), to manage the data models' records.
+
+***
+
+## How to Create a Data Model
+
+In a module, you can create a data model in a TypeScript or JavaScript file under the module's `models` directory.
+
+So, for example, assuming you have a Blog Module at `src/modules/blog`, you can create a `Post` data model by creating the `src/modules/blog/models/post.ts` file with the following content:
+
+
+
+```ts title="src/modules/blog/models/post.ts"
+import { model } from "@medusajs/framework/utils"
+
+const Post = model.define("post", {
+ id: model.id().primaryKey(),
+ title: model.text(),
+})
+
+export default Post
+```
+
+You define the data model using the `define` method of the DML. It accepts two parameters:
+
+1. The first one is the name of the data model's table in the database. Use snake-case names.
+2. The second is an object, which is the data model's schema. The schema's properties are defined using the `model`'s methods, such as `text` and `id`.
+ - Data models automatically have the date properties `created_at`, `updated_at`, and `deleted_at`, so you don't need to add them manually.
+
+The code snippet above defines a `Post` data model with `id` and `title` properties.
+
+***
+
+## Generate Migrations
+
+After you create a data model in a module, then [register that module in your Medusa configurations](https://docs.medusajs.com/learn/fundamentals/modules#4-add-module-to-medusas-configurations/index.html.md), you must generate a migration to create the data model's table in the database.
+
+A migration is a TypeScript or JavaScript file that defines database changes made by a module. Migrations are useful when you re-use a module or you're working in a team, so that when one member of a team makes a database change, everyone else can reflect it on their side by running the migrations.
+
+For example, to generate a migration for the Blog Module, run the following command in your Medusa application's directory:
+
+If you're creating the module in a plugin, use the [plugin:db:generate command](https://docs.medusajs.com/resources/medusa-cli/commands/plugin#plugindbgenerate/index.html.md) instead.
+
+```bash
+npx medusa db:generate blog
+```
+
+The `db:generate` command of the Medusa CLI accepts one or more module names to generate the migration for. It will create a migration file for the Blog Module in the directory `src/modules/blog/migrations` similar to the following:
+
+```ts
+import { Migration } from "@mikro-orm/migrations"
+
+export class Migration20241121103722 extends Migration {
+
+ async up(): Promise {
+ this.addSql("create table if not exists \"post\" (\"id\" text not null, \"title\" text not null, \"created_at\" timestamptz not null default now(), \"updated_at\" timestamptz not null default now(), \"deleted_at\" timestamptz null, constraint \"post_pkey\" primary key (\"id\"));")
+ }
+
+ async down(): Promise {
+ this.addSql("drop table if exists \"post\" cascade;")
+ }
+
+}
+```
+
+In the migration class, the `up` method creates the table `post` and defines its columns using PostgreSQL syntax. The `down` method drops the table.
+
+### Run Migrations
+
+To reflect the changes in the generated migration file on the database, run the `db:migrate` command:
+
+If you're creating the module in a plugin, run this command on the Medusa application that the plugin is installed in.
+
+```bash
+npx medusa db:migrate
+```
+
+This creates the `post` table in the database.
+
+### Migrations on Data Model Changes
+
+Whenever you make a change to a data model, you must generate and run the migrations.
+
+For example, if you add a new column to the `Post` data model, you must generate a new migration and run it.
+
+***
+
+## Manage Data Models
+
+Your module's service should extend the [service factory](https://docs.medusajs.com/learn/fundamentals/modules/service-factory/index.html.md), which generates data-management methods for your module's data models.
+
+For example, the Blog Module's service would have methods like `retrievePost` and `createPosts`.
+
+Refer to the [Service Factory](https://docs.medusajs.com/learn/fundamentals/modules/service-factory/index.html.md) chapter to learn more about how to extend the service factory and manage data models, and refer to the [Service Factory Reference](https://docs.medusajs.com/resources/service-factory-reference/index.html.md) for the full list of generated methods and how to use them.
+
+
+# Medusa Container
+
+In this chapter, you’ll learn about the Medusa container and how to use it.
+
+## What is the Medusa Container?
+
+The Medusa container is a registry of framework and commerce tools that's accessible across your application. Medusa automatically registers these tools in the container, including custom ones that you've built, so that you can use them in your customizations.
+
+In other platforms, if you have a resource A (for example, a class) that depends on a resource B, you have to manually add resource B to the container or specify it beforehand as A's dependency, which is often done in a file separate from A's code. This becomes difficult to manage as you maintain larger applications with many changing dependencies.
+
+Medusa simplifies this process by giving you access to the container, with the tools or resources already registered, at all times in your customizations. When you reach a point in your code where you need a tool, you resolve it from the container and use it.
+
+For example, consider you're creating an API route that retrieves products based on filters using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md), a tool that fetches data across the application. In the API route's function, you can resolve Query from the container passed to the API route and use it:
+
+```ts highlights={highlights}
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework"
+import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const query = req.scope.resolve("query")
+
+ const { data: products } = await query.graph({
+ entity: "product",
+ fields: ["*"],
+ filters: {
+ id: "prod_123",
+ },
+ })
+
+ res.json({
+ products,
+ })
+}
+```
+
+The API route accepts as a first parameter a request object that has a `scope` property, which is the Medusa container. It has a `resolve` method that resolves a resource from the container by the key it's registered with.
+
+You can learn more about how Query works in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md).
+
+***
+
+## List of Resources Registered in the Medusa Container
+
+Find a full list of the registered resources and their registration key in [this reference](https://docs.medusajs.com/resources/medusa-container-resources/index.html.md)
+
+***
+
+## How to Resolve From the Medusa Container
+
+This section gives quick examples of how to resolve resources from the Medusa container in customizations other than an API route, which is covered in the section above.
+
+### Subscriber
+
+A [subscriber](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) function, which is executed when an event is emitted, accepts as a parameter an object with a `container` property, whose value is the Medusa container. Use its `resolve` method to resolve a resource by its registration key:
+
+```ts highlights={subscriberHighlights}
+import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework"
+import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
+
+export default async function productCreateHandler({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const query = container.resolve(ContainerRegistrationKeys.QUERY)
+
+ const { data: products } = await query.graph({
+ entity: "product",
+ fields: ["*"],
+ filters: {
+ id: data.id,
+ },
+ })
+
+ console.log(`You created a product with the title ${products[0].title}`)
+}
+
+export const config: SubscriberConfig = {
+ event: `product.created`,
+}
+```
+
+### Scheduled Job
+
+A [scheduled job](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md) function, which is executed at a specified interval, accepts the Medusa container as a parameter. Use its `resolve` method to resolve a resource by its registration key:
+
+```ts highlights={scheduledJobHighlights}
+import { MedusaContainer } from "@medusajs/framework/types"
+import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
+
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const query = container.resolve(ContainerRegistrationKeys.QUERY)
+
+ const { data: products } = await query.graph({
+ entity: "product",
+ fields: ["*"],
+ filters: {
+ id: "prod_123",
+ },
+ })
+
+ console.log(
+ `You have ${products.length} matching your filters.`
+ )
+}
+
+export const config = {
+ name: "every-minute-message",
+ // execute every minute
+ schedule: "* * * * *",
+}
+```
+
+### Workflow Step
+
+A [step in a workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), which is a special function where you build durable execution logic across multiple modules, accepts in its second parameter a `container` property, whose value is the Medusa container. Use its `resolve` method to resolve a resource by its registration key:
+
+```ts highlights={workflowStepsHighlight}
+import {
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
+
+const step1 = createStep("step-1", async (_, { container }) => {
+ const query = container.resolve(ContainerRegistrationKeys.QUERY)
+
+ const { data: products } = await query.graph({
+ entity: "product",
+ fields: ["*"],
+ filters: {
+ id: "prod_123",
+ },
+ })
+
+ return new StepResponse(products)
+})
+```
+
+### Module Services and Loaders
+
+A [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), which is a package of functionalities for a single feature or domain, has its own container, so it can't resolve resources from the Medusa container.
+
+Learn more about the module's container in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/container/index.html.md).
+
+
+# Plugins
+
+In this chapter, you'll learn what a plugin is in Medusa.
+
+Plugins are available starting from [Medusa v2.3.0](https://github.com/medusajs/medusa/releases/tag/v2.3.0).
+
+## What is a Plugin?
+
+A plugin is a package of reusable Medusa customizations that you can install in any Medusa application. The supported customizations are [Modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), [API Routes](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md), [Workflows](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), [Workflow Hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md), [Links](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md), [Subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md), [Scheduled Jobs](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md), and [Admin Extensions](https://docs.medusajs.com/learn/fundamentals/admin/index.html.md).
+
+Plugins allow you to reuse your Medusa customizations across multiple projects or share them with the community. They can be published to npm and installed in any Medusa project.
+
+
+
+Learn how to create a wishlist plugin in [this guide](https://docs.medusajs.com/resources/plugins/guides/wishlist/index.html.md).
+
+***
+
+## Plugin vs Module
+
+A [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md) is an isolated package related to a single domain or functionality, such as product reviews or integrating a Content Management System. A module can't access any resources in the Medusa application that are outside its codebase.
+
+A plugin, on the other hand, can contain multiple Medusa customizations, including modules. Your plugin can define a module, then build flows around it.
+
+For example, in a plugin, you can define a module that integrates a third-party service, then add a workflow that uses the module when a certain event occurs to sync data to that service.
+
+- You want to reuse your Medusa customizations across multiple projects.
+- You want to share your Medusa customizations with the community.
+
+- You want to build a custom feature related to a single domain or integrate a third-party service. Instead, use a [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md). You can wrap that module in a plugin if it's used in other customizations, such as if it has a module link or it's used in a workflow.
+
+***
+
+## How to Create a Plugin?
+
+The next chapter explains how you can create and publish a plugin.
+
+
+# Scheduled Jobs
+
+In this chapter, you’ll learn about scheduled jobs and how to use them.
+
+## What is a Scheduled Job?
+
+When building your commerce application, you may need to automate tasks and run them repeatedly at a specific schedule. For example, you need to automatically sync products to a third-party service once a day.
+
+In other commerce platforms, this feature isn't natively supported. Instead, you have to setup a separate application to execute cron jobs, which adds complexity as to how you expose this task to be executed in a cron job, or how do you debug it when it's not running within the platform's tooling.
+
+Medusa removes this overhead by supporting this feature natively with scheduled jobs. A scheduled job is an asynchronous function that the Medusa application runs at the interval you specify during the Medusa application's runtime. Your efforts are only spent on implementing the functionality performed by the job, such as syncing products to an ERP.
+
+- You want the action to execute at a specified schedule while the Medusa application **isn't** running. Instead, use the operating system's equivalent of a cron job.
+- You want to execute the action once when the application loads. Use [loaders](https://docs.medusajs.com/learn/fundamentals/modules/loaders/index.html.md) instead.
+- You want to execute the action if an event occurs. Use [subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) instead.
+
+***
+
+## How to Create a Scheduled Job?
+
+You create a scheduled job in a TypeScript or JavaScript file under the `src/jobs` directory. The file exports the asynchronous function to run, and the configurations indicating the schedule to run the function.
+
+For example, create the file `src/jobs/hello-world.ts` with the following content:
+
+
+
+```ts title="src/jobs/hello-world.ts" highlights={highlights}
+import { MedusaContainer } from "@medusajs/framework/types"
+
+export default async function greetingJob(container: MedusaContainer) {
+ const logger = container.resolve("logger")
+
+ logger.info("Greeting!")
+}
+
+export const config = {
+ name: "greeting-every-minute",
+ schedule: "* * * * *",
+}
+```
+
+You export an asynchronous function that receives the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) as a parameter. In the function, you resolve the [Logger utility](https://docs.medusajs.com/learn/debugging-and-testing/logging/index.html.md) from the Medusa container and log a message.
+
+You also export a `config` object that has the following properties:
+
+- `name`: A unique name for the job.
+- `schedule`: A string that holds a [cron expression](https://crontab.guru/) indicating the schedule to run the job.
+
+This scheduled job executes every minute and logs into the terminal `Greeting!`.
+
+### Test the Scheduled Job
+
+To test out your scheduled job, start the Medusa application:
+
+```bash npm2yarn
+npm run dev
+```
+
+After a minute, the following message will be logged to the terminal:
+
+```bash
+info: Greeting!
+```
+
+***
+
+## Example: Sync Products Once a Day
+
+In this section, you'll find a brief example of how you use a scheduled job to sync products to a third-party service.
+
+When implementing flows spanning across systems or [modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), you use [workflows](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md). A workflow is a task made up of a series of steps, and you construct it like you would a regular function, but it's a special function that supports rollback mechanism in case of errors, background execution, and more.
+
+You can learn how to create a workflow in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), but this example assumes you already have a `syncProductToErpWorkflow` implemented. To execute this workflow once a day, create a scheduled job at `src/jobs/sync-products.ts` with the following content:
+
+```ts title="src/jobs/sync-products.ts"
+import { MedusaContainer } from "@medusajs/framework/types"
+import { syncProductToErpWorkflow } from "../workflows/sync-products-to-erp"
+
+export default async function syncProductsJob(container: MedusaContainer) {
+ await syncProductToErpWorkflow(container)
+ .run()
+}
+
+export const config = {
+ name: "sync-products-job",
+ schedule: "0 0 * * *",
+}
+```
+
+In the scheduled job function, you execute the `syncProductToErpWorkflow` by invoking it and passing it the container, then invoking the `run` method. You also specify in the exported configurations the schedule `0 0 * * *` which indicates midnight time of every day.
+
+The next time you start the Medusa application, it will run this job every day at midnight.
+
+
+# Module Link
+
+In this chapter, you’ll learn what a module link is and how to define one.
+
+## What is a Module Link?
+
+Medusa's modular architecture isolates modules from one another to ensure they can be integrated into your application without side effects. Module isolation has other benefits, which you can learn about in the [Module Isolation chapter](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md). Since modules are isolated, you can't access another module's data models to add a relation to it or extend it. Instead, you use a module link.
+
+A module link forms an association between two data models of different modules while maintaining module isolation. Using module links, you can build virtual relations between your custom data models and data models in the commerce modules, which is useful as you extend the features provided by the commerce modules. Then, Medusa creates a link table in the database to store the IDs of the linked records. You'll learn more about link tables later in this chapter.
+
+For example, the [Brand Customizations Tutorial](https://docs.medusajs.com/learn/customization/extend-features/index.html.md) shows how to create a Brand Module that adds the concept of brands to your application, then link those brands to a product.
+
+***
+
+## How to Define a Module Link?
+
+### 1. Create Link File
+
+Module links are defined in a TypeScript or JavaScript file under the `src/links` directory. The file defines the link using `defineLink` from the Modules SDK and exports it.
+
+For example:
+
+```ts title="src/links/blog-product.ts" highlights={highlights}
+import BlogModule from "../modules/blog"
+import ProductModule from "@medusajs/medusa/product"
+import { defineLink } from "@medusajs/framework/utils"
+
+export default defineLink(
+ ProductModule.linkable.product,
+ BlogModule.linkable.post
+)
+```
+
+The `defineLink` function accepts as parameters the link configurations of each module's data model. A module has a special `linkable` property that holds these configurations for its data models.
+
+In this example, you define a module link between the `blog` module's `post` data model and the Product Module's `Product` data model.
+
+### 2. Sync Links
+
+After defining the link, run the `db:sync-links` command:
+
+```bash
+npx medusa db:sync-links
+```
+
+The Medusa application creates a new table for your module link to store the IDs of linked records.
+
+You can also use the `db:migrate` command, which runs both the migrations and syncs the links.
+
+Use either of these commands whenever you make changes to your link definitions. For example, run this command if you remove your link definition file.
+
+***
+
+### Module Link's Database Table
+
+When you define a module link, the Medusa application creates a table in the database for that module link. The table's name is a combination of the names of the two data models linked in the format `module1_table1_module2_table2`, where:
+
+- `module1` and `module2` are the names of the modules.
+- `table1` and `table2` are the table names of the data models.
+
+For example, if you define a link between the `Product` data model from the [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md) and a `Post` data model from a Blog Module, the table name would be `product_product_blog_post`.
+
+The table has two columns, each storing the ID of a record from the linked data models. For example, the `product_product_blog_post` table would have columns `product_id` and `post_id`. These columns store only the IDs of the linked records and do not hold a foreign key constraint.
+
+Then, when you create links between records of the data models, the IDs of these data models are stored as a new record in the link's table.
+
+You can also add custom columns in the link table as explained in the [Add Columns to Link Table chapter](https://docs.medusajs.com/learn/fundamentals/module-links/custom-columns/index.html.md).
+
+
+
+***
+
+## When to Use Module Links
+
+- You want to create a relation between data models from different modules.
+- You want to extend the data model of another module.
+
+You want to create a relationship between data models in the same module. Use data model relationships instead.
+
+***
+
+## Define a List Module Link
+
+By default, a module link establishes a one-to-one relation: a record of a data model is linked to one record of the other data model.
+
+To specify that a data model can have multiple of its records linked to the other data model's record, use the `isList` option.
+
+For example:
+
+```ts
+import BlogModule from "../modules/blog"
+import ProductModule from "@medusajs/medusa/product"
+import { defineLink } from "@medusajs/framework/utils"
+
+export default defineLink(
+ ProductModule.linkable.product,
+ {
+ linkable: BlogModule.linkable.post,
+ isList: true,
+ }
+)
+```
+
+In this case, you pass an object of configuration as a parameter instead. The object accepts the following properties:
+
+- `linkable`: The data model's link configuration.
+- `isList`: Whether multiple records can be linked to one record of the other data model.
+
+In this example, a record of `product` can be linked to more than one record of `post`.
+
+### Many-to-Many Module Link
+
+Your module link can also establish a many-to-many relation between the linked data models. To do this, enable `isList` on both sides of the link.
+
+For example:
+
+```ts
+import BlogModule from "../modules/blog"
+import ProductModule from "@medusajs/medusa/product"
+import { defineLink } from "@medusajs/framework/utils"
+
+export default defineLink(
+ {
+ linkable: ProductModule.linkable.product,
+ isList: true,
+ },
+ {
+ linkable: BlogModule.linkable.post,
+ isList: true,
+ }
+)
+```
+
+***
+
+## Set Delete Cascades on Link
+
+To enable delete cascade on a link so that when a record is deleted, its linked records are also deleted, pass the `deleteCascade` property in the object passed to `defineLink`.
+
+For example:
+
+```ts
+import BlogModule from "../modules/blog"
+import ProductModule from "@medusajs/medusa/product"
+import { defineLink } from "@medusajs/framework/utils"
+
+export default defineLink(
+ ProductModule.linkable.product,
+ {
+ linkable: BlogModule.linkable.post,
+ deleteCascade: true,
+ }
+)
+```
+
+In this example, when a product is deleted, its linked `post` record is also deleted.
+
+***
+
+## Renaming Participants in a Module Link
+
+As mentioned in the [Module Link's Database Table](#module-links-database-table) section, the name of a link's table consists of the names of the modules and the data models' table names.
+
+So, if you rename a module or a data model's table, then run the `db:sync-links` or `db:migrate` commands, you'll be asked to delete the old link table and create a new one.
+
+A data model's table name is passed in the first parameter of `model.define`, and a module's name is passed in the first parameter of `Module` in the module's `index.ts` file.
+
+For example, if you have the link table `product_product_blog_post` and you rename the Blog Module from `blog` to `article`, Medusa considers the old link definition deleted. Then, when you run the `db:sync-links` or `db:migrate` command, Medusa will ask if you want to delete the old link table, and will create a new one with the new name `product_product_article_post`.
+
+To resolve this, you can rename the link table in the link definition.
+
+### Rename Link Table
+
+If you need to rename a module or its data model's table, you can persist the old name by passing a third parameter to `defineLink`. This parameter is an object of additional configurations. It accepts a `database` property that allows you to configure the link's table name.
+
+For example, after renaming the Blog Module to `article`, you can persist the old name `blog` in the link table name:
+
+```ts highlights={renameHighlights}
+import ArticleModule from "../modules/article"
+import ProductModule from "@medusajs/medusa/product"
+import { defineLink } from "@medusajs/framework/utils"
+
+export default defineLink(
+ ProductModule.linkable.product,
+ {
+ linkable: ArticleModule.linkable.post,
+ isList: true,
+ },
+ {
+ database: {
+ table: "product_product_blog_post",
+ },
+ }
+)
+```
+
+In this example, you set the `table` property in the `database` object to the old link table name `product_product_blog_post`, ensuring that the old link table is not deleted.
+
+This is enough to rename the link table when you rename a module. If you renamed a data model's table, you need to also run the `db:sync-links` or `db:migrate` commands, which will update the column names in the link table automatically:
+
+```bash
+npx medusa db:migrate
+```
+
+***
+
+## Delete Module Link Definition
+
+To delete a module link definition, remove the link file from the `src/links` directory. Then, run the `db:sync-links` or `db:migrate` command to delete the link table from the database:
+
+```bash
+npx medusa db:migrate
+```
+
+
+# Modules
+
+In this chapter, you’ll learn about modules and how to create them.
+
+## What is a Module?
+
+A module is a reusable package of functionalities related to a single domain or integration. Medusa comes with multiple pre-built modules for core commerce needs, such as the [Cart Module](https://docs.medusajs.com/resources/commerce-modules/cart/index.html.md) that holds the data models and business logic for cart operations.
+
+When building a commerce application, you often need to introduce custom behavior specific to your products, tech stack, or your general ways of working. In other commerce platforms, introducing custom business logic and data models requires setting up separate applications to manage these customizations.
+
+Medusa removes this overhead by allowing you to easily write custom modules that integrate into the Medusa application without affecting the existing setup. You can also re-use your modules across Medusa projects.
+
+As you learn more about Medusa, you will see that modules are central to customizations and integrations. With modules, your Medusa application can turn into a middleware solution for your commerce ecosystem.
+
+- You want to build a custom feature related to a single domain or integrate a third-party service.
+
+- You want to create a reusable package of customizations that include not only modules, but also API routes, workflows, and other customizations. Instead, use a [plugin](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
+
+***
+
+## How to Create a Module?
+
+In a module, you define data models that represent new tables in the database, and you manage these models in a class called a service. Then, the Medusa application registers the module's service in the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) so that you can build commerce flows and features around the functionalities provided by the module.
+
+In this section, you'll build a Blog Module that has a `Post` data model and a service to manage that data model. You'll also expose an API endpoint to create a blog post.
+
+Modules are created in a sub-directory of `src/modules`. So, start by creating the directory `src/modules/blog`.
+
+### 1. Create Data Model
+
+A data model represents a table in the database. You create data models using Medusa's data modeling language (DML). It simplifies defining a table's columns, relations, and indexes with straightforward methods and configurations.
+
+You create a data model in a TypeScript or JavaScript file under the `models` directory of a module. So, to create a `Post` data model in the Blog Module, create the file `src/modules/blog/models/post.ts` with the following content:
+
+
+
+```ts title="src/modules/blog/models/post.ts"
+import { model } from "@medusajs/framework/utils"
+
+const Post = model.define("post", {
+ id: model.id().primaryKey(),
+ title: model.text(),
+})
+
+export default Post
+```
+
+You define the data model using the `define` method of the DML. It accepts two parameters:
+
+1. The first one is the name of the data model's table in the database. Use snake-case names.
+2. The second is an object, which is the data model's schema. The schema's properties are defined using the `model`'s methods, such as `text` and `id`.
+ - Data models automatically have the date properties `created_at`, `updated_at`, and `deleted_at`, so you don't need to add them manually.
+
+Learn about other property types in [this chapter](https://docs.medusajs.com/learn/fundamentals/data-models/properties#property-types/index.html.md).
+
+The code snippet above defines a `Post` data model with `id` and `title` properties.
+
+### 2. Create Service
+
+You perform database operations on your data models in a service, which is a class exported by the module and acts like an interface to its functionalities. Medusa registers the service in its [container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md), allowing you to resolve and use it when building custom commerce flows.
+
+You define a service in a `service.ts` or `service.js` file at the root of your module's directory. So, to create the Blog Module's service, create the file `src/modules/blog/service.ts` with the following content:
+
+
+
+```ts title="src/modules/blog/service.ts" highlights={highlights}
+import { MedusaService } from "@medusajs/framework/utils"
+import Post from "./models/post"
+
+class BlogModuleService extends MedusaService({
+ Post,
+}){
+}
+
+export default BlogModuleService
+```
+
+Your module's service extends a class generated by `MedusaService` from the Modules SDK. This class comes with generated methods for data-management Create, Read, Update, and Delete (CRUD) operations on each of your modules, saving you time that can be spent on building custom business logic.
+
+The `MedusaService` function accepts an object of data models to generate methods for. You can pass all data models in your module in this object.
+
+For example, the `BlogModuleService` now has a `createPosts` method to create post records, and a `retrievePost` method to retrieve a post record. The suffix of each method (except for `retrieve`) is the pluralized name of the data model.
+
+Find all methods generated by the `MedusaService` in [this reference](https://docs.medusajs.com/resources/service-factory-reference/index.html.md)
+
+If a module doesn't have data models, such as when it's integrating a third-party service, it doesn't need to extend `MedusaService`.
+
+### 3. Export Module Definition
+
+The final piece to a module is its definition, which is exported in an `index.ts` file at its root directory. This definition tells Medusa the name of the module and its main service. Medusa will then register the main service in the container under the module's name.
+
+So, to export the definition of the Blog Module, create the file `src/modules/blog/index.ts` with the following content:
+
+
+
+```ts title="src/modules/blog/index.ts" highlights={moduleDefinitionHighlights}
+import BlogModuleService from "./service"
+import { Module } from "@medusajs/framework/utils"
+
+export const BLOG_MODULE = "blog"
+
+export default Module(BLOG_MODULE, {
+ service: BlogModuleService,
+})
+```
+
+You use `Module` from the Modules SDK to create the module's definition. It accepts two parameters:
+
+1. The name that the module's main service is registered under (`blog`). The module name can contain only alphanumeric characters and underscores.
+2. An object with a required property `service` indicating the module's main service.
+
+You export `BLOG_MODULE` to reference the module's name more reliably when resolving its service in other customizations.
+
+### 4. Add Module to Medusa's Configurations
+
+If you're creating the module in a plugin, this step isn't required as the module is registered when the plugin is registered. Learn more about plugins in [this documentation](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
+
+Once you finish building the module, add it to Medusa's configurations to start using it. Medusa will then register the module's main service in the Medusa container, allowing you to resolve and use it in other customizations.
+
+In `medusa-config.ts`, add a `modules` property and pass an array with your custom module:
+
+```ts title="medusa-config.ts" highlights={[["7"]]}
+module.exports = defineConfig({
+ projectConfig: {
+ // ...
+ },
+ modules: [
+ {
+ resolve: "./src/modules/blog",
+ },
+ ],
+})
+```
+
+Each object in the `modules` array has a `resolve` property, whose value is either a path to the module's directory, or an `npm` package’s name.
+
+### 5. Generate Migrations
+
+Since data models represent tables in the database, you define how they're created in the database with migrations. A migration is a TypeScript or JavaScript file that defines database changes made by a module.
+
+Migrations are useful when you re-use a module or you're working in a team, so that when one member of a team makes a database change, everyone else can reflect it on their side by running the migrations.
+
+You don't have to write migrations yourself. Medusa's CLI tool has a command that generates the migrations for you. You can also use this command again when you make changes to the module at a later point, and it will generate new migrations for that change.
+
+To generate a migration for the Blog Module, run the following command in your Medusa application's directory:
+
+If you're creating the module in a plugin, use the [plugin:db:generate command](https://docs.medusajs.com/resources/medusa-cli/commands/plugin#plugindbgenerate/index.html.md) instead.
+
+```bash
+npx medusa db:generate blog
+```
+
+The `db:generate` command of the Medusa CLI accepts one or more module names to generate the migration for. It will create a migration file for the Blog Module in the directory `src/modules/blog/migrations` similar to the following:
+
+```ts
+import { Migration } from "@mikro-orm/migrations"
+
+export class Migration20241121103722 extends Migration {
+
+ async up(): Promise {
+ this.addSql("create table if not exists \"post\" (\"id\" text not null, \"title\" text not null, \"created_at\" timestamptz not null default now(), \"updated_at\" timestamptz not null default now(), \"deleted_at\" timestamptz null, constraint \"post_pkey\" primary key (\"id\"));")
+ }
+
+ async down(): Promise {
+ this.addSql("drop table if exists \"post\" cascade;")
+ }
+
+}
+```
+
+In the migration class, the `up` method creates the table `post` and defines its columns using PostgreSQL syntax. The `down` method drops the table.
+
+### 6. Run Migrations
+
+To reflect the changes in the generated migration file on the database, run the `db:migrate` command:
+
+If you're creating the module in a plugin, run this command on the Medusa application that the plugin is installed in.
+
+```bash
+npx medusa db:migrate
+```
+
+This creates the `post` table in the database.
+
+***
+
+## Test the Module
+
+Since the module's main service is registered in the Medusa container, you can resolve it in other customizations to use its methods.
+
+To test out the Blog Module, you'll add the functionality to create a post in a [workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), which is a special function that performs a task in a series of steps with rollback logic. Then, you'll expose an [API route](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md) that creates a blog post by executing the workflow.
+
+By building a commerce feature in a workflow, you can execute it in other customizations while ensuring data consistency across systems. If an error occurs during execution, every step has its own rollback logic to undo its actions. Workflows have other special features which you can learn about in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md).
+
+To create the workflow, create the file `src/workflows/create-post.ts` with the following content:
+
+```ts title="src/workflows/create-post.ts" highlights={workflowHighlights}
+import {
+ createStep,
+ createWorkflow,
+ StepResponse,
+ WorkflowResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { BLOG_MODULE } from "../modules/blog"
+import BlogModuleService from "../modules/blog/service"
+
+type CreatePostWorkflowInput = {
+ title: string
+}
+
+const createPostStep = createStep(
+ "create-post",
+ async ({ title }: CreatePostWorkflowInput, { container }) => {
+ const blogModuleService: BlogModuleService = container.resolve(BLOG_MODULE)
+
+ const post = await blogModuleService.createPosts({
+ title,
+ })
+
+ return new StepResponse(post, post)
+ },
+ async (post, { container }) => {
+ const blogModuleService: BlogModuleService = container.resolve(BLOG_MODULE)
+
+ await blogModuleService.deletePosts(post.id)
+ }
+)
+
+export const createPostWorkflow = createWorkflow(
+ "create-post",
+ (postInput: CreatePostWorkflowInput) => {
+ const post = createPostStep(postInput)
+
+ return new WorkflowResponse(post)
+ }
+)
+```
+
+The workflow has a single step `createPostStep` that creates a post. In the step, you resolve the Blog Module's service from the Medusa container, which the step receives as a parameter. Then, you create the post using the method `createPosts` of the service, which was generated by `MedusaService`.
+
+The step also has a compensation function, which is a function passed as a third-parameter to `createStep` that implements the logic to rollback the change made by a step in case an error occurs during the workflow's execution.
+
+You'll now execute that workflow in an API route to expose the feature of creating blog posts to clients. To create an API route, create the file `src/api/blog/posts/route.ts` with the following content:
+
+```ts
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import {
+ createPostWorkflow,
+} from "../../../workflows/create-post"
+
+export async function POST(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result: post } = await createPostWorkflow(req.scope)
+ .run({
+ input: {
+ title: "My Post",
+ },
+ })
+
+ res.json({
+ post,
+ })
+}
+```
+
+This adds a `POST` API route at `/blog/posts`. In the API route, you execute the `createPostWorkflow` by invoking it, passing it the Medusa container in `req.scope`, then invoking the `run` method. In the `run` method, you pass the workflow's input in the `input` property.
+
+To test this out, start the Medusa application:
+
+```bash npm2yarn
+npm run dev
+```
+
+Then, send a `POST` request to `/blog/posts`:
+
+```bash
+curl -X POST http://localhost:9000/blog/posts
+```
+
+This will create a post and return it in the response:
+
+```json
+{
+ "post": {
+ "id": "123...",
+ "title": "My Post",
+ "created_at": "...",
+ "updated_at": "..."
+ }
+}
+```
+
+You can also execute the workflow from a [subscriber](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) when an event occurs, or from a [scheduled job](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md) to run it at a specified interval.
+
+
+# Medusa's Architecture
+
+In this chapter, you'll learn about the architectural layers in Medusa.
+
+Find the full architectural diagram at the [end of this chapter](#full-diagram-of-medusas-architecture).
+
+## HTTP, Workflow, and Module Layers
+
+Medusa is a headless commerce platform. So, storefronts, admin dashboards, and other clients consume Medusa's functionalities through its API routes.
+
+In a common Medusa application, requests go through four layers in the stack. In order of entry, those are:
+
+1. API Routes (HTTP): Our API Routes are the typical entry point. The Medusa server is based on Express.js, which handles incoming requests. It can also connect to a Redis database that stores the server session data.
+2. Workflows: API Routes consume workflows that hold the opinionated business logic of your application.
+3. Modules: Workflows use domain-specific modules for resource management.
+4. Data store: Modules query the underlying datastore, which is a PostgreSQL database in common cases.
+
+These layers of stack can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
+
+
+
+***
+
+## Database Layer
+
+The Medusa application injects into each module, including your [custom modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), a connection to the configured PostgreSQL database. Modules use that connection to read and write data to the database.
+
+Modules can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
+
+
+
+***
+
+## Third-Party Integrations Layer
+
+Third-party services and systems are integrated through Medusa's Commerce and Architectural modules. You also create custom third-party integrations through a [custom module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
+
+Modules can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
+
+### Commerce Modules
+
+[Commerce modules](https://docs.medusajs.com/resources/commerce-modules/index.html.md) integrate third-party services relevant for commerce or user-facing features. For example, you can integrate [Stripe](https://docs.medusajs.com/resources/commerce-modules/payment/payment-provider/stripe/index.html.md) through a Payment Module Provider, or [ShipStation](https://docs.medusajs.com/resources/integrations/guides/shipstation/index.html.md) through a Fulfillment Module Provider.
+
+You can also integrate third-party services for custom functionalities. For example, you can integrate [Sanity](https://docs.medusajs.com/resources/integrations/guides/sanity/index.html.md) for rich CMS capabilities, or [Odoo](https://docs.medusajs.com/resources/recipes/erp/odoo/index.html.md) to sync your Medusa application with your ERP system.
+
+You can replace any of the third-party services mentioned above to build your preferred commerce ecosystem.
+
+
+
+### Architectural Modules
+
+[Architectural modules](https://docs.medusajs.com/resources/architectural-modules/index.html.md) integrate third-party services and systems for architectural features. Medusa has the following Architectural modules:
+
+- [Cache Module](https://docs.medusajs.com/resources/architectural-modules/cache/index.html.md): Caches data that require heavy computation. You can integrate a custom module to handle the caching with services like Memcached, or use the existing [Redis Cache Module](https://docs.medusajs.com/resources/architectural-modules/cache/redis/index.html.md).
+- [Event Module](https://docs.medusajs.com/resources/architectural-modules/event/index.html.md): A pub/sub system that allows you to subscribe to events and trigger them. You can integrate [Redis](https://docs.medusajs.com/resources/architectural-modules/event/redis/index.html.md) as the pub/sub system.
+- [File Module](https://docs.medusajs.com/resources/architectural-modules/file/index.html.md): Manages file uploads and storage, such as upload of product images. You can integrate [AWS S3](https://docs.medusajs.com/resources/architectural-modules/file/s3/index.html.md) for file storage.
+- [Locking Module](https://docs.medusajs.com/resources/architectural-modules/locking/index.html.md): Manages access to shared resources by multiple processes or threads, preventing conflict between processes and ensuring data consistency. You can integrate [Redis](https://docs.medusajs.com/resources/architectural-modules/locking/redis/index.html.md) for locking.
+- [Notification Module](https://docs.medusajs.com/resources/architectural-modules/notification/index.html.md): Sends notifications to customers and users, such as for order updates or newsletters. You can integrate [SendGrid](https://docs.medusajs.com/resources/architectural-modules/notification/sendgrid/index.html.md) for sending emails.
+- [Workflow Engine Module](https://docs.medusajs.com/resources/architectural-modules/workflow-engine/index.html.md): Orchestrates workflows that hold the business logic of your application. You can integrate [Redis](https://docs.medusajs.com/resources/architectural-modules/workflow-engine/redis/index.html.md) to orchestrate workflows.
+
+All of the third-party services mentioned above can be replaced to help you build your preferred architecture and ecosystem.
+
+
+
+***
+
+## Full Diagram of Medusa's Architecture
+
+The following diagram illustrates Medusa's architecture including all its layers.
+
+
+
+
+# Workflows
+
+In this chapter, you’ll learn about workflows and how to define and execute them.
+
+## What is a Workflow?
+
+In digital commerce you typically have many systems involved in your operations. For example, you may have an ERP system that holds product master data and accounting reports, a CMS system for content, a CRM system for managing customer campaigns, a payment service to process credit cards, and so on. Sometimes you may even have custom built applications that need to participate in the commerce stack. One of the biggest challenges when operating a stack like this is ensuring consistency in the data spread across systems.
+
+Medusa has a built-in durable execution engine to help complete tasks that span multiple systems. You orchestrate your operations across systems in Medusa instead of having to manage it yourself. Other commerce platforms don't have this capability, which makes them a bottleneck to building customizations and iterating quickly.
+
+A workflow is a series of queries and actions, called steps, that complete a task. You construct a workflow similar to how you create a JavaScript function.
+
+However, unlike regular functions, workflows:
+
+- Create an internal representation of your steps, allowing you to track them and their progress.
+- Support defining roll-back logic for each step, so that you can handle errors gracefully and your data remain consistent across systems.
+- Perform long actions asynchronously, giving you control over when a step starts and finishes.
+
+You implement all custom flows within workflows, then execute them from [API routes](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md), [subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md), and [scheduled jobs](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md).
+
+***
+
+## How to Create and Execute a Workflow?
+
+### 1. Create the Steps
+
+A workflow is made of a series of steps. A step is created using `createStep` from the Workflows SDK.
+
+Create the file `src/workflows/hello-world.ts` with the following content:
+
+
+
+```ts title="src/workflows/hello-world.ts" highlights={step1Highlights}
+import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
+
+const step1 = createStep(
+ "step-1",
+ async () => {
+ return new StepResponse(`Hello from step one!`)
+ }
+)
+```
+
+The `createStep` function accepts the step's unique name as a first parameter, and the step's function as a second parameter.
+
+Steps must return an instance of `StepResponse`, whose parameter is the data to return to the workflow executing the step.
+
+Steps can accept input parameters. For example, add the following to `src/workflows/hello-world.ts`:
+
+```ts title="src/workflows/hello-world.ts" highlights={step2Highlights}
+type WorkflowInput = {
+ name: string
+}
+
+const step2 = createStep(
+ "step-2",
+ async ({ name }: WorkflowInput) => {
+ return new StepResponse(`Hello ${name} from step two!`)
+ }
+)
+```
+
+This adds another step whose function accepts as a parameter an object with a `name` property.
+
+### 2. Create a Workflow
+
+Next, add the following to the same file to create the workflow using the `createWorkflow` function:
+
+```ts title="src/workflows/hello-world.ts" highlights={workflowHighlights}
+import {
+ // other imports...
+ createWorkflow,
+ WorkflowResponse,
+} from "@medusajs/framework/workflows-sdk"
+
+// ...
+
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input: WorkflowInput) {
+ const str1 = step1()
+ // to pass input
+ const str2 = step2(input)
+
+ return new WorkflowResponse({
+ message: str2,
+ })
+ }
+)
+
+export default myWorkflow
+```
+
+The `createWorkflow` function accepts the workflow's unique name as a first parameter, and the workflow's function as a second parameter. The workflow can accept input which is passed as a parameter to the function.
+
+The workflow must return an instance of `WorkflowResponse`, whose parameter is returned to workflow executors.
+
+### 3. Execute the Workflow
+
+You can execute a workflow from different customizations:
+
+- Execute in an API route to expose the workflow's functionalities to clients.
+- Execute in a subscriber to use the workflow's functionalities when a commerce operation is performed.
+- Execute in a scheduled job to run the workflow's functionalities automatically at a specified repeated interval.
+
+To execute the workflow, invoke it passing the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) as a parameter. Then, use its `run` method:
+
+### API Route
+
+```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"], ["13"], ["14"], ["15"], ["16"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import myWorkflow from "../../workflows/hello-world"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result } = await myWorkflow(req.scope)
+ .run({
+ input: {
+ name: "John",
+ },
+ })
+
+ res.send(result)
+}
+```
+
+### Subscriber
+
+```ts title="src/subscribers/order-placed.ts" highlights={[["11"], ["12"], ["13"], ["14"], ["15"], ["16"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import {
+ type SubscriberConfig,
+ type SubscriberArgs,
+} from "@medusajs/framework"
+import myWorkflow from "../workflows/hello-world"
+
+export default async function handleOrderPlaced({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const { result } = await myWorkflow(container)
+ .run({
+ input: {
+ name: "John",
+ },
+ })
+
+ console.log(result)
+}
+
+export const config: SubscriberConfig = {
+ event: "order.placed",
+}
+```
+
+### Scheduled Job
+
+```ts title="src/jobs/message-daily.ts" highlights={[["7"], ["8"], ["9"], ["10"], ["11"], ["12"]]}
+import { MedusaContainer } from "@medusajs/framework/types"
+import myWorkflow from "../workflows/hello-world"
+
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const { result } = await myWorkflow(container)
+ .run({
+ input: {
+ name: "John",
+ },
+ })
+
+ console.log(result.message)
+}
+
+export const config = {
+ name: "run-once-a-day",
+ schedule: `0 0 * * *`,
+};
+```
+
+### 4. Test Workflow
+
+To test out your workflow, start your Medusa application:
+
+```bash npm2yarn
+npm run dev
+```
+
+Then, if you added the API route above, send a `GET` request to `/workflow`:
+
+```bash
+curl http://localhost:9000/workflow
+```
+
+You’ll receive the following response:
+
+```json title="Example Response"
+{
+ "message": "Hello John from step two!"
+}
+```
+
+***
+
+## Access Medusa Container in Workflow Steps
+
+A step receives an object as a second parameter with configurations and context-related properties. One of these properties is the `container` property, which is the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) to allow you to resolve framework and commerce tools in your application.
+
+For example, consider you want to implement a workflow that returns the total products in your application. Create the file `src/workflows/product-count.ts` with the following content:
+
+```ts title="src/workflows/product-count.ts" highlights={highlights} collapsibleLines="1-7" expandButtonLabel="Show Imports"
+import {
+ createStep,
+ StepResponse,
+ createWorkflow,
+ WorkflowResponse,
+} from "@medusajs/framework/workflows-sdk"
+
+const getProductCountStep = createStep(
+ "get-product-count",
+ async (_, { container }) => {
+ const productModuleService = container.resolve("product")
+
+ const [, count] = await productModuleService.listAndCountProducts()
+
+ return new StepResponse(count)
+ }
+)
+
+const productCountWorkflow = createWorkflow(
+ "product-count",
+ function () {
+ const count = getProductCountStep()
+
+ return new WorkflowResponse({
+ count,
+ })
+ }
+)
+
+export default productCountWorkflow
+```
+
+In `getProductCountStep`, you use the `container` to resolve the Product Module's main service. Then, you use its `listAndCountProducts` method to retrieve the total count of products and return it in the step's response. You then execute this step in the `productCountWorkflow`.
+
+You can now execute this workflow in a custom API route, scheduled job, or subscriber to get the total count of products.
+
+Find a full list of the registered resources in the Medusa container and their registration key in [this reference](https://docs.medusajs.com/resources/medusa-container-resources/index.html.md). You can use these resources in your custom workflows.
+
+
# Worker Mode of Medusa Instance
In this chapter, you'll learn about the different modes of running a Medusa instance and how to configure the mode.
@@ -3833,72 +3835,6 @@ ADMIN_DISABLED=true
```
-# Next.js Starter Storefront
-
-The Medusa application is made up of a Node.js server and an admin dashboard. The storefront is installed and hosted separately from the Medusa application, giving you the flexibility to choose the frontend tech stack that you and your team are proficient in, and implement unique design systems and user experience.
-
-The Next.js Starter storefront provides rich commerce features and a sleek design. Developers and businesses can use it as-is or build on top of it to tailor it for the business's unique use case, design, and customer experience.
-
-In this chapter, you’ll learn how to install the Next.js Starter storefront separately from the Medusa application. You can also install it while installing the Medusa application as explained in [the installation chapter](https://docs.medusajs.com/learn/installation/index.html.md).
-
-## Install Next.js Starter
-
-### Prerequisites
-
-- [Node.js v20+](https://nodejs.org/en/download)
-- [Git CLI tool](https://git-scm.com/downloads)
-
-If you already have a Medusa application installed with at least one region, you can install the Next.js Starter storefront with the following steps:
-
-1. Clone the [Next.js Starter](https://github.com/medusajs/nextjs-starter-medusa):
-
-```bash
-git clone https://github.com/medusajs/nextjs-starter-medusa my-medusa-storefront
-```
-
-2. Change to the `my-medusa-storefront` directory, install the dependencies, and rename the template environment variable file:
-
-```bash npm2yarn
-cd my-medusa-storefront
-npm install
-mv .env.template .env.local
-```
-
-3. Set the Medusa application's publishable API key in the `NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY` environment variable. You can retrieve the publishable API key in on the Medusa Admin dashboard by going to Settings -> Publishable API Keys
-
-```bash
-NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY=pk_123...
-```
-
-4. While the Medusa application is running, start the Next.js Starter storefront:
-
-```bash npm2yarn
-npm run dev
-```
-
-Your Next.js Starter storefront is now running at `http://localhost:8000`.
-
-***
-
-## Customize Storefront
-
-To customize the storefront, refer to the following directories:
-
-- `src/app`: The storefront’s pages.
-- `src/modules`: The storefront’s components.
-- `src/styles`: The storefront’s styles.
-
-You can learn more about development with Next.js through [their documentation](https://nextjs.org/docs/getting-started).
-
-***
-
-## Configurations and Integrations
-
-The Next.js Starter is compatible with some Medusa integrations out-of-the-box, such as the Stripe provider module. You can also change some of its configurations if necessary.
-
-Refer to the [Next.js Starter reference](https://docs.medusajs.com/resources/nextjs-starter/index.html.md) for more details.
-
-
# Usage Information
At Medusa, we strive to provide the best experience for developers using our platform. For that reason, Medusa collects anonymous and non-sensitive data that provides a global understanding of how users are using Medusa.
@@ -3989,6 +3925,273 @@ MEDUSA_FF_ANALYTICS=false
```
+# Next.js Starter Storefront
+
+The Medusa application is made up of a Node.js server and an admin dashboard. The storefront is installed and hosted separately from the Medusa application, giving you the flexibility to choose the frontend tech stack that you and your team are proficient in, and implement unique design systems and user experience.
+
+The Next.js Starter storefront provides rich commerce features and a sleek design. Developers and businesses can use it as-is or build on top of it to tailor it for the business's unique use case, design, and customer experience.
+
+In this chapter, you’ll learn how to install the Next.js Starter storefront separately from the Medusa application. You can also install it while installing the Medusa application as explained in [the installation chapter](https://docs.medusajs.com/learn/installation/index.html.md).
+
+## Install Next.js Starter
+
+### Prerequisites
+
+- [Node.js v20+](https://nodejs.org/en/download)
+- [Git CLI tool](https://git-scm.com/downloads)
+
+If you already have a Medusa application installed with at least one region, you can install the Next.js Starter storefront with the following steps:
+
+1. Clone the [Next.js Starter](https://github.com/medusajs/nextjs-starter-medusa):
+
+```bash
+git clone https://github.com/medusajs/nextjs-starter-medusa my-medusa-storefront
+```
+
+2. Change to the `my-medusa-storefront` directory, install the dependencies, and rename the template environment variable file:
+
+```bash npm2yarn
+cd my-medusa-storefront
+npm install
+mv .env.template .env.local
+```
+
+3. Set the Medusa application's publishable API key in the `NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY` environment variable. You can retrieve the publishable API key in on the Medusa Admin dashboard by going to Settings -> Publishable API Keys
+
+```bash
+NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY=pk_123...
+```
+
+4. While the Medusa application is running, start the Next.js Starter storefront:
+
+```bash npm2yarn
+npm run dev
+```
+
+Your Next.js Starter storefront is now running at `http://localhost:8000`.
+
+***
+
+## Customize Storefront
+
+To customize the storefront, refer to the following directories:
+
+- `src/app`: The storefront’s pages.
+- `src/modules`: The storefront’s components.
+- `src/styles`: The storefront’s styles.
+
+You can learn more about development with Next.js through [their documentation](https://nextjs.org/docs/getting-started).
+
+***
+
+## Configurations and Integrations
+
+The Next.js Starter is compatible with some Medusa integrations out-of-the-box, such as the Stripe provider module. You can also change some of its configurations if necessary.
+
+Refer to the [Next.js Starter reference](https://docs.medusajs.com/resources/nextjs-starter/index.html.md) for more details.
+
+
+# Write Integration Tests
+
+In this chapter, you'll learn about `medusaIntegrationTestRunner` from Medusa's Testing Framework and how to use it to write integration tests.
+
+### Prerequisites
+
+- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md)
+
+## medusaIntegrationTestRunner Utility
+
+The `medusaIntegrationTestRunner` is from Medusa's Testing Framework and it's used to create integration tests in your Medusa project. It runs a full Medusa application, allowing you test API routes, workflows, or other customizations.
+
+For example:
+
+```ts title="integration-tests/http/test.spec.ts" highlights={highlights}
+import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
+
+medusaIntegrationTestRunner({
+ testSuite: ({ api, getContainer }) => {
+ // TODO write tests...
+ },
+})
+
+jest.setTimeout(60 * 1000)
+```
+
+The `medusaIntegrationTestRunner` function accepts an object as a parameter. The object has a required property `testSuite`.
+
+`testSuite`'s value is a function that defines the tests to run. The function accepts as a parameter an object that has the following properties:
+
+- `api`: a set of utility methods used to send requests to the Medusa application. It has the following methods:
+ - `get`: Send a `GET` request to an API route.
+ - `post`: Send a `POST` request to an API route.
+ - `delete`: Send a `DELETE` request to an API route.
+- `getContainer`: a function that retrieves the Medusa Container. Use the `getContainer().resolve` method to resolve resources from the Medusa Container.
+
+The tests in the `testSuite` function are written using [Jest](https://jestjs.io/).
+
+### Jest Timeout
+
+Since your tests connect to the database and perform actions that require more time than the typical tests, make sure to increase the timeout in your test:
+
+```ts title="integration-tests/http/test.spec.ts"
+// in your test's file
+jest.setTimeout(60 * 1000)
+```
+
+***
+
+### Run Tests
+
+Run the following command to run your tests:
+
+```bash npm2yarn
+npm run test:integration
+```
+
+If you don't have a `test:integration` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md).
+
+This runs your Medusa application and runs the tests available under the `src/integrations/http` directory.
+
+***
+
+## Other Options and Inputs
+
+Refer to [the Test Tooling Reference](https://docs.medusajs.com/resources/test-tools-reference/medusaIntegrationTestRunner/index.html.md) for other available parameter options and inputs of the `testSuite` function.
+
+***
+
+## Database Used in Tests
+
+The `medusaIntegrationTestRunner` function creates a database with a random name before running the tests. Then, it drops that database after all the tests end.
+
+To manage that database, such as changing its name or perform operations on it in your tests, refer to [the Test Tooling Reference](https://docs.medusajs.com/resources/test-tools-reference/medusaIntegrationTestRunner/index.html.md).
+
+***
+
+## Example Integration Tests
+
+The next chapters provide examples of writing integration tests for API routes and workflows.
+
+
+# Write Tests for Modules
+
+In this chapter, you'll learn about `moduleIntegrationTestRunner` from Medusa's Testing Framework and how to use it to write integration tests for a module's main service.
+
+### Prerequisites
+
+- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md)
+
+## moduleIntegrationTestRunner Utility
+
+`moduleIntegrationTestRunner` creates integration tests for a module. The integration tests run on a test Medusa application with only the specified module enabled.
+
+For example, assuming you have a `blog` module, create a test file at `src/modules/blog/__tests__/service.spec.ts`:
+
+```ts title="src/modules/blog/__tests__/service.spec.ts"
+import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
+import { BLOG_MODULE } from ".."
+import BlogModuleService from "../service"
+import Post from "../models/post"
+
+moduleIntegrationTestRunner({
+ moduleName: BLOG_MODULE,
+ moduleModels: [Post],
+ resolve: "./src/modules/blog",
+ testSuite: ({ service }) => {
+ // TODO write tests
+ },
+})
+
+jest.setTimeout(60 * 1000)
+```
+
+The `moduleIntegrationTestRunner` function accepts as a parameter an object with the following properties:
+
+- `moduleName`: The name of the module.
+- `moduleModels`: An array of models in the module. Refer to [this section](#write-tests-for-modules-without-data-models) if your module doesn't have data models.
+- `resolve`: The path to the model.
+- `testSuite`: A function that defines the tests to run.
+
+The `testSuite` function accepts as a parameter an object having the `service` property, which is an instance of the module's main service.
+
+The type argument provided to the `moduleIntegrationTestRunner` function is used as the type of the `service` property.
+
+The tests in the `testSuite` function are written using [Jest](https://jestjs.io/).
+
+***
+
+## Run Tests
+
+Run the following command to run your module integration tests:
+
+```bash npm2yarn
+npm run test:integration:modules
+```
+
+If you don't have a `test:integration:modules` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md).
+
+This runs your Medusa application and runs the tests available in any `__tests__` directory under the `src/modules` directory.
+
+***
+
+## Pass Module Options
+
+If your module accepts options, you can set them using the `moduleOptions` property of the `moduleIntegrationTestRunner`'s parameter.
+
+For example:
+
+```ts
+import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
+import BlogModuleService from "../service"
+
+moduleIntegrationTestRunner({
+ moduleOptions: {
+ apiKey: "123",
+ },
+ // ...
+})
+```
+
+***
+
+## Write Tests for Modules without Data Models
+
+If your module doesn't have a data model, pass a dummy model in the `moduleModels` property.
+
+For example:
+
+```ts
+import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
+import BlogModuleService from "../service"
+import { model } from "@medusajs/framework/utils"
+
+const DummyModel = model.define("dummy_model", {
+ id: model.id().primaryKey(),
+})
+
+moduleIntegrationTestRunner({
+ moduleModels: [DummyModel],
+ // ...
+})
+
+jest.setTimeout(60 * 1000)
+```
+
+***
+
+### Other Options and Inputs
+
+Refer to [the Test Tooling Reference](https://docs.medusajs.com/resources/test-tools-reference/moduleIntegrationTestRunner/index.html.md) for other available parameter options and inputs of the `testSuite` function.
+
+***
+
+## Database Used in Tests
+
+The `moduleIntegrationTestRunner` function creates a database with a random name before running the tests. Then, it drops that database after all the tests end.
+
+To manage that database, such as changing its name or perform operations on it in your tests, refer to [the Test Tooling Reference](https://docs.medusajs.com/resources/test-tools-reference/moduleIntegrationTestRunner/index.html.md).
+
+
# Guide: Create Brand API Route
In the previous two chapters, you created a [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) that added the concepts of brands to your application, then created a [workflow to create a brand](https://docs.medusajs.com/learn/customization/custom-features/workflow/index.html.md). In this chapter, you'll expose an API route that allows admin users to create a brand using the workflow from the previous chapter.
@@ -4197,144 +4400,6 @@ Now that you have brands in your Medusa application, you want to associate a bra
In the next chapters, you'll learn how to build associations between data models defined in different modules.
-# Guide: Create Brand Workflow
-
-This chapter builds on the work from the [previous chapter](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) where you created a Brand Module.
-
-After adding custom modules to your application, you build commerce features around them using workflows. A workflow is a series of queries and actions, called steps, that complete a task spanning across modules. You construct a workflow similar to a regular function, but it's a special function that allows you to define roll-back logic, retry configurations, and more advanced features.
-
-The workflow you'll create in this chapter will use the Brand Module's service to implement the feature of creating a brand. In the [next chapter](https://docs.medusajs.com/learn/customization/custom-features/api-route/index.html.md), you'll expose an API route that allows admin users to create a brand, and you'll use this workflow in the route's implementation.
-
-Learn more about workflows in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md).
-
-### Prerequisites
-
-- [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md)
-
-***
-
-## 1. Create createBrandStep
-
-A workflow consists of a series of steps, each step created in a TypeScript or JavaScript file under the `src/workflows` directory. A step is defined using `createStep` from the Workflows SDK
-
-The workflow you're creating in this guide has one step to create the brand. So, create the file `src/workflows/create-brand.ts` with the following content:
-
-
-
-```ts title="src/workflows/create-brand.ts"
-import {
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { BRAND_MODULE } from "../modules/brand"
-import BrandModuleService from "../modules/brand/service"
-
-export type CreateBrandStepInput = {
- name: string
-}
-
-export const createBrandStep = createStep(
- "create-brand-step",
- async (input: CreateBrandStepInput, { container }) => {
- const brandModuleService: BrandModuleService = container.resolve(
- BRAND_MODULE
- )
-
- const brand = await brandModuleService.createBrands(input)
-
- return new StepResponse(brand, brand.id)
- }
-)
-```
-
-You create a `createBrandStep` using the `createStep` function. It accepts the step's unique name as a first parameter, and the step's function as a second parameter.
-
-The step function receives two parameters: input passed to the step when it's invoked, and an object of general context and configurations. This object has a `container` property, which is the Medusa container.
-
-The [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) is a registry of framework and commerce tools accessible in your customizations, such as a workflow's step. The Medusa application registers the services of core and custom modules in the container, allowing you to resolve and use them.
-
-So, In the step function, you use the Medusa container to resolve the Brand Module's service and use its generated `createBrands` method, which accepts an object of brands to create.
-
-Learn more about the generated `create` method's usage in [this reference](https://docs.medusajs.com/resources/service-factory-reference/methods/create/index.html.md).
-
-A step must return an instance of `StepResponse`. Its first parameter is the data returned by the step, and the second is the data passed to the compensation function, which you'll learn about next.
-
-### Add Compensation Function to Step
-
-You define for each step a compensation function that's executed when an error occurs in the workflow. The compensation function defines the logic to roll-back the changes made by the step. This ensures your data remains consistent if an error occurs, which is especially useful when you integrate third-party services.
-
-Learn more about the compensation function in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/compensation-function/index.html.md).
-
-To add a compensation function to the `createBrandStep`, pass it as a third parameter to `createStep`:
-
-```ts title="src/workflows/create-brand.ts"
-export const createBrandStep = createStep(
- // ...
- async (id: string, { container }) => {
- const brandModuleService: BrandModuleService = container.resolve(
- BRAND_MODULE
- )
-
- await brandModuleService.deleteBrands(id)
- }
-)
-```
-
-The compensation function's first parameter is the brand's ID which you passed as a second parameter to the step function's returned `StepResponse`. It also accepts a context object with a `container` property as a second parameter, similar to the step function.
-
-In the compensation function, you resolve the Brand Module's service from the Medusa container, then use its generated `deleteBrands` method to delete the brand created by the step. This method accepts the ID of the brand to delete.
-
-Learn more about the generated `delete` method's usage in [this reference](https://docs.medusajs.com/resources/service-factory-reference/methods/delete/index.html.md).
-
-So, if an error occurs during the workflow's execution, the brand that was created by the step is deleted to maintain data consistency.
-
-***
-
-## 2. Create createBrandWorkflow
-
-You can now create the workflow that runs the `createBrandStep`. A workflow is created in a TypeScript or JavaScript file under the `src/workflows` directory. In the file, you use `createWorkflow` from the Workflows SDK to create the workflow.
-
-Add the following content in the same `src/workflows/create-brand.ts` file:
-
-```ts title="src/workflows/create-brand.ts"
-// other imports...
-import {
- // ...
- createWorkflow,
- WorkflowResponse,
-} from "@medusajs/framework/workflows-sdk"
-
-// ...
-
-type CreateBrandWorkflowInput = {
- name: string
-}
-
-export const createBrandWorkflow = createWorkflow(
- "create-brand",
- (input: CreateBrandWorkflowInput) => {
- const brand = createBrandStep(input)
-
- return new WorkflowResponse(brand)
- }
-)
-```
-
-You create the `createBrandWorkflow` using the `createWorkflow` function. This function accepts two parameters: the workflow's unique name, and the workflow's constructor function holding the workflow's implementation.
-
-The constructor function accepts the workflow's input as a parameter. In the function, you invoke the `createBrandStep` you created in the previous step to create a brand.
-
-A workflow must return an instance of `WorkflowResponse`. It accepts as a parameter the data to return to the workflow's executor.
-
-***
-
-## Next Steps: Expose Create Brand API Route
-
-You now have a `createBrandWorkflow` that you can execute to create a brand.
-
-In the next chapter, you'll add an API route that allows admin users to create a brand. You'll learn how to create the API route, and execute in it the workflow you implemented in this chapter.
-
-
# Guide: Implement Brand Module
In this chapter, you'll build a Brand Module that adds a `brand` table to the database and provides data-management features for it.
@@ -4491,6 +4556,562 @@ The Brand Module now creates a `brand` table in the database and provides a clas
In the next chapter, you'll implement the functionality to create a brand in a workflow. You'll then use that workflow in a later chapter to expose an endpoint that allows admin users to create a brand.
+# Guide: Create Brand Workflow
+
+This chapter builds on the work from the [previous chapter](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) where you created a Brand Module.
+
+After adding custom modules to your application, you build commerce features around them using workflows. A workflow is a series of queries and actions, called steps, that complete a task spanning across modules. You construct a workflow similar to a regular function, but it's a special function that allows you to define roll-back logic, retry configurations, and more advanced features.
+
+The workflow you'll create in this chapter will use the Brand Module's service to implement the feature of creating a brand. In the [next chapter](https://docs.medusajs.com/learn/customization/custom-features/api-route/index.html.md), you'll expose an API route that allows admin users to create a brand, and you'll use this workflow in the route's implementation.
+
+Learn more about workflows in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md).
+
+### Prerequisites
+
+- [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md)
+
+***
+
+## 1. Create createBrandStep
+
+A workflow consists of a series of steps, each step created in a TypeScript or JavaScript file under the `src/workflows` directory. A step is defined using `createStep` from the Workflows SDK
+
+The workflow you're creating in this guide has one step to create the brand. So, create the file `src/workflows/create-brand.ts` with the following content:
+
+
+
+```ts title="src/workflows/create-brand.ts"
+import {
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { BRAND_MODULE } from "../modules/brand"
+import BrandModuleService from "../modules/brand/service"
+
+export type CreateBrandStepInput = {
+ name: string
+}
+
+export const createBrandStep = createStep(
+ "create-brand-step",
+ async (input: CreateBrandStepInput, { container }) => {
+ const brandModuleService: BrandModuleService = container.resolve(
+ BRAND_MODULE
+ )
+
+ const brand = await brandModuleService.createBrands(input)
+
+ return new StepResponse(brand, brand.id)
+ }
+)
+```
+
+You create a `createBrandStep` using the `createStep` function. It accepts the step's unique name as a first parameter, and the step's function as a second parameter.
+
+The step function receives two parameters: input passed to the step when it's invoked, and an object of general context and configurations. This object has a `container` property, which is the Medusa container.
+
+The [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) is a registry of framework and commerce tools accessible in your customizations, such as a workflow's step. The Medusa application registers the services of core and custom modules in the container, allowing you to resolve and use them.
+
+So, In the step function, you use the Medusa container to resolve the Brand Module's service and use its generated `createBrands` method, which accepts an object of brands to create.
+
+Learn more about the generated `create` method's usage in [this reference](https://docs.medusajs.com/resources/service-factory-reference/methods/create/index.html.md).
+
+A step must return an instance of `StepResponse`. Its first parameter is the data returned by the step, and the second is the data passed to the compensation function, which you'll learn about next.
+
+### Add Compensation Function to Step
+
+You define for each step a compensation function that's executed when an error occurs in the workflow. The compensation function defines the logic to roll-back the changes made by the step. This ensures your data remains consistent if an error occurs, which is especially useful when you integrate third-party services.
+
+Learn more about the compensation function in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/compensation-function/index.html.md).
+
+To add a compensation function to the `createBrandStep`, pass it as a third parameter to `createStep`:
+
+```ts title="src/workflows/create-brand.ts"
+export const createBrandStep = createStep(
+ // ...
+ async (id: string, { container }) => {
+ const brandModuleService: BrandModuleService = container.resolve(
+ BRAND_MODULE
+ )
+
+ await brandModuleService.deleteBrands(id)
+ }
+)
+```
+
+The compensation function's first parameter is the brand's ID which you passed as a second parameter to the step function's returned `StepResponse`. It also accepts a context object with a `container` property as a second parameter, similar to the step function.
+
+In the compensation function, you resolve the Brand Module's service from the Medusa container, then use its generated `deleteBrands` method to delete the brand created by the step. This method accepts the ID of the brand to delete.
+
+Learn more about the generated `delete` method's usage in [this reference](https://docs.medusajs.com/resources/service-factory-reference/methods/delete/index.html.md).
+
+So, if an error occurs during the workflow's execution, the brand that was created by the step is deleted to maintain data consistency.
+
+***
+
+## 2. Create createBrandWorkflow
+
+You can now create the workflow that runs the `createBrandStep`. A workflow is created in a TypeScript or JavaScript file under the `src/workflows` directory. In the file, you use `createWorkflow` from the Workflows SDK to create the workflow.
+
+Add the following content in the same `src/workflows/create-brand.ts` file:
+
+```ts title="src/workflows/create-brand.ts"
+// other imports...
+import {
+ // ...
+ createWorkflow,
+ WorkflowResponse,
+} from "@medusajs/framework/workflows-sdk"
+
+// ...
+
+type CreateBrandWorkflowInput = {
+ name: string
+}
+
+export const createBrandWorkflow = createWorkflow(
+ "create-brand",
+ (input: CreateBrandWorkflowInput) => {
+ const brand = createBrandStep(input)
+
+ return new WorkflowResponse(brand)
+ }
+)
+```
+
+You create the `createBrandWorkflow` using the `createWorkflow` function. This function accepts two parameters: the workflow's unique name, and the workflow's constructor function holding the workflow's implementation.
+
+The constructor function accepts the workflow's input as a parameter. In the function, you invoke the `createBrandStep` you created in the previous step to create a brand.
+
+A workflow must return an instance of `WorkflowResponse`. It accepts as a parameter the data to return to the workflow's executor.
+
+***
+
+## Next Steps: Expose Create Brand API Route
+
+You now have a `createBrandWorkflow` that you can execute to create a brand.
+
+In the next chapter, you'll add an API route that allows admin users to create a brand. You'll learn how to create the API route, and execute in it the workflow you implemented in this chapter.
+
+
+# Guide: Define Module Link Between Brand and Product
+
+In this chapter, you'll learn how to define a module link between a brand defined in the [custom Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md), and a product defined in the [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md) that's available in your Medusa application out-of-the-box.
+
+Modules are [isolated](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md) from other resources, ensuring that they're integrated into the Medusa application without side effects. However, you may need to associate data models of different modules, or you're trying to extend data models from commerce modules with custom properties. To do that, you define module links.
+
+A module link forms an association between two data models of different modules while maintaining module isolation. You can then manage and query linked records of the data models using Medusa's Modules SDK.
+
+In this chapter, you'll define a module link between the `Brand` data model of the Brand Module, and the `Product` data model of the Product Module. In later chapters, you'll manage and retrieve linked product and brand records.
+
+Learn more about module links in [this chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md).
+
+### Prerequisites
+
+- [Brand Module having a Brand data model](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md)
+
+## 1. Define Link
+
+Links are defined in a TypeScript or JavaScript file under the `src/links` directory. The file defines and exports the link using `defineLink` from the Modules SDK.
+
+So, to define a link between the `Product` and `Brand` models, create the file `src/links/product-brand.ts` with the following content:
+
+
+
+```ts title="src/links/product-brand.ts" highlights={highlights}
+import BrandModule from "../modules/brand"
+import ProductModule from "@medusajs/medusa/product"
+import { defineLink } from "@medusajs/framework/utils"
+
+export default defineLink(
+ {
+ linkable: ProductModule.linkable.product,
+ isList: true,
+ },
+ BrandModule.linkable.brand
+)
+```
+
+You import each module's definition object from the `index.ts` file of the module's directory. Each module object has a special `linkable` property that holds the data models' link configurations.
+
+The `defineLink` function accepts two parameters of the same type, which is either:
+
+- The data model's link configuration, which you access from the Module's `linkable` property;
+- Or an object that has two properties:
+ - `linkable`: the data model's link configuration, which you access from the Module's `linkable` property.
+ - `isList`: A boolean indicating whether many records of the data model can be linked to the other model.
+
+So, in the above code snippet, you define a link between the `Product` and `Brand` data models. Since a brand can be associated with multiple products, you enable `isList` in the `Product` model's object.
+
+***
+
+## 2. Sync the Link to the Database
+
+A module link is represented in the database as a table that stores the IDs of linked records. So, after defining the link, run the following command to create the module link's table in the database:
+
+```bash
+npx medusa db:migrate
+```
+
+This command reflects migrations on the database and syncs module links, which creates a table for the `product-brand` link.
+
+You can also run the `npx medusa db:sync-links` to just sync module links without running migrations.
+
+***
+
+## Next Steps: Extend Create Product Flow
+
+In the next chapter, you'll extend Medusa's workflow and API route that create a product to allow associating a brand with a product. You'll also learn how to link brand and product records.
+
+
+# Guide: Extend Create Product Flow
+
+After linking the [custom Brand data model](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) and Medusa's [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md) in the [previous chapter](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md), 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](https://docs.medusajs.com/api/admin#products_postproducts), accept an `additional_data` request body parameter. This parameter can hold custom data that's passed to the [hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md) 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](https://docs.medusajs.com/resources/references/medusa-workflows/createProductsWorkflow#productsCreated/index.html.md) hook of the [createProductsWorkflow](https://docs.medusajs.com/resources/references/medusa-workflows/createProductsWorkflow/index.html.md), 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`.
+
+To learn more about the `additional_data` property and the API routes that accept additional data, refer to [this chapter](https://docs.medusajs.com/learn/fundamentals/api-routes/additional-data/index.html.md).
+
+### Prerequisites
+
+- [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md)
+- [Defined link between the Brand and Product data models.](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md)
+
+***
+
+## 1. Consume the productsCreated 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.
+
+Learn more about the workflow hooks in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md).
+
+The [createProductsWorkflow](https://docs.medusajs.com/resources/references/medusa-workflows/createProductsWorkflow/index.html.md) used in the [Create Product API route](https://docs.medusajs.com/api/admin#products_postproducts) 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:
+
+
+
+```ts title="src/workflows/hooks/created-product.ts" highlights={hook1Highlights}
+import { createProductsWorkflow } from "@medusajs/medusa/core-flows"
+import { StepResponse } from "@medusajs/framework/workflows-sdk"
+import { Modules } from "@medusajs/framework/utils"
+import { LinkDefinition } from "@medusajs/framework/types"
+import { BRAND_MODULE } from "../../modules/brand"
+import BrandModuleService from "../../modules/brand/service"
+
+createProductsWorkflow.hooks.productsCreated(
+ (async ({ products, additional_data }, { container }) => {
+ if (!additional_data?.brand_id) {
+ return new StepResponse([], [])
+ }
+
+ const brandModuleService: BrandModuleService = container.resolve(
+ BRAND_MODULE
+ )
+ // if the brand doesn't exist, an error is thrown.
+ await brandModuleService.retrieveBrand(additional_data.brand_id as string)
+
+ // TODO link brand to product
+ })
+)
+```
+
+Workflows have a special `hooks` property to access its hooks and consume them. Each hook, such as `productsCreated`, accepts a step function as a parameter. The step function accepts the following parameters:
+
+1. An object having an `additional_data` property, which is the custom data passed in the request body under `additional_data`. The object will also have properties passed from the workflow to the hook, which in this case is the `products` property that holds an array of the created products.
+2. An object of properties related to the step's context. It has a `container` property whose value is the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) 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 Link, which is a class from the Modules SDK that provides methods to manage linked records.
+
+Learn more about Link in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/link/index.html.md).
+
+To use Link in the `productsCreated` hook, replace the `TODO` with the following:
+
+```ts title="src/workflows/hooks/created-product.ts" highlights={hook2Highlights}
+const link = container.resolve("link")
+const logger = container.resolve("logger")
+
+const links: LinkDefinition[] = []
+
+for (const product of products) {
+ links.push({
+ [Modules.PRODUCT]: {
+ product_id: product.id,
+ },
+ [BRAND_MODULE]: {
+ brand_id: additional_data.brand_id,
+ },
+ })
+}
+
+await link.create(links)
+
+logger.info("Linked brand to products")
+
+return new StepResponse(links, links)
+```
+
+You resolve 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 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`:
+
+```ts title="src/workflows/hooks/created-product.ts"
+createProductsWorkflow.hooks.productsCreated(
+ // ...
+ (async (links, { container }) => {
+ if (!links?.length) {
+ return
+ }
+
+ const link = container.resolve("link")
+
+ await link.dismiss(links)
+ })
+)
+```
+
+In the compensation function, if the `links` parameter isn't empty, you resolve 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 `productsCreated` 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:
+
+
+
+```ts title="src/api/middlewares.ts"
+import { defineMiddlewares } from "@medusajs/framework/http"
+import { z } from "zod"
+
+// ...
+
+export default defineMiddlewares({
+ routes: [
+ // ...
+ {
+ matcher: "/admin/products",
+ method: ["POST"],
+ additionalDataValidator: {
+ brand_id: z.string().optional(),
+ },
+ },
+ ],
+})
+```
+
+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](https://zod.dev/).
+
+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`:
+
+```bash
+curl -X POST 'http://localhost:9000/auth/user/emailpass' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "email": "admin@medusa-test.com",
+ "password": "supersecret"
+}'
+```
+
+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:
+
+```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_id": "{brand_id}"
+ }
+}'
+```
+
+Make sure to replace `{token}` with the token you received from the previous request, `shipping_profile_id` with the ID of a shipping profile in your application, and `{brand_id}` with the ID of a brand in your application. You can retrieve the ID of a shipping profile either from the Medusa Admin, or the [List Shipping Profiles API route](https://docs.medusajs.com/api/admin#shipping-profiles_getshippingprofiles).
+
+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.
+
+
+# Guide: Query Product's Brands
+
+In the previous chapters, you [defined a link](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md) between the [custom Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) and Medusa's [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md), then [extended the create-product flow](https://docs.medusajs.com/learn/customization/extend-features/extend-create-product/index.html.md) to link a product to a brand.
+
+In this chapter, you'll learn how to retrieve a product's brand (and vice-versa) in two ways: Using Medusa's existing API route, or in customizations, such as a custom API route.
+
+### Prerequisites
+
+- [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md)
+- [Defined link between the Brand and Product data models.](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md)
+
+***
+
+## Approach 1: Retrieve Brands in Existing API Routes
+
+Medusa's existing API routes accept a `fields` query parameter that allows you to specify the fields and relations of a model to retrieve. So, when you send a request to the [List Products](https://docs.medusajs.com/api/admin#products_getproducts), [Get Product](https://docs.medusajs.com/api/admin#products_getproductsid), or any product-related store or admin routes that accept a `fields` query parameter, you can specify in this parameter to return the product's brands.
+
+Learn more about selecting fields and relations in the [API Reference](https://docs.medusajs.com/api/admin#select-fields-and-relations).
+
+For example, send the following request to retrieve the list of products with their brands:
+
+```bash
+curl 'http://localhost:9000/admin/products?fields=+brand.*' \
+--header 'Authorization: Bearer {token}'
+```
+
+Make sure to replace `{token}` with your admin user's authentication token. Learn how to retrieve it in the [API reference](https://docs.medusajs.com/api/store#authentication).
+
+Any product that is linked to a brand will have a `brand` property in its object:
+
+```json title="Example Product Object"
+{
+ "id": "prod_123",
+ // ...
+ "brand": {
+ "id": "01JEB44M61BRM3ARM2RRMK7GJF",
+ "name": "Acme",
+ "created_at": "2024-12-05T09:59:08.737Z",
+ "updated_at": "2024-12-05T09:59:08.737Z",
+ "deleted_at": null
+ }
+}
+```
+
+By using the `fields` query parameter, you don't have to re-create existing API routes to get custom data models that you linked to core data models.
+
+***
+
+## Approach 2: Use Query to Retrieve Linked Records
+
+You can also retrieve linked records using Query. Query allows you to retrieve data across modules with filters, pagination, and more. You can resolve Query from the Medusa container and use it in your API route or workflow.
+
+Learn more about Query in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md).
+
+For example, you can create an API route that retrieves brands and their products. If you followed the [Create Brands API route chapter](https://docs.medusajs.com/learn/customization/custom-features/api-route/index.html.md), you'll have the file `src/api/admin/brands/route.ts` with a `POST` API route. Add a new `GET` function to the same file:
+
+```ts title="src/api/admin/brands/route.ts" highlights={highlights}
+// other imports...
+import {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ const query = req.scope.resolve("query")
+
+ const { data: brands } = await query.graph({
+ entity: "brand",
+ fields: ["*", "products.*"],
+ })
+
+ res.json({ brands })
+}
+```
+
+This adds a `GET` API route at `/admin/brands`. In the API route, you resolve Query from the Medusa container. Query has a `graph` method that runs a query to retrieve data. It accepts an object having the following properties:
+
+- `entity`: The data model's name as specified in the first parameter of `model.define`.
+- `fields`: An array of properties and relations to retrieve. You can pass:
+ - A property's name, such as `id`, or `*` for all properties.
+ - A relation or linked model's name, such as `products` (use the plural name since brands are linked to list of products). You suffix the name with `.*` to retrieve all its properties.
+
+`graph` returns an object having a `data` property, which is the retrieved brands. You return the brands in the response.
+
+### Test it Out
+
+To test the API route out, send a `GET` request to `/admin/brands`:
+
+```bash
+curl 'http://localhost:9000/admin/brands' \
+-H 'Authorization: Bearer {token}'
+```
+
+Make sure to replace `{token}` with your admin user's authentication token. Learn how to retrieve it in the [API reference](https://docs.medusajs.com/api/store#authentication).
+
+This returns the brands in your store with their linked products. For example:
+
+```json title="Example Response"
+{
+ "brands": [
+ {
+ "id": "123",
+ // ...
+ "products": [
+ {
+ "id": "prod_123",
+ // ...
+ }
+ ]
+ }
+ ]
+}
+```
+
+***
+
+## Summary
+
+By following the examples of the previous chapters, you:
+
+- Defined a link between the Brand and Product modules's data models, allowing you to associate a product with a brand.
+- Extended the create-product workflow and route to allow setting the product's brand while creating the product.
+- Queried a product's brand, and vice versa.
+
+***
+
+## Next Steps: Customize Medusa Admin
+
+Clients, such as the Medusa Admin dashboard, can now use brand-related features, such as creating a brand or setting the brand of a product.
+
+In the next chapters, you'll learn how to customize the Medusa Admin to show a product's brand on its details page, and to show a new page with the list of brands in your store.
+
+
# Create Brands UI Route in Admin
In this chapter, you'll add a UI route to the admin dashboard that shows all [brands](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) in a new page. You'll retrieve the brands from the server and display them in a table with pagination.
@@ -5017,165 +5638,6 @@ The [Admin Components guides](https://docs.medusajs.com/resources/admin-componen
In the next chapter, you'll add a UI route that displays the list of brands in your application and allows admin users.
-# Guide: Integrate CMS Brand System
-
-In the previous chapters, you've created a [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) that adds brands to your application. In this chapter, you'll integrate a dummy Content-Management System (CMS) in a new module. The module's service will provide methods to retrieve and manage brands in the CMS. You'll later use this service to sync data from and to the CMS.
-
-Learn more about modules in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
-
-## 1. Create Module Directory
-
-You'll integrate the third-party system in a new CMS Module. So, create the directory `src/modules/cms` that will hold the module's resources.
-
-
-
-***
-
-## 2. Create Module Service
-
-Next, you'll create the module's service. It will provide methods to connect and perform actions with the third-party system.
-
-Create the CMS Module's service at `src/modules/cms/service.ts` with the following content:
-
-
-
-```ts title="src/modules/cms/service.ts" highlights={serviceHighlights}
-import { Logger, ConfigModule } from "@medusajs/framework/types"
-
-export type ModuleOptions = {
- apiKey: string
-}
-
-type InjectedDependencies = {
- logger: Logger
- configModule: ConfigModule
-}
-
-class CmsModuleService {
- private options_: ModuleOptions
- private logger_: Logger
-
- constructor({ logger }: InjectedDependencies, options: ModuleOptions) {
- this.logger_ = logger
- this.options_ = options
-
- // TODO initialize SDK
- }
-}
-
-export default CmsModuleService
-```
-
-You create a `CmsModuleService` that will hold the methods to connect to the third-party CMS. A service's constructor accepts two parameters:
-
-1. The module's container. Since a module is [isolated](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md), it has a [local container](https://docs.medusajs.com/learn/fundamentals/modules/container/index.html.md) different than the Medusa container you use in other customizations. This container holds framework tools like the [Logger utility](https://docs.medusajs.com/learn/debugging-and-testing/logging/index.html.md) and resources within the module.
-2. Options passed to the module when it's later added in Medusa's configurations. These options are useful to pass secret keys or configurations that ensure your module is re-usable across applications. For the CMS Module, you accept the API key to connect to the dummy CMS as an option.
-
-When integrating a third-party system that has a Node.js SDK or client, you can initialize that client in the constructor to be used in the service's methods.
-
-### Integration Methods
-
-Next, you'll add methods that simulate sending requests to a third-party CMS. You'll use these methods later to sync brands from and to the CMS.
-
-Add the following methods in the `CmsModuleService`:
-
-```ts title="src/modules/cms/service.ts" highlights={methodsHighlights}
-export class CmsModuleService {
- // ...
-
- // a dummy method to simulate sending a request,
- // in a realistic scenario, you'd use an SDK, fetch, or axios clients
- private async sendRequest(url: string, method: string, data?: any) {
- this.logger_.info(`Sending a ${method} request to ${url}.`)
- this.logger_.info(`Request Data: ${JSON.stringify(data, null, 2)}`)
- this.logger_.info(`API Key: ${JSON.stringify(this.options_.apiKey, null, 2)}`)
- }
-
- async createBrand(brand: Record) {
- await this.sendRequest("/brands", "POST", brand)
- }
-
- async deleteBrand(id: string) {
- await this.sendRequest(`/brands/${id}`, "DELETE")
- }
-
- async retrieveBrands(): Promise[]> {
- await this.sendRequest("/brands", "GET")
-
- return []
- }
-}
-```
-
-The `sendRequest` method sends requests to the third-party CMS. Since this guide isn't using a real CMS, it only simulates the sending by logging messages in the terminal.
-
-You also add three methods that use the `sendRequest` method:
-
-- `createBrand` that creates a brand in the third-party system.
-- `deleteBrand` that deletes the brand in the third-party system.
-- `retrieveBrands` to retrieve a brand from the third-party system.
-
-***
-
-## 3. Export Module Definition
-
-After creating the module's service, you'll export the module definition indicating the module's name and service.
-
-Create the file `src/modules/cms/index.ts` with the following content:
-
-
-
-```ts title="src/modules/cms/index.ts"
-import { Module } from "@medusajs/framework/utils"
-import CmsModuleService from "./service"
-
-export const CMS_MODULE = "cms"
-
-export default Module(CMS_MODULE, {
- service: CmsModuleService,
-})
-```
-
-You use `Module` from the Modules SDK to export the module's defintion, indicating that the module's name is `cms` and its service is `CmsModuleService`.
-
-***
-
-## 4. Add Module to Medusa's Configurations
-
-Finally, add the module to the Medusa configurations at `medusa-config.ts`:
-
-```ts title="medusa-config.ts"
-module.exports = defineConfig({
- // ...
- modules: [
- // ...
- {
- resolve: "./src/modules/cms",
- options: {
- apiKey: process.env.CMS_API_KEY,
- },
- },
- ],
-})
-```
-
-The object passed in `modules` accept an `options` property, whose value is an object of options to pass to the module. These are the options you receive in the `CmsModuleService`'s constructor.
-
-You can add the `CMS_API_KEY` environment variable to `.env`:
-
-```bash
-CMS_API_KEY=123
-```
-
-***
-
-## Next Steps: Sync Brand From Medusa to CMS
-
-You can now use the CMS Module's service to perform actions on the third-party CMS.
-
-In the next chapter, you'll learn how to emit an event when a brand is created, then handle that event to sync the brand from Medusa to the third-party service.
-
-
# Guide: Sync Brands from Medusa to CMS
In the [previous chapter](https://docs.medusajs.com/learn/customization/integrate-systems/service/index.html.md), you created a CMS Module that integrates a dummy third-party system. You can now perform actions using that module within your custom flows.
@@ -5448,6 +5910,165 @@ info: API Key: "123"
You can also automate syncing data from a third-party system to Medusa at a regular interval. In the next chapter, you'll learn how to sync brands from the third-party CMS to Medusa once a day.
+# Guide: Integrate CMS Brand System
+
+In the previous chapters, you've created a [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) that adds brands to your application. In this chapter, you'll integrate a dummy Content-Management System (CMS) in a new module. The module's service will provide methods to retrieve and manage brands in the CMS. You'll later use this service to sync data from and to the CMS.
+
+Learn more about modules in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
+
+## 1. Create Module Directory
+
+You'll integrate the third-party system in a new CMS Module. So, create the directory `src/modules/cms` that will hold the module's resources.
+
+
+
+***
+
+## 2. Create Module Service
+
+Next, you'll create the module's service. It will provide methods to connect and perform actions with the third-party system.
+
+Create the CMS Module's service at `src/modules/cms/service.ts` with the following content:
+
+
+
+```ts title="src/modules/cms/service.ts" highlights={serviceHighlights}
+import { Logger, ConfigModule } from "@medusajs/framework/types"
+
+export type ModuleOptions = {
+ apiKey: string
+}
+
+type InjectedDependencies = {
+ logger: Logger
+ configModule: ConfigModule
+}
+
+class CmsModuleService {
+ private options_: ModuleOptions
+ private logger_: Logger
+
+ constructor({ logger }: InjectedDependencies, options: ModuleOptions) {
+ this.logger_ = logger
+ this.options_ = options
+
+ // TODO initialize SDK
+ }
+}
+
+export default CmsModuleService
+```
+
+You create a `CmsModuleService` that will hold the methods to connect to the third-party CMS. A service's constructor accepts two parameters:
+
+1. The module's container. Since a module is [isolated](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md), it has a [local container](https://docs.medusajs.com/learn/fundamentals/modules/container/index.html.md) different than the Medusa container you use in other customizations. This container holds framework tools like the [Logger utility](https://docs.medusajs.com/learn/debugging-and-testing/logging/index.html.md) and resources within the module.
+2. Options passed to the module when it's later added in Medusa's configurations. These options are useful to pass secret keys or configurations that ensure your module is re-usable across applications. For the CMS Module, you accept the API key to connect to the dummy CMS as an option.
+
+When integrating a third-party system that has a Node.js SDK or client, you can initialize that client in the constructor to be used in the service's methods.
+
+### Integration Methods
+
+Next, you'll add methods that simulate sending requests to a third-party CMS. You'll use these methods later to sync brands from and to the CMS.
+
+Add the following methods in the `CmsModuleService`:
+
+```ts title="src/modules/cms/service.ts" highlights={methodsHighlights}
+export class CmsModuleService {
+ // ...
+
+ // a dummy method to simulate sending a request,
+ // in a realistic scenario, you'd use an SDK, fetch, or axios clients
+ private async sendRequest(url: string, method: string, data?: any) {
+ this.logger_.info(`Sending a ${method} request to ${url}.`)
+ this.logger_.info(`Request Data: ${JSON.stringify(data, null, 2)}`)
+ this.logger_.info(`API Key: ${JSON.stringify(this.options_.apiKey, null, 2)}`)
+ }
+
+ async createBrand(brand: Record) {
+ await this.sendRequest("/brands", "POST", brand)
+ }
+
+ async deleteBrand(id: string) {
+ await this.sendRequest(`/brands/${id}`, "DELETE")
+ }
+
+ async retrieveBrands(): Promise[]> {
+ await this.sendRequest("/brands", "GET")
+
+ return []
+ }
+}
+```
+
+The `sendRequest` method sends requests to the third-party CMS. Since this guide isn't using a real CMS, it only simulates the sending by logging messages in the terminal.
+
+You also add three methods that use the `sendRequest` method:
+
+- `createBrand` that creates a brand in the third-party system.
+- `deleteBrand` that deletes the brand in the third-party system.
+- `retrieveBrands` to retrieve a brand from the third-party system.
+
+***
+
+## 3. Export Module Definition
+
+After creating the module's service, you'll export the module definition indicating the module's name and service.
+
+Create the file `src/modules/cms/index.ts` with the following content:
+
+
+
+```ts title="src/modules/cms/index.ts"
+import { Module } from "@medusajs/framework/utils"
+import CmsModuleService from "./service"
+
+export const CMS_MODULE = "cms"
+
+export default Module(CMS_MODULE, {
+ service: CmsModuleService,
+})
+```
+
+You use `Module` from the Modules SDK to export the module's defintion, indicating that the module's name is `cms` and its service is `CmsModuleService`.
+
+***
+
+## 4. Add Module to Medusa's Configurations
+
+Finally, add the module to the Medusa configurations at `medusa-config.ts`:
+
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ // ...
+ modules: [
+ // ...
+ {
+ resolve: "./src/modules/cms",
+ options: {
+ apiKey: process.env.CMS_API_KEY,
+ },
+ },
+ ],
+})
+```
+
+The object passed in `modules` accept an `options` property, whose value is an object of options to pass to the module. These are the options you receive in the `CmsModuleService`'s constructor.
+
+You can add the `CMS_API_KEY` environment variable to `.env`:
+
+```bash
+CMS_API_KEY=123
+```
+
+***
+
+## Next Steps: Sync Brand From Medusa to CMS
+
+You can now use the CMS Module's service to perform actions on the third-party CMS.
+
+In the next chapter, you'll learn how to emit an event when a brand is created, then handle that event to sync the brand from Medusa to the third-party service.
+
+
# Guide: Schedule Syncing Brands from CMS
In the previous chapters, you've [integrated a third-party CMS](https://docs.medusajs.com/learn/customization/integrate-systems/service/index.html.md) and implemented the logic to [sync created brands](https://docs.medusajs.com/learn/customization/integrate-systems/handle-event/index.html.md) from Medusa to the CMS.
@@ -5757,212 +6378,6 @@ By following the previous chapters, you utilized Medusa's framework and orchestr
With Medusa, you can integrate any service from your commerce ecosystem with ease. You don't have to set up separate applications to manage your different customizations, or worry about data inconsistency across systems. Your efforts only go into implementing the business logic that ties your systems together.
-# Guide: Define Module Link Between Brand and Product
-
-In this chapter, you'll learn how to define a module link between a brand defined in the [custom Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md), and a product defined in the [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md) that's available in your Medusa application out-of-the-box.
-
-Modules are [isolated](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md) from other resources, ensuring that they're integrated into the Medusa application without side effects. However, you may need to associate data models of different modules, or you're trying to extend data models from commerce modules with custom properties. To do that, you define module links.
-
-A module link forms an association between two data models of different modules while maintaining module isolation. You can then manage and query linked records of the data models using Medusa's Modules SDK.
-
-In this chapter, you'll define a module link between the `Brand` data model of the Brand Module, and the `Product` data model of the Product Module. In later chapters, you'll manage and retrieve linked product and brand records.
-
-Learn more about module links in [this chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md).
-
-### Prerequisites
-
-- [Brand Module having a Brand data model](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md)
-
-## 1. Define Link
-
-Links are defined in a TypeScript or JavaScript file under the `src/links` directory. The file defines and exports the link using `defineLink` from the Modules SDK.
-
-So, to define a link between the `Product` and `Brand` models, create the file `src/links/product-brand.ts` with the following content:
-
-
-
-```ts title="src/links/product-brand.ts" highlights={highlights}
-import BrandModule from "../modules/brand"
-import ProductModule from "@medusajs/medusa/product"
-import { defineLink } from "@medusajs/framework/utils"
-
-export default defineLink(
- {
- linkable: ProductModule.linkable.product,
- isList: true,
- },
- BrandModule.linkable.brand
-)
-```
-
-You import each module's definition object from the `index.ts` file of the module's directory. Each module object has a special `linkable` property that holds the data models' link configurations.
-
-The `defineLink` function accepts two parameters of the same type, which is either:
-
-- The data model's link configuration, which you access from the Module's `linkable` property;
-- Or an object that has two properties:
- - `linkable`: the data model's link configuration, which you access from the Module's `linkable` property.
- - `isList`: A boolean indicating whether many records of the data model can be linked to the other model.
-
-So, in the above code snippet, you define a link between the `Product` and `Brand` data models. Since a brand can be associated with multiple products, you enable `isList` in the `Product` model's object.
-
-***
-
-## 2. Sync the Link to the Database
-
-A module link is represented in the database as a table that stores the IDs of linked records. So, after defining the link, run the following command to create the module link's table in the database:
-
-```bash
-npx medusa db:migrate
-```
-
-This command reflects migrations on the database and syncs module links, which creates a table for the `product-brand` link.
-
-You can also run the `npx medusa db:sync-links` to just sync module links without running migrations.
-
-***
-
-## Next Steps: Extend Create Product Flow
-
-In the next chapter, you'll extend Medusa's workflow and API route that create a product to allow associating a brand with a product. You'll also learn how to link brand and product records.
-
-
-# Guide: Query Product's Brands
-
-In the previous chapters, you [defined a link](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md) between the [custom Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) and Medusa's [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md), then [extended the create-product flow](https://docs.medusajs.com/learn/customization/extend-features/extend-create-product/index.html.md) to link a product to a brand.
-
-In this chapter, you'll learn how to retrieve a product's brand (and vice-versa) in two ways: Using Medusa's existing API route, or in customizations, such as a custom API route.
-
-### Prerequisites
-
-- [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md)
-- [Defined link between the Brand and Product data models.](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md)
-
-***
-
-## Approach 1: Retrieve Brands in Existing API Routes
-
-Medusa's existing API routes accept a `fields` query parameter that allows you to specify the fields and relations of a model to retrieve. So, when you send a request to the [List Products](https://docs.medusajs.com/api/admin#products_getproducts), [Get Product](https://docs.medusajs.com/api/admin#products_getproductsid), or any product-related store or admin routes that accept a `fields` query parameter, you can specify in this parameter to return the product's brands.
-
-Learn more about selecting fields and relations in the [API Reference](https://docs.medusajs.com/api/admin#select-fields-and-relations).
-
-For example, send the following request to retrieve the list of products with their brands:
-
-```bash
-curl 'http://localhost:9000/admin/products?fields=+brand.*' \
---header 'Authorization: Bearer {token}'
-```
-
-Make sure to replace `{token}` with your admin user's authentication token. Learn how to retrieve it in the [API reference](https://docs.medusajs.com/api/store#authentication).
-
-Any product that is linked to a brand will have a `brand` property in its object:
-
-```json title="Example Product Object"
-{
- "id": "prod_123",
- // ...
- "brand": {
- "id": "01JEB44M61BRM3ARM2RRMK7GJF",
- "name": "Acme",
- "created_at": "2024-12-05T09:59:08.737Z",
- "updated_at": "2024-12-05T09:59:08.737Z",
- "deleted_at": null
- }
-}
-```
-
-By using the `fields` query parameter, you don't have to re-create existing API routes to get custom data models that you linked to core data models.
-
-***
-
-## Approach 2: Use Query to Retrieve Linked Records
-
-You can also retrieve linked records using Query. Query allows you to retrieve data across modules with filters, pagination, and more. You can resolve Query from the Medusa container and use it in your API route or workflow.
-
-Learn more about Query in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md).
-
-For example, you can create an API route that retrieves brands and their products. If you followed the [Create Brands API route chapter](https://docs.medusajs.com/learn/customization/custom-features/api-route/index.html.md), you'll have the file `src/api/admin/brands/route.ts` with a `POST` API route. Add a new `GET` function to the same file:
-
-```ts title="src/api/admin/brands/route.ts" highlights={highlights}
-// other imports...
-import {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- const query = req.scope.resolve("query")
-
- const { data: brands } = await query.graph({
- entity: "brand",
- fields: ["*", "products.*"],
- })
-
- res.json({ brands })
-}
-```
-
-This adds a `GET` API route at `/admin/brands`. In the API route, you resolve Query from the Medusa container. Query has a `graph` method that runs a query to retrieve data. It accepts an object having the following properties:
-
-- `entity`: The data model's name as specified in the first parameter of `model.define`.
-- `fields`: An array of properties and relations to retrieve. You can pass:
- - A property's name, such as `id`, or `*` for all properties.
- - A relation or linked model's name, such as `products` (use the plural name since brands are linked to list of products). You suffix the name with `.*` to retrieve all its properties.
-
-`graph` returns an object having a `data` property, which is the retrieved brands. You return the brands in the response.
-
-### Test it Out
-
-To test the API route out, send a `GET` request to `/admin/brands`:
-
-```bash
-curl 'http://localhost:9000/admin/brands' \
--H 'Authorization: Bearer {token}'
-```
-
-Make sure to replace `{token}` with your admin user's authentication token. Learn how to retrieve it in the [API reference](https://docs.medusajs.com/api/store#authentication).
-
-This returns the brands in your store with their linked products. For example:
-
-```json title="Example Response"
-{
- "brands": [
- {
- "id": "123",
- // ...
- "products": [
- {
- "id": "prod_123",
- // ...
- }
- ]
- }
- ]
-}
-```
-
-***
-
-## Summary
-
-By following the examples of the previous chapters, you:
-
-- Defined a link between the Brand and Product modules's data models, allowing you to associate a product with a brand.
-- Extended the create-product workflow and route to allow setting the product's brand while creating the product.
-- Queried a product's brand, and vice versa.
-
-***
-
-## Next Steps: Customize Medusa Admin
-
-Clients, such as the Medusa Admin dashboard, can now use brand-related features, such as creating a brand or setting the brand of a product.
-
-In the next chapters, you'll learn how to customize the Medusa Admin to show a product's brand on its details page, and to show a new page with the list of brands in your store.
-
-
# Admin Development Constraints
This chapter lists some constraints of admin widgets and UI routes.
@@ -6008,218 +6423,6 @@ export const config = defineWidgetConfig({
```
-# Guide: Extend Create Product Flow
-
-After linking the [custom Brand data model](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) and Medusa's [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md) in the [previous chapter](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md), 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](https://docs.medusajs.com/api/admin#products_postproducts), accept an `additional_data` request body parameter. This parameter can hold custom data that's passed to the [hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md) 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](https://docs.medusajs.com/resources/references/medusa-workflows/createProductsWorkflow#productsCreated/index.html.md) hook of the [createProductsWorkflow](https://docs.medusajs.com/resources/references/medusa-workflows/createProductsWorkflow/index.html.md), 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`.
-
-To learn more about the `additional_data` property and the API routes that accept additional data, refer to [this chapter](https://docs.medusajs.com/learn/fundamentals/api-routes/additional-data/index.html.md).
-
-### Prerequisites
-
-- [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md)
-- [Defined link between the Brand and Product data models.](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md)
-
-***
-
-## 1. Consume the productsCreated 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.
-
-Learn more about the workflow hooks in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md).
-
-The [createProductsWorkflow](https://docs.medusajs.com/resources/references/medusa-workflows/createProductsWorkflow/index.html.md) used in the [Create Product API route](https://docs.medusajs.com/api/admin#products_postproducts) 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:
-
-
-
-```ts title="src/workflows/hooks/created-product.ts" highlights={hook1Highlights}
-import { createProductsWorkflow } from "@medusajs/medusa/core-flows"
-import { StepResponse } from "@medusajs/framework/workflows-sdk"
-import { Modules } from "@medusajs/framework/utils"
-import { LinkDefinition } from "@medusajs/framework/types"
-import { BRAND_MODULE } from "../../modules/brand"
-import BrandModuleService from "../../modules/brand/service"
-
-createProductsWorkflow.hooks.productsCreated(
- (async ({ products, additional_data }, { container }) => {
- if (!additional_data?.brand_id) {
- return new StepResponse([], [])
- }
-
- const brandModuleService: BrandModuleService = container.resolve(
- BRAND_MODULE
- )
- // if the brand doesn't exist, an error is thrown.
- await brandModuleService.retrieveBrand(additional_data.brand_id as string)
-
- // TODO link brand to product
- })
-)
-```
-
-Workflows have a special `hooks` property to access its hooks and consume them. Each hook, such as `productsCreated`, accepts a step function as a parameter. The step function accepts the following parameters:
-
-1. An object having an `additional_data` property, which is the custom data passed in the request body under `additional_data`. The object will also have properties passed from the workflow to the hook, which in this case is the `products` property that holds an array of the created products.
-2. An object of properties related to the step's context. It has a `container` property whose value is the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) 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 Link, which is a class from the Modules SDK that provides methods to manage linked records.
-
-Learn more about Link in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/link/index.html.md).
-
-To use Link in the `productsCreated` hook, replace the `TODO` with the following:
-
-```ts title="src/workflows/hooks/created-product.ts" highlights={hook2Highlights}
-const link = container.resolve("link")
-const logger = container.resolve("logger")
-
-const links: LinkDefinition[] = []
-
-for (const product of products) {
- links.push({
- [Modules.PRODUCT]: {
- product_id: product.id,
- },
- [BRAND_MODULE]: {
- brand_id: additional_data.brand_id,
- },
- })
-}
-
-await link.create(links)
-
-logger.info("Linked brand to products")
-
-return new StepResponse(links, links)
-```
-
-You resolve 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 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`:
-
-```ts title="src/workflows/hooks/created-product.ts"
-createProductsWorkflow.hooks.productsCreated(
- // ...
- (async (links, { container }) => {
- if (!links?.length) {
- return
- }
-
- const link = container.resolve("link")
-
- await link.dismiss(links)
- })
-)
-```
-
-In the compensation function, if the `links` parameter isn't empty, you resolve 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 `productsCreated` 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:
-
-
-
-```ts title="src/api/middlewares.ts"
-import { defineMiddlewares } from "@medusajs/framework/http"
-import { z } from "zod"
-
-// ...
-
-export default defineMiddlewares({
- routes: [
- // ...
- {
- matcher: "/admin/products",
- method: ["POST"],
- additionalDataValidator: {
- brand_id: z.string().optional(),
- },
- },
- ],
-})
-```
-
-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](https://zod.dev/).
-
-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`:
-
-```bash
-curl -X POST 'http://localhost:9000/auth/user/emailpass' \
--H 'Content-Type: application/json' \
---data-raw '{
- "email": "admin@medusa-test.com",
- "password": "supersecret"
-}'
-```
-
-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:
-
-```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_id": "{brand_id}"
- }
-}'
-```
-
-Make sure to replace `{token}` with the token you received from the previous request, `shipping_profile_id` with the ID of a shipping profile in your application, and `{brand_id}` with the ID of a brand in your application. You can retrieve the ID of a shipping profile either from the Medusa Admin, or the [List Shipping Profiles API route](https://docs.medusajs.com/api/admin#shipping-profiles_getshippingprofiles).
-
-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.
-
-
# Environment Variables in Admin Customizations
In this chapter, you'll learn how to use environment variables in your admin customizations.
@@ -6289,242 +6492,6 @@ To check the current environment, Vite exposes two variables:
Learn more about other Vite environment variables in the [Vite documentation](https://vite.dev/guide/env-and-mode).
-# Admin UI Routes
-
-In this chapter, you’ll learn how to create a UI route in the admin dashboard.
-
-## What is a UI Route?
-
-The Medusa Admin dashboard is customizable, allowing you to add new pages, called UI routes. You create a UI route as a React component showing custom content that allow admin users to perform custom actions.
-
-For example, you can add a new page to show and manage product reviews, which aren't available natively in Medusa.
-
-***
-
-## How to Create a UI Route?
-
-### Prerequisites
-
-- [Medusa application installed](https://docs.medusajs.com/learn/installation/index.html.md)
-
-You create a UI route in a `page.tsx` file under a sub-directory of `src/admin/routes` directory. The file's path relative to `src/admin/routes` determines its path in the dashboard. The file’s default export must be the UI route’s React component.
-
-For example, create the file `src/admin/routes/custom/page.tsx` with the following content:
-
-
-
-```tsx title="src/admin/routes/custom/page.tsx"
-import { Container, Heading } from "@medusajs/ui"
-
-const CustomPage = () => {
- return (
-
-
- This is my custom route
-
-
- )
-}
-
-export default CustomPage
-```
-
-You add a new route at `http://localhost:9000/app/custom`. The `CustomPage` component holds the page's content, which currently only shows a heading.
-
-In the route, you use [Medusa UI](https://docs.medusajs.com/ui/index.html.md), a package that Medusa maintains to allow you to customize the dashboard with the same components used to build it.
-
-The UI route component must be created as an arrow function.
-
-### Test the UI Route
-
-To test the UI route, start the Medusa application:
-
-```bash npm2yarn
-npm run dev
-```
-
-Then, after logging into the admin dashboard, open the page `http://localhost:9000/app/custom` to see your custom page.
-
-***
-
-## Show UI Route in the Sidebar
-
-To add a sidebar item for your custom UI route, export a configuration object in the UI route's file:
-
-```tsx title="src/admin/routes/custom/page.tsx" highlights={highlights}
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { ChatBubbleLeftRight } from "@medusajs/icons"
-import { Container, Heading } from "@medusajs/ui"
-
-const CustomPage = () => {
- return (
-
-
- This is my custom route
-
-
- )
-}
-
-export const config = defineRouteConfig({
- label: "Custom Route",
- icon: ChatBubbleLeftRight,
-})
-
-export default CustomPage
-```
-
-The configuration object is created using `defineRouteConfig` from the Medusa Framework. It accepts the following properties:
-
-- `label`: the sidebar item’s label.
-- `icon`: an optional React component used as an icon in the sidebar.
-
-The above example adds a new sidebar item with the label `Custom Route` and an icon from the [Medusa UI Icons package](https://docs.medusajs.com/ui/icons/overview/index.html.md).
-
-### Nested UI Routes
-
-Consider that along the UI route above at `src/admin/routes/custom/page.tsx` you create a nested UI route at `src/admin/routes/custom/nested/page.tsx` that also exports route configurations:
-
-
-
-```tsx title="src/admin/routes/custom/nested/page.tsx"
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { Container, Heading } from "@medusajs/ui"
-
-const NestedCustomPage = () => {
- return (
-
-
- This is my nested custom route
-
-
- )
-}
-
-export const config = defineRouteConfig({
- label: "Nested Route",
-})
-
-export default NestedCustomPage
-```
-
-This UI route is shown in the sidebar as an item nested in the parent "Custom Route" item. Nested items are only shown when the parent sidebar items (in this case, "Custom Route") are clicked.
-
-#### Caveats
-
-Some caveats for nested UI routes in the sidebar:
-
-- Nested dynamic UI routes, such as one created at `src/admin/routes/custom/[id]/page.tsx` aren't added to the sidebar as it's not possible to link to a dynamic route. If the dynamic route exports route configurations, a warning is logged in the browser's console.
-- Nested routes in setting pages aren't shown in the sidebar to follow the admin's design conventions.
-- The `icon` configuration is ignored for the sidebar item of nested UI route to follow the admin's design conventions.
-
-### Route Under Existing Admin Route
-
-You can add a custom UI route under an existing route. For example, you can add a route under the orders route:
-
-```tsx title="src/admin/routes/orders/nested/page.tsx"
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { Container, Heading } from "@medusajs/ui"
-
-const NestedOrdersPage = () => {
- return (
-
-
- Nested Orders Page
-
-
- )
-}
-
-export const config = defineRouteConfig({
- label: "Nested Orders",
- nested: "/orders",
-})
-
-export default NestedOrdersPage
-```
-
-The `nested` property passed to `defineRouteConfig` specifies which route this custom route is nested under. This route will now show in the sidebar under the existing "Orders" sidebar item.
-
-***
-
-## Create Settings Page
-
-To create a page under the settings section of the admin dashboard, create a UI route under the path `src/admin/routes/settings`.
-
-For example, create a UI route at `src/admin/routes/settings/custom/page.tsx`:
-
-
-
-```tsx title="src/admin/routes/settings/custom/page.tsx"
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { Container, Heading } from "@medusajs/ui"
-
-const CustomSettingPage = () => {
- return (
-
-
- Custom Setting Page
-
-
- )
-}
-
-export const config = defineRouteConfig({
- label: "Custom",
-})
-
-export default CustomSettingPage
-```
-
-This adds a page under the path `/app/settings/custom`. An item is also added to the settings sidebar with the label `Custom`.
-
-***
-
-## Path Parameters
-
-A UI route can accept path parameters if the name of any of the directories in its path is of the format `[param]`.
-
-For example, create the file `src/admin/routes/custom/[id]/page.tsx` with the following content:
-
-
-
-```tsx title="src/admin/routes/custom/[id]/page.tsx" highlights={[["5", "", "Retrieve the path parameter."], ["10", "{id}", "Show the path parameter."]]}
-import { useParams } from "react-router-dom"
-import { Container, Heading } from "@medusajs/ui"
-
-const CustomPage = () => {
- const { id } = useParams()
-
- return (
-
-
- Passed ID: {id}
-
-
- )
-}
-
-export default CustomPage
-```
-
-You access the passed parameter using `react-router-dom`'s [useParams hook](https://reactrouter.com/en/main/hooks/use-params).
-
-If you run the Medusa application and go to `localhost:9000/app/custom/123`, you'll see `123` printed in the page.
-
-***
-
-## Admin Components List
-
-To build admin customizations that match the Medusa Admin's designs and layouts, refer to [this guide](https://docs.medusajs.com/resources/admin-components/index.html.md) to find common components.
-
-***
-
-## More Routes Customizations
-
-For more customizations related to routes, refer to the [Routing Customizations chapter](https://docs.medusajs.com/learn/fundamentals/admin/routing/index.html.md).
-
-
# Admin Routing Customizations
The Medusa Admin dashboard uses [React Router](https://reactrouter.com) under the hood to manage routing. So, you can have more flexibility in routing-related customizations using some of React Router's utilities, hooks, and components.
@@ -6928,6 +6895,242 @@ Refer to [this reference](https://docs.medusajs.com/resources/admin-widget-injec
To build admin customizations that match the Medusa Admin's designs and layouts, refer to [this guide](https://docs.medusajs.com/resources/admin-components/index.html.md) to find common components.
+# Admin UI Routes
+
+In this chapter, you’ll learn how to create a UI route in the admin dashboard.
+
+## What is a UI Route?
+
+The Medusa Admin dashboard is customizable, allowing you to add new pages, called UI routes. You create a UI route as a React component showing custom content that allow admin users to perform custom actions.
+
+For example, you can add a new page to show and manage product reviews, which aren't available natively in Medusa.
+
+***
+
+## How to Create a UI Route?
+
+### Prerequisites
+
+- [Medusa application installed](https://docs.medusajs.com/learn/installation/index.html.md)
+
+You create a UI route in a `page.tsx` file under a sub-directory of `src/admin/routes` directory. The file's path relative to `src/admin/routes` determines its path in the dashboard. The file’s default export must be the UI route’s React component.
+
+For example, create the file `src/admin/routes/custom/page.tsx` with the following content:
+
+
+
+```tsx title="src/admin/routes/custom/page.tsx"
+import { Container, Heading } from "@medusajs/ui"
+
+const CustomPage = () => {
+ return (
+
+
+ This is my custom route
+
+
+ )
+}
+
+export default CustomPage
+```
+
+You add a new route at `http://localhost:9000/app/custom`. The `CustomPage` component holds the page's content, which currently only shows a heading.
+
+In the route, you use [Medusa UI](https://docs.medusajs.com/ui/index.html.md), a package that Medusa maintains to allow you to customize the dashboard with the same components used to build it.
+
+The UI route component must be created as an arrow function.
+
+### Test the UI Route
+
+To test the UI route, start the Medusa application:
+
+```bash npm2yarn
+npm run dev
+```
+
+Then, after logging into the admin dashboard, open the page `http://localhost:9000/app/custom` to see your custom page.
+
+***
+
+## Show UI Route in the Sidebar
+
+To add a sidebar item for your custom UI route, export a configuration object in the UI route's file:
+
+```tsx title="src/admin/routes/custom/page.tsx" highlights={highlights}
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import { ChatBubbleLeftRight } from "@medusajs/icons"
+import { Container, Heading } from "@medusajs/ui"
+
+const CustomPage = () => {
+ return (
+
+
+ This is my custom route
+
+
+ )
+}
+
+export const config = defineRouteConfig({
+ label: "Custom Route",
+ icon: ChatBubbleLeftRight,
+})
+
+export default CustomPage
+```
+
+The configuration object is created using `defineRouteConfig` from the Medusa Framework. It accepts the following properties:
+
+- `label`: the sidebar item’s label.
+- `icon`: an optional React component used as an icon in the sidebar.
+
+The above example adds a new sidebar item with the label `Custom Route` and an icon from the [Medusa UI Icons package](https://docs.medusajs.com/ui/icons/overview/index.html.md).
+
+### Nested UI Routes
+
+Consider that along the UI route above at `src/admin/routes/custom/page.tsx` you create a nested UI route at `src/admin/routes/custom/nested/page.tsx` that also exports route configurations:
+
+
+
+```tsx title="src/admin/routes/custom/nested/page.tsx"
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import { Container, Heading } from "@medusajs/ui"
+
+const NestedCustomPage = () => {
+ return (
+
+
+ This is my nested custom route
+
+
+ )
+}
+
+export const config = defineRouteConfig({
+ label: "Nested Route",
+})
+
+export default NestedCustomPage
+```
+
+This UI route is shown in the sidebar as an item nested in the parent "Custom Route" item. Nested items are only shown when the parent sidebar items (in this case, "Custom Route") are clicked.
+
+#### Caveats
+
+Some caveats for nested UI routes in the sidebar:
+
+- Nested dynamic UI routes, such as one created at `src/admin/routes/custom/[id]/page.tsx` aren't added to the sidebar as it's not possible to link to a dynamic route. If the dynamic route exports route configurations, a warning is logged in the browser's console.
+- Nested routes in setting pages aren't shown in the sidebar to follow the admin's design conventions.
+- The `icon` configuration is ignored for the sidebar item of nested UI route to follow the admin's design conventions.
+
+### Route Under Existing Admin Route
+
+You can add a custom UI route under an existing route. For example, you can add a route under the orders route:
+
+```tsx title="src/admin/routes/orders/nested/page.tsx"
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import { Container, Heading } from "@medusajs/ui"
+
+const NestedOrdersPage = () => {
+ return (
+
+
+ Nested Orders Page
+
+
+ )
+}
+
+export const config = defineRouteConfig({
+ label: "Nested Orders",
+ nested: "/orders",
+})
+
+export default NestedOrdersPage
+```
+
+The `nested` property passed to `defineRouteConfig` specifies which route this custom route is nested under. This route will now show in the sidebar under the existing "Orders" sidebar item.
+
+***
+
+## Create Settings Page
+
+To create a page under the settings section of the admin dashboard, create a UI route under the path `src/admin/routes/settings`.
+
+For example, create a UI route at `src/admin/routes/settings/custom/page.tsx`:
+
+
+
+```tsx title="src/admin/routes/settings/custom/page.tsx"
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import { Container, Heading } from "@medusajs/ui"
+
+const CustomSettingPage = () => {
+ return (
+
+
+ Custom Setting Page
+
+
+ )
+}
+
+export const config = defineRouteConfig({
+ label: "Custom",
+})
+
+export default CustomSettingPage
+```
+
+This adds a page under the path `/app/settings/custom`. An item is also added to the settings sidebar with the label `Custom`.
+
+***
+
+## Path Parameters
+
+A UI route can accept path parameters if the name of any of the directories in its path is of the format `[param]`.
+
+For example, create the file `src/admin/routes/custom/[id]/page.tsx` with the following content:
+
+
+
+```tsx title="src/admin/routes/custom/[id]/page.tsx" highlights={[["5", "", "Retrieve the path parameter."], ["10", "{id}", "Show the path parameter."]]}
+import { useParams } from "react-router-dom"
+import { Container, Heading } from "@medusajs/ui"
+
+const CustomPage = () => {
+ const { id } = useParams()
+
+ return (
+
+
+ Passed ID: {id}
+
+
+ )
+}
+
+export default CustomPage
+```
+
+You access the passed parameter using `react-router-dom`'s [useParams hook](https://reactrouter.com/en/main/hooks/use-params).
+
+If you run the Medusa application and go to `localhost:9000/app/custom/123`, you'll see `123` printed in the page.
+
+***
+
+## Admin Components List
+
+To build admin customizations that match the Medusa Admin's designs and layouts, refer to [this guide](https://docs.medusajs.com/resources/admin-components/index.html.md) to find common components.
+
+***
+
+## More Routes Customizations
+
+For more customizations related to routes, refer to the [Routing Customizations chapter](https://docs.medusajs.com/learn/fundamentals/admin/routing/index.html.md).
+
+
# Pass Additional Data to Medusa's API Route
In this chapter, you'll learn how to pass additional data in requests to Medusa's API Route.
@@ -7239,301 +7442,6 @@ export default defineMiddlewares({
This retrieves the configurations exported from `medusa-config.ts` and applies the `storeCors` to routes starting with `/custom`.
-# Seed Data with Custom CLI Script
-
-In this chapter, you'll learn how to seed data using a custom CLI script.
-
-## How to Seed Data
-
-To seed dummy data for development or demo purposes, use a custom CLI script.
-
-In the CLI script, use your custom workflows or Medusa's existing workflows, which you can browse in [this reference](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md), to seed data.
-
-### Example: Seed Dummy Products
-
-In this section, you'll follow an example of creating a custom CLI script that seeds fifty dummy products.
-
-First, install the [Faker](https://fakerjs.dev/) library to generate random data in your script:
-
-```bash npm2yarn
-npm install --save-dev @faker-js/faker
-```
-
-Then, create the file `src/scripts/demo-products.ts` with the following content:
-
-```ts title="src/scripts/demo-products.ts" highlights={highlights} collapsibleLines="1-12" expandButtonLabel="Show Imports"
-import { ExecArgs } from "@medusajs/framework/types"
-import { faker } from "@faker-js/faker"
-import {
- ContainerRegistrationKeys,
- Modules,
- ProductStatus,
-} from "@medusajs/framework/utils"
-import {
- createInventoryLevelsWorkflow,
- createProductsWorkflow,
-} from "@medusajs/medusa/core-flows"
-
-export default async function seedDummyProducts({
- container,
-}: ExecArgs) {
- const salesChannelModuleService = container.resolve(
- Modules.SALES_CHANNEL
- )
- const logger = container.resolve(
- ContainerRegistrationKeys.LOGGER
- )
- const query = container.resolve(
- ContainerRegistrationKeys.QUERY
- )
-
- const defaultSalesChannel = await salesChannelModuleService
- .listSalesChannels({
- name: "Default Sales Channel",
- })
-
- const sizeOptions = ["S", "M", "L", "XL"]
- const colorOptions = ["Black", "White"]
- const currency_code = "eur"
- const productsNum = 50
-
- // TODO seed products
-}
-```
-
-So far, in the script, you:
-
-- Resolve the Sales Channel Module's main service to retrieve the application's default sales channel. This is the sales channel the dummy products will be available in.
-- Resolve the Logger to log messages in the terminal, and Query to later retrieve data useful for the seeded products.
-- Initialize some default data to use when seeding the products next.
-
-Next, replace the `TODO` with the following:
-
-```ts title="src/scripts/demo-products.ts"
-const productsData = new Array(productsNum).fill(0).map((_, index) => {
- const title = faker.commerce.product() + "_" + index
- return {
- title,
- is_giftcard: true,
- description: faker.commerce.productDescription(),
- status: ProductStatus.PUBLISHED,
- options: [
- {
- title: "Size",
- values: sizeOptions,
- },
- {
- title: "Color",
- values: colorOptions,
- },
- ],
- images: [
- {
- url: faker.image.urlPlaceholder({
- text: title,
- }),
- },
- {
- url: faker.image.urlPlaceholder({
- text: title,
- }),
- },
- ],
- variants: new Array(10).fill(0).map((_, variantIndex) => ({
- title: `${title} ${variantIndex}`,
- sku: `variant-${variantIndex}${index}`,
- prices: new Array(10).fill(0).map((_, priceIndex) => ({
- currency_code,
- amount: 10 * priceIndex,
- })),
- options: {
- Size: sizeOptions[Math.floor(Math.random() * 3)],
- },
- })),
- shipping_profile_id: "sp_123",
- sales_channels: [
- {
- id: defaultSalesChannel[0].id,
- },
- ],
- }
-})
-
-// TODO seed products
-```
-
-You generate fifty products using the sales channel and variables you initialized, and using Faker for random data, such as the product's title or images.
-
-Then, replace the new `TODO` with the following:
-
-```ts title="src/scripts/demo-products.ts"
-const { result: products } = await createProductsWorkflow(container).run({
- input: {
- products: productsData,
- },
-})
-
-logger.info(`Seeded ${products.length} products.`)
-
-// TODO add inventory levels
-```
-
-You create the generated products using the `createProductsWorkflow` imported previously from `@medusajs/medusa/core-flows`. It accepts the product data as input, and returns the created products.
-
-Only thing left is to create inventory levels for the products. So, replace the last `TODO` with the following:
-
-```ts title="src/scripts/demo-products.ts"
-logger.info("Seeding inventory levels.")
-
-const { data: stockLocations } = await query.graph({
- entity: "stock_location",
- fields: ["id"],
-})
-
-const { data: inventoryItems } = await query.graph({
- entity: "inventory_item",
- fields: ["id"],
-})
-
-const inventoryLevels = inventoryItems.map((inventoryItem) => ({
- location_id: stockLocations[0].id,
- stocked_quantity: 1000000,
- inventory_item_id: inventoryItem.id,
-}))
-
-await createInventoryLevelsWorkflow(container).run({
- input: {
- inventory_levels: inventoryLevels,
- },
-})
-
-logger.info("Finished seeding inventory levels data.")
-```
-
-You use Query to retrieve the stock location, to use the first location in the application, and the inventory items.
-
-Then, you generate inventory levels for each inventory item, associating it with the first stock location.
-
-Finally, you use the `createInventoryLevelsWorkflow` from Medusa's core workflows to create the inventory levels.
-
-### Test Script
-
-To test out the script, run the following command in your project's directory:
-
-```bash
-npx medusa exec ./src/scripts/demo-products.ts
-```
-
-This seeds the products to your database. If you run your Medusa application and view the products in the dashboard, you'll find fifty new products.
-
-
-# Throwing and Handling Errors
-
-In this guide, you'll learn how to throw errors in your Medusa application, how it affects an API route's response, and how to change the default error handler of your Medusa application.
-
-## Throw MedusaError
-
-When throwing an error in your API routes, middlewares, workflows, or any customization, throw a `MedusaError` from the Medusa Framework.
-
-The Medusa application's API route error handler then wraps your thrown error in a uniform object and returns it in the response.
-
-For example:
-
-```ts
-import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
-import { MedusaError } from "@medusajs/framework/utils"
-
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- if (!req.query.q) {
- throw new MedusaError(
- MedusaError.Types.INVALID_DATA,
- "The `q` query parameter is required."
- )
- }
-
- // ...
-}
-```
-
-The `MedusaError` class accepts in its constructor two parameters:
-
-1. The first is the error's type. `MedusaError` has a static property `Types` that you can use. `Types` is an enum whose possible values are explained in the next section.
-2. The second is the message to show in the error response.
-
-### Error Object in Response
-
-The error object returned in the response has two properties:
-
-- `type`: The error's type.
-- `message`: The error message, if available.
-- `code`: A common snake-case code. Its values can be:
- - `invalid_request_error` for the `DUPLICATE_ERROR` type.
- - `api_error`: for the `DB_ERROR` type.
- - `invalid_state_error` for `CONFLICT` error type.
- - `unknown_error` for any unidentified error type.
- - For other error types, this property won't be available unless you provide a code as a third parameter to the `MedusaError` constructor.
-
-### MedusaError Types
-
-|Type|Description|Status Code|
-|---|---|---|---|---|
-|\`DB\_ERROR\`|Indicates a database error.|\`500\`|
-|\`DUPLICATE\_ERROR\`|Indicates a duplicate of a record already exists. For example, when trying to create a customer whose email is registered by another customer.|\`422\`|
-|\`INVALID\_ARGUMENT\`|Indicates an error that occurred due to incorrect arguments or other unexpected state.|\`500\`|
-|\`INVALID\_DATA\`|Indicates a validation error.|\`400\`|
-|\`UNAUTHORIZED\`|Indicates that a user is not authorized to perform an action or access a route.|\`401\`|
-|\`NOT\_FOUND\`|Indicates that the requested resource, such as a route or a record, isn't found.|\`404\`|
-|\`NOT\_ALLOWED\`|Indicates that an operation isn't allowed.|\`400\`|
-|\`CONFLICT\`|Indicates that a request conflicts with another previous or ongoing request. The error message in this case is ignored for a default message.|\`409\`|
-|\`PAYMENT\_AUTHORIZATION\_ERROR\`|Indicates an error has occurred while authorizing a payment.|\`422\`|
-|Other error types|Any other error type results in an |\`500\`|
-
-***
-
-## Override Error Handler
-
-The `defineMiddlewares` function used to apply middlewares on routes accepts an `errorHandler` in its object parameter. Use it to override the default error handler for API routes.
-
-This error handler will also be used for errors thrown in Medusa's API routes and resources.
-
-For example, create `src/api/middlewares.ts` with the following:
-
-```ts title="src/api/middlewares.ts" collapsibleLines="1-8" expandMoreLabel="Show Imports"
-import {
- defineMiddlewares,
- MedusaNextFunction,
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import { MedusaError } from "@medusajs/framework/utils"
-
-export default defineMiddlewares({
- errorHandler: (
- error: MedusaError | any,
- req: MedusaRequest,
- res: MedusaResponse,
- next: MedusaNextFunction
- ) => {
- res.status(400).json({
- error: "Something happened.",
- })
- },
-})
-```
-
-The `errorHandler` property's value is a function that accepts four parameters:
-
-1. The error thrown. Its type can be `MedusaError` or any other thrown error type.
-2. A request object of type `MedusaRequest`.
-3. A response object of type `MedusaResponse`.
-4. A function of type MedusaNextFunction that executes the next middleware in the stack.
-
-This example overrides Medusa's default error handler with a handler that always returns a `400` status code with the same message.
-
-
# HTTP Methods
In this chapter, you'll learn about how to add new API routes for each HTTP method.
@@ -7925,6 +7833,113 @@ A middleware can not override an existing middleware. Instead, middlewares are a
For example, if you define a custom validation middleware, such as `validateAndTransformBody`, on an existing route, then both the original and the custom validation middleware will run.
+# Throwing and Handling Errors
+
+In this guide, you'll learn how to throw errors in your Medusa application, how it affects an API route's response, and how to change the default error handler of your Medusa application.
+
+## Throw MedusaError
+
+When throwing an error in your API routes, middlewares, workflows, or any customization, throw a `MedusaError` from the Medusa Framework.
+
+The Medusa application's API route error handler then wraps your thrown error in a uniform object and returns it in the response.
+
+For example:
+
+```ts
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import { MedusaError } from "@medusajs/framework/utils"
+
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ if (!req.query.q) {
+ throw new MedusaError(
+ MedusaError.Types.INVALID_DATA,
+ "The `q` query parameter is required."
+ )
+ }
+
+ // ...
+}
+```
+
+The `MedusaError` class accepts in its constructor two parameters:
+
+1. The first is the error's type. `MedusaError` has a static property `Types` that you can use. `Types` is an enum whose possible values are explained in the next section.
+2. The second is the message to show in the error response.
+
+### Error Object in Response
+
+The error object returned in the response has two properties:
+
+- `type`: The error's type.
+- `message`: The error message, if available.
+- `code`: A common snake-case code. Its values can be:
+ - `invalid_request_error` for the `DUPLICATE_ERROR` type.
+ - `api_error`: for the `DB_ERROR` type.
+ - `invalid_state_error` for `CONFLICT` error type.
+ - `unknown_error` for any unidentified error type.
+ - For other error types, this property won't be available unless you provide a code as a third parameter to the `MedusaError` constructor.
+
+### MedusaError Types
+
+|Type|Description|Status Code|
+|---|---|---|---|---|
+|\`DB\_ERROR\`|Indicates a database error.|\`500\`|
+|\`DUPLICATE\_ERROR\`|Indicates a duplicate of a record already exists. For example, when trying to create a customer whose email is registered by another customer.|\`422\`|
+|\`INVALID\_ARGUMENT\`|Indicates an error that occurred due to incorrect arguments or other unexpected state.|\`500\`|
+|\`INVALID\_DATA\`|Indicates a validation error.|\`400\`|
+|\`UNAUTHORIZED\`|Indicates that a user is not authorized to perform an action or access a route.|\`401\`|
+|\`NOT\_FOUND\`|Indicates that the requested resource, such as a route or a record, isn't found.|\`404\`|
+|\`NOT\_ALLOWED\`|Indicates that an operation isn't allowed.|\`400\`|
+|\`CONFLICT\`|Indicates that a request conflicts with another previous or ongoing request. The error message in this case is ignored for a default message.|\`409\`|
+|\`PAYMENT\_AUTHORIZATION\_ERROR\`|Indicates an error has occurred while authorizing a payment.|\`422\`|
+|Other error types|Any other error type results in an |\`500\`|
+
+***
+
+## Override Error Handler
+
+The `defineMiddlewares` function used to apply middlewares on routes accepts an `errorHandler` in its object parameter. Use it to override the default error handler for API routes.
+
+This error handler will also be used for errors thrown in Medusa's API routes and resources.
+
+For example, create `src/api/middlewares.ts` with the following:
+
+```ts title="src/api/middlewares.ts" collapsibleLines="1-8" expandMoreLabel="Show Imports"
+import {
+ defineMiddlewares,
+ MedusaNextFunction,
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import { MedusaError } from "@medusajs/framework/utils"
+
+export default defineMiddlewares({
+ errorHandler: (
+ error: MedusaError | any,
+ req: MedusaRequest,
+ res: MedusaResponse,
+ next: MedusaNextFunction
+ ) => {
+ res.status(400).json({
+ error: "Something happened.",
+ })
+ },
+})
+```
+
+The `errorHandler` property's value is a function that accepts four parameters:
+
+1. The error thrown. Its type can be `MedusaError` or any other thrown error type.
+2. A request object of type `MedusaRequest`.
+3. A response object of type `MedusaResponse`.
+4. A function of type MedusaNextFunction that executes the next middleware in the stack.
+
+This example overrides Medusa's default error handler with a handler that always returns a `400` status code with the same message.
+
+
# API Route Parameters
In this chapter, you’ll learn about path, query, and request body parameters.
@@ -8443,108 +8458,6 @@ export const GET = async (
In the route handler, you resolve the User Module's main service, then use it to retrieve the logged-in admin user.
-# API Route Response
-
-In this chapter, you'll learn how to send a response in your API route.
-
-## Send a JSON Response
-
-To send a JSON response, use the `json` method of the `MedusaResponse` object passed as the second parameter of your API route handler.
-
-For example:
-
-```ts title="src/api/custom/route.ts" highlights={jsonHighlights}
-import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
-
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- res.json({
- message: "Hello, World!",
- })
-}
-```
-
-This API route returns the following JSON object:
-
-```json
-{
- "message": "Hello, World!"
-}
-```
-
-***
-
-## Set Response Status Code
-
-By default, setting the JSON data using the `json` method returns a response with a `200` status code.
-
-To change the status code, use the `status` method of the `MedusaResponse` object.
-
-For example:
-
-```ts title="src/api/custom/route.ts" highlights={statusHighlight}
-import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
-
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- res.status(201).json({
- message: "Hello, World!",
- })
-}
-```
-
-The response of this API route has the status code `201`.
-
-***
-
-## Change Response Content Type
-
-To return response data other than a JSON object, use the `writeHead` method of the `MedusaResponse` object. It allows you to set the response headers, including the content type.
-
-For example, to create an API route that returns an event stream:
-
-```ts highlights={streamHighlights}
-import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
-
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- res.writeHead(200, {
- "Content-Type": "text/event-stream",
- "Cache-Control": "no-cache",
- Connection: "keep-alive",
- })
-
- const interval = setInterval(() => {
- res.write("Streaming data...\n")
- }, 3000)
-
- req.on("end", () => {
- clearInterval(interval)
- res.end()
- })
-}
-```
-
-The `writeHead` method accepts two parameters:
-
-1. The first one is the response's status code.
-2. The second is an object of key-value pairs to set the headers of the response.
-
-This API route opens a stream by setting the `Content-Type` in the header to `text/event-stream`. It then simulates a stream by creating an interval that writes the stream data every three seconds.
-
-***
-
-## Do More with Responses
-
-The `MedusaResponse` type is based on [Express's Response](https://expressjs.com/en/api.html#res). Refer to their API reference for other uses of responses.
-
-
# Request Body and Query Parameter Validation
In this chapter, you'll learn how to validate request body and query parameters in your custom API route.
@@ -8794,223 +8707,294 @@ For example, if you omit the `a` parameter, you'll receive a `400` response code
To see different examples and learn more about creating a validation schema, refer to [Zod's documentation](https://zod.dev).
-# Add Columns to a Link Table
+# Seed Data with Custom CLI Script
-In this chapter, you'll learn how to add custom columns to a link definition's table and manage them.
+In this chapter, you'll learn how to seed data using a custom CLI script.
-## Link Table's Default Columns
+## How to Seed Data
-When you define a link between two data models, Medusa creates a link table in the database to store the IDs of the linked records. You can learn more about the created table in the [Module Links chapter](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md).
+To seed dummy data for development or demo purposes, use a custom CLI script.
-In various cases, you might need to store additional data in the link table. For example, if you define a link between a `product` and a `post`, you might want to store the publish date of the product's post in the link table.
+In the CLI script, use your custom workflows or Medusa's existing workflows, which you can browse in [this reference](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md), to seed data.
-In those cases, you can add a custom column to a link's table in the link definition. You can later set that column whenever you create or update a link between the linked records.
+### Example: Seed Dummy Products
-***
+In this section, you'll follow an example of creating a custom CLI script that seeds fifty dummy products.
-## How to Add Custom Columns to a Link's Table?
+First, install the [Faker](https://fakerjs.dev/) library to generate random data in your script:
-The `defineLink` function used to define a link accepts a third parameter, which is an object of options.
+```bash npm2yarn
+npm install --save-dev @faker-js/faker
+```
-To add custom columns to a link's table, pass in the third parameter of `defineLink` a `database` property:
+Then, create the file `src/scripts/demo-products.ts` with the following content:
-```ts highlights={linkHighlights}
-import BlogModule from "../modules/blog"
-import ProductModule from "@medusajs/medusa/product"
-import { defineLink } from "@medusajs/framework/utils"
+```ts title="src/scripts/demo-products.ts" highlights={highlights} collapsibleLines="1-12" expandButtonLabel="Show Imports"
+import { ExecArgs } from "@medusajs/framework/types"
+import { faker } from "@faker-js/faker"
+import {
+ ContainerRegistrationKeys,
+ Modules,
+ ProductStatus,
+} from "@medusajs/framework/utils"
+import {
+ createInventoryLevelsWorkflow,
+ createProductsWorkflow,
+} from "@medusajs/medusa/core-flows"
-export default defineLink(
- ProductModule.linkable.product,
- BlogModule.linkable.blog,
- {
- database: {
- extraColumns: {
- metadata: {
- type: "json",
- },
+export default async function seedDummyProducts({
+ container,
+}: ExecArgs) {
+ const salesChannelModuleService = container.resolve(
+ Modules.SALES_CHANNEL
+ )
+ const logger = container.resolve(
+ ContainerRegistrationKeys.LOGGER
+ )
+ const query = container.resolve(
+ ContainerRegistrationKeys.QUERY
+ )
+
+ const defaultSalesChannel = await salesChannelModuleService
+ .listSalesChannels({
+ name: "Default Sales Channel",
+ })
+
+ const sizeOptions = ["S", "M", "L", "XL"]
+ const colorOptions = ["Black", "White"]
+ const currency_code = "eur"
+ const productsNum = 50
+
+ // TODO seed products
+}
+```
+
+So far, in the script, you:
+
+- Resolve the Sales Channel Module's main service to retrieve the application's default sales channel. This is the sales channel the dummy products will be available in.
+- Resolve the Logger to log messages in the terminal, and Query to later retrieve data useful for the seeded products.
+- Initialize some default data to use when seeding the products next.
+
+Next, replace the `TODO` with the following:
+
+```ts title="src/scripts/demo-products.ts"
+const productsData = new Array(productsNum).fill(0).map((_, index) => {
+ const title = faker.commerce.product() + "_" + index
+ return {
+ title,
+ is_giftcard: true,
+ description: faker.commerce.productDescription(),
+ status: ProductStatus.PUBLISHED,
+ options: [
+ {
+ title: "Size",
+ values: sizeOptions,
},
- },
+ {
+ title: "Color",
+ values: colorOptions,
+ },
+ ],
+ images: [
+ {
+ url: faker.image.urlPlaceholder({
+ text: title,
+ }),
+ },
+ {
+ url: faker.image.urlPlaceholder({
+ text: title,
+ }),
+ },
+ ],
+ variants: new Array(10).fill(0).map((_, variantIndex) => ({
+ title: `${title} ${variantIndex}`,
+ sku: `variant-${variantIndex}${index}`,
+ prices: new Array(10).fill(0).map((_, priceIndex) => ({
+ currency_code,
+ amount: 10 * priceIndex,
+ })),
+ options: {
+ Size: sizeOptions[Math.floor(Math.random() * 3)],
+ },
+ })),
+ shipping_profile_id: "sp_123",
+ sales_channels: [
+ {
+ id: defaultSalesChannel[0].id,
+ },
+ ],
}
-)
+})
+
+// TODO seed products
```
-This adds to the table created for the link between `product` and `blog` a `metadata` column of type `json`.
+You generate fifty products using the sales channel and variables you initialized, and using Faker for random data, such as the product's title or images.
-### Database Options
+Then, replace the new `TODO` with the following:
-The `database` property defines configuration for the table created in the database.
+```ts title="src/scripts/demo-products.ts"
+const { result: products } = await createProductsWorkflow(container).run({
+ input: {
+ products: productsData,
+ },
+})
-Its `extraColumns` property defines custom columns to create in the link's table.
+logger.info(`Seeded ${products.length} products.`)
-`extraColumns`'s value is an object whose keys are the names of the columns, and values are the column's configurations as an object.
+// TODO add inventory levels
+```
-### Column Configurations
+You create the generated products using the `createProductsWorkflow` imported previously from `@medusajs/medusa/core-flows`. It accepts the product data as input, and returns the created products.
-The column's configurations object accepts the following properties:
+Only thing left is to create inventory levels for the products. So, replace the last `TODO` with the following:
-- `type`: The column's type. Possible values are:
- - `string`
- - `text`
- - `integer`
- - `boolean`
- - `date`
- - `time`
- - `datetime`
- - `enum`
- - `json`
- - `array`
- - `enumArray`
- - `float`
- - `double`
- - `decimal`
- - `bigint`
- - `mediumint`
- - `smallint`
- - `tinyint`
- - `blob`
- - `uuid`
- - `uint8array`
-- `defaultValue`: The column's default value.
-- `nullable`: Whether the column can have `null` values.
+```ts title="src/scripts/demo-products.ts"
+logger.info("Seeding inventory levels.")
-***
+const { data: stockLocations } = await query.graph({
+ entity: "stock_location",
+ fields: ["id"],
+})
-## Set Custom Column when Creating Link
+const { data: inventoryItems } = await query.graph({
+ entity: "inventory_item",
+ fields: ["id"],
+})
-The object you pass to Link's `create` method accepts a `data` property. Its value is an object whose keys are custom column names, and values are the value of the custom column for this link.
+const inventoryLevels = inventoryItems.map((inventoryItem) => ({
+ location_id: stockLocations[0].id,
+ stocked_quantity: 1000000,
+ inventory_item_id: inventoryItem.id,
+}))
+
+await createInventoryLevelsWorkflow(container).run({
+ input: {
+ inventory_levels: inventoryLevels,
+ },
+})
+
+logger.info("Finished seeding inventory levels data.")
+```
+
+You use Query to retrieve the stock location, to use the first location in the application, and the inventory items.
+
+Then, you generate inventory levels for each inventory item, associating it with the first stock location.
+
+Finally, you use the `createInventoryLevelsWorkflow` from Medusa's core workflows to create the inventory levels.
+
+### Test Script
+
+To test out the script, run the following command in your project's directory:
+
+```bash
+npx medusa exec ./src/scripts/demo-products.ts
+```
+
+This seeds the products to your database. If you run your Medusa application and view the products in the dashboard, you'll find fifty new products.
+
+
+# API Route Response
+
+In this chapter, you'll learn how to send a response in your API route.
+
+## Send a JSON Response
+
+To send a JSON response, use the `json` method of the `MedusaResponse` object passed as the second parameter of your API route handler.
For example:
-Learn more about Link, how to resolve it, and its methods in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/link/index.html.md).
+```ts title="src/api/custom/route.ts" highlights={jsonHighlights}
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
-```ts
-await link.create({
- [Modules.PRODUCT]: {
- product_id: "123",
- },
- [BLOG_MODULE]: {
- post_id: "321",
- },
- data: {
- metadata: {
- test: true,
- },
- },
-})
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ res.json({
+ message: "Hello, World!",
+ })
+}
+```
+
+This API route returns the following JSON object:
+
+```json
+{
+ "message": "Hello, World!"
+}
```
***
-## Retrieve Custom Column with Link
+## Set Response Status Code
-To retrieve linked records with their custom columns, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md). A module link's definition, exported by a file under `src/links`, has a special `entryPoint` property. Use this property when specifying the `entity` property in Query's `graph` method.
+By default, setting the JSON data using the `json` method returns a response with a `200` status code.
+
+To change the status code, use the `status` method of the `MedusaResponse` object.
For example:
-```ts highlights={retrieveHighlights}
-import productPostLink from "../links/product-post"
+```ts title="src/api/custom/route.ts" highlights={statusHighlight}
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
-// ...
-
-const { data } = await query.graph({
- entity: productPostLink.entryPoint,
- fields: ["metadata", "product.*", "post.*"],
- filters: {
- product_id: "prod_123",
- },
-})
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ res.status(201).json({
+ message: "Hello, World!",
+ })
+}
```
-This retrieves the product of id `prod_123` and its linked `post` records.
-
-In the `fields` array you pass `metadata`, which is the custom column to retrieve of the link.
+The response of this API route has the status code `201`.
***
-## Update Custom Column's Value
+## Change Response Content Type
-Link's `create` method updates a link's data if the link between the specified records already exists.
+To return response data other than a JSON object, use the `writeHead` method of the `MedusaResponse` object. It allows you to set the response headers, including the content type.
-So, to update the value of a custom column in a created link, use the `create` method again passing it a new value for the custom column.
+For example, to create an API route that returns an event stream:
-For example:
+```ts highlights={streamHighlights}
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
-```ts
-await link.create({
- [Modules.PRODUCT]: {
- product_id: "123",
- },
- [BLOG_MODULE]: {
- post_id: "321",
- },
- data: {
- metadata: {
- test: false,
- },
- },
-})
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ res.writeHead(200, {
+ "Content-Type": "text/event-stream",
+ "Cache-Control": "no-cache",
+ Connection: "keep-alive",
+ })
+
+ const interval = setInterval(() => {
+ res.write("Streaming data...\n")
+ }, 3000)
+
+ req.on("end", () => {
+ clearInterval(interval)
+ res.end()
+ })
+}
```
+The `writeHead` method accepts two parameters:
-# Module Link Direction
+1. The first one is the response's status code.
+2. The second is an object of key-value pairs to set the headers of the response.
-In this chapter, you'll learn about the difference in module link directions, and which to use based on your use case.
-
-The details in this chapter don't apply to [Read-Only Module Links](https://docs.medusajs.com/learn/fundamentals/module-links/read-only/index.html.md). Refer to the [Read-Only Module Links chapter](https://docs.medusajs.com/learn/fundamentals/module-links/read-only/index.html.md) for more information on read-only links and their direction.
-
-## Link Direction
-
-The module link's direction depends on the order you pass the data model configuration parameters to `defineLink`.
-
-For example, the following defines a link from the Blog Module's `post` data model to the Product Module's `product` data model:
-
-```ts
-export default defineLink(
- BlogModule.linkable.post,
- ProductModule.linkable.product
-)
-```
-
-Whereas the following defines a link from the Product Module's `product` data model to the Blog Module's `post` data model:
-
-```ts
-export default defineLink(
- ProductModule.linkable.product,
- BlogModule.linkable.post
-)
-```
-
-The above links are two different links that serve different purposes.
+This API route opens a stream by setting the `Content-Type` in the header to `text/event-stream`. It then simulates a stream by creating an interval that writes the stream data every three seconds.
***
-## Which Link Direction to Use?
+## Do More with Responses
-### Extend Data Models
-
-If you're adding a link to a data model to extend it and add new fields, define the link from the main data model to the custom data model.
-
-For example, consider you want to add a `subtitle` custom field to the `product` data model. To do that, you define a `Subtitle` data model in your module, then define a link from the `Product` data model to it:
-
-```ts
-export default defineLink(
- ProductModule.linkable.product,
- BlogModule.linkable.subtitle
-)
-```
-
-### Associate Data Models
-
-If you're linking data models to indicate an association between them, define the link from the custom data model to the main data model.
-
-For example, consider you have `Post` data model representing a blog post, and you want to associate a blog post with a product. To do that, define a link from the `Post` data model to `Product`:
-
-```ts
-export default defineLink(
- BlogModule.linkable.post,
- ProductModule.linkable.product
-)
-```
+The `MedusaResponse` type is based on [Express's Response](https://expressjs.com/en/api.html#res). Refer to their API reference for other uses of responses.
# Event Data Payload
@@ -9058,6 +9042,230 @@ This logs the product ID received in the `product.created` event’s data payloa
Refer to [this reference](!resources!/events-reference) for a full list of events emitted by Medusa and their data payloads. */}
+# Add Data Model Check Constraints
+
+In this chapter, you'll learn how to add check constraints to your data model.
+
+## What is a Check Constraint?
+
+A check constraint is a condition that must be satisfied by records inserted into a database table, otherwise an error is thrown.
+
+For example, if you have a data model with a `price` property, you want to only allow positive number values. So, you add a check constraint that fails when inserting a record with a negative price value.
+
+***
+
+## How to Set a Check Constraint?
+
+To set check constraints on a data model, use the `checks` method. This method accepts an array of check constraints to apply on the data model.
+
+For example, to set a check constraint on a `price` property that ensures its value can only be a positive number:
+
+```ts highlights={checks1Highlights}
+import { model } from "@medusajs/framework/utils"
+
+const CustomProduct = model.define("custom_product", {
+ // ...
+ price: model.bigNumber(),
+})
+.checks([
+ (columns) => `${columns.price} >= 0`,
+])
+```
+
+The item passed in the array parameter of `checks` can be a callback function that accepts as a parameter an object whose keys are the names of the properties in the data model schema, and values the respective column name in the database.
+
+The function returns a string indicating the [SQL check constraint expression](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS). In the expression, use the `columns` parameter to access a property's column name.
+
+You can also pass an object to the `checks` method:
+
+```ts highlights={checks2Highlights}
+import { model } from "@medusajs/framework/utils"
+
+const CustomProduct = model.define("custom_product", {
+ // ...
+ price: model.bigNumber(),
+})
+.checks([
+ {
+ name: "custom_product_price_check",
+ expression: (columns) => `${columns.price} >= 0`,
+ },
+])
+```
+
+The object accepts the following properties:
+
+- `name`: The check constraint's name.
+- `expression`: A function similar to the one that can be passed to the array. It accepts an object of columns and returns an [SQL check constraint expression](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS).
+
+***
+
+## Apply in Migrations
+
+After adding the check constraint, make sure to generate and run migrations if you already have the table in the database. Otherwise, the check constraint won't be reflected.
+
+To generate a migration for the data model's module then reflect it on the database, run the following command:
+
+```bash
+npx medusa db:generate custom_module
+npx medusa db:migrate
+```
+
+The first command generates the migration under the `migrations` directory of your module's directory, and the second reflects it on the database.
+
+
+# Data Model Database Index
+
+In this chapter, you’ll learn how to define a database index on a data model.
+
+You can also define an index on a property as explained in the [Properties chapter](https://docs.medusajs.com/learn/fundamentals/data-models/properties#define-database-index-on-property/index.html.md).
+
+## Define Database Index on Data Model
+
+A data model has an `indexes` method that defines database indices on its properties.
+
+The index can be on multiple columns (composite index). For example:
+
+```ts highlights={dataModelIndexHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const MyCustom = model.define("my_custom", {
+ id: model.id().primaryKey(),
+ name: model.text(),
+ age: model.number(),
+}).indexes([
+ {
+ on: ["name", "age"],
+ },
+])
+
+export default MyCustom
+```
+
+The `indexes` method receives an array of indices as a parameter. Each index is an object with a required `on` property indicating the properties to apply the index on.
+
+In the above example, you define a composite index on the `name` and `age` properties.
+
+### Index Conditions
+
+An index can have conditions. For example:
+
+```ts highlights={conditionHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const MyCustom = model.define("my_custom", {
+ id: model.id().primaryKey(),
+ name: model.text(),
+ age: model.number(),
+}).indexes([
+ {
+ on: ["name", "age"],
+ where: {
+ age: 30,
+ },
+ },
+])
+
+export default MyCustom
+```
+
+The index object passed to `indexes` accepts a `where` property whose value is an object of conditions. The object's key is a property's name, and its value is the condition on that property.
+
+In the example above, the composite index is created on the `name` and `age` properties when the `age`'s value is `30`.
+
+A property's condition can be a negation. For example:
+
+```ts highlights={negationHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const MyCustom = model.define("my_custom", {
+ id: model.id().primaryKey(),
+ name: model.text(),
+ age: model.number().nullable(),
+}).indexes([
+ {
+ on: ["name", "age"],
+ where: {
+ age: {
+ $ne: null,
+ },
+ },
+ },
+])
+
+export default MyCustom
+```
+
+A property's value in `where` can be an object having a `$ne` property. `$ne`'s value indicates what the specified property's value shouldn't be.
+
+In the example above, the composite index is created on the `name` and `age` properties when `age`'s value is not `null`.
+
+### Unique Database Index
+
+The object passed to `indexes` accepts a `unique` property indicating that the created index must be a unique index.
+
+For example:
+
+```ts highlights={uniqueHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const MyCustom = model.define("my_custom", {
+ id: model.id().primaryKey(),
+ name: model.text(),
+ age: model.number(),
+}).indexes([
+ {
+ on: ["name", "age"],
+ unique: true,
+ },
+])
+
+export default MyCustom
+```
+
+This creates a unique composite index on the `name` and `age` properties.
+
+
+# Infer Type of Data Model
+
+In this chapter, you'll learn how to infer the type of a data model.
+
+## How to Infer Type of Data Model?
+
+Consider you have a `Post` data model. You can't reference this data model in a type, such as a workflow input or service method output types, since it's a variable.
+
+Instead, Medusa provides `InferTypeOf` that transforms your data model to a type.
+
+For example:
+
+```ts
+import { InferTypeOf } from "@medusajs/framework/types"
+import { Post } from "../modules/blog/models/post" // relative path to the model
+
+export type Post = InferTypeOf
+```
+
+`InferTypeOf` accepts as a type argument the type of the data model.
+
+Since the `Post` data model is a variable, use the `typeof` operator to pass the data model as a type argument to `InferTypeOf`.
+
+You can now use the `Post` type to reference a data model in other types, such as in workflow inputs or service method outputs:
+
+```ts title="Example Service"
+// other imports...
+import { InferTypeOf } from "@medusajs/framework/types"
+import { Post } from "../models/post"
+
+type Post = InferTypeOf
+
+class BlogModuleService extends MedusaService({ Post }) {
+ async doSomething(): Promise {
+ // ...
+ }
+}
+```
+
+
# Emit Workflow and Service Events
In this chapter, you'll learn about event types and how to emit an event in a service or workflow.
@@ -9226,1758 +9434,6 @@ If you execute the `performAction` method of your service, the event is emitted
Any subscribers listening to the event are also executed.
-# Link
-
-In this chapter, you’ll learn what Link is and how to use it to manage links.
-
-As of [Medusa v2.2.0](https://github.com/medusajs/medusa/releases/tag/v2.2.0), Remote Link has been deprecated in favor of Link. They have the same usage, so you only need to change the key used to resolve the tool from the Medusa container as explained below.
-
-## What is Link?
-
-Link is a class with utility methods to manage links between data models. It’s registered in the Medusa container under the `link` registration name.
-
-For example:
-
-```ts collapsibleLines="1-9" expandButtonLabel="Show Imports"
-import {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import {
- ContainerRegistrationKeys,
-} from "@medusajs/framework/utils"
-
-export async function POST(
- req: MedusaRequest,
- res: MedusaResponse
-): Promise {
- const link = req.scope.resolve(
- ContainerRegistrationKeys.LINK
- )
-
- // ...
-}
-```
-
-You can use its methods to manage links, such as create or delete links.
-
-***
-
-## Create Link
-
-To create a link between records of two data models, use the `create` method of Link.
-
-For example:
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
- "helloModuleService": {
- my_custom_id: "mc_123",
- },
-})
-```
-
-The `create` method accepts as a parameter an object. The object’s keys are the names of the linked modules.
-
-The keys (names of linked modules) must be in the same [direction](https://docs.medusajs.com/learn/fundamentals/module-links/directions/index.html.md) of the link definition.
-
-The value of each module’s property is an object, whose keys are of the format `{data_model_snake_name}_id`, and values are the IDs of the linked record.
-
-So, in the example above, you link a record of the `MyCustom` data model in a `hello` module to a `Product` record in the Product Module.
-
-***
-
-## Dismiss Link
-
-To remove a link between records of two data models, use the `dismiss` method of Link.
-
-For example:
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.dismiss({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
- "helloModuleService": {
- my_custom_id: "mc_123",
- },
-})
-```
-
-The `dismiss` method accepts the same parameter type as the [create method](#create-link).
-
-The keys (names of linked modules) must be in the same [direction](https://docs.medusajs.com/learn/fundamentals/module-links/directions/index.html.md) of the link definition.
-
-***
-
-## Cascade Delete Linked Records
-
-If a record is deleted, use the `delete` method of Link to delete all linked records.
-
-For example:
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await productModuleService.deleteVariants([variant.id])
-
-await link.delete({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
-})
-```
-
-This deletes all records linked to the deleted product.
-
-***
-
-## Restore Linked Records
-
-If a record that was previously soft-deleted is now restored, use the `restore` method of Link to restore all linked records.
-
-For example:
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await productModuleService.restoreProducts(["prod_123"])
-
-await link.restore({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
-})
-```
-
-
-# Query
-
-In this chapter, you’ll learn about Query and how to use it to fetch data from modules.
-
-## What is Query?
-
-Query fetches data across modules. It’s a set of methods registered in the Medusa container under the `query` key.
-
-In all resources that can access the [Medusa Container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md), such as API routes or workflows, you can resolve Query to fetch data across custom modules and Medusa’s commerce modules.
-
-***
-
-## Query Example
-
-For example, create the route `src/api/query/route.ts` with the following content:
-
-```ts title="src/api/query/route.ts" highlights={exampleHighlights} collapsibleLines="1-8" expandButtonLabel="Show Imports"
-import {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import {
- ContainerRegistrationKeys,
-} from "@medusajs/framework/utils"
-
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
-
- const { data: myCustoms } = await query.graph({
- entity: "my_custom",
- fields: ["id", "name"],
- })
-
- res.json({ my_customs: myCustoms })
-}
-```
-
-In the above example, you resolve Query from the Medusa container using the `ContainerRegistrationKeys.QUERY` (`query`) key.
-
-Then, you run a query using its `graph` method. This method accepts as a parameter an object with the following required properties:
-
-- `entity`: The data model's name, as specified in the first parameter of the `model.define` method used for the data model's definition.
-- `fields`: An array of the data model’s properties to retrieve in the result.
-
-The method returns an object that has a `data` property, which holds an array of the retrieved data. For example:
-
-```json title="Returned Data"
-{
- "data": [
- {
- "id": "123",
- "name": "test"
- }
- ]
-}
-```
-
-***
-
-## Querying the Graph
-
-When you use the `query.graph` method, you're running a query through an internal graph that the Medusa application creates.
-
-This graph collects data models of all modules in your application, including commerce and custom modules, and identifies relations and links between them.
-
-***
-
-## Retrieve Linked Records
-
-Retrieve the records of a linked data model by passing in `fields` the data model's name suffixed with `.*`.
-
-For example:
-
-```ts highlights={[["6"]]}
-const { data: myCustoms } = await query.graph({
- entity: "my_custom",
- fields: [
- "id",
- "name",
- "product.*",
- ],
-})
-```
-
-`.*` means that all of data model's properties should be retrieved. To retrieve a specific property, replace the `*` with the property's name. For example, `product.title`.
-
-### Retrieve List Link Records
-
-If the linked data model has `isList` enabled in the link definition, pass in `fields` the data model's plural name suffixed with `.*`.
-
-For example:
-
-```ts highlights={[["6"]]}
-const { data: myCustoms } = await query.graph({
- entity: "my_custom",
- fields: [
- "id",
- "name",
- "products.*",
- ],
-})
-```
-
-### Apply Filters and Pagination on Linked Records
-
-Consider you want to apply filters or pagination configurations on the product(s) linked to `my_custom`. To do that, you must query the module link's table instead.
-
-As mentioned in the [Module Link](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md) documentation, Medusa creates a table for your module link. So, not only can you retrieve linked records, but you can also retrieve the records in a module link's table.
-
-A module link's definition, exported by a file under `src/links`, has a special `entryPoint` property. Use this property when specifying the `entity` property in Query's `graph` method.
-
-For example:
-
-```ts highlights={queryLinkTableHighlights}
-import productCustomLink from "../../../links/product-custom"
-
-// ...
-
-const { data: productCustoms } = await query.graph({
- entity: productCustomLink.entryPoint,
- fields: ["*", "product.*", "my_custom.*"],
- pagination: {
- take: 5,
- skip: 0,
- },
-})
-```
-
-In the object passed to the `graph` method:
-
-- You pass the `entryPoint` property of the link definition as the value for `entity`. So, Query will retrieve records from the module link's table.
-- You pass three items to the `field` property:
- - `*` to retrieve the link table's fields. This is useful if the link table has [custom columns](https://docs.medusajs.com/learn/fundamentals/module-links/custom-columns/index.html.md).
- - `product.*` to retrieve the fields of a product record linked to a `MyCustom` record.
- - `my_custom.*` to retrieve the fields of a `MyCustom` record linked to a product record.
-
-You can then apply any [filters](#apply-filters) or [pagination configurations](#apply-pagination).
-
-The returned `data` is similar to the following:
-
-```json title="Example Result"
-[{
- "id": "123",
- "product_id": "prod_123",
- "my_custom_id": "123",
- "product": {
- "id": "prod_123",
- // other product fields...
- },
- "my_custom": {
- "id": "123",
- // other my_custom fields...
- }
-}]
-```
-
-***
-
-## Apply Filters
-
-```ts highlights={[["6"], ["7"], ["8"], ["9"]]}
-const { data: myCustoms } = await query.graph({
- entity: "my_custom",
- fields: ["id", "name"],
- filters: {
- id: [
- "mc_01HWSVWR4D2XVPQ06DQ8X9K7AX",
- "mc_01HWSVWK3KYHKQEE6QGS2JC3FX",
- ],
- },
-})
-```
-
-The `query.graph` function accepts a `filters` property. You can use this property to filter retrieved records.
-
-In the example above, you filter the `my_custom` records by multiple IDs.
-
-Filters don't apply on fields of linked data models from other modules.
-
-***
-
-## Apply Pagination
-
-```ts highlights={[["8", "skip", "The number of records to skip before fetching the results."], ["9", "take", "The number of records to fetch."]]}
-const {
- data: myCustoms,
- metadata: { count, take, skip } = {},
-} = await query.graph({
- entity: "my_custom",
- fields: ["id", "name"],
- pagination: {
- skip: 0,
- take: 10,
- },
-})
-```
-
-The `graph` method's object parameter accepts a `pagination` property to configure the pagination of retrieved records.
-
-To paginate the returned records, pass the following properties to `pagination`:
-
-- `skip`: (required to apply pagination) The number of records to skip before fetching the results.
-- `take`: The number of records to fetch.
-
-When you provide the pagination fields, the `query.graph` method's returned object has a `metadata` property. Its value is an object having the following properties:
-
-- skip: (\`number\`) The number of records skipped.
-- take: (\`number\`) The number of records requested to fetch.
-- count: (\`number\`) The total number of records.
-
-### Sort Records
-
-```ts highlights={[["5"], ["6"], ["7"]]}
-const { data: myCustoms } = await query.graph({
- entity: "my_custom",
- fields: ["id", "name"],
- pagination: {
- order: {
- name: "DESC",
- },
- },
-})
-```
-
-Sorting doesn't work on fields of linked data models from other modules.
-
-To sort returned records, pass an `order` property to `pagination`.
-
-The `order` property is an object whose keys are property names, and values are either:
-
-- `ASC` to sort records by that property in ascending order.
-- `DESC` to sort records by that property in descending order.
-
-***
-
-## Request Query Configurations
-
-For API routes that retrieve a single or list of resources, Medusa provides a `validateAndTransformQuery` middleware that:
-
-- Validates accepted query parameters, as explained in [this documentation](https://docs.medusajs.com/learn/fundamentals/api-routes/validation/index.html.md).
-- Parses configurations that are received as query parameters to be passed to Query.
-
-Using this middleware allows you to have default configurations for retrieved fields and relations or pagination, while allowing clients to customize them per request.
-
-### Step 1: Add Middleware
-
-The first step is to use the `validateAndTransformQuery` middleware on the `GET` route. You add the middleware in `src/api/middlewares.ts`:
-
-```ts title="src/api/middlewares.ts"
-import {
- validateAndTransformQuery,
- defineMiddlewares,
-} from "@medusajs/framework/http"
-import { createFindParams } from "@medusajs/medusa/api/utils/validators"
-
-export const GetCustomSchema = createFindParams()
-
-export default defineMiddlewares({
- routes: [
- {
- matcher: "/customs",
- method: "GET",
- middlewares: [
- validateAndTransformQuery(
- GetCustomSchema,
- {
- defaults: [
- "id",
- "name",
- "products.*",
- ],
- isList: true,
- }
- ),
- ],
- },
- ],
-})
-```
-
-The `validateAndTransformQuery` accepts two parameters:
-
-1. A Zod validation schema for the query parameters, which you can learn more about in the [API Route Validation documentation](https://docs.medusajs.com/learn/fundamentals/api-routes/validation/index.html.md). Medusa has a `createFindParams` utility that generates a Zod schema that accepts four query parameters:
- 1. `fields`: The fields and relations to retrieve in the returned resources.
- 2. `offset`: The number of items to skip before retrieving the returned items.
- 3. `limit`: The maximum number of items to return.
- 4. `order`: The fields to order the returned items by in ascending or descending order.
-2. A Query configuration object. It accepts the following properties:
- 1. `defaults`: An array of default fields and relations to retrieve in each resource.
- 2. `isList`: A boolean indicating whether a list of items are returned in the response.
- 3. `allowed`: An array of fields and relations allowed to be passed in the `fields` query parameter.
- 4. `defaultLimit`: A number indicating the default limit to use if no limit is provided. By default, it's `50`.
-
-### Step 2: Use Configurations in API Route
-
-After applying this middleware, your API route now accepts the `fields`, `offset`, `limit`, and `order` query parameters mentioned above.
-
-The middleware transforms these parameters to configurations that you can pass to Query in your API route handler. These configurations are stored in the `queryConfig` parameter of the `MedusaRequest` object.
-
-As of [Medusa v2.2.0](https://github.com/medusajs/medusa/releases/tag/v2.2.0), `remoteQueryConfig` has been depercated in favor of `queryConfig`. Their usage is still the same, only the property name has changed.
-
-For example, Create the file `src/api/customs/route.ts` with the following content:
-
-```ts title="src/api/customs/route.ts"
-import {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import {
- ContainerRegistrationKeys,
-} from "@medusajs/framework/utils"
-
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
-
- const { data: myCustoms } = await query.graph({
- entity: "my_custom",
- ...req.queryConfig,
- })
-
- res.json({ my_customs: myCustoms })
-}
-```
-
-This adds a `GET` API route at `/customs`, which is the API route you added the middleware for.
-
-In the API route, you pass `req.queryConfig` to `query.graph`. `queryConfig` has properties like `fields` and `pagination` to configure the query based on the default values you specified in the middleware, and the query parameters passed in the request.
-
-### Test it Out
-
-To test it out, start your Medusa application and send a `GET` request to the `/customs` API route. A list of records are retrieved with the specified fields in the middleware.
-
-```json title="Returned Data"
-{
- "my_customs": [
- {
- "id": "123",
- "name": "test"
- }
- ]
-}
-```
-
-Try passing one of the Query configuration parameters, like `fields` or `limit`, and you'll see its impact on the returned result.
-
-Learn more about [specifing fields and relations](https://docs.medusajs.com/api/store#select-fields-and-relations) and [pagination](https://docs.medusajs.com/api/store#pagination) in the API reference.
-
-
-# Read-Only Module Link
-
-In this chapter, you’ll learn what a read-only module link is and how to define one.
-
-## What is a Read-Only Module Link?
-
-Consider a scenario where you need to access related records from another module, but don't want the overhead of managing or storing the links between them. This can include cases where you're working with external data models not stored in your Medusa database, such as third-party systems.
-
-In those cases, instead of defining a [Module Link](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md) whose linked records must be stored in a link table in the database, you can use a read-only module link. A read-only module link builds a virtual relation from one data model to another in a different module without creating a link table in the database. Instead, the linked record's ID is stored in the first data model's field.
-
-For example, Medusa creates a read-only module link from the `Cart` data model of the [Cart Module](https://docs.medusajs.com/resources/commerce-modules/cart/index.html.md) to the `Customer` data model of the [Customer Module](https://docs.medusajs.com/resources/commerce-modules/customer/index.html.md). This link allows you to access the details of the cart's customer without managing the link. Instead, the customer's ID is stored in the `Cart` data model.
-
-
-
-***
-
-## How to Define a Read-Only Module Link
-
-The `defineLink` function accepts an optional third-parameter object that can hold additional configurations for the module link.
-
-If you're not familiar with the `defineLink` function, refer to the [Module Links chapter](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md) for more information.
-
-To make the module link read-only, pass the `readOnly` property as `true`. You must also set in the link configuration of the first data model a `field` property that specifies the data model's field where the linked record's ID is stored.
-
-For example:
-
-```ts highlights={highlights}
-import BlogModule from "../modules/blog"
-import ProductModule from "@medusajs/medusa/product"
-import { defineLink } from "@medusajs/framework/utils"
-
-export default defineLink(
- {
- linkable: BlogModule.linkable.post,
- field: "product_id",
- },
- ProductModule.linkable.product,
- {
- readOnly: true
- }
-)
-```
-
-In this example, you define a read-only module link from the Blog Module's `post` data model to the Product Module's `product` data model. You do that by:
-
-- Passing an object as a first parameter that accepts the linkable configuration and the field where the linked record's ID is stored.
-- Setting the `readOnly` property to `true` in the third parameter.
-
-Unlike the stored module link, Medusa will not create a table in the database for this link. Instead, Medusa uses the ID stored in the specified field of the first data model to retrieve the linked record.
-
-***
-
-## Retrieve Read-Only Linked Record
-
-[Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md) allows you to retrieve records linked through a read-only module link.
-
-For example, assuming you have the module link created in [the above section](#how-to-define-a-read-only-module-link), you can retrieve a post and its linked product as follows:
-
-```ts
-const { result } = await query.graph({
- entity: "post",
- fields: ["id", "product.*"],
- filters: {
- id: "post_123"
- }
-})
-```
-
-In the above example, you retrieve a post and its linked product. Medusa will use the ID of the product in the post's `product_id` field to determine which product should be retrieved.
-
-***
-
-## Read-Only Module Link Direction
-
-A read-only module is uni-directional. So, you can only retrieve the linked record from the first data model. If you need to access the linked record from the second data model, you must define another read-only module link in the opposite direction.
-
-In the `blog` -> `product` example, you can access a post's product, but you can't access a product's posts. You would have to define another read-only module link from `product` to `blog` to access a product's posts.
-
-***
-
-## Inverse Read-Only Module Link
-
-An inverse read-only module link is a read-only module link that allows you to access the linked record based on the ID stored in the second data model.
-
-For example, consider you want to access a product's posts. You can define a read-only module link from the Product Module's `product` data model to the Blog Module's `post` data model:
-
-```ts
-import BlogModule from "../modules/blog"
-import ProductModule from "@medusajs/medusa/product"
-import { defineLink } from "@medusajs/framework/utils"
-
-export default defineLink(
- {
- linkable: ProductModule.linkable.product,
- field: "id",
- },
- {
- ...BlogModule.linkable.post.id,
- primaryKey: "product_id",
- },
- {
- readOnly: true
- }
-)
-```
-
-In the above example, you define a read-only module link from the Product Module's `product` data model to the Blog Module's `post` data model. This link allows you to access a product's posts.
-
-Since you can't add a `post_id` field to the `product` data model, you must:
-
-1. Set the `field` property in the first data model's link configuration to the product's ID field.
-2. Spread the `BlogModule.linkable.post.id` object in the second parameter object and set the `primaryKey` property to the field in the `post` data model that holds the product's ID.
-
-You can now retrieve a product and its linked posts:
-
-```ts
-const { result } = await query.graph({
- entity: "product",
- fields: ["id", "post.*"],
- filters: {
- id: "prod_123"
- }
-})
-```
-
-***
-
-## One-to-One or One-to-Many?
-
-When you retrieve the linked record through a read-only module link, the retrieved data may be an object (one-to-one) or an array of objects (one-to-many) based on different criteria.
-
-|Scenario|Relation Type|
-|---|---|---|
-|The first data model's |One-to-one relation|
-|The first data model's |One-to-many relation|
-|The read-only module link is inversed.|One-to-many relation if multiple records in the second data model have the same ID of the first data model. Otherwise, one-to-one relation.|
-
-### One-to-One Relation
-
-Consider the first read-only module link you defined in this chapter:
-
-```ts
-import BlogModule from "../modules/blog"
-import ProductModule from "@medusajs/medusa/product"
-
-export default defineLink(
- {
- linkable: BlogModule.linkable.post,
- field: "product_id",
- },
- ProductModule.linkable.product,
- {
- readOnly: true
- }
-)
-```
-
-Since the `product_id` field of a post stores the ID of a single product, the link is a one-to-one relation. When querying a post, you'll get a single product object:
-
-```json title="Example Data"
-[
- {
- "id": "post_123",
- "product_id": "prod_123",
- "product": {
- "id": "prod_123",
- // ...
- }
- }
-]
-```
-
-### One-to-Many Relation
-
-Consider the read-only module link from the `post` data model uses an array of product IDs:
-
-```ts
-import BlogModule from "../modules/blog"
-import ProductModule from "@medusajs/medusa/product"
-
-export default defineLink(
- {
- linkable: BlogModule.linkable.post,
- field: "product_ids",
- },
- ProductModule.linkable.product,
- {
- readOnly: true
- }
-)
-```
-
-Where `product_ids` in the `post` data model is an array of strings. In this case, the link would be a one-to-many relation. So, an array of products would be returned when querying a post:
-
-```json title="Example Data"
-[
- {
- "id": "post_123",
- "product_ids": ["prod_123", "prod_124"],
- "product": [
- {
- "id": "prod_123",
- // ...
- },
- {
- "id": "prod_124",
- // ...
- }
- ]
- }
-]
-```
-
-### Relation with Inversed Read-Only Link
-
-If you define an inversed read-only module link where the ID of the linked record is stored in the second data model, the link can be either one-to-one or one-to-many based on the number of records in the second data model that have the same ID of the first data model.
-
-For example, consider the `product` -> `post` link you defined in an earlier section:
-
-```ts
-import BlogModule from "../modules/blog"
-import ProductModule from "@medusajs/medusa/product"
-import { defineLink } from "@medusajs/framework/utils"
-
-export default defineLink(
- {
- linkable: ProductModule.linkable.product,
- field: "id",
- },
- {
- ...BlogModule.linkable.post.id,
- primaryKey: "product_id",
- },
- {
- readOnly: true
- }
-)
-```
-
-In the above snippet, the ID of the product is stored in the `post`'s `product_id` string field.
-
-When you retrieve the post of a product, it may be a post object, or an array of post objects if multiple posts are linked to the product:
-
-```json title="Example Data"
-[
- {
- "id": "prod_123",
- "post": {
- "id": "post_123",
- "product_id": "prod_123"
- // ...
- }
- },
- {
- "id": "prod_321",
- "post": [
- {
- "id": "post_123",
- "product_id": "prod_321"
- // ...
- },
- {
- "id": "post_124",
- "product_id": "prod_321"
- // ...
- }
- ]
- }
-]
-```
-
-If, however, you use an array field in `post`, the relation would always be one-to-many:
-
-```json title="Example Data"
-[
- {
- "id": "prod_123",
- "post": [
- {
- "id": "post_123",
- "product_id": "prod_123"
- // ...
- }
- ]
- }
-]
-```
-
-#### Force One-to-Many Relation
-
-Alternatively, you can force a one-to-many relation by setting `isList` to `true` in the first data model's link configuration. For example:
-
-```ts
-import BlogModule from "../modules/blog"
-import ProductModule from "@medusajs/medusa/product"
-import { defineLink } from "@medusajs/framework/utils"
-
-export default defineLink(
- {
- linkable: ProductModule.linkable.product,
- field: "id",
- isList: true
- },
- {
- ...BlogModule.linkable.post.id,
- primaryKey: "product_id"
- },
- {
- readOnly: true
- }
-)
-```
-
-In this case, the relation would always be one-to-many, even if only one post is linked to a product:
-
-```json title="Example Data"
-[
- {
- "id": "prod_123",
- "post": [
- {
- "id": "post_123",
- "product_id": "prod_123"
- // ...
- }
- ]
- }
-]
-```
-
-***
-
-## Example: Read-Only Module Link for Virtual Data Models
-
-Read-only module links are most useful when working with data models that aren't stored in your Medusa database. For example, data that is stored in a third-party system. In those cases, you can define a read-only module link between a data model in Medusa and the data model in the external system, facilitating the retrieval of the linked data.
-
-To define the read-only module link to a virtual data model, you must:
-
-1. Create a `list` method in the custom module's service. This method retrieves the linked records filtered by the ID(s) of the first data model.
-2. Define the read-only module link from the first data model to the virtual data model.
-3. Use Query to retrieve the first data model and its linked records from the virtual data model.
-
-For example, consider you have a third-party Content-Management System (CMS) that you're integrating with Medusa, and you want to retrieve the posts in the CMS associated with a product in Medusa.
-
-To do that, first, create a CMS Module having the following service:
-
-Refer to the [Modules chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md) to learn how to create a module and its service.
-
-```ts title="src/modules/cms/service.ts"
-type CmsModuleOptions = {
- apiKey: string
-}
-
-export default class CmsModuleService {
- private client
-
- constructor({}, options: CmsModuleOptions) {
- this.client = new Client(options)
- }
-
- async list(
- filter: {
- id: string | string[]
- }
- ) {
- return this.client.getPosts(filter)
- /**
- - Example of returned data:
- -
- - [
- - {
- - "id": "post_123",
- - "product_id": "prod_321"
- - },
- - {
- - "id": "post_456",
- - "product_id": "prod_654"
- - }
- - ]
- */
- }
-}
-```
-
-The above service initializes a client, assuming your CMS has an SDK that allows you to retrieve posts.
-
-The service must have a `list` method to be part of the read-only module link. This method accepts the ID(s) of the products to retrieve their associated posts. The posts must include the product's ID in a field, such as `product_id`.
-
-Next, define a read-only module link from the Product Module to the CMS Module:
-
-```ts title="src/links/product-cms.ts"
-import { defineLink } from "@medusajs/framework/utils"
-import ProductModule from "@medusajs/medusa/product"
-import { CMS_MODULE } from "../modules/cms"
-
-export default defineLink(
- {
- linkable: ProductModule.linkable.product,
- field: "id"
- },
- {
- linkable: {
- serviceName: CMS_MODULE,
- alias: "cms_post",
- primaryKey: "product_id",
- }
- },
- {
- readOnly: true,
- }
-)
-```
-
-To define the read-only module link, you must pass to `defineLink`:
-
-1. The first parameter: an object with the linkable configuration of the data model in Medusa, and the fields that will be passed as a filter to the CMS service. For example, if you want to filter by product title instead, you can pass `title` instead of `id`.
-2. The second parameter: an object with the linkable configuration of the virtual data model in the CMS. This object must have the following properties:
- - `serviceName`: The name of the service, which is the CMS Module's name. Medusa uses this name to resolve the module's service from the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md).
- - `alias`: The alias to use when querying the linked records. You'll see how that works in a bit.
- - `primaryKey`: The field in the CMS data model that holds the ID of a product.
-3. The third parameter: an object with the `readOnly` property set to `true`.
-
-Now, you can use Query to retrieve a product and its linked post from the CMS:
-
-```ts
-const { data } = await query.graph({
- entity: "product",
- fields: ["id", "cms_post.*"],
-})
-```
-
-In the above example, each product that has a CMS post with the `product_id` field set to the product's ID will be retrieved:
-
-```json title="Example Data"
-[
- {
- "id": "prod_123",
- "cms_post": {
- "id": "post_123",
- "product_id": "prod_123",
- // ...
- }
- }
-]
-```
-
-If multiple posts have their `product_id` set to a product's ID, an array of posts is returned instead:
-
-```json title="Example Data"
-[
- {
- "id": "prod_123",
- "cms_post": [
- {
- "id": "post_123",
- "product_id": "prod_123",
- // ...
- },
- {
- "id": "post_124",
- "product_id": "prod_123",
- // ...
- }
- ]
- }
-]
-```
-
-[Sanity Integration Tutorial](https://docs.medusajs.com/resources/integrations/guides/sanity/index.html.md).
-
-
-# Query Context
-
-In this chapter, you'll learn how to pass contexts when retrieving data with [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md).
-
-## What is Query Context?
-
-Query context is a way to pass additional information when retrieving data with Query. This data can be useful when applying custom transformations to the retrieved data based on the current context.
-
-For example, consider you have a Blog Module with posts and authors. You can accept the user's language as a context and return the posts in the user's language. Another example is how Medusa uses Query Context to [retrieve product variants' prices based on the customer's currency](https://docs.medusajs.com/resources/commerce-modules/product/guides/price/index.html.md).
-
-***
-
-## How to Use Query Context
-
-The `query.graph` method accepts an optional `context` parameter that can be used to pass additional context either to the data model you're retrieving (for example, `post`), or its related and linked models (for example, `author`).
-
-You initialize a context using `QueryContext` from the Modules SDK. It accepts an object of contexts as an argument.
-
-For example, to retrieve posts using Query while passing the user's language as a context:
-
-```ts
-const { data } = await query.graph({
- entity: "post",
- fields: ["*"],
- context: QueryContext({
- lang: "es",
- }),
-})
-```
-
-In this example, you pass in the context a `lang` property whose value is `es`.
-
-Then, to handle the context while retrieving records of the data model, in the associated module's service you override the generated `list` method of the data model.
-
-For example, continuing the example above, you can override the `listPosts` method of the Blog Module's service to handle the context:
-
-```ts highlights={highlights2}
-import { MedusaContext, MedusaService } from "@medusajs/framework/utils"
-import { Context, FindConfig } from "@medusajs/framework/types"
-import Post from "./models/post"
-import Author from "./models/author"
-
-class BlogModuleService extends MedusaService({
- Post,
- Author,
-}){
- // @ts-ignore
- async listPosts(
- filters?: any,
- config?: FindConfig | undefined,
- @MedusaContext() sharedContext?: Context | undefined
- ) {
- const context = filters.context ?? {}
- delete filters.context
-
- let posts = await super.listPosts(filters, config, sharedContext)
-
- if (context.lang === "es") {
- posts = posts.map((post) => {
- return {
- ...post,
- title: post.title + " en español",
- }
- })
- }
-
- return posts
- }
-}
-
-export default BlogModuleService
-```
-
-In the above example, you override the generated `listPosts` method. This method receives as a first parameter the filters passed to the query, but it also includes a `context` property that holds the context passed to the query.
-
-You extract the context from `filters`, then retrieve the posts using the parent's `listPosts` method. After that, if the language is set in the context, you transform the titles of the posts.
-
-All posts returned will now have their titles appended with "en español".
-
-Learn more about the generated `list` method in [this reference](https://docs.medusajs.com/resources/service-factory-reference/methods/list/index.html.md).
-
-### Using Pagination with Query
-
-If you pass pagination fields to `query.graph`, you must also override the `listAndCount` method in the service.
-
-For example, following along with the previous example, you must override the `listAndCountPosts` method of the Blog Module's service:
-
-```ts
-import { MedusaContext, MedusaService } from "@medusajs/framework/utils"
-import { Context, FindConfig } from "@medusajs/framework/types"
-import Post from "./models/post"
-import Author from "./models/author"
-
-class BlogModuleService extends MedusaService({
- Post,
- Author,
-}){
- // @ts-ignore
- async listAndCountPosts(
- filters?: any,
- config?: FindConfig | undefined,
- @MedusaContext() sharedContext?: Context | undefined
- ) {
- const context = filters.context ?? {}
- delete filters.context
-
- const result = await super.listAndCountPosts(
- filters,
- config,
- sharedContext
- )
-
- if (context.lang === "es") {
- result.posts = posts.map((post) => {
- return {
- ...post,
- title: post.title + " en español",
- }
- })
- }
-
- return result
- }
-}
-
-export default BlogModuleService
-```
-
-Now, the `listAndCountPosts` method will handle the context passed to `query.graph` when you pass pagination fields. You can also move the logic to transform the posts' titles to a separate method and call it from both `listPosts` and `listAndCountPosts`.
-
-***
-
-## Passing Query Context to Related Data Models
-
-If you're retrieving a data model and you want to pass context to its associated model in the same module, you can pass them as part of `QueryContext`'s parameter, then handle them in the same `list` method.
-
-For linked data models, check out the [next section](#passing-query-context-to-linked-data-models).
-
-For example, to pass a context for the post's authors:
-
-```ts highlights={highlights3}
-const { data } = await query.graph({
- entity: "post",
- fields: ["*"],
- context: QueryContext({
- lang: "es",
- author: QueryContext({
- lang: "es",
- }),
- }),
-})
-```
-
-Then, in the `listPosts` method, you can handle the context for the post's authors:
-
-```ts highlights={highlights4}
-import { MedusaContext, MedusaService } from "@medusajs/framework/utils"
-import { Context, FindConfig } from "@medusajs/framework/types"
-import Post from "./models/post"
-import Author from "./models/author"
-
-class BlogModuleService extends MedusaService({
- Post,
- Author,
-}){
- // @ts-ignore
- async listPosts(
- filters?: any,
- config?: FindConfig | undefined,
- @MedusaContext() sharedContext?: Context | undefined
- ) {
- const context = filters.context ?? {}
- delete filters.context
-
- let posts = await super.listPosts(filters, config, sharedContext)
-
- const isPostLangEs = context.lang === "es"
- const isAuthorLangEs = context.author?.lang === "es"
-
- if (isPostLangEs || isAuthorLangEs) {
- posts = posts.map((post) => {
- return {
- ...post,
- title: isPostLangEs ? post.title + " en español" : post.title,
- author: {
- ...post.author,
- name: isAuthorLangEs ? post.author.name + " en español" : post.author.name,
- },
- }
- })
- }
-
- return posts
- }
-}
-
-export default BlogModuleService
-```
-
-The context in `filters` will also have the context for `author`, which you can use to make transformations to the post's authors.
-
-***
-
-## Passing Query Context to Linked Data Models
-
-If you're retrieving a data model and you want to pass context to a linked model in a different module, pass to the `context` property an object instead, where its keys are the linked model's name and the values are the context for that linked model.
-
-For example, consider the Product Module's `Product` data model is linked to the Blog Module's `Post` data model. You can pass context to the `Post` data model while retrieving products like so:
-
-```ts highlights={highlights5}
-const { data } = await query.graph({
- entity: "product",
- fields: ["*", "post.*"],
- context: {
- post: QueryContext({
- lang: "es",
- }),
- },
-})
-```
-
-In this example, you retrieve products and their associated posts. You also pass a context for `post`, indicating the customer's language.
-
-To handle the context, you override the generated `listPosts` method of the Blog Module as explained [previously](#how-to-use-query-context).
-
-
-# Data Model Database Index
-
-In this chapter, you’ll learn how to define a database index on a data model.
-
-You can also define an index on a property as explained in the [Properties chapter](https://docs.medusajs.com/learn/fundamentals/data-models/properties#define-database-index-on-property/index.html.md).
-
-## Define Database Index on Data Model
-
-A data model has an `indexes` method that defines database indices on its properties.
-
-The index can be on multiple columns (composite index). For example:
-
-```ts highlights={dataModelIndexHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const MyCustom = model.define("my_custom", {
- id: model.id().primaryKey(),
- name: model.text(),
- age: model.number(),
-}).indexes([
- {
- on: ["name", "age"],
- },
-])
-
-export default MyCustom
-```
-
-The `indexes` method receives an array of indices as a parameter. Each index is an object with a required `on` property indicating the properties to apply the index on.
-
-In the above example, you define a composite index on the `name` and `age` properties.
-
-### Index Conditions
-
-An index can have conditions. For example:
-
-```ts highlights={conditionHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const MyCustom = model.define("my_custom", {
- id: model.id().primaryKey(),
- name: model.text(),
- age: model.number(),
-}).indexes([
- {
- on: ["name", "age"],
- where: {
- age: 30,
- },
- },
-])
-
-export default MyCustom
-```
-
-The index object passed to `indexes` accepts a `where` property whose value is an object of conditions. The object's key is a property's name, and its value is the condition on that property.
-
-In the example above, the composite index is created on the `name` and `age` properties when the `age`'s value is `30`.
-
-A property's condition can be a negation. For example:
-
-```ts highlights={negationHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const MyCustom = model.define("my_custom", {
- id: model.id().primaryKey(),
- name: model.text(),
- age: model.number().nullable(),
-}).indexes([
- {
- on: ["name", "age"],
- where: {
- age: {
- $ne: null,
- },
- },
- },
-])
-
-export default MyCustom
-```
-
-A property's value in `where` can be an object having a `$ne` property. `$ne`'s value indicates what the specified property's value shouldn't be.
-
-In the example above, the composite index is created on the `name` and `age` properties when `age`'s value is not `null`.
-
-### Unique Database Index
-
-The object passed to `indexes` accepts a `unique` property indicating that the created index must be a unique index.
-
-For example:
-
-```ts highlights={uniqueHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const MyCustom = model.define("my_custom", {
- id: model.id().primaryKey(),
- name: model.text(),
- age: model.number(),
-}).indexes([
- {
- on: ["name", "age"],
- unique: true,
- },
-])
-
-export default MyCustom
-```
-
-This creates a unique composite index on the `name` and `age` properties.
-
-
-# Create a Plugin
-
-In this chapter, you'll learn how to create a Medusa plugin and publish it.
-
-A [plugin](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md) is a package of reusable Medusa customizations that you can install in any Medusa application. By creating and publishing a plugin, you can reuse your Medusa customizations across multiple projects or share them with the community.
-
-Plugins are available starting from [Medusa v2.3.0](https://github.com/medusajs/medusa/releases/tag/v2.3.0).
-
-## 1. Create a Plugin Project
-
-Plugins are created in a separate Medusa project. This makes the development and publishing of the plugin easier. Later, you'll install that plugin in your Medusa application to test it out and use it.
-
-Medusa's `create-medusa-app` CLI tool provides the option to create a plugin project. Run the following command to create a new plugin project:
-
-```bash
-npx create-medusa-app my-plugin --plugin
-```
-
-This will create a new Medusa plugin project in the `my-plugin` directory.
-
-### Plugin Directory Structure
-
-After the installation is done, the plugin structure will look like this:
-
-
-
-- `src/`: Contains the Medusa customizations.
-- `src/admin`: Contains [admin extensions](https://docs.medusajs.com/learn/fundamentals/admin/index.html.md).
-- `src/api`: Contains [API routes](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md) and [middlewares](https://docs.medusajs.com/learn/fundamentals/api-routes/middlewares/index.html.md). You can add store, admin, or any custom API routes.
-- `src/jobs`: Contains [scheduled jobs](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md).
-- `src/links`: Contains [module links](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md).
-- `src/modules`: Contains [modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
-- `src/provider`: Contains [module providers](#create-module-providers).
-- `src/subscribers`: Contains [subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md).
-- `src/workflows`: Contains [workflows](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md). You can also add [hooks](https://docs.medusajs.com/learn/fundamentals/workflows/add-workflow-hook/index.html.md) under `src/workflows/hooks`.
-- `package.json`: Contains the plugin's package information, including general information and dependencies.
-- `tsconfig.json`: Contains the TypeScript configuration for the plugin.
-
-***
-
-## 2. Prepare Plugin
-
-### Package Name
-
-Before developing, testing, and publishing your plugin, make sure its name in `package.json` is correct. This is the name you'll use to install the plugin in your Medusa application.
-
-For example:
-
-```json title="package.json"
-{
- "name": "@myorg/plugin-name",
- // ...
-}
-```
-
-### Package Keywords
-
-In addition, make sure that the `keywords` field in `package.json` includes the keyword `medusa-plugin` and `medusa-v2`. This helps Medusa list community plugins on the Medusa website:
-
-```json title="package.json"
-{
- "keywords": [
- "medusa-plugin",
- "medusa-v2"
- ],
- // ...
-}
-```
-
-### Package Dependencies
-
-Your plugin project will already have the dependencies mentioned in this section. If you haven't made any changes to the dependencies, you can skip this section.
-
-In the `package.json` file you must have the Medusa dependencies as `devDependencies` and `peerDependencies`. In addition, you must have `@swc/core` as a `devDependency`, as it's used by the plugin CLI tools.
-
-For example, assuming `2.5.0` is the latest Medusa version:
-
-```json title="package.json"
-{
- "devDependencies": {
- "@medusajs/admin-sdk": "2.5.0",
- "@medusajs/cli": "2.5.0",
- "@medusajs/framework": "2.5.0",
- "@medusajs/medusa": "2.5.0",
- "@medusajs/test-utils": "2.5.0",
- "@medusajs/ui": "4.0.4",
- "@medusajs/icons": "2.5.0",
- "@swc/core": "1.5.7",
- },
- "peerDependencies": {
- "@medusajs/admin-sdk": "2.5.0",
- "@medusajs/cli": "2.5.0",
- "@medusajs/framework": "2.5.0",
- "@medusajs/test-utils": "2.5.0",
- "@medusajs/medusa": "2.5.0",
- "@medusajs/ui": "4.0.3",
- "@medusajs/icons": "2.5.0",
- }
-}
-```
-
-***
-
-## 3. Publish Plugin Locally for Development and Testing
-
-Medusa's CLI tool provides commands to simplify developing and testing your plugin in a local Medusa application. You start by publishing your plugin in the local package registry, then install it in your Medusa application. You can then watch for changes in the plugin as you develop it.
-
-### Publish and Install Local Package
-
-### Prerequisites
-
-- [Medusa application installed.](https://docs.medusajs.com/learn/installation/index.html.md)
-
-The first time you create your plugin, you need to publish the package into a local package registry, then install it in your Medusa application. This is a one-time only process.
-
-To publish the plugin to the local registry, run the following command in your plugin project:
-
-```bash title="Plugin project"
-npx medusa plugin:publish
-```
-
-This command uses [Yalc](https://github.com/wclr/yalc) under the hood to publish the plugin to a local package registry. The plugin is published locally under the name you specified in `package.json`.
-
-Next, navigate to your Medusa application:
-
-```bash title="Medusa application"
-cd ~/path/to/medusa-app
-```
-
-Make sure to replace `~/path/to/medusa-app` with the path to your Medusa application.
-
-Then, if your project was created before v2.3.1 of Medusa, make sure to install `yalc` as a development dependency:
-
-```bash npm2yarn title="Medusa application"
-npm install --save-dev yalc
-```
-
-After that, run the following Medusa CLI command to install the plugin:
-
-```bash title="Medusa application"
-npx medusa plugin:add @myorg/plugin-name
-```
-
-Make sure to replace `@myorg/plugin-name` with the name of your plugin as specified in `package.json`. Your plugin will be installed from the local package registry into your Medusa application.
-
-### Register Plugin in Medusa Application
-
-After installing the plugin, you need to register it in your Medusa application in the configurations defined in `medusa-config.ts`.
-
-Add the plugin to the `plugins` array in the `medusa-config.ts` file:
-
-```ts title="medusa-config.ts" highlights={pluginHighlights}
-module.exports = defineConfig({
- // ...
- plugins: [
- {
- resolve: "@myorg/plugin-name",
- options: {},
- },
- ],
-})
-```
-
-The `plugins` configuration is an array of objects where each object has a `resolve` key whose value is the name of the plugin package.
-
-#### Pass Module Options through Plugin
-
-Each plugin configuration also accepts an `options` property, whose value is an object of options to pass to the plugin's modules.
-
-For example:
-
-```ts title="medusa-config.ts" highlights={pluginOptionsHighlight}
-module.exports = defineConfig({
- // ...
- plugins: [
- {
- resolve: "@myorg/plugin-name",
- options: {
- apiKey: true,
- },
- },
- ],
-})
-```
-
-The `options` property in the plugin configuration is passed to all modules in the plugin. Learn more about module options in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/options/index.html.md).
-
-### Watch Plugin Changes During Development
-
-While developing your plugin, you can watch for changes in the plugin and automatically update the plugin in the Medusa application using it. This is the only command you'll continuously need during your plugin development.
-
-To do that, run the following command in your plugin project:
-
-```bash title="Plugin project"
-npx medusa plugin:develop
-```
-
-This command will:
-
-- Watch for changes in the plugin. Whenever a file is changed, the plugin is automatically built.
-- Publish the plugin changes to the local package registry. This will automatically update the plugin in the Medusa application using it. You can also benefit from real-time HMR updates of admin extensions.
-
-### Start Medusa Application
-
-You can start your Medusa application's development server to test out your plugin:
-
-```bash npm2yarn title="Medusa application"
-npm run dev
-```
-
-While your Medusa application is running and the plugin is being watched, you can test your plugin while developing it in the Medusa application.
-
-***
-
-## 4. Create Customizations in the Plugin
-
-You can now build your plugin's customizations. The following guide explains how to build different customizations in your plugin.
-
-- [Create a module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md)
-- [Create a module link](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md)
-- [Create a workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md)
-- [Add a workflow hook](https://docs.medusajs.com/learn/fundamentals/workflows/add-workflow-hook/index.html.md)
-- [Create an API route](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md)
-- [Add a subscriber](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md)
-- [Add a scheduled job](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md)
-- [Add an admin widget](https://docs.medusajs.com/learn/fundamentals/admin/widgets/index.html.md)
-- [Add an admin UI route](https://docs.medusajs.com/learn/fundamentals/admin/ui-routes/index.html.md)
-
-While building those customizations, you can test them in your Medusa application by [watching the plugin changes](#watch-plugin-changes-during-development) and [starting the Medusa application](#start-medusa-application).
-
-### Generating Migrations for Modules
-
-During your development, you may need to generate migrations for modules in your plugin. To do that, use the `plugin:db:generate` command:
-
-```bash title="Plugin project"
-npx medusa plugin:db:generate
-```
-
-This command generates migrations for all modules in the plugin. You can then run these migrations on the Medusa application that the plugin is installed in using the `db:migrate` command:
-
-```bash title="Medusa application"
-npx medusa db:migrate
-```
-
-### Importing Module Resources
-
-Your plugin project should have the following exports in `package.json`:
-
-```json title="package.json"
-{
- "exports": {
- "./package.json": "./package.json",
- "./workflows": "./.medusa/server/src/workflows/index.js",
- "./.medusa/server/src/modules/*": "./.medusa/server/src/modules/*/index.js",
- "./providers/*": "./.medusa/server/src/providers/*/index.js",
- "./*": "./.medusa/server/src/*.js"
- }
-}
-```
-
-Aside from the `./package.json` and `./providers`, these exports are only a recommendation. You can cherry-pick the files and directories you want to export.
-
-The plugin exports the following files and directories:
-
-- `./package.json`: The package.json file. Medusa needs to access the `package.json` when registering the plugin.
-- `./workflows`: The workflows exported in `./src/workflows/index.ts`.
-- `./.medusa/server/src/modules/*`: The definition file of modules. This is useful if you create links to the plugin's modules in the Medusa application.
-- `./providers/*`: The definition file of module providers. This allows you to register the plugin's providers in the Medusa application.
-- `./*`: Any other files in the plugin's `src` directory.
-
-With these exports, you can import the plugin's resources in the Medusa application's code like this:
-
-`@myorg/plugin-name` is the plugin package's name.
-
-```ts
-import { Workflow1, Workflow2 } from "@myorg/plugin-name/workflows"
-import BlogModule from "@myorg/plugin-name/modules/blog"
-// import other files created in plugin like ./src/types/blog.ts
-import BlogType from "@myorg/plugin-name/types/blog"
-```
-
-And you can register a module provider in the Medusa application's `medusa-config.ts` like this:
-
-```ts highlights={[["9"]]} title="medusa-config.ts"
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "@medusajs/medusa/notification",
- options: {
- providers: [
- {
- resolve: "@myorg/plugin-name/providers/my-notification",
- id: "my-notification",
- options: {
- channels: ["email"],
- // provider options...
- },
- },
- ],
- },
- },
- ],
-})
-```
-
-You pass to `resolve` the path to the provider relative to the plugin package. So, in this example, the `my-notification` provider is located in `./src/providers/my-notification/index.ts` of the plugin.
-
-### Create Module Providers
-
-To learn how to create module providers, refer to the following guides:
-
-- [File Module Provider](https://docs.medusajs.com/resources/references/file-provider-module/index.html.md)
-- [Notification Module Provider](https://docs.medusajs.com/resources/references/notification-provider-module/index.html.md)
-- [Auth Module Provider](https://docs.medusajs.com/resources/references/auth/provider/index.html.md)
-- [Payment Module Provider](https://docs.medusajs.com/resources/references/payment/provider/index.html.md)
-- [Fulfillment Module Provider](https://docs.medusajs.com/resources/references/fulfillment/provider/index.html.md)
-- [Tax Module Provider](https://docs.medusajs.com/resources/references/tax/provider/index.html.md)
-
-***
-
-## 5. Publish Plugin to NPM
-
-Medusa's CLI tool provides a command that bundles your plugin to be published to npm. Once you're ready to publish your plugin publicly, run the following command in your plugin project:
-
-```bash
-npx medusa plugin:build
-```
-
-The command will compile an output in the `.medusa/server` directory.
-
-You can now publish the plugin to npm using the [NPM CLI tool](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). Run the following command to publish the plugin to npm:
-
-```bash
-npm publish
-```
-
-If you haven't logged in before with your NPM account, you'll be asked to log in first. Then, your package is published publicly to be used in any Medusa application.
-
-### Install Public Plugin in Medusa Application
-
-You install a plugin that's published publicly using your package manager. For example:
-
-```bash npm2yarn
-npm install @myorg/plugin-name
-```
-
-Where `@myorg/plugin-name` is the name of your plugin as published on NPM.
-
-Then, register the plugin in your Medusa application's configurations as explained in [this section](#register-plugin-in-medusa-application).
-
-***
-
-## Update a Published Plugin
-
-To update the Medusa dependencies in a plugin, refer to [this documentation](https://docs.medusajs.com/learn/update#update-plugin-project/index.html.md).
-
-If you've published a plugin and you've made changes to it, you'll have to publish the update to NPM again.
-
-First, run the following command to change the version of the plugin:
-
-```bash
-npm version
-```
-
-Where `` indicates the type of version update you’re publishing. For example, it can be `major` or `minor`. Refer to the [npm version documentation](https://docs.npmjs.com/cli/v10/commands/npm-version) for more information.
-
-Then, re-run the same commands for publishing a plugin:
-
-```bash
-npx medusa plugin:build
-npm publish
-```
-
-This will publish an updated version of your plugin under a new version.
-
-
-# Add Data Model Check Constraints
-
-In this chapter, you'll learn how to add check constraints to your data model.
-
-## What is a Check Constraint?
-
-A check constraint is a condition that must be satisfied by records inserted into a database table, otherwise an error is thrown.
-
-For example, if you have a data model with a `price` property, you want to only allow positive number values. So, you add a check constraint that fails when inserting a record with a negative price value.
-
-***
-
-## How to Set a Check Constraint?
-
-To set check constraints on a data model, use the `checks` method. This method accepts an array of check constraints to apply on the data model.
-
-For example, to set a check constraint on a `price` property that ensures its value can only be a positive number:
-
-```ts highlights={checks1Highlights}
-import { model } from "@medusajs/framework/utils"
-
-const CustomProduct = model.define("custom_product", {
- // ...
- price: model.bigNumber(),
-})
-.checks([
- (columns) => `${columns.price} >= 0`,
-])
-```
-
-The item passed in the array parameter of `checks` can be a callback function that accepts as a parameter an object whose keys are the names of the properties in the data model schema, and values the respective column name in the database.
-
-The function returns a string indicating the [SQL check constraint expression](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS). In the expression, use the `columns` parameter to access a property's column name.
-
-You can also pass an object to the `checks` method:
-
-```ts highlights={checks2Highlights}
-import { model } from "@medusajs/framework/utils"
-
-const CustomProduct = model.define("custom_product", {
- // ...
- price: model.bigNumber(),
-})
-.checks([
- {
- name: "custom_product_price_check",
- expression: (columns) => `${columns.price} >= 0`,
- },
-])
-```
-
-The object accepts the following properties:
-
-- `name`: The check constraint's name.
-- `expression`: A function similar to the one that can be passed to the array. It accepts an object of columns and returns an [SQL check constraint expression](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS).
-
-***
-
-## Apply in Migrations
-
-After adding the check constraint, make sure to generate and run migrations if you already have the table in the database. Otherwise, the check constraint won't be reflected.
-
-To generate a migration for the data model's module then reflect it on the database, run the following command:
-
-```bash
-npx medusa db:generate custom_module
-npx medusa db:migrate
-```
-
-The first command generates the migration under the `migrations` directory of your module's directory, and the second reflects it on the database.
-
-
# Data Model Properties
In this chapter, you'll learn about the different property types you can use in a data model and how to configure a data model's properties.
@@ -11625,46 +10081,6 @@ The `cascades` method accepts an object. Its key is the operation’s name, such
In the example above, when a store is deleted, its associated products are also deleted.
-# Infer Type of Data Model
-
-In this chapter, you'll learn how to infer the type of a data model.
-
-## How to Infer Type of Data Model?
-
-Consider you have a `Post` data model. You can't reference this data model in a type, such as a workflow input or service method output types, since it's a variable.
-
-Instead, Medusa provides `InferTypeOf` that transforms your data model to a type.
-
-For example:
-
-```ts
-import { InferTypeOf } from "@medusajs/framework/types"
-import { Post } from "../modules/blog/models/post" // relative path to the model
-
-export type Post = InferTypeOf
-```
-
-`InferTypeOf` accepts as a type argument the type of the data model.
-
-Since the `Post` data model is a variable, use the `typeof` operator to pass the data model as a type argument to `InferTypeOf`.
-
-You can now use the `Post` type to reference a data model in other types, such as in workflow inputs or service method outputs:
-
-```ts title="Example Service"
-// other imports...
-import { InferTypeOf } from "@medusajs/framework/types"
-import { Post } from "../models/post"
-
-type Post = InferTypeOf
-
-class BlogModuleService extends MedusaService({ Post }) {
- async doSomething(): Promise {
- // ...
- }
-}
-```
-
-
# Migrations
In this chapter, you'll learn what a migration is and how to generate a migration or write it manually.
@@ -11763,37 +10179,6 @@ So, always rollback the migration before deleting it.
To learn more about the Medusa CLI's database commands, refer to [this CLI reference](https://docs.medusajs.com/resources/medusa-cli/commands/db/index.html.md).
-# Architectural Modules
-
-In this chapter, you’ll learn about architectural modules.
-
-## What is an Architectural Module?
-
-An architectural module implements features and mechanisms related to the Medusa application’s architecture and infrastructure.
-
-Since modules are interchangeable, you have more control over Medusa’s architecture. For example, you can choose to use Memcached for event handling instead of Redis.
-
-***
-
-## Architectural Module Types
-
-There are different architectural module types including:
-
-
-
-- Cache Module: Defines the caching mechanism or logic to cache computational results.
-- Event Module: Integrates a pub/sub service to handle subscribing to and emitting events.
-- Workflow Engine Module: Integrates a service to store and track workflow executions and steps.
-- File Module: Integrates a storage service to handle uploading and managing files.
-- Notification Module: Integrates a third-party service or defines custom logic to send notifications to users and customers.
-
-***
-
-## Architectural Modules List
-
-Refer to the [Architectural Modules reference](https://docs.medusajs.com/resources/architectural-modules/index.html.md) for a list of Medusa’s architectural modules, available modules to install, and how to create an architectural module.
-
-
# Manage Relationships
In this chapter, you'll learn how to manage relationships between data models when creating, updating, or retrieving records using the module's main service.
@@ -12012,6 +10397,2020 @@ const product = await blogModuleService.retrieveProducts(
In the example above, the retrieved product has an `orders` property, whose value is an array of orders associated with the product.
+# Create a Plugin
+
+In this chapter, you'll learn how to create a Medusa plugin and publish it.
+
+A [plugin](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md) is a package of reusable Medusa customizations that you can install in any Medusa application. By creating and publishing a plugin, you can reuse your Medusa customizations across multiple projects or share them with the community.
+
+Plugins are available starting from [Medusa v2.3.0](https://github.com/medusajs/medusa/releases/tag/v2.3.0).
+
+## 1. Create a Plugin Project
+
+Plugins are created in a separate Medusa project. This makes the development and publishing of the plugin easier. Later, you'll install that plugin in your Medusa application to test it out and use it.
+
+Medusa's `create-medusa-app` CLI tool provides the option to create a plugin project. Run the following command to create a new plugin project:
+
+```bash
+npx create-medusa-app my-plugin --plugin
+```
+
+This will create a new Medusa plugin project in the `my-plugin` directory.
+
+### Plugin Directory Structure
+
+After the installation is done, the plugin structure will look like this:
+
+
+
+- `src/`: Contains the Medusa customizations.
+- `src/admin`: Contains [admin extensions](https://docs.medusajs.com/learn/fundamentals/admin/index.html.md).
+- `src/api`: Contains [API routes](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md) and [middlewares](https://docs.medusajs.com/learn/fundamentals/api-routes/middlewares/index.html.md). You can add store, admin, or any custom API routes.
+- `src/jobs`: Contains [scheduled jobs](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md).
+- `src/links`: Contains [module links](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md).
+- `src/modules`: Contains [modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
+- `src/provider`: Contains [module providers](#create-module-providers).
+- `src/subscribers`: Contains [subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md).
+- `src/workflows`: Contains [workflows](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md). You can also add [hooks](https://docs.medusajs.com/learn/fundamentals/workflows/add-workflow-hook/index.html.md) under `src/workflows/hooks`.
+- `package.json`: Contains the plugin's package information, including general information and dependencies.
+- `tsconfig.json`: Contains the TypeScript configuration for the plugin.
+
+***
+
+## 2. Prepare Plugin
+
+### Package Name
+
+Before developing, testing, and publishing your plugin, make sure its name in `package.json` is correct. This is the name you'll use to install the plugin in your Medusa application.
+
+For example:
+
+```json title="package.json"
+{
+ "name": "@myorg/plugin-name",
+ // ...
+}
+```
+
+### Package Keywords
+
+In addition, make sure that the `keywords` field in `package.json` includes the keyword `medusa-plugin` and `medusa-v2`. This helps Medusa list community plugins on the Medusa website:
+
+```json title="package.json"
+{
+ "keywords": [
+ "medusa-plugin",
+ "medusa-v2"
+ ],
+ // ...
+}
+```
+
+### Package Dependencies
+
+Your plugin project will already have the dependencies mentioned in this section. If you haven't made any changes to the dependencies, you can skip this section.
+
+In the `package.json` file you must have the Medusa dependencies as `devDependencies` and `peerDependencies`. In addition, you must have `@swc/core` as a `devDependency`, as it's used by the plugin CLI tools.
+
+For example, assuming `2.5.0` is the latest Medusa version:
+
+```json title="package.json"
+{
+ "devDependencies": {
+ "@medusajs/admin-sdk": "2.5.0",
+ "@medusajs/cli": "2.5.0",
+ "@medusajs/framework": "2.5.0",
+ "@medusajs/medusa": "2.5.0",
+ "@medusajs/test-utils": "2.5.0",
+ "@medusajs/ui": "4.0.4",
+ "@medusajs/icons": "2.5.0",
+ "@swc/core": "1.5.7",
+ },
+ "peerDependencies": {
+ "@medusajs/admin-sdk": "2.5.0",
+ "@medusajs/cli": "2.5.0",
+ "@medusajs/framework": "2.5.0",
+ "@medusajs/test-utils": "2.5.0",
+ "@medusajs/medusa": "2.5.0",
+ "@medusajs/ui": "4.0.3",
+ "@medusajs/icons": "2.5.0",
+ }
+}
+```
+
+***
+
+## 3. Publish Plugin Locally for Development and Testing
+
+Medusa's CLI tool provides commands to simplify developing and testing your plugin in a local Medusa application. You start by publishing your plugin in the local package registry, then install it in your Medusa application. You can then watch for changes in the plugin as you develop it.
+
+### Publish and Install Local Package
+
+### Prerequisites
+
+- [Medusa application installed.](https://docs.medusajs.com/learn/installation/index.html.md)
+
+The first time you create your plugin, you need to publish the package into a local package registry, then install it in your Medusa application. This is a one-time only process.
+
+To publish the plugin to the local registry, run the following command in your plugin project:
+
+```bash title="Plugin project"
+npx medusa plugin:publish
+```
+
+This command uses [Yalc](https://github.com/wclr/yalc) under the hood to publish the plugin to a local package registry. The plugin is published locally under the name you specified in `package.json`.
+
+Next, navigate to your Medusa application:
+
+```bash title="Medusa application"
+cd ~/path/to/medusa-app
+```
+
+Make sure to replace `~/path/to/medusa-app` with the path to your Medusa application.
+
+Then, if your project was created before v2.3.1 of Medusa, make sure to install `yalc` as a development dependency:
+
+```bash npm2yarn title="Medusa application"
+npm install --save-dev yalc
+```
+
+After that, run the following Medusa CLI command to install the plugin:
+
+```bash title="Medusa application"
+npx medusa plugin:add @myorg/plugin-name
+```
+
+Make sure to replace `@myorg/plugin-name` with the name of your plugin as specified in `package.json`. Your plugin will be installed from the local package registry into your Medusa application.
+
+### Register Plugin in Medusa Application
+
+After installing the plugin, you need to register it in your Medusa application in the configurations defined in `medusa-config.ts`.
+
+Add the plugin to the `plugins` array in the `medusa-config.ts` file:
+
+```ts title="medusa-config.ts" highlights={pluginHighlights}
+module.exports = defineConfig({
+ // ...
+ plugins: [
+ {
+ resolve: "@myorg/plugin-name",
+ options: {},
+ },
+ ],
+})
+```
+
+The `plugins` configuration is an array of objects where each object has a `resolve` key whose value is the name of the plugin package.
+
+#### Pass Module Options through Plugin
+
+Each plugin configuration also accepts an `options` property, whose value is an object of options to pass to the plugin's modules.
+
+For example:
+
+```ts title="medusa-config.ts" highlights={pluginOptionsHighlight}
+module.exports = defineConfig({
+ // ...
+ plugins: [
+ {
+ resolve: "@myorg/plugin-name",
+ options: {
+ apiKey: true,
+ },
+ },
+ ],
+})
+```
+
+The `options` property in the plugin configuration is passed to all modules in the plugin. Learn more about module options in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/options/index.html.md).
+
+### Watch Plugin Changes During Development
+
+While developing your plugin, you can watch for changes in the plugin and automatically update the plugin in the Medusa application using it. This is the only command you'll continuously need during your plugin development.
+
+To do that, run the following command in your plugin project:
+
+```bash title="Plugin project"
+npx medusa plugin:develop
+```
+
+This command will:
+
+- Watch for changes in the plugin. Whenever a file is changed, the plugin is automatically built.
+- Publish the plugin changes to the local package registry. This will automatically update the plugin in the Medusa application using it. You can also benefit from real-time HMR updates of admin extensions.
+
+### Start Medusa Application
+
+You can start your Medusa application's development server to test out your plugin:
+
+```bash npm2yarn title="Medusa application"
+npm run dev
+```
+
+While your Medusa application is running and the plugin is being watched, you can test your plugin while developing it in the Medusa application.
+
+***
+
+## 4. Create Customizations in the Plugin
+
+You can now build your plugin's customizations. The following guide explains how to build different customizations in your plugin.
+
+- [Create a module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md)
+- [Create a module link](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md)
+- [Create a workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md)
+- [Add a workflow hook](https://docs.medusajs.com/learn/fundamentals/workflows/add-workflow-hook/index.html.md)
+- [Create an API route](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md)
+- [Add a subscriber](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md)
+- [Add a scheduled job](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md)
+- [Add an admin widget](https://docs.medusajs.com/learn/fundamentals/admin/widgets/index.html.md)
+- [Add an admin UI route](https://docs.medusajs.com/learn/fundamentals/admin/ui-routes/index.html.md)
+
+While building those customizations, you can test them in your Medusa application by [watching the plugin changes](#watch-plugin-changes-during-development) and [starting the Medusa application](#start-medusa-application).
+
+### Generating Migrations for Modules
+
+During your development, you may need to generate migrations for modules in your plugin. To do that, use the `plugin:db:generate` command:
+
+```bash title="Plugin project"
+npx medusa plugin:db:generate
+```
+
+This command generates migrations for all modules in the plugin. You can then run these migrations on the Medusa application that the plugin is installed in using the `db:migrate` command:
+
+```bash title="Medusa application"
+npx medusa db:migrate
+```
+
+### Importing Module Resources
+
+Your plugin project should have the following exports in `package.json`:
+
+```json title="package.json"
+{
+ "exports": {
+ "./package.json": "./package.json",
+ "./workflows": "./.medusa/server/src/workflows/index.js",
+ "./.medusa/server/src/modules/*": "./.medusa/server/src/modules/*/index.js",
+ "./providers/*": "./.medusa/server/src/providers/*/index.js",
+ "./*": "./.medusa/server/src/*.js"
+ }
+}
+```
+
+Aside from the `./package.json` and `./providers`, these exports are only a recommendation. You can cherry-pick the files and directories you want to export.
+
+The plugin exports the following files and directories:
+
+- `./package.json`: The package.json file. Medusa needs to access the `package.json` when registering the plugin.
+- `./workflows`: The workflows exported in `./src/workflows/index.ts`.
+- `./.medusa/server/src/modules/*`: The definition file of modules. This is useful if you create links to the plugin's modules in the Medusa application.
+- `./providers/*`: The definition file of module providers. This allows you to register the plugin's providers in the Medusa application.
+- `./*`: Any other files in the plugin's `src` directory.
+
+With these exports, you can import the plugin's resources in the Medusa application's code like this:
+
+`@myorg/plugin-name` is the plugin package's name.
+
+```ts
+import { Workflow1, Workflow2 } from "@myorg/plugin-name/workflows"
+import BlogModule from "@myorg/plugin-name/modules/blog"
+// import other files created in plugin like ./src/types/blog.ts
+import BlogType from "@myorg/plugin-name/types/blog"
+```
+
+And you can register a module provider in the Medusa application's `medusa-config.ts` like this:
+
+```ts highlights={[["9"]]} title="medusa-config.ts"
+module.exports = defineConfig({
+ // ...
+ modules: [
+ {
+ resolve: "@medusajs/medusa/notification",
+ options: {
+ providers: [
+ {
+ resolve: "@myorg/plugin-name/providers/my-notification",
+ id: "my-notification",
+ options: {
+ channels: ["email"],
+ // provider options...
+ },
+ },
+ ],
+ },
+ },
+ ],
+})
+```
+
+You pass to `resolve` the path to the provider relative to the plugin package. So, in this example, the `my-notification` provider is located in `./src/providers/my-notification/index.ts` of the plugin.
+
+### Create Module Providers
+
+To learn how to create module providers, refer to the following guides:
+
+- [File Module Provider](https://docs.medusajs.com/resources/references/file-provider-module/index.html.md)
+- [Notification Module Provider](https://docs.medusajs.com/resources/references/notification-provider-module/index.html.md)
+- [Auth Module Provider](https://docs.medusajs.com/resources/references/auth/provider/index.html.md)
+- [Payment Module Provider](https://docs.medusajs.com/resources/references/payment/provider/index.html.md)
+- [Fulfillment Module Provider](https://docs.medusajs.com/resources/references/fulfillment/provider/index.html.md)
+- [Tax Module Provider](https://docs.medusajs.com/resources/references/tax/provider/index.html.md)
+
+***
+
+## 5. Publish Plugin to NPM
+
+Medusa's CLI tool provides a command that bundles your plugin to be published to npm. Once you're ready to publish your plugin publicly, run the following command in your plugin project:
+
+```bash
+npx medusa plugin:build
+```
+
+The command will compile an output in the `.medusa/server` directory.
+
+You can now publish the plugin to npm using the [NPM CLI tool](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). Run the following command to publish the plugin to npm:
+
+```bash
+npm publish
+```
+
+If you haven't logged in before with your NPM account, you'll be asked to log in first. Then, your package is published publicly to be used in any Medusa application.
+
+### Install Public Plugin in Medusa Application
+
+You install a plugin that's published publicly using your package manager. For example:
+
+```bash npm2yarn
+npm install @myorg/plugin-name
+```
+
+Where `@myorg/plugin-name` is the name of your plugin as published on NPM.
+
+Then, register the plugin in your Medusa application's configurations as explained in [this section](#register-plugin-in-medusa-application).
+
+***
+
+## Update a Published Plugin
+
+To update the Medusa dependencies in a plugin, refer to [this documentation](https://docs.medusajs.com/learn/update#update-plugin-project/index.html.md).
+
+If you've published a plugin and you've made changes to it, you'll have to publish the update to NPM again.
+
+First, run the following command to change the version of the plugin:
+
+```bash
+npm version
+```
+
+Where `` indicates the type of version update you’re publishing. For example, it can be `major` or `minor`. Refer to the [npm version documentation](https://docs.npmjs.com/cli/v10/commands/npm-version) for more information.
+
+Then, re-run the same commands for publishing a plugin:
+
+```bash
+npx medusa plugin:build
+npm publish
+```
+
+This will publish an updated version of your plugin under a new version.
+
+
+# Scheduled Jobs Number of Executions
+
+In this chapter, you'll learn how to set a limit on the number of times a scheduled job is executed.
+
+## numberOfExecutions Option
+
+The export configuration object of the scheduled job accepts an optional property `numberOfExecutions`. Its value is a number indicating how many times the scheduled job can be executed during the Medusa application's runtime.
+
+For example:
+
+```ts highlights={highlights}
+export default async function myCustomJob() {
+ console.log("I'll be executed three times only.")
+}
+
+export const config = {
+ name: "hello-world",
+ // execute every minute
+ schedule: "* * * * *",
+ numberOfExecutions: 3,
+}
+```
+
+The above scheduled job has the `numberOfExecutions` configuration set to `3`.
+
+So, it'll only execute 3 times, each every minute, then it won't be executed anymore.
+
+If you restart the Medusa application, the scheduled job will be executed again until reaching the number of executions specified.
+
+
+# Add Columns to a Link Table
+
+In this chapter, you'll learn how to add custom columns to a link definition's table and manage them.
+
+## Link Table's Default Columns
+
+When you define a link between two data models, Medusa creates a link table in the database to store the IDs of the linked records. You can learn more about the created table in the [Module Links chapter](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md).
+
+In various cases, you might need to store additional data in the link table. For example, if you define a link between a `product` and a `post`, you might want to store the publish date of the product's post in the link table.
+
+In those cases, you can add a custom column to a link's table in the link definition. You can later set that column whenever you create or update a link between the linked records.
+
+***
+
+## How to Add Custom Columns to a Link's Table?
+
+The `defineLink` function used to define a link accepts a third parameter, which is an object of options.
+
+To add custom columns to a link's table, pass in the third parameter of `defineLink` a `database` property:
+
+```ts highlights={linkHighlights}
+import BlogModule from "../modules/blog"
+import ProductModule from "@medusajs/medusa/product"
+import { defineLink } from "@medusajs/framework/utils"
+
+export default defineLink(
+ ProductModule.linkable.product,
+ BlogModule.linkable.blog,
+ {
+ database: {
+ extraColumns: {
+ metadata: {
+ type: "json",
+ },
+ },
+ },
+ }
+)
+```
+
+This adds to the table created for the link between `product` and `blog` a `metadata` column of type `json`.
+
+### Database Options
+
+The `database` property defines configuration for the table created in the database.
+
+Its `extraColumns` property defines custom columns to create in the link's table.
+
+`extraColumns`'s value is an object whose keys are the names of the columns, and values are the column's configurations as an object.
+
+### Column Configurations
+
+The column's configurations object accepts the following properties:
+
+- `type`: The column's type. Possible values are:
+ - `string`
+ - `text`
+ - `integer`
+ - `boolean`
+ - `date`
+ - `time`
+ - `datetime`
+ - `enum`
+ - `json`
+ - `array`
+ - `enumArray`
+ - `float`
+ - `double`
+ - `decimal`
+ - `bigint`
+ - `mediumint`
+ - `smallint`
+ - `tinyint`
+ - `blob`
+ - `uuid`
+ - `uint8array`
+- `defaultValue`: The column's default value.
+- `nullable`: Whether the column can have `null` values.
+
+***
+
+## Set Custom Column when Creating Link
+
+The object you pass to Link's `create` method accepts a `data` property. Its value is an object whose keys are custom column names, and values are the value of the custom column for this link.
+
+For example:
+
+Learn more about Link, how to resolve it, and its methods in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/link/index.html.md).
+
+```ts
+await link.create({
+ [Modules.PRODUCT]: {
+ product_id: "123",
+ },
+ [BLOG_MODULE]: {
+ post_id: "321",
+ },
+ data: {
+ metadata: {
+ test: true,
+ },
+ },
+})
+```
+
+***
+
+## Retrieve Custom Column with Link
+
+To retrieve linked records with their custom columns, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md). A module link's definition, exported by a file under `src/links`, has a special `entryPoint` property. Use this property when specifying the `entity` property in Query's `graph` method.
+
+For example:
+
+```ts highlights={retrieveHighlights}
+import productPostLink from "../links/product-post"
+
+// ...
+
+const { data } = await query.graph({
+ entity: productPostLink.entryPoint,
+ fields: ["metadata", "product.*", "post.*"],
+ filters: {
+ product_id: "prod_123",
+ },
+})
+```
+
+This retrieves the product of id `prod_123` and its linked `post` records.
+
+In the `fields` array you pass `metadata`, which is the custom column to retrieve of the link.
+
+***
+
+## Update Custom Column's Value
+
+Link's `create` method updates a link's data if the link between the specified records already exists.
+
+So, to update the value of a custom column in a created link, use the `create` method again passing it a new value for the custom column.
+
+For example:
+
+```ts
+await link.create({
+ [Modules.PRODUCT]: {
+ product_id: "123",
+ },
+ [BLOG_MODULE]: {
+ post_id: "321",
+ },
+ data: {
+ metadata: {
+ test: false,
+ },
+ },
+})
+```
+
+
+# Module Link Direction
+
+In this chapter, you'll learn about the difference in module link directions, and which to use based on your use case.
+
+The details in this chapter don't apply to [Read-Only Module Links](https://docs.medusajs.com/learn/fundamentals/module-links/read-only/index.html.md). Refer to the [Read-Only Module Links chapter](https://docs.medusajs.com/learn/fundamentals/module-links/read-only/index.html.md) for more information on read-only links and their direction.
+
+## Link Direction
+
+The module link's direction depends on the order you pass the data model configuration parameters to `defineLink`.
+
+For example, the following defines a link from the Blog Module's `post` data model to the Product Module's `product` data model:
+
+```ts
+export default defineLink(
+ BlogModule.linkable.post,
+ ProductModule.linkable.product
+)
+```
+
+Whereas the following defines a link from the Product Module's `product` data model to the Blog Module's `post` data model:
+
+```ts
+export default defineLink(
+ ProductModule.linkable.product,
+ BlogModule.linkable.post
+)
+```
+
+The above links are two different links that serve different purposes.
+
+***
+
+## Which Link Direction to Use?
+
+### Extend Data Models
+
+If you're adding a link to a data model to extend it and add new fields, define the link from the main data model to the custom data model.
+
+For example, consider you want to add a `subtitle` custom field to the `product` data model. To do that, you define a `Subtitle` data model in your module, then define a link from the `Product` data model to it:
+
+```ts
+export default defineLink(
+ ProductModule.linkable.product,
+ BlogModule.linkable.subtitle
+)
+```
+
+### Associate Data Models
+
+If you're linking data models to indicate an association between them, define the link from the custom data model to the main data model.
+
+For example, consider you have `Post` data model representing a blog post, and you want to associate a blog post with a product. To do that, define a link from the `Post` data model to `Product`:
+
+```ts
+export default defineLink(
+ BlogModule.linkable.post,
+ ProductModule.linkable.product
+)
+```
+
+
+# Query Context
+
+In this chapter, you'll learn how to pass contexts when retrieving data with [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md).
+
+## What is Query Context?
+
+Query context is a way to pass additional information when retrieving data with Query. This data can be useful when applying custom transformations to the retrieved data based on the current context.
+
+For example, consider you have a Blog Module with posts and authors. You can accept the user's language as a context and return the posts in the user's language. Another example is how Medusa uses Query Context to [retrieve product variants' prices based on the customer's currency](https://docs.medusajs.com/resources/commerce-modules/product/guides/price/index.html.md).
+
+***
+
+## How to Use Query Context
+
+The `query.graph` method accepts an optional `context` parameter that can be used to pass additional context either to the data model you're retrieving (for example, `post`), or its related and linked models (for example, `author`).
+
+You initialize a context using `QueryContext` from the Modules SDK. It accepts an object of contexts as an argument.
+
+For example, to retrieve posts using Query while passing the user's language as a context:
+
+```ts
+const { data } = await query.graph({
+ entity: "post",
+ fields: ["*"],
+ context: QueryContext({
+ lang: "es",
+ }),
+})
+```
+
+In this example, you pass in the context a `lang` property whose value is `es`.
+
+Then, to handle the context while retrieving records of the data model, in the associated module's service you override the generated `list` method of the data model.
+
+For example, continuing the example above, you can override the `listPosts` method of the Blog Module's service to handle the context:
+
+```ts highlights={highlights2}
+import { MedusaContext, MedusaService } from "@medusajs/framework/utils"
+import { Context, FindConfig } from "@medusajs/framework/types"
+import Post from "./models/post"
+import Author from "./models/author"
+
+class BlogModuleService extends MedusaService({
+ Post,
+ Author,
+}){
+ // @ts-ignore
+ async listPosts(
+ filters?: any,
+ config?: FindConfig | undefined,
+ @MedusaContext() sharedContext?: Context | undefined
+ ) {
+ const context = filters.context ?? {}
+ delete filters.context
+
+ let posts = await super.listPosts(filters, config, sharedContext)
+
+ if (context.lang === "es") {
+ posts = posts.map((post) => {
+ return {
+ ...post,
+ title: post.title + " en español",
+ }
+ })
+ }
+
+ return posts
+ }
+}
+
+export default BlogModuleService
+```
+
+In the above example, you override the generated `listPosts` method. This method receives as a first parameter the filters passed to the query, but it also includes a `context` property that holds the context passed to the query.
+
+You extract the context from `filters`, then retrieve the posts using the parent's `listPosts` method. After that, if the language is set in the context, you transform the titles of the posts.
+
+All posts returned will now have their titles appended with "en español".
+
+Learn more about the generated `list` method in [this reference](https://docs.medusajs.com/resources/service-factory-reference/methods/list/index.html.md).
+
+### Using Pagination with Query
+
+If you pass pagination fields to `query.graph`, you must also override the `listAndCount` method in the service.
+
+For example, following along with the previous example, you must override the `listAndCountPosts` method of the Blog Module's service:
+
+```ts
+import { MedusaContext, MedusaService } from "@medusajs/framework/utils"
+import { Context, FindConfig } from "@medusajs/framework/types"
+import Post from "./models/post"
+import Author from "./models/author"
+
+class BlogModuleService extends MedusaService({
+ Post,
+ Author,
+}){
+ // @ts-ignore
+ async listAndCountPosts(
+ filters?: any,
+ config?: FindConfig | undefined,
+ @MedusaContext() sharedContext?: Context | undefined
+ ) {
+ const context = filters.context ?? {}
+ delete filters.context
+
+ const result = await super.listAndCountPosts(
+ filters,
+ config,
+ sharedContext
+ )
+
+ if (context.lang === "es") {
+ result.posts = posts.map((post) => {
+ return {
+ ...post,
+ title: post.title + " en español",
+ }
+ })
+ }
+
+ return result
+ }
+}
+
+export default BlogModuleService
+```
+
+Now, the `listAndCountPosts` method will handle the context passed to `query.graph` when you pass pagination fields. You can also move the logic to transform the posts' titles to a separate method and call it from both `listPosts` and `listAndCountPosts`.
+
+***
+
+## Passing Query Context to Related Data Models
+
+If you're retrieving a data model and you want to pass context to its associated model in the same module, you can pass them as part of `QueryContext`'s parameter, then handle them in the same `list` method.
+
+For linked data models, check out the [next section](#passing-query-context-to-linked-data-models).
+
+For example, to pass a context for the post's authors:
+
+```ts highlights={highlights3}
+const { data } = await query.graph({
+ entity: "post",
+ fields: ["*"],
+ context: QueryContext({
+ lang: "es",
+ author: QueryContext({
+ lang: "es",
+ }),
+ }),
+})
+```
+
+Then, in the `listPosts` method, you can handle the context for the post's authors:
+
+```ts highlights={highlights4}
+import { MedusaContext, MedusaService } from "@medusajs/framework/utils"
+import { Context, FindConfig } from "@medusajs/framework/types"
+import Post from "./models/post"
+import Author from "./models/author"
+
+class BlogModuleService extends MedusaService({
+ Post,
+ Author,
+}){
+ // @ts-ignore
+ async listPosts(
+ filters?: any,
+ config?: FindConfig | undefined,
+ @MedusaContext() sharedContext?: Context | undefined
+ ) {
+ const context = filters.context ?? {}
+ delete filters.context
+
+ let posts = await super.listPosts(filters, config, sharedContext)
+
+ const isPostLangEs = context.lang === "es"
+ const isAuthorLangEs = context.author?.lang === "es"
+
+ if (isPostLangEs || isAuthorLangEs) {
+ posts = posts.map((post) => {
+ return {
+ ...post,
+ title: isPostLangEs ? post.title + " en español" : post.title,
+ author: {
+ ...post.author,
+ name: isAuthorLangEs ? post.author.name + " en español" : post.author.name,
+ },
+ }
+ })
+ }
+
+ return posts
+ }
+}
+
+export default BlogModuleService
+```
+
+The context in `filters` will also have the context for `author`, which you can use to make transformations to the post's authors.
+
+***
+
+## Passing Query Context to Linked Data Models
+
+If you're retrieving a data model and you want to pass context to a linked model in a different module, pass to the `context` property an object instead, where its keys are the linked model's name and the values are the context for that linked model.
+
+For example, consider the Product Module's `Product` data model is linked to the Blog Module's `Post` data model. You can pass context to the `Post` data model while retrieving products like so:
+
+```ts highlights={highlights5}
+const { data } = await query.graph({
+ entity: "product",
+ fields: ["*", "post.*"],
+ context: {
+ post: QueryContext({
+ lang: "es",
+ }),
+ },
+})
+```
+
+In this example, you retrieve products and their associated posts. You also pass a context for `post`, indicating the customer's language.
+
+To handle the context, you override the generated `listPosts` method of the Blog Module as explained [previously](#how-to-use-query-context).
+
+
+# Link
+
+In this chapter, you’ll learn what Link is and how to use it to manage links.
+
+As of [Medusa v2.2.0](https://github.com/medusajs/medusa/releases/tag/v2.2.0), Remote Link has been deprecated in favor of Link. They have the same usage, so you only need to change the key used to resolve the tool from the Medusa container as explained below.
+
+## What is Link?
+
+Link is a class with utility methods to manage links between data models. It’s registered in the Medusa container under the `link` registration name.
+
+For example:
+
+```ts collapsibleLines="1-9" expandButtonLabel="Show Imports"
+import {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import {
+ ContainerRegistrationKeys,
+} from "@medusajs/framework/utils"
+
+export async function POST(
+ req: MedusaRequest,
+ res: MedusaResponse
+): Promise {
+ const link = req.scope.resolve(
+ ContainerRegistrationKeys.LINK
+ )
+
+ // ...
+}
+```
+
+You can use its methods to manage links, such as create or delete links.
+
+***
+
+## Create Link
+
+To create a link between records of two data models, use the `create` method of Link.
+
+For example:
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
+ "helloModuleService": {
+ my_custom_id: "mc_123",
+ },
+})
+```
+
+The `create` method accepts as a parameter an object. The object’s keys are the names of the linked modules.
+
+The keys (names of linked modules) must be in the same [direction](https://docs.medusajs.com/learn/fundamentals/module-links/directions/index.html.md) of the link definition.
+
+The value of each module’s property is an object, whose keys are of the format `{data_model_snake_name}_id`, and values are the IDs of the linked record.
+
+So, in the example above, you link a record of the `MyCustom` data model in a `hello` module to a `Product` record in the Product Module.
+
+***
+
+## Dismiss Link
+
+To remove a link between records of two data models, use the `dismiss` method of Link.
+
+For example:
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.dismiss({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
+ "helloModuleService": {
+ my_custom_id: "mc_123",
+ },
+})
+```
+
+The `dismiss` method accepts the same parameter type as the [create method](#create-link).
+
+The keys (names of linked modules) must be in the same [direction](https://docs.medusajs.com/learn/fundamentals/module-links/directions/index.html.md) of the link definition.
+
+***
+
+## Cascade Delete Linked Records
+
+If a record is deleted, use the `delete` method of Link to delete all linked records.
+
+For example:
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await productModuleService.deleteVariants([variant.id])
+
+await link.delete({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
+})
+```
+
+This deletes all records linked to the deleted product.
+
+***
+
+## Restore Linked Records
+
+If a record that was previously soft-deleted is now restored, use the `restore` method of Link to restore all linked records.
+
+For example:
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await productModuleService.restoreProducts(["prod_123"])
+
+await link.restore({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
+})
+```
+
+
+# Query
+
+In this chapter, you’ll learn about Query and how to use it to fetch data from modules.
+
+## What is Query?
+
+Query fetches data across modules. It’s a set of methods registered in the Medusa container under the `query` key.
+
+In all resources that can access the [Medusa Container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md), such as API routes or workflows, you can resolve Query to fetch data across custom modules and Medusa’s commerce modules.
+
+***
+
+## Query Example
+
+For example, create the route `src/api/query/route.ts` with the following content:
+
+```ts title="src/api/query/route.ts" highlights={exampleHighlights} collapsibleLines="1-8" expandButtonLabel="Show Imports"
+import {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import {
+ ContainerRegistrationKeys,
+} from "@medusajs/framework/utils"
+
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
+
+ const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ })
+
+ res.json({ posts })
+}
+```
+
+In the above example, you resolve Query from the Medusa container using the `ContainerRegistrationKeys.QUERY` (`query`) key.
+
+Then, you run a query using its `graph` method. This method accepts as a parameter an object with the following required properties:
+
+- `entity`: The data model's name, as specified in the first parameter of the `model.define` method used for the data model's definition.
+- `fields`: An array of the data model’s properties to retrieve in the result.
+
+The method returns an object that has a `data` property, which holds an array of the retrieved data. For example:
+
+```json title="Returned Data"
+{
+ "data": [
+ {
+ "id": "123",
+ "title": "My Post"
+ }
+ ]
+}
+```
+
+***
+
+## Querying the Graph
+
+When you use the `query.graph` method, you're running a query through an internal graph that the Medusa application creates.
+
+This graph collects data models of all modules in your application, including commerce and custom modules, and identifies relations and links between them.
+
+***
+
+## Retrieve Linked Records
+
+Retrieve the records of a linked data model by passing in `fields` the data model's name suffixed with `.*`.
+
+For example:
+
+```ts highlights={[["6"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: [
+ "id",
+ "title",
+ "product.*",
+ ],
+})
+```
+
+`.*` means that all of data model's properties should be retrieved. You can also retrieve specific properties by replacing the `*` with the property name, for each property.
+
+For example:
+
+```ts
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: [
+ "id",
+ "title",
+ "product.id",
+ "product.title",
+ ],
+})
+```
+
+In the example above, you retrieve only the `id` and `title` properties of the `product` linked to a `post`.
+
+### Retrieve List Link Records
+
+If the linked data model has `isList` enabled in the link definition, pass in `fields` the data model's plural name suffixed with `.*`.
+
+For example:
+
+```ts highlights={[["6"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: [
+ "id",
+ "title",
+ "products.*",
+ ],
+})
+```
+
+In the example above, you retrieve all products linked to a post.
+
+### Apply Filters and Pagination on Linked Records
+
+Consider you want to apply filters or pagination configurations on the product(s) linked to `post`. To do that, you must query the module link's table instead.
+
+As mentioned in the [Module Link](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md) documentation, Medusa creates a table for your module link. So, not only can you retrieve linked records, but you can also retrieve the records in a module link's table.
+
+A module link's definition, exported by a file under `src/links`, has a special `entryPoint` property. Use this property when specifying the `entity` property in Query's `graph` method.
+
+For example:
+
+```ts highlights={queryLinkTableHighlights}
+import ProductPostLink from "../../../links/product-post"
+
+// ...
+
+const { data: productCustoms } = await query.graph({
+ entity: ProductPostLink.entryPoint,
+ fields: ["*", "product.*", "post.*"],
+ pagination: {
+ take: 5,
+ skip: 0,
+ },
+})
+```
+
+In the object passed to the `graph` method:
+
+- You pass the `entryPoint` property of the link definition as the value for `entity`. So, Query will retrieve records from the module link's table.
+- You pass three items to the `field` property:
+ - `*` to retrieve the link table's fields. This is useful if the link table has [custom columns](https://docs.medusajs.com/learn/fundamentals/module-links/custom-columns/index.html.md).
+ - `product.*` to retrieve the fields of a product record linked to a `Post` record.
+ - `post.*` to retrieve the fields of a `Post` record linked to a product record.
+
+You can then apply any [filters](#apply-filters) or [pagination configurations](#apply-pagination).
+
+The returned `data` is similar to the following:
+
+```json title="Example Result"
+[{
+ "id": "123",
+ "product_id": "prod_123",
+ "post_id": "123",
+ "product": {
+ "id": "prod_123",
+ // other product fields...
+ },
+ "post": {
+ "id": "123",
+ // other post fields...
+ }
+}]
+```
+
+***
+
+## Apply Filters
+
+```ts highlights={[["4"], ["5"], ["6"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ id: "post_123",
+ },
+})
+```
+
+The `query.graph` function accepts a `filters` property. You can use this property to filter retrieved records.
+
+In the example above, you filter the `post` records by the ID `post_123`.
+
+You can also filter by multiple values of a property. For example:
+
+```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"], ["9"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ id: [
+ "post_123",
+ "post_321",
+ ],
+ },
+})
+```
+
+In the example above, you filter the `post` records by multiple IDs.
+
+Filters don't apply on fields of linked data models from other modules. Refer to the [Retrieve Linked Records](#retrieve-linked-records) section for an alternative solution.
+
+### Advanced Query Filters
+
+Under the hood, Query uses the `listX` (`listPosts`) method of the data model's module's service to retrieve records. This method accepts a filter object that can be used to filter records.
+
+Those filters don't just allow you to filter by exact values. You can also filter by properties that don't match a value, match multiple values, and other filter types.
+
+Refer to the [Service Factory Reference](https://docs.medusajs.com/resources/service-factory-reference/tips/filtering/index.html.md) for examples of advanced filters. The following sections provide some quick examples.
+
+#### Filter by Not Matching a Value
+
+```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ title: {
+ $ne: null,
+ },
+ },
+})
+```
+
+In the example above, only posts that have a title are retrieved.
+
+#### Filter by Not Matching Multiple Values
+
+```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ title: {
+ $nin: ["My Post", "Another Post"],
+ },
+ },
+})
+```
+
+In the example above, only posts that don't have the title `My Post` or `Another Post` are retrieved.
+
+#### Filter by a Range
+
+```ts highlights={[["10"], ["11"], ["12"], ["13"], ["14"], ["15"]]}
+const startToday = new Date()
+startToday.setHours(0, 0, 0, 0)
+
+const endToday = new Date()
+endToday.setHours(23, 59, 59, 59)
+
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ published_at: {
+ $gt: startToday,
+ $lt: endToday,
+ },
+ },
+})
+```
+
+In the example above, only posts that were published today are retrieved.
+
+#### Filter Text by Like Value
+
+This filter only applies to text-like properties, including `text`, `id`, and `enum` properties.
+
+```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ title: {
+ $like: "%My%",
+ },
+ },
+})
+```
+
+In the example above, only posts that have the word `My` in their title are retrieved.
+
+#### Filter a Relation's Property
+
+```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ author: {
+ name: "John",
+ },
+ },
+})
+```
+
+While it's not possible to filter by a linked data model's property, you can filter by a relation's property (that is, the property of a related data model that is defined in the same module).
+
+In the example above, only posts that have an author with the name `John` are retrieved.
+
+***
+
+## Apply Pagination
+
+```ts highlights={[["8", "skip", "The number of records to skip before fetching the results."], ["9", "take", "The number of records to fetch."]]}
+const {
+ data: posts,
+ metadata: { count, take, skip } = {},
+} = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ pagination: {
+ skip: 0,
+ take: 10,
+ },
+})
+```
+
+The `graph` method's object parameter accepts a `pagination` property to configure the pagination of retrieved records.
+
+To paginate the returned records, pass the following properties to `pagination`:
+
+- `skip`: (required to apply pagination) The number of records to skip before fetching the results.
+- `take`: The number of records to fetch.
+
+When you provide the pagination fields, the `query.graph` method's returned object has a `metadata` property. Its value is an object having the following properties:
+
+- skip: (\`number\`) The number of records skipped.
+- take: (\`number\`) The number of records requested to fetch.
+- count: (\`number\`) The total number of records.
+
+### Sort Records
+
+```ts highlights={[["5"], ["6"], ["7"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ pagination: {
+ order: {
+ name: "DESC",
+ },
+ },
+})
+```
+
+Sorting doesn't work on fields of linked data models from other modules.
+
+To sort returned records, pass an `order` property to `pagination`.
+
+The `order` property is an object whose keys are property names, and values are either:
+
+- `ASC` to sort records by that property in ascending order.
+- `DESC` to sort records by that property in descending order.
+
+***
+
+## Configure Query to Throw Errors
+
+By default, if Query doesn't find records matching your query, it returns an empty array. You can add option to configure Query to throw an error when no records are found.
+
+The `query.graph` method accepts as a second parameter an object that can have a `throwIfKeyNotFound` property. Its value is a boolean indicating whether to throw an error if no record is found when filtering by IDs. By default, it's `false`.
+
+For example:
+
+```ts
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ id: "post_123",
+ },
+}, {
+ throwIfKeyNotFound: true,
+})
+```
+
+In the example above, if no post is found with the ID `post_123`, Query will throw an error. This is useful to stop execution when a record is expected to exist.
+
+### Throw Error on Related Data Model
+
+The `throwIfKeyNotFound` option can also be used to throw an error if the ID of a related data model's record (in the same module) is passed in the filters, and the related record doesn't exist.
+
+For example:
+
+```ts
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title", "author.*"],
+ filters: {
+ id: "post_123",
+ author_id: "author_123",
+ },
+}, {
+ throwIfKeyNotFound: true,
+})
+```
+
+In the example above, Query throws an error either if no post is found with the ID `post_123` or if its found but its author ID isn't `author_123`.
+
+In the above example, it's assumed that a post belongs to an author, so it has an `author_id` property. However, this also works in the opposite case, where an author has many posts.
+
+For example:
+
+```ts
+const { data: posts } = await query.graph({
+ entity: "author",
+ fields: ["id", "name", "posts.*"],
+ filters: {
+ id: "author_123",
+ posts: {
+ id: "post_123",
+ },
+ },
+}, {
+ throwIfKeyNotFound: true,
+})
+```
+
+In the example above, Query throws an error if no author is found with the ID `author_123` or if the author is found but doesn't have a post with the ID `post_123`.
+
+***
+
+## Request Query Configurations
+
+For API routes that retrieve a single or list of resources, Medusa provides a `validateAndTransformQuery` middleware that:
+
+- Validates accepted query parameters, as explained in [this documentation](https://docs.medusajs.com/learn/fundamentals/api-routes/validation/index.html.md).
+- Parses configurations that are received as query parameters to be passed to Query.
+
+Using this middleware allows you to have default configurations for retrieved fields and relations or pagination, while allowing clients to customize them per request.
+
+### Step 1: Add Middleware
+
+The first step is to use the `validateAndTransformQuery` middleware on the `GET` route. You add the middleware in `src/api/middlewares.ts`:
+
+```ts title="src/api/middlewares.ts"
+import {
+ validateAndTransformQuery,
+ defineMiddlewares,
+} from "@medusajs/framework/http"
+import { createFindParams } from "@medusajs/medusa/api/utils/validators"
+
+export const GetCustomSchema = createFindParams()
+
+export default defineMiddlewares({
+ routes: [
+ {
+ matcher: "/customs",
+ method: "GET",
+ middlewares: [
+ validateAndTransformQuery(
+ GetCustomSchema,
+ {
+ defaults: [
+ "id",
+ "title",
+ "products.*",
+ ],
+ isList: true,
+ }
+ ),
+ ],
+ },
+ ],
+})
+```
+
+The `validateAndTransformQuery` accepts two parameters:
+
+1. A Zod validation schema for the query parameters, which you can learn more about in the [API Route Validation documentation](https://docs.medusajs.com/learn/fundamentals/api-routes/validation/index.html.md). Medusa has a `createFindParams` utility that generates a Zod schema that accepts four query parameters:
+ 1. `fields`: The fields and relations to retrieve in the returned resources.
+ 2. `offset`: The number of items to skip before retrieving the returned items.
+ 3. `limit`: The maximum number of items to return.
+ 4. `order`: The fields to order the returned items by in ascending or descending order.
+2. A Query configuration object. It accepts the following properties:
+ 1. `defaults`: An array of default fields and relations to retrieve in each resource.
+ 2. `isList`: A boolean indicating whether a list of items are returned in the response.
+ 3. `allowed`: An array of fields and relations allowed to be passed in the `fields` query parameter.
+ 4. `defaultLimit`: A number indicating the default limit to use if no limit is provided. By default, it's `50`.
+
+### Step 2: Use Configurations in API Route
+
+After applying this middleware, your API route now accepts the `fields`, `offset`, `limit`, and `order` query parameters mentioned above.
+
+The middleware transforms these parameters to configurations that you can pass to Query in your API route handler. These configurations are stored in the `queryConfig` parameter of the `MedusaRequest` object.
+
+As of [Medusa v2.2.0](https://github.com/medusajs/medusa/releases/tag/v2.2.0), `remoteQueryConfig` has been depercated in favor of `queryConfig`. Their usage is still the same, only the property name has changed.
+
+For example, Create the file `src/api/customs/route.ts` with the following content:
+
+```ts title="src/api/customs/route.ts"
+import {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import {
+ ContainerRegistrationKeys,
+} from "@medusajs/framework/utils"
+
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
+
+ const { data: posts } = await query.graph({
+ entity: "post",
+ ...req.queryConfig,
+ })
+
+ res.json({ posts: posts })
+}
+```
+
+This adds a `GET` API route at `/customs`, which is the API route you added the middleware for.
+
+In the API route, you pass `req.queryConfig` to `query.graph`. `queryConfig` has properties like `fields` and `pagination` to configure the query based on the default values you specified in the middleware, and the query parameters passed in the request.
+
+### Test it Out
+
+To test it out, start your Medusa application and send a `GET` request to the `/customs` API route. A list of records are retrieved with the specified fields in the middleware.
+
+```json title="Returned Data"
+{
+ "posts": [
+ {
+ "id": "123",
+ "title": "test"
+ }
+ ]
+}
+```
+
+Try passing one of the Query configuration parameters, like `fields` or `limit`, and you'll see its impact on the returned result.
+
+Learn more about [specifing fields and relations](https://docs.medusajs.com/api/store#select-fields-and-relations) and [pagination](https://docs.medusajs.com/api/store#pagination) in the API reference.
+
+
+# Read-Only Module Link
+
+In this chapter, you’ll learn what a read-only module link is and how to define one.
+
+## What is a Read-Only Module Link?
+
+Consider a scenario where you need to access related records from another module, but don't want the overhead of managing or storing the links between them. This can include cases where you're working with external data models not stored in your Medusa database, such as third-party systems.
+
+In those cases, instead of defining a [Module Link](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md) whose linked records must be stored in a link table in the database, you can use a read-only module link. A read-only module link builds a virtual relation from one data model to another in a different module without creating a link table in the database. Instead, the linked record's ID is stored in the first data model's field.
+
+For example, Medusa creates a read-only module link from the `Cart` data model of the [Cart Module](https://docs.medusajs.com/resources/commerce-modules/cart/index.html.md) to the `Customer` data model of the [Customer Module](https://docs.medusajs.com/resources/commerce-modules/customer/index.html.md). This link allows you to access the details of the cart's customer without managing the link. Instead, the customer's ID is stored in the `Cart` data model.
+
+
+
+***
+
+## How to Define a Read-Only Module Link
+
+The `defineLink` function accepts an optional third-parameter object that can hold additional configurations for the module link.
+
+If you're not familiar with the `defineLink` function, refer to the [Module Links chapter](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md) for more information.
+
+To make the module link read-only, pass the `readOnly` property as `true`. You must also set in the link configuration of the first data model a `field` property that specifies the data model's field where the linked record's ID is stored.
+
+For example:
+
+```ts highlights={highlights}
+import BlogModule from "../modules/blog"
+import ProductModule from "@medusajs/medusa/product"
+import { defineLink } from "@medusajs/framework/utils"
+
+export default defineLink(
+ {
+ linkable: BlogModule.linkable.post,
+ field: "product_id",
+ },
+ ProductModule.linkable.product,
+ {
+ readOnly: true,
+ }
+)
+```
+
+In this example, you define a read-only module link from the Blog Module's `post` data model to the Product Module's `product` data model. You do that by:
+
+- Passing an object as a first parameter that accepts the linkable configuration and the field where the linked record's ID is stored.
+- Setting the `readOnly` property to `true` in the third parameter.
+
+Unlike the stored module link, Medusa will not create a table in the database for this link. Instead, Medusa uses the ID stored in the specified field of the first data model to retrieve the linked record.
+
+***
+
+## Retrieve Read-Only Linked Record
+
+[Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md) allows you to retrieve records linked through a read-only module link.
+
+For example, assuming you have the module link created in [the above section](#how-to-define-a-read-only-module-link), you can retrieve a post and its linked product as follows:
+
+```ts
+const { result } = await query.graph({
+ entity: "post",
+ fields: ["id", "product.*"],
+ filters: {
+ id: "post_123",
+ },
+})
+```
+
+In the above example, you retrieve a post and its linked product. Medusa will use the ID of the product in the post's `product_id` field to determine which product should be retrieved.
+
+***
+
+## Read-Only Module Link Direction
+
+A read-only module is uni-directional. So, you can only retrieve the linked record from the first data model. If you need to access the linked record from the second data model, you must define another read-only module link in the opposite direction.
+
+In the `blog` -> `product` example, you can access a post's product, but you can't access a product's posts. You would have to define another read-only module link from `product` to `blog` to access a product's posts.
+
+***
+
+## Inverse Read-Only Module Link
+
+An inverse read-only module link is a read-only module link that allows you to access the linked record based on the ID stored in the second data model.
+
+For example, consider you want to access a product's posts. You can define a read-only module link from the Product Module's `product` data model to the Blog Module's `post` data model:
+
+```ts
+import BlogModule from "../modules/blog"
+import ProductModule from "@medusajs/medusa/product"
+import { defineLink } from "@medusajs/framework/utils"
+
+export default defineLink(
+ {
+ linkable: ProductModule.linkable.product,
+ field: "id",
+ },
+ {
+ ...BlogModule.linkable.post.id,
+ primaryKey: "product_id",
+ },
+ {
+ readOnly: true,
+ }
+)
+```
+
+In the above example, you define a read-only module link from the Product Module's `product` data model to the Blog Module's `post` data model. This link allows you to access a product's posts.
+
+Since you can't add a `post_id` field to the `product` data model, you must:
+
+1. Set the `field` property in the first data model's link configuration to the product's ID field.
+2. Spread the `BlogModule.linkable.post.id` object in the second parameter object and set the `primaryKey` property to the field in the `post` data model that holds the product's ID.
+
+You can now retrieve a product and its linked posts:
+
+```ts
+const { result } = await query.graph({
+ entity: "product",
+ fields: ["id", "post.*"],
+ filters: {
+ id: "prod_123",
+ },
+})
+```
+
+***
+
+## One-to-One or One-to-Many?
+
+When you retrieve the linked record through a read-only module link, the retrieved data may be an object (one-to-one) or an array of objects (one-to-many) based on different criteria.
+
+|Scenario|Relation Type|
+|---|---|---|
+|The first data model's |One-to-one relation|
+|The first data model's |One-to-many relation|
+|The read-only module link is inversed.|One-to-many relation if multiple records in the second data model have the same ID of the first data model. Otherwise, one-to-one relation.|
+
+### One-to-One Relation
+
+Consider the first read-only module link you defined in this chapter:
+
+```ts
+import BlogModule from "../modules/blog"
+import ProductModule from "@medusajs/medusa/product"
+
+export default defineLink(
+ {
+ linkable: BlogModule.linkable.post,
+ field: "product_id",
+ },
+ ProductModule.linkable.product,
+ {
+ readOnly: true,
+ }
+)
+```
+
+Since the `product_id` field of a post stores the ID of a single product, the link is a one-to-one relation. When querying a post, you'll get a single product object:
+
+```json title="Example Data"
+[
+ {
+ "id": "post_123",
+ "product_id": "prod_123",
+ "product": {
+ "id": "prod_123",
+ // ...
+ }
+ }
+]
+```
+
+### One-to-Many Relation
+
+Consider the read-only module link from the `post` data model uses an array of product IDs:
+
+```ts
+import BlogModule from "../modules/blog"
+import ProductModule from "@medusajs/medusa/product"
+
+export default defineLink(
+ {
+ linkable: BlogModule.linkable.post,
+ field: "product_ids",
+ },
+ ProductModule.linkable.product,
+ {
+ readOnly: true,
+ }
+)
+```
+
+Where `product_ids` in the `post` data model is an array of strings. In this case, the link would be a one-to-many relation. So, an array of products would be returned when querying a post:
+
+```json title="Example Data"
+[
+ {
+ "id": "post_123",
+ "product_ids": ["prod_123", "prod_124"],
+ "product": [
+ {
+ "id": "prod_123",
+ // ...
+ },
+ {
+ "id": "prod_124",
+ // ...
+ }
+ ]
+ }
+]
+```
+
+### Relation with Inversed Read-Only Link
+
+If you define an inversed read-only module link where the ID of the linked record is stored in the second data model, the link can be either one-to-one or one-to-many based on the number of records in the second data model that have the same ID of the first data model.
+
+For example, consider the `product` -> `post` link you defined in an earlier section:
+
+```ts
+import BlogModule from "../modules/blog"
+import ProductModule from "@medusajs/medusa/product"
+import { defineLink } from "@medusajs/framework/utils"
+
+export default defineLink(
+ {
+ linkable: ProductModule.linkable.product,
+ field: "id",
+ },
+ {
+ ...BlogModule.linkable.post.id,
+ primaryKey: "product_id",
+ },
+ {
+ readOnly: true,
+ }
+)
+```
+
+In the above snippet, the ID of the product is stored in the `post`'s `product_id` string field.
+
+When you retrieve the post of a product, it may be a post object, or an array of post objects if multiple posts are linked to the product:
+
+```json title="Example Data"
+[
+ {
+ "id": "prod_123",
+ "post": {
+ "id": "post_123",
+ "product_id": "prod_123"
+ // ...
+ }
+ },
+ {
+ "id": "prod_321",
+ "post": [
+ {
+ "id": "post_123",
+ "product_id": "prod_321"
+ // ...
+ },
+ {
+ "id": "post_124",
+ "product_id": "prod_321"
+ // ...
+ }
+ ]
+ }
+]
+```
+
+If, however, you use an array field in `post`, the relation would always be one-to-many:
+
+```json title="Example Data"
+[
+ {
+ "id": "prod_123",
+ "post": [
+ {
+ "id": "post_123",
+ "product_id": "prod_123"
+ // ...
+ }
+ ]
+ }
+]
+```
+
+#### Force One-to-Many Relation
+
+Alternatively, you can force a one-to-many relation by setting `isList` to `true` in the first data model's link configuration. For example:
+
+```ts
+import BlogModule from "../modules/blog"
+import ProductModule from "@medusajs/medusa/product"
+import { defineLink } from "@medusajs/framework/utils"
+
+export default defineLink(
+ {
+ linkable: ProductModule.linkable.product,
+ field: "id",
+ isList: true,
+ },
+ {
+ ...BlogModule.linkable.post.id,
+ primaryKey: "product_id",
+ },
+ {
+ readOnly: true,
+ }
+)
+```
+
+In this case, the relation would always be one-to-many, even if only one post is linked to a product:
+
+```json title="Example Data"
+[
+ {
+ "id": "prod_123",
+ "post": [
+ {
+ "id": "post_123",
+ "product_id": "prod_123"
+ // ...
+ }
+ ]
+ }
+]
+```
+
+***
+
+## Example: Read-Only Module Link for Virtual Data Models
+
+Read-only module links are most useful when working with data models that aren't stored in your Medusa database. For example, data that is stored in a third-party system. In those cases, you can define a read-only module link between a data model in Medusa and the data model in the external system, facilitating the retrieval of the linked data.
+
+To define the read-only module link to a virtual data model, you must:
+
+1. Create a `list` method in the custom module's service. This method retrieves the linked records filtered by the ID(s) of the first data model.
+2. Define the read-only module link from the first data model to the virtual data model.
+3. Use Query to retrieve the first data model and its linked records from the virtual data model.
+
+For example, consider you have a third-party Content-Management System (CMS) that you're integrating with Medusa, and you want to retrieve the posts in the CMS associated with a product in Medusa.
+
+To do that, first, create a CMS Module having the following service:
+
+Refer to the [Modules chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md) to learn how to create a module and its service.
+
+```ts title="src/modules/cms/service.ts"
+type CmsModuleOptions = {
+ apiKey: string
+}
+
+export default class CmsModuleService {
+ private client
+
+ constructor({}, options: CmsModuleOptions) {
+ this.client = new Client(options)
+ }
+
+ async list(
+ filter: {
+ id: string | string[]
+ }
+ ) {
+ return this.client.getPosts(filter)
+ /**
+ - Example of returned data:
+ -
+ - [
+ - {
+ - "id": "post_123",
+ - "product_id": "prod_321"
+ - },
+ - {
+ - "id": "post_456",
+ - "product_id": "prod_654"
+ - }
+ - ]
+ */
+ }
+}
+```
+
+The above service initializes a client, assuming your CMS has an SDK that allows you to retrieve posts.
+
+The service must have a `list` method to be part of the read-only module link. This method accepts the ID(s) of the products to retrieve their associated posts. The posts must include the product's ID in a field, such as `product_id`.
+
+Next, define a read-only module link from the Product Module to the CMS Module:
+
+```ts title="src/links/product-cms.ts"
+import { defineLink } from "@medusajs/framework/utils"
+import ProductModule from "@medusajs/medusa/product"
+import { CMS_MODULE } from "../modules/cms"
+
+export default defineLink(
+ {
+ linkable: ProductModule.linkable.product,
+ field: "id",
+ },
+ {
+ linkable: {
+ serviceName: CMS_MODULE,
+ alias: "cms_post",
+ primaryKey: "product_id",
+ },
+ },
+ {
+ readOnly: true,
+ }
+)
+```
+
+To define the read-only module link, you must pass to `defineLink`:
+
+1. The first parameter: an object with the linkable configuration of the data model in Medusa, and the fields that will be passed as a filter to the CMS service. For example, if you want to filter by product title instead, you can pass `title` instead of `id`.
+2. The second parameter: an object with the linkable configuration of the virtual data model in the CMS. This object must have the following properties:
+ - `serviceName`: The name of the service, which is the CMS Module's name. Medusa uses this name to resolve the module's service from the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md).
+ - `alias`: The alias to use when querying the linked records. You'll see how that works in a bit.
+ - `primaryKey`: The field in the CMS data model that holds the ID of a product.
+3. The third parameter: an object with the `readOnly` property set to `true`.
+
+Now, you can use Query to retrieve a product and its linked post from the CMS:
+
+```ts
+const { data } = await query.graph({
+ entity: "product",
+ fields: ["id", "cms_post.*"],
+})
+```
+
+In the above example, each product that has a CMS post with the `product_id` field set to the product's ID will be retrieved:
+
+```json title="Example Data"
+[
+ {
+ "id": "prod_123",
+ "cms_post": {
+ "id": "post_123",
+ "product_id": "prod_123",
+ // ...
+ }
+ }
+]
+```
+
+If multiple posts have their `product_id` set to a product's ID, an array of posts is returned instead:
+
+```json title="Example Data"
+[
+ {
+ "id": "prod_123",
+ "cms_post": [
+ {
+ "id": "post_123",
+ "product_id": "prod_123",
+ // ...
+ },
+ {
+ "id": "post_124",
+ "product_id": "prod_123",
+ // ...
+ }
+ ]
+ }
+]
+```
+
+[Sanity Integration Tutorial](https://docs.medusajs.com/resources/integrations/guides/sanity/index.html.md).
+
+
# Commerce Modules
In this chapter, you'll learn about Medusa's commerce modules.
@@ -12056,6 +12455,37 @@ export const countProductsStep = createStep(
Your workflow can use services of both custom and commerce modules, supporting you in building custom flows without having to re-build core commerce features.
+# Architectural Modules
+
+In this chapter, you’ll learn about architectural modules.
+
+## What is an Architectural Module?
+
+An architectural module implements features and mechanisms related to the Medusa application’s architecture and infrastructure.
+
+Since modules are interchangeable, you have more control over Medusa’s architecture. For example, you can choose to use Memcached for event handling instead of Redis.
+
+***
+
+## Architectural Module Types
+
+There are different architectural module types including:
+
+
+
+- Cache Module: Defines the caching mechanism or logic to cache computational results.
+- Event Module: Integrates a pub/sub service to handle subscribing to and emitting events.
+- Workflow Engine Module: Integrates a service to store and track workflow executions and steps.
+- File Module: Integrates a storage service to handle uploading and managing files.
+- Notification Module: Integrates a third-party service or defines custom logic to send notifications to users and customers.
+
+***
+
+## Architectural Modules List
+
+Refer to the [Architectural Modules reference](https://docs.medusajs.com/resources/architectural-modules/index.html.md) for a list of Medusa’s architectural modules, available modules to install, and how to create an architectural module.
+
+
# Module Container
In this chapter, you'll learn about the module's container and how to resolve resources in that container.
@@ -12122,107 +12552,6 @@ export default async function helloWorldLoader({
```
-# Module Isolation
-
-In this chapter, you'll learn how modules are isolated, and what that means for your custom development.
-
-- Modules can't access resources, such as services or data models, from other modules.
-- Use Medusa's linking concepts, as explained in the [Module Links chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md), to extend a module's data models and retrieve data across modules.
-
-## How are Modules Isolated?
-
-A module is unaware of any resources other than its own, such as services or data models. This means it can't access these resources if they're implemented in another module.
-
-For example, your custom module can't resolve the Product Module's main service or have direct relationships from its data model to the Product Module's data models.
-
-***
-
-## Why are Modules Isolated
-
-Some of the module isolation's benefits include:
-
-- Integrate your module into any Medusa application without side-effects to your setup.
-- Replace existing modules with your custom implementation, if your use case is drastically different.
-- Use modules in other environments, such as Edge functions and Next.js apps.
-
-***
-
-## How to Extend Data Model of Another Module?
-
-To extend the data model of another module, such as the `product` data model of the Product Module, use Medusa's linking concepts as explained in the [Module Links chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md).
-
-***
-
-## How to Use Services of Other Modules?
-
-If you're building a feature that uses functionalities from different modules, use a workflow whose steps resolve the modules' services to perform these functionalities.
-
-Workflows ensure data consistency through their roll-back mechanism and tracking of each execution's status, steps, input, and output.
-
-### Example
-
-For example, consider you have two modules:
-
-1. A module that stores and manages brands in your application.
-2. A module that integrates a third-party Content Management System (CMS).
-
-To sync brands from your application to the third-party system, create the following steps:
-
-```ts title="Example Steps" highlights={stepsHighlights}
-const retrieveBrandsStep = createStep(
- "retrieve-brands",
- async (_, { container }) => {
- const brandModuleService = container.resolve(
- "brandModuleService"
- )
-
- const brands = await brandModuleService.listBrands()
-
- return new StepResponse(brands)
- }
-)
-
-const createBrandsInCmsStep = createStep(
- "create-brands-in-cms",
- async ({ brands }, { container }) => {
- const cmsModuleService = container.resolve(
- "cmsModuleService"
- )
-
- const cmsBrands = await cmsModuleService.createBrands(brands)
-
- return new StepResponse(cmsBrands, cmsBrands)
- },
- async (brands, { container }) => {
- const cmsModuleService = container.resolve(
- "cmsModuleService"
- )
-
- await cmsModuleService.deleteBrands(
- brands.map((brand) => brand.id)
- )
- }
-)
-```
-
-The `retrieveBrandsStep` retrieves the brands from a brand module, and the `createBrandsInCmsStep` creates the brands in a third-party system using a CMS module.
-
-Then, create the following workflow that uses these steps:
-
-```ts title="Example Workflow"
-export const syncBrandsWorkflow = createWorkflow(
- "sync-brands",
- () => {
- const brands = retrieveBrandsStep()
-
- createBrandsInCmsStep({ brands })
- }
-)
-```
-
-You can then use this workflow in an API route, scheduled job, or other resources that use this functionality.
-
-
# Perform Database Operations in a Service
In this chapter, you'll learn how to perform database operations in a module's service.
@@ -12248,7 +12577,8 @@ import {
InjectManager,
MedusaContext,
} from "@medusajs/framework/utils"
-import { SqlEntityManager } from "@mikro-orm/knex"
+import { Context } from "@medusajs/framework/types"
+import { EntityManager } from "@mikro-orm/knex"
class BlogModuleService {
// ...
@@ -12256,19 +12586,19 @@ class BlogModuleService {
@InjectManager()
async getCount(
@MedusaContext() sharedContext?: Context
- ): Promise {
- return await sharedContext.manager.count("my_custom")
+ ): Promise {
+ return await sharedContext?.manager?.count("my_custom")
}
@InjectManager()
async getCountSql(
@MedusaContext() sharedContext?: Context
): Promise {
- const data = await sharedContext.manager.execute(
+ const data = await sharedContext?.manager?.execute(
"SELECT COUNT(*) as num FROM my_custom"
)
- return parseInt(data[0].num)
+ return parseInt(data?.[0].num || 0)
}
}
```
@@ -12313,8 +12643,8 @@ class BlogModuleService {
},
@MedusaContext() sharedContext?: Context
): Promise {
- const transactionManager = sharedContext.transactionManager
- await transactionManager.nativeUpdate(
+ const transactionManager = sharedContext?.transactionManager
+ await transactionManager?.nativeUpdate(
"my_custom",
{
id: input.id,
@@ -12325,7 +12655,7 @@ class BlogModuleService {
)
// retrieve again
- const updatedRecord = await transactionManager.execute(
+ const updatedRecord = await transactionManager?.execute(
`SELECT * FROM my_custom WHERE id = '${input.id}'`
)
@@ -12368,10 +12698,22 @@ For example, the `update` method could be changed to the following:
```ts
// other imports...
+import {
+ InjectManager,
+ InjectTransactionManager,
+ MedusaContext,
+} from "@medusajs/framework/utils"
+import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
class BlogModuleService {
// ...
+ @InjectTransactionManager()
+ protected async update_(
+ // ...
+ ): Promise {
+ // ...
+ }
@InjectManager()
async update(
input: {
@@ -12382,7 +12724,8 @@ class BlogModuleService {
) {
const newData = await this.update_(input, sharedContext)
- await sendNewDataToSystem(newData)
+ // example method that sends data to another system
+ await this.sendNewDataToSystem(newData)
return newData
}
@@ -12399,6 +12742,11 @@ For example:
```ts
// other imports...
+import {
+ InjectTransactionManager,
+ MedusaContext,
+} from "@medusajs/framework/utils"
+import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
class BlogModuleService {
@@ -12526,7 +12874,7 @@ class BlogModuleService {
return updatedRecord
},
{
- transaction: sharedContext.transactionManager,
+ transaction: sharedContext?.transactionManager,
}
)
}
@@ -12561,6 +12909,12 @@ The second parameter of the `baseRepository_.transaction` method is an object of
```ts highlights={[["16"]]}
// other imports...
import { EntityManager } from "@mikro-orm/knex"
+import {
+ InjectTransactionManager,
+ MedusaContext,
+} from "@medusajs/framework/utils"
+import { Context } from "@medusajs/framework/types"
+import { EntityManager } from "@mikro-orm/knex"
class BlogModuleService {
// ...
@@ -12577,7 +12931,7 @@ class BlogModuleService {
// ...
},
{
- transaction: sharedContext.transactionManager,
+ transaction: sharedContext?.transactionManager,
}
)
}
@@ -12593,6 +12947,12 @@ class BlogModuleService {
```ts highlights={[["19"]]}
// other imports...
+import {
+ InjectTransactionManager,
+ MedusaContext,
+} from "@medusajs/framework/utils"
+import { Context } from "@medusajs/framework/types"
+import { EntityManager } from "@mikro-orm/knex"
import { IsolationLevel } from "@mikro-orm/core"
class BlogModuleService {
@@ -12621,6 +12981,14 @@ class BlogModuleService {
- If `transaction` is provided and this is disabled, the manager in `transaction` is re-used.
```ts highlights={[["16"]]}
+// other imports...
+import {
+ InjectTransactionManager,
+ MedusaContext,
+} from "@medusajs/framework/utils"
+import { Context } from "@medusajs/framework/types"
+import { EntityManager } from "@mikro-orm/knex"
+
class BlogModuleService {
// ...
@InjectTransactionManager()
@@ -12887,6 +13255,107 @@ info: Connected to MongoDB
You can now resolve the MongoDB Module's main service in your customizations to perform operations on the MongoDB database.
+# Module Isolation
+
+In this chapter, you'll learn how modules are isolated, and what that means for your custom development.
+
+- Modules can't access resources, such as services or data models, from other modules.
+- Use Medusa's linking concepts, as explained in the [Module Links chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md), to extend a module's data models and retrieve data across modules.
+
+## How are Modules Isolated?
+
+A module is unaware of any resources other than its own, such as services or data models. This means it can't access these resources if they're implemented in another module.
+
+For example, your custom module can't resolve the Product Module's main service or have direct relationships from its data model to the Product Module's data models.
+
+***
+
+## Why are Modules Isolated
+
+Some of the module isolation's benefits include:
+
+- Integrate your module into any Medusa application without side-effects to your setup.
+- Replace existing modules with your custom implementation, if your use case is drastically different.
+- Use modules in other environments, such as Edge functions and Next.js apps.
+
+***
+
+## How to Extend Data Model of Another Module?
+
+To extend the data model of another module, such as the `product` data model of the Product Module, use Medusa's linking concepts as explained in the [Module Links chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md).
+
+***
+
+## How to Use Services of Other Modules?
+
+If you're building a feature that uses functionalities from different modules, use a workflow whose steps resolve the modules' services to perform these functionalities.
+
+Workflows ensure data consistency through their roll-back mechanism and tracking of each execution's status, steps, input, and output.
+
+### Example
+
+For example, consider you have two modules:
+
+1. A module that stores and manages brands in your application.
+2. A module that integrates a third-party Content Management System (CMS).
+
+To sync brands from your application to the third-party system, create the following steps:
+
+```ts title="Example Steps" highlights={stepsHighlights}
+const retrieveBrandsStep = createStep(
+ "retrieve-brands",
+ async (_, { container }) => {
+ const brandModuleService = container.resolve(
+ "brandModuleService"
+ )
+
+ const brands = await brandModuleService.listBrands()
+
+ return new StepResponse(brands)
+ }
+)
+
+const createBrandsInCmsStep = createStep(
+ "create-brands-in-cms",
+ async ({ brands }, { container }) => {
+ const cmsModuleService = container.resolve(
+ "cmsModuleService"
+ )
+
+ const cmsBrands = await cmsModuleService.createBrands(brands)
+
+ return new StepResponse(cmsBrands, cmsBrands)
+ },
+ async (brands, { container }) => {
+ const cmsModuleService = container.resolve(
+ "cmsModuleService"
+ )
+
+ await cmsModuleService.deleteBrands(
+ brands.map((brand) => brand.id)
+ )
+ }
+)
+```
+
+The `retrieveBrandsStep` retrieves the brands from a brand module, and the `createBrandsInCmsStep` creates the brands in a third-party system using a CMS module.
+
+Then, create the following workflow that uses these steps:
+
+```ts title="Example Workflow"
+export const syncBrandsWorkflow = createWorkflow(
+ "sync-brands",
+ () => {
+ const brands = retrieveBrandsStep()
+
+ createBrandsInCmsStep({ brands })
+ }
+)
+```
+
+You can then use this workflow in an API route, scheduled job, or other resources that use this functionality.
+
+
# Modules Directory Structure
In this document, you'll learn about the expected files and directories in your module.
@@ -13420,6 +13889,51 @@ export default BlogModuleService
```
+# Access Workflow Errors
+
+In this chapter, you’ll learn how to access errors that occur during a workflow’s execution.
+
+## How to Access Workflow Errors?
+
+By default, when an error occurs in a workflow, it throws that error, and the execution stops.
+
+You can configure the workflow to return the errors instead so that you can access and handle them differently.
+
+For example:
+
+```ts title="src/api/workflows/route.ts" highlights={highlights} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import myWorkflow from "../../../workflows/hello-world"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result, errors } = await myWorkflow(req.scope)
+ .run({
+ // ...
+ throwOnError: false,
+ })
+
+ if (errors.length) {
+ return res.send({
+ errors: errors.map((error) => error.error),
+ })
+ }
+
+ res.send(result)
+}
+
+```
+
+The object passed to the `run` method accepts a `throwOnError` property. When disabled, the errors are returned in the `errors` property of `run`'s output.
+
+The value of `errors` is an array of error objects. Each object has an `error` property, whose value is the name or text of the thrown error.
+
+
# Expose a Workflow Hook
In this chapter, you'll learn how to expose a hook in your workflow.
@@ -13489,81 +14003,6 @@ The hook is available on the workflow's `hooks` property using its name `product
You invoke the hook, passing a step function (the hook handler) as a parameter.
-# Access Workflow Errors
-
-In this chapter, you’ll learn how to access errors that occur during a workflow’s execution.
-
-## How to Access Workflow Errors?
-
-By default, when an error occurs in a workflow, it throws that error, and the execution stops.
-
-You can configure the workflow to return the errors instead so that you can access and handle them differently.
-
-For example:
-
-```ts title="src/api/workflows/route.ts" highlights={highlights} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import myWorkflow from "../../../workflows/hello-world"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result, errors } = await myWorkflow(req.scope)
- .run({
- // ...
- throwOnError: false,
- })
-
- if (errors.length) {
- return res.send({
- errors: errors.map((error) => error.error),
- })
- }
-
- res.send(result)
-}
-
-```
-
-The object passed to the `run` method accepts a `throwOnError` property. When disabled, the errors are returned in the `errors` property of `run`'s output.
-
-The value of `errors` is an array of error objects. Each object has an `error` property, whose value is the name or text of the thrown error.
-
-
-# Scheduled Jobs Number of Executions
-
-In this chapter, you'll learn how to set a limit on the number of times a scheduled job is executed.
-
-## numberOfExecutions Option
-
-The export configuration object of the scheduled job accepts an optional property `numberOfExecutions`. Its value is a number indicating how many times the scheduled job can be executed during the Medusa application's runtime.
-
-For example:
-
-```ts highlights={highlights}
-export default async function myCustomJob() {
- console.log("I'll be executed three times only.")
-}
-
-export const config = {
- name: "hello-world",
- // execute every minute
- schedule: "* * * * *",
- numberOfExecutions: 3,
-}
-```
-
-The above scheduled job has the `numberOfExecutions` configuration set to `3`.
-
-So, it'll only execute 3 times, each every minute, then it won't be executed anymore.
-
-If you restart the Medusa application, the scheduled job will be executed again until reaching the number of executions specified.
-
-
# Compensation Function
In this chapter, you'll learn what a compensation function is and how to add it to a step.
@@ -14325,136 +14764,6 @@ const step1 = createStep(
```
-# Execute Another Workflow
-
-In this chapter, you'll learn how to execute a workflow in another.
-
-## Execute in a Workflow
-
-To execute a workflow in another, use the `runAsStep` method that every workflow has.
-
-For example:
-
-```ts highlights={workflowsHighlights} collapsibleLines="1-7" expandMoreButton="Show Imports"
-import {
- createWorkflow,
-} from "@medusajs/framework/workflows-sdk"
-import {
- createProductsWorkflow,
-} from "@medusajs/medusa/core-flows"
-
-const workflow = createWorkflow(
- "hello-world",
- async (input) => {
- const products = createProductsWorkflow.runAsStep({
- input: {
- products: [
- // ...
- ],
- },
- })
-
- // ...
- }
-)
-```
-
-Instead of invoking the workflow and passing it the container, you use its `runAsStep` method and pass it an object as a parameter.
-
-The object has an `input` property to pass input to the workflow.
-
-***
-
-## Preparing Input Data
-
-If you need to perform some data manipulation to prepare the other workflow's input data, use `transform` from the Workflows SDK.
-
-Learn about transform in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/variable-manipulation/index.html.md).
-
-For example:
-
-```ts highlights={transformHighlights} collapsibleLines="1-12"
-import {
- createWorkflow,
- transform,
-} from "@medusajs/framework/workflows-sdk"
-import {
- createProductsWorkflow,
-} from "@medusajs/medusa/core-flows"
-
-type WorkflowInput = {
- title: string
-}
-
-const workflow = createWorkflow(
- "hello-product",
- async (input: WorkflowInput) => {
- const createProductsData = transform({
- input,
- }, (data) => [
- {
- title: `Hello ${data.input.title}`,
- },
- ])
-
- const products = createProductsWorkflow.runAsStep({
- input: {
- products: createProductsData,
- },
- })
-
- // ...
- }
-)
-```
-
-In this example, you use the `transform` function to prepend `Hello` to the title of the product. Then, you pass the result as an input to the `createProductsWorkflow`.
-
-***
-
-## Run Workflow Conditionally
-
-To run a workflow in another based on a condition, use when-then from the Workflows SDK.
-
-Learn about when-then in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/conditions/index.html.md).
-
-For example:
-
-```ts highlights={whenHighlights} collapsibleLines="1-16"
-import {
- createWorkflow,
- when,
-} from "@medusajs/framework/workflows-sdk"
-import {
- createProductsWorkflow,
-} from "@medusajs/medusa/core-flows"
-import {
- CreateProductWorkflowInputDTO,
-} from "@medusajs/framework/types"
-
-type WorkflowInput = {
- product?: CreateProductWorkflowInputDTO
- should_create?: boolean
-}
-
-const workflow = createWorkflow(
- "hello-product",
- async (input: WorkflowInput) => {
- const product = when(input, ({ should_create }) => should_create)
- .then(() => {
- return createProductsWorkflow.runAsStep({
- input: {
- products: [input.product],
- },
- })
- })
- }
-)
-```
-
-In this example, you use when-then to run the `createProductsWorkflow` only if `should_create` (passed in the `input`) is enabled.
-
-
# Long-Running Workflows
In this chapter, you’ll learn what a long-running workflow is and how to configure it.
@@ -14743,6 +15052,136 @@ To find a full example of a long-running workflow, refer to the [restaurant-deli
In the recipe, you use a long-running workflow that moves an order from placed to completed. The workflow waits for the restaurant to accept the order, the driver to pick up the order, and other external actions.
+# Execute Another Workflow
+
+In this chapter, you'll learn how to execute a workflow in another.
+
+## Execute in a Workflow
+
+To execute a workflow in another, use the `runAsStep` method that every workflow has.
+
+For example:
+
+```ts highlights={workflowsHighlights} collapsibleLines="1-7" expandMoreButton="Show Imports"
+import {
+ createWorkflow,
+} from "@medusajs/framework/workflows-sdk"
+import {
+ createProductsWorkflow,
+} from "@medusajs/medusa/core-flows"
+
+const workflow = createWorkflow(
+ "hello-world",
+ async (input) => {
+ const products = createProductsWorkflow.runAsStep({
+ input: {
+ products: [
+ // ...
+ ],
+ },
+ })
+
+ // ...
+ }
+)
+```
+
+Instead of invoking the workflow and passing it the container, you use its `runAsStep` method and pass it an object as a parameter.
+
+The object has an `input` property to pass input to the workflow.
+
+***
+
+## Preparing Input Data
+
+If you need to perform some data manipulation to prepare the other workflow's input data, use `transform` from the Workflows SDK.
+
+Learn about transform in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/variable-manipulation/index.html.md).
+
+For example:
+
+```ts highlights={transformHighlights} collapsibleLines="1-12"
+import {
+ createWorkflow,
+ transform,
+} from "@medusajs/framework/workflows-sdk"
+import {
+ createProductsWorkflow,
+} from "@medusajs/medusa/core-flows"
+
+type WorkflowInput = {
+ title: string
+}
+
+const workflow = createWorkflow(
+ "hello-product",
+ async (input: WorkflowInput) => {
+ const createProductsData = transform({
+ input,
+ }, (data) => [
+ {
+ title: `Hello ${data.input.title}`,
+ },
+ ])
+
+ const products = createProductsWorkflow.runAsStep({
+ input: {
+ products: createProductsData,
+ },
+ })
+
+ // ...
+ }
+)
+```
+
+In this example, you use the `transform` function to prepend `Hello` to the title of the product. Then, you pass the result as an input to the `createProductsWorkflow`.
+
+***
+
+## Run Workflow Conditionally
+
+To run a workflow in another based on a condition, use when-then from the Workflows SDK.
+
+Learn about when-then in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/conditions/index.html.md).
+
+For example:
+
+```ts highlights={whenHighlights} collapsibleLines="1-16"
+import {
+ createWorkflow,
+ when,
+} from "@medusajs/framework/workflows-sdk"
+import {
+ createProductsWorkflow,
+} from "@medusajs/medusa/core-flows"
+import {
+ CreateProductWorkflowInputDTO,
+} from "@medusajs/framework/types"
+
+type WorkflowInput = {
+ product?: CreateProductWorkflowInputDTO
+ should_create?: boolean
+}
+
+const workflow = createWorkflow(
+ "hello-product",
+ async (input: WorkflowInput) => {
+ const product = when(input, ({ should_create }) => should_create)
+ .then(() => {
+ return createProductsWorkflow.runAsStep({
+ input: {
+ products: [input.product],
+ },
+ })
+ })
+ }
+)
+```
+
+In this example, you use when-then to run the `createProductsWorkflow` only if `should_create` (passed in the `input`) is enabled.
+
+
# Run Workflow Steps in Parallel
In this chapter, you’ll learn how to run workflow steps in parallel.
@@ -14797,92 +15236,6 @@ It returns an array of the steps' results in the same order they're passed to th
So, `prices` is the result of `createPricesStep`, and `productSalesChannel` is the result of `attachProductToSalesChannelStep`.
-# Retry Failed Steps
-
-In this chapter, you’ll learn how to configure steps to allow retrial on failure.
-
-## Configure a Step’s Retrial
-
-By default, when an error occurs in a step, the step and the workflow fail, and the execution stops.
-
-You can configure the step to retry on failure. The `createStep` function can accept a configuration object instead of the step’s name as a first parameter.
-
-For example:
-
-```ts title="src/workflows/hello-world.ts" highlights={[["10"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import {
- createStep,
- createWorkflow,
- WorkflowResponse,
-} from "@medusajs/framework/workflows-sdk"
-
-const step1 = createStep(
- {
- name: "step-1",
- maxRetries: 2,
- },
- async () => {
- console.log("Executing step 1")
-
- throw new Error("Oops! Something happened.")
- }
-)
-
-const myWorkflow = createWorkflow(
- "hello-world",
- function () {
- const str1 = step1()
-
- return new WorkflowResponse({
- message: str1,
- })
-})
-
-export default myWorkflow
-```
-
-The step’s configuration object accepts a `maxRetries` property, which is a number indicating the number of times a step can be retried when it fails.
-
-When you execute the above workflow, you’ll see the following result in the terminal:
-
-```bash
-Executing step 1
-Executing step 1
-Executing step 1
-error: Oops! Something happened.
-Error: Oops! Something happened.
-```
-
-The first line indicates the first time the step was executed, and the next two lines indicate the times the step was retried. After that, the step and workflow fail.
-
-***
-
-## Step Retry Intervals
-
-By default, a step is retried immediately after it fails. To specify a wait time before a step is retried, pass a `retryInterval` property to the step's configuration object. Its value is a number of seconds to wait before retrying the step.
-
-For example:
-
-```ts title="src/workflows/hello-world.ts" highlights={[["5"]]}
-const step1 = createStep(
- {
- name: "step-1",
- maxRetries: 2,
- retryInterval: 2, // 2 seconds
- },
- async () => {
- // ...
- }
-)
-```
-
-### Interval Changes Workflow to Long-Running
-
-By setting `retryInterval` on a step, a workflow becomes a [long-running workflow](https://docs.medusajs.com/learn/fundamentals/workflows/long-running-workflow/index.html.md) that runs asynchronously in the background. So, you won't receive its result or errors immediately when you execute the workflow.
-
-Instead, you must subscribe to the workflow's execution using the Workflow Engine Module Service. Learn more about it in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/long-running-workflow#access-long-running-workflow-status-and-result/index.html.md).
-
-
# Multiple Step Usage in Workflow
In this chapter, you'll learn how to use a step multiple times in a workflow.
@@ -15102,6 +15455,92 @@ if (workflowExecution.state === "failed") {
Other state values include `done`, `invoking`, and `compensating`.
+# Retry Failed Steps
+
+In this chapter, you’ll learn how to configure steps to allow retrial on failure.
+
+## Configure a Step’s Retrial
+
+By default, when an error occurs in a step, the step and the workflow fail, and the execution stops.
+
+You can configure the step to retry on failure. The `createStep` function can accept a configuration object instead of the step’s name as a first parameter.
+
+For example:
+
+```ts title="src/workflows/hello-world.ts" highlights={[["10"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import {
+ createStep,
+ createWorkflow,
+ WorkflowResponse,
+} from "@medusajs/framework/workflows-sdk"
+
+const step1 = createStep(
+ {
+ name: "step-1",
+ maxRetries: 2,
+ },
+ async () => {
+ console.log("Executing step 1")
+
+ throw new Error("Oops! Something happened.")
+ }
+)
+
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function () {
+ const str1 = step1()
+
+ return new WorkflowResponse({
+ message: str1,
+ })
+})
+
+export default myWorkflow
+```
+
+The step’s configuration object accepts a `maxRetries` property, which is a number indicating the number of times a step can be retried when it fails.
+
+When you execute the above workflow, you’ll see the following result in the terminal:
+
+```bash
+Executing step 1
+Executing step 1
+Executing step 1
+error: Oops! Something happened.
+Error: Oops! Something happened.
+```
+
+The first line indicates the first time the step was executed, and the next two lines indicate the times the step was retried. After that, the step and workflow fail.
+
+***
+
+## Step Retry Intervals
+
+By default, a step is retried immediately after it fails. To specify a wait time before a step is retried, pass a `retryInterval` property to the step's configuration object. Its value is a number of seconds to wait before retrying the step.
+
+For example:
+
+```ts title="src/workflows/hello-world.ts" highlights={[["5"]]}
+const step1 = createStep(
+ {
+ name: "step-1",
+ maxRetries: 2,
+ retryInterval: 2, // 2 seconds
+ },
+ async () => {
+ // ...
+ }
+)
+```
+
+### Interval Changes Workflow to Long-Running
+
+By setting `retryInterval` on a step, a workflow becomes a [long-running workflow](https://docs.medusajs.com/learn/fundamentals/workflows/long-running-workflow/index.html.md) that runs asynchronously in the background. So, you won't receive its result or errors immediately when you execute the workflow.
+
+Instead, you must subscribe to the workflow's execution using the Workflow Engine Module Service. Learn more about it in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/long-running-workflow#access-long-running-workflow-status-and-result/index.html.md).
+
+
# Variable Manipulation in Workflows with transform
In this chapter, you'll learn how to use `transform` from the Workflows SDK to manipulate variables in a workflow.
@@ -15307,92 +15746,6 @@ const myWorkflow = createWorkflow(
```
-# Workflow Timeout
-
-In this chapter, you’ll learn how to set a timeout for workflows and steps.
-
-## What is a Workflow Timeout?
-
-By default, a workflow doesn’t have a timeout. It continues execution until it’s finished or an error occurs.
-
-You can configure a workflow’s timeout to indicate how long the workflow can execute. If a workflow's execution time passes the configured timeout, it is failed and an error is thrown.
-
-### Timeout Doesn't Stop Step Execution
-
-Configuring a timeout doesn't stop the execution of a step in progress. The timeout only affects the status of the workflow and its result.
-
-***
-
-## Configure Workflow Timeout
-
-The `createWorkflow` function can accept a configuration object instead of the workflow’s name.
-
-In the configuration object, you pass a `timeout` property, whose value is a number indicating the timeout in seconds.
-
-For example:
-
-```ts title="src/workflows/hello-world.ts" highlights={[["16"]]} collapsibleLines="1-13" expandButtonLabel="Show More"
-import {
- createStep,
- createWorkflow,
- WorkflowResponse,
-} from "@medusajs/framework/workflows-sdk"
-
-const step1 = createStep(
- "step-1",
- async () => {
- // ...
- }
-)
-
-const myWorkflow = createWorkflow({
- name: "hello-world",
- timeout: 2, // 2 seconds
-}, function () {
- const str1 = step1()
-
- return new WorkflowResponse({
- message: str1,
- })
-})
-
-export default myWorkflow
-
-```
-
-This workflow's executions fail if they run longer than two seconds.
-
-A workflow’s timeout error is returned in the `errors` property of the workflow’s execution, as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/access-workflow-errors/index.html.md). The error’s name is `TransactionTimeoutError`.
-
-***
-
-## Configure Step Timeout
-
-Alternatively, you can configure the timeout for a step rather than the entire workflow.
-
-As mentioned in the previous section, the timeout doesn't stop the execution of the step. It only affects the step's status and output.
-
-The step’s configuration object accepts a `timeout` property, whose value is a number indicating the timeout in seconds.
-
-For example:
-
-```tsx
-const step1 = createStep(
- {
- name: "step-1",
- timeout: 2, // 2 seconds
- },
- async () => {
- // ...
- }
-)
-```
-
-This step's executions fail if they run longer than two seconds.
-
-A step’s timeout error is returned in the `errors` property of the workflow’s execution, as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/access-workflow-errors/index.html.md). The error’s name is `TransactionStepTimeoutError`.
-
-
# Workflow Hooks
In this chapter, you'll learn what a workflow hook is and how to consume them.
@@ -15517,86 +15870,90 @@ export async function POST(req: MedusaRequest, res: MedusaResponse) {
Your hook handler then receives that passed data in the `additional_data` object.
-# Write Integration Tests
+# Workflow Timeout
-In this chapter, you'll learn about `medusaIntegrationTestRunner` from Medusa's Testing Framework and how to use it to write integration tests.
+In this chapter, you’ll learn how to set a timeout for workflows and steps.
-### Prerequisites
+## What is a Workflow Timeout?
-- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md)
+By default, a workflow doesn’t have a timeout. It continues execution until it’s finished or an error occurs.
-## medusaIntegrationTestRunner Utility
+You can configure a workflow’s timeout to indicate how long the workflow can execute. If a workflow's execution time passes the configured timeout, it is failed and an error is thrown.
-The `medusaIntegrationTestRunner` is from Medusa's Testing Framework and it's used to create integration tests in your Medusa project. It runs a full Medusa application, allowing you test API routes, workflows, or other customizations.
+### Timeout Doesn't Stop Step Execution
+
+Configuring a timeout doesn't stop the execution of a step in progress. The timeout only affects the status of the workflow and its result.
+
+***
+
+## Configure Workflow Timeout
+
+The `createWorkflow` function can accept a configuration object instead of the workflow’s name.
+
+In the configuration object, you pass a `timeout` property, whose value is a number indicating the timeout in seconds.
For example:
-```ts title="integration-tests/http/test.spec.ts" highlights={highlights}
-import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
+```ts title="src/workflows/hello-world.ts" highlights={[["16"]]} collapsibleLines="1-13" expandButtonLabel="Show More"
+import {
+ createStep,
+ createWorkflow,
+ WorkflowResponse,
+} from "@medusajs/framework/workflows-sdk"
-medusaIntegrationTestRunner({
- testSuite: ({ api, getContainer }) => {
- // TODO write tests...
- },
+const step1 = createStep(
+ "step-1",
+ async () => {
+ // ...
+ }
+)
+
+const myWorkflow = createWorkflow({
+ name: "hello-world",
+ timeout: 2, // 2 seconds
+}, function () {
+ const str1 = step1()
+
+ return new WorkflowResponse({
+ message: str1,
+ })
})
-jest.setTimeout(60 * 1000)
+export default myWorkflow
+
```
-The `medusaIntegrationTestRunner` function accepts an object as a parameter. The object has a required property `testSuite`.
+This workflow's executions fail if they run longer than two seconds.
-`testSuite`'s value is a function that defines the tests to run. The function accepts as a parameter an object that has the following properties:
+A workflow’s timeout error is returned in the `errors` property of the workflow’s execution, as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/access-workflow-errors/index.html.md). The error’s name is `TransactionTimeoutError`.
-- `api`: a set of utility methods used to send requests to the Medusa application. It has the following methods:
- - `get`: Send a `GET` request to an API route.
- - `post`: Send a `POST` request to an API route.
- - `delete`: Send a `DELETE` request to an API route.
-- `getContainer`: a function that retrieves the Medusa Container. Use the `getContainer().resolve` method to resolve resources from the Medusa Container.
+***
-The tests in the `testSuite` function are written using [Jest](https://jestjs.io/).
+## Configure Step Timeout
-### Jest Timeout
+Alternatively, you can configure the timeout for a step rather than the entire workflow.
-Since your tests connect to the database and perform actions that require more time than the typical tests, make sure to increase the timeout in your test:
+As mentioned in the previous section, the timeout doesn't stop the execution of the step. It only affects the step's status and output.
-```ts title="integration-tests/http/test.spec.ts"
-// in your test's file
-jest.setTimeout(60 * 1000)
+The step’s configuration object accepts a `timeout` property, whose value is a number indicating the timeout in seconds.
+
+For example:
+
+```tsx
+const step1 = createStep(
+ {
+ name: "step-1",
+ timeout: 2, // 2 seconds
+ },
+ async () => {
+ // ...
+ }
+)
```
-***
+This step's executions fail if they run longer than two seconds.
-### Run Tests
-
-Run the following command to run your tests:
-
-```bash npm2yarn
-npm run test:integration
-```
-
-If you don't have a `test:integration` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md).
-
-This runs your Medusa application and runs the tests available under the `src/integrations/http` directory.
-
-***
-
-## Other Options and Inputs
-
-Refer to [the Test Tooling Reference](https://docs.medusajs.com/resources/test-tools-reference/medusaIntegrationTestRunner/index.html.md) for other available parameter options and inputs of the `testSuite` function.
-
-***
-
-## Database Used in Tests
-
-The `medusaIntegrationTestRunner` function creates a database with a random name before running the tests. Then, it drops that database after all the tests end.
-
-To manage that database, such as changing its name or perform operations on it in your tests, refer to [the Test Tooling Reference](https://docs.medusajs.com/resources/test-tools-reference/medusaIntegrationTestRunner/index.html.md).
-
-***
-
-## Example Integration Tests
-
-The next chapters provide examples of writing integration tests for API routes and workflows.
+A step’s timeout error is returned in the `errors` property of the workflow’s execution, as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/access-workflow-errors/index.html.md). The error’s name is `TransactionStepTimeoutError`.
# Docs Contribution Guidelines
@@ -15852,217 +16209,135 @@ console.log("This block can't use semi colons")
~~~ */}
-# Write Tests for Modules
+# Example: Write Integration Tests for Workflows
-In this chapter, you'll learn about `moduleIntegrationTestRunner` from Medusa's Testing Framework and how to use it to write integration tests for a module's main service.
+In this chapter, you'll learn how to write integration tests for workflows using [medusaIntegrationTestRunner](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/integration-tests/index.html.md) from Medusa's Testing Framwork.
### Prerequisites
- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md)
-## moduleIntegrationTestRunner Utility
+## Write Integration Test for Workflow
-`moduleIntegrationTestRunner` creates integration tests for a module. The integration tests run on a test Medusa application with only the specified module enabled.
+Consider you have the following workflow defined at `src/workflows/hello-world.ts`:
-For example, assuming you have a `blog` module, create a test file at `src/modules/blog/__tests__/service.spec.ts`:
+```ts title="src/workflows/hello-world.ts"
+import {
+ createWorkflow,
+ createStep,
+ StepResponse,
+ WorkflowResponse,
+} from "@medusajs/framework/workflows-sdk"
-```ts title="src/modules/blog/__tests__/service.spec.ts"
-import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
-import { BLOG_MODULE } from ".."
-import BlogModuleService from "../service"
-import Post from "../models/post"
+const step1 = createStep("step-1", () => {
+ return new StepResponse("Hello, World!")
+})
-moduleIntegrationTestRunner({
- moduleName: BLOG_MODULE,
- moduleModels: [Post],
- resolve: "./src/modules/blog",
- testSuite: ({ service }) => {
- // TODO write tests
+export const helloWorldWorkflow = createWorkflow(
+ "hello-world-workflow",
+ () => {
+ const message = step1()
+
+ return new WorkflowResponse(message)
+ }
+)
+```
+
+To write a test for this workflow, create the file `integration-tests/http/workflow.spec.ts` with the following content:
+
+```ts title="integration-tests/http/workflow.spec.ts"
+import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
+import { helloWorldWorkflow } from "../../src/workflows/hello-world"
+
+medusaIntegrationTestRunner({
+ testSuite: ({ getContainer }) => {
+ describe("Test hello-world workflow", () => {
+ it("returns message", async () => {
+ const { result } = await helloWorldWorkflow(getContainer())
+ .run()
+
+ expect(result).toEqual("Hello, World!")
+ })
+ })
},
})
jest.setTimeout(60 * 1000)
```
-The `moduleIntegrationTestRunner` function accepts as a parameter an object with the following properties:
+You use the `medusaIntegrationTestRunner` to write an integration test for the workflow. The test pases if the workflow returns the string `"Hello, World!"`.
-- `moduleName`: The name of the module.
-- `moduleModels`: An array of models in the module. Refer to [this section](#write-tests-for-modules-without-data-models) if your module doesn't have data models.
-- `resolve`: The path to the model.
-- `testSuite`: A function that defines the tests to run.
+### Jest Timeout
-The `testSuite` function accepts as a parameter an object having the `service` property, which is an instance of the module's main service.
+Since your tests connect to the database and perform actions that require more time than the typical tests, make sure to increase the timeout in your test:
-The type argument provided to the `moduleIntegrationTestRunner` function is used as the type of the `service` property.
-
-The tests in the `testSuite` function are written using [Jest](https://jestjs.io/).
+```ts title="integration-tests/http/custom-routes.spec.ts"
+// in your test's file
+jest.setTimeout(60 * 1000)
+```
***
-## Run Tests
+## Run Test
-Run the following command to run your module integration tests:
+Run the following command to run your tests:
```bash npm2yarn
-npm run test:integration:modules
+npm run test:integration
```
-If you don't have a `test:integration:modules` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md).
+If you don't have a `test:integration` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md).
-This runs your Medusa application and runs the tests available in any `__tests__` directory under the `src/modules` directory.
+This runs your Medusa application and runs the tests available under the `integrations/http` directory.
***
-## Pass Module Options
+## Test That a Workflow Throws an Error
-If your module accepts options, you can set them using the `moduleOptions` property of the `moduleIntegrationTestRunner`'s parameter.
+You might want to test that a workflow throws an error in certain cases. To test this:
-For example:
+- Disable the `throwOnError` option when executing the workflow.
+- Use the returned `errors` property to check what errors were thrown.
-```ts
-import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
-import BlogModuleService from "../service"
+For example, if you have a step that throws this error:
-moduleIntegrationTestRunner({
- moduleOptions: {
- apiKey: "123",
+```ts title="src/workflows/hello-world.ts"
+import { MedusaError } from "@medusajs/framework/utils"
+import { createStep } from "@medusajs/framework/workflows-sdk"
+
+const step1 = createStep("step-1", () => {
+ throw new MedusaError(MedusaError.Types.NOT_FOUND, "Item doesn't exist")
+})
+```
+
+You can write the following test to ensure that the workflow throws that error:
+
+```ts title="integration-tests/http/workflow.spec.ts"
+import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
+import { helloWorldWorkflow } from "../../src/workflows/hello-world"
+
+medusaIntegrationTestRunner({
+ testSuite: ({ getContainer }) => {
+ describe("Test hello-world workflow", () => {
+ it("returns message", async () => {
+ const { errors } = await helloWorldWorkflow(getContainer())
+ .run({
+ throwOnError: false,
+ })
+
+ expect(errors.length).toBeGreaterThan(0)
+ expect(errors[0].error.message).toBe("Item doesn't exist")
+ })
+ })
},
- // ...
-})
-```
-
-***
-
-## Write Tests for Modules without Data Models
-
-If your module doesn't have a data model, pass a dummy model in the `moduleModels` property.
-
-For example:
-
-```ts
-import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
-import BlogModuleService from "../service"
-import { model } from "@medusajs/framework/utils"
-
-const DummyModel = model.define("dummy_model", {
- id: model.id().primaryKey(),
-})
-
-moduleIntegrationTestRunner({
- moduleModels: [DummyModel],
- // ...
})
jest.setTimeout(60 * 1000)
```
-***
+The `errors` property contains an array of errors thrown during the execution of the workflow. Each error item has an `error` object, being the error thrown.
-### Other Options and Inputs
-
-Refer to [the Test Tooling Reference](https://docs.medusajs.com/resources/test-tools-reference/moduleIntegrationTestRunner/index.html.md) for other available parameter options and inputs of the `testSuite` function.
-
-***
-
-## Database Used in Tests
-
-The `moduleIntegrationTestRunner` function creates a database with a random name before running the tests. Then, it drops that database after all the tests end.
-
-To manage that database, such as changing its name or perform operations on it in your tests, refer to [the Test Tooling Reference](https://docs.medusajs.com/resources/test-tools-reference/moduleIntegrationTestRunner/index.html.md).
-
-
-# Translate Medusa Admin
-
-The Medusa Admin supports multiple languages, with the default being English. In this documentation, you'll learn how to contribute to the community by translating the Medusa Admin to a language you're fluent in.
-
-{/* vale docs.We = NO */}
-
-You can contribute either by translating the admin to a new language, or fixing translations for existing languages. As we can't validate every language's translations, some translations may be incorrect. Your contribution is welcome to fix any translation errors you find.
-
-{/* vale docs.We = YES */}
-
-Check out the translated languages either in the admin dashboard's settings or on [GitHub](https://github.com/medusajs/medusa/blob/develop/packages/admin/dashboard/src/i18n/languages.ts).
-
-***
-
-## How to Contribute Translation
-
-1. Clone the [Medusa monorepository](https://github.com/medusajs/medusa) to your local machine:
-
-```bash
-git clone https://github.com/medusajs/medusa.git
-```
-
-If you already have it cloned, make sure to pull the latest changes from the `develop` branch.
-
-2. Install the monorepository's dependencies. Since it's a Yarn workspace, it's highly recommended to use yarn:
-
-```bash
-yarn install
-```
-
-3. Create a branch that you'll use to open the pull request later:
-
-```bash
-git checkout -b feat/translate-
-```
-
-Where `` is your language name. For example, `feat/translate-da`.
-
-4. Translation files are under `packages/admin/dashboard/src/i18n/translations` as JSON files whose names are the ISO-2 name of the language.
- - If you're adding a new language, copy the file `packages/admin/dashboard/src/i18n/translations/en.json` and paste it with the ISO-2 name for your language. For example, if you're adding Danish translations, copy the `en.json` file and paste it as `packages/admin/dashboard/src/i18n/translations/de.json`.
- - If you're fixing a translation, find the JSON file of the language under `packages/admin/dashboard/src/i18n/translations`.
-
-5. Start translating the keys in the JSON file (or updating the targeted ones). All keys in the JSON file must be translated, and your PR tests will fail otherwise.
- - You can check whether the JSON file is valid by running the following command in `packages/admin/dashboard`, replacing `da.json` with the JSON file's name:
-
-```bash title="packages/admin/dashboard"
-yarn i18n:validate da.json
-```
-
-6. After finishing the translation, if you're adding a new language, import its JSON file in `packages/admin/dashboard/src/i18n/translations/index.ts` and add it to the exported object:
-
-```ts title="packages/admin/dashboard/src/i18n/translations/index.ts" highlights={[["2"], ["6"], ["7"], ["8"]]}
-// other imports...
-import da from "./da.json"
-
-export default {
- // other languages...
- da: {
- translation: da,
- },
-}
-```
-
-The language's key in the object is the ISO-2 name of the language.
-
-7. If you're adding a new language, add it to the file `packages/admin/dashboard/src/i18n/languages.ts`:
-
-```ts title="packages/admin/dashboard/src/i18n/languages.ts" highlights={languageHighlights}
-import { da } from "date-fns/locale"
-// other imports...
-
-export const languages: Language[] = [
- // other languages...
- {
- code: "da",
- display_name: "Danish",
- ltr: true,
- date_locale: da,
- },
-]
-```
-
-`languages` is an array having the following properties:
-
-- `code`: The ISO-2 name of the language. For example, `da` for Danish.
-- `display_name`: The language's name to be displayed in the admin.
-- `ltr`: Whether the language supports a left-to-right layout. For example, set this to `false` for languages like Arabic.
-- `date_locale`: An instance of the locale imported from the [date-fns/locale](https://date-fns.org/) package.
-
-8. Once you're done, push the changes into your branch and open a pull request on GitHub.
-
-Our team will perform a general review on your PR and merge it if no issues are found. The translation will be available in the admin after the next release.
+If you threw a `MedusaError`, then you can check the error message in `errors[0].error.message`.
# Example: Write Integration Tests for API Routes
@@ -16634,135 +16909,98 @@ const response = await api.post(`/custom`, form, {
```
-# Example: Write Integration Tests for Workflows
+# Translate Medusa Admin
-In this chapter, you'll learn how to write integration tests for workflows using [medusaIntegrationTestRunner](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/integration-tests/index.html.md) from Medusa's Testing Framwork.
+The Medusa Admin supports multiple languages, with the default being English. In this documentation, you'll learn how to contribute to the community by translating the Medusa Admin to a language you're fluent in.
-### Prerequisites
+{/* vale docs.We = NO */}
-- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md)
+You can contribute either by translating the admin to a new language, or fixing translations for existing languages. As we can't validate every language's translations, some translations may be incorrect. Your contribution is welcome to fix any translation errors you find.
-## Write Integration Test for Workflow
+{/* vale docs.We = YES */}
-Consider you have the following workflow defined at `src/workflows/hello-world.ts`:
-
-```ts title="src/workflows/hello-world.ts"
-import {
- createWorkflow,
- createStep,
- StepResponse,
- WorkflowResponse,
-} from "@medusajs/framework/workflows-sdk"
-
-const step1 = createStep("step-1", () => {
- return new StepResponse("Hello, World!")
-})
-
-export const helloWorldWorkflow = createWorkflow(
- "hello-world-workflow",
- () => {
- const message = step1()
-
- return new WorkflowResponse(message)
- }
-)
-```
-
-To write a test for this workflow, create the file `integration-tests/http/workflow.spec.ts` with the following content:
-
-```ts title="integration-tests/http/workflow.spec.ts"
-import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
-import { helloWorldWorkflow } from "../../src/workflows/hello-world"
-
-medusaIntegrationTestRunner({
- testSuite: ({ getContainer }) => {
- describe("Test hello-world workflow", () => {
- it("returns message", async () => {
- const { result } = await helloWorldWorkflow(getContainer())
- .run()
-
- expect(result).toEqual("Hello, World!")
- })
- })
- },
-})
-
-jest.setTimeout(60 * 1000)
-```
-
-You use the `medusaIntegrationTestRunner` to write an integration test for the workflow. The test pases if the workflow returns the string `"Hello, World!"`.
-
-### Jest Timeout
-
-Since your tests connect to the database and perform actions that require more time than the typical tests, make sure to increase the timeout in your test:
-
-```ts title="integration-tests/http/custom-routes.spec.ts"
-// in your test's file
-jest.setTimeout(60 * 1000)
-```
+Check out the translated languages either in the admin dashboard's settings or on [GitHub](https://github.com/medusajs/medusa/blob/develop/packages/admin/dashboard/src/i18n/languages.ts).
***
-## Run Test
+## How to Contribute Translation
-Run the following command to run your tests:
+1. Clone the [Medusa monorepository](https://github.com/medusajs/medusa) to your local machine:
-```bash npm2yarn
-npm run test:integration
+```bash
+git clone https://github.com/medusajs/medusa.git
```
-If you don't have a `test:integration` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md).
+If you already have it cloned, make sure to pull the latest changes from the `develop` branch.
-This runs your Medusa application and runs the tests available under the `integrations/http` directory.
+2. Install the monorepository's dependencies. Since it's a Yarn workspace, it's highly recommended to use yarn:
-***
-
-## Test That a Workflow Throws an Error
-
-You might want to test that a workflow throws an error in certain cases. To test this:
-
-- Disable the `throwOnError` option when executing the workflow.
-- Use the returned `errors` property to check what errors were thrown.
-
-For example, if you have a step that throws this error:
-
-```ts title="src/workflows/hello-world.ts"
-import { MedusaError } from "@medusajs/framework/utils"
-import { createStep } from "@medusajs/framework/workflows-sdk"
-
-const step1 = createStep("step-1", () => {
- throw new MedusaError(MedusaError.Types.NOT_FOUND, "Item doesn't exist")
-})
+```bash
+yarn install
```
-You can write the following test to ensure that the workflow throws that error:
+3. Create a branch that you'll use to open the pull request later:
-```ts title="integration-tests/http/workflow.spec.ts"
-import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
-import { helloWorldWorkflow } from "../../src/workflows/hello-world"
+```bash
+git checkout -b feat/translate-
+```
-medusaIntegrationTestRunner({
- testSuite: ({ getContainer }) => {
- describe("Test hello-world workflow", () => {
- it("returns message", async () => {
- const { errors } = await helloWorldWorkflow(getContainer())
- .run({
- throwOnError: false,
- })
+Where `` is your language name. For example, `feat/translate-da`.
- expect(errors.length).toBeGreaterThan(0)
- expect(errors[0].error.message).toBe("Item doesn't exist")
- })
- })
+4. Translation files are under `packages/admin/dashboard/src/i18n/translations` as JSON files whose names are the ISO-2 name of the language.
+ - If you're adding a new language, copy the file `packages/admin/dashboard/src/i18n/translations/en.json` and paste it with the ISO-2 name for your language. For example, if you're adding Danish translations, copy the `en.json` file and paste it as `packages/admin/dashboard/src/i18n/translations/de.json`.
+ - If you're fixing a translation, find the JSON file of the language under `packages/admin/dashboard/src/i18n/translations`.
+
+5. Start translating the keys in the JSON file (or updating the targeted ones). All keys in the JSON file must be translated, and your PR tests will fail otherwise.
+ - You can check whether the JSON file is valid by running the following command in `packages/admin/dashboard`, replacing `da.json` with the JSON file's name:
+
+```bash title="packages/admin/dashboard"
+yarn i18n:validate da.json
+```
+
+6. After finishing the translation, if you're adding a new language, import its JSON file in `packages/admin/dashboard/src/i18n/translations/index.ts` and add it to the exported object:
+
+```ts title="packages/admin/dashboard/src/i18n/translations/index.ts" highlights={[["2"], ["6"], ["7"], ["8"]]}
+// other imports...
+import da from "./da.json"
+
+export default {
+ // other languages...
+ da: {
+ translation: da,
},
-})
-
-jest.setTimeout(60 * 1000)
+}
```
-The `errors` property contains an array of errors thrown during the execution of the workflow. Each error item has an `error` object, being the error thrown.
+The language's key in the object is the ISO-2 name of the language.
-If you threw a `MedusaError`, then you can check the error message in `errors[0].error.message`.
+7. If you're adding a new language, add it to the file `packages/admin/dashboard/src/i18n/languages.ts`:
+
+```ts title="packages/admin/dashboard/src/i18n/languages.ts" highlights={languageHighlights}
+import { da } from "date-fns/locale"
+// other imports...
+
+export const languages: Language[] = [
+ // other languages...
+ {
+ code: "da",
+ display_name: "Danish",
+ ltr: true,
+ date_locale: da,
+ },
+]
+```
+
+`languages` is an array having the following properties:
+
+- `code`: The ISO-2 name of the language. For example, `da` for Danish.
+- `display_name`: The language's name to be displayed in the admin.
+- `ltr`: Whether the language supports a left-to-right layout. For example, set this to `false` for languages like Arabic.
+- `date_locale`: An instance of the locale imported from the [date-fns/locale](https://date-fns.org/) package.
+
+8. Once you're done, push the changes into your branch and open a pull request on GitHub.
+
+Our team will perform a general review on your PR and merge it if no issues are found. The translation will be available in the admin after the next release.
# Example: Integration Tests for a Module
@@ -17015,304 +17253,6 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
-# Currency Module
-
-In this section of the documentation, you will find resources to learn more about the Currency Module and how to use it in your application.
-
-Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/store/index.html.md) to learn how to manage your store's currencies using the dashboard.
-
-Medusa has currency related features available out-of-the-box through the Currency Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Currency Module.
-
-Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-
-## Currency Features
-
-- [Currency Management and Retrieval](https://docs.medusajs.com/references/currency/listAndCountCurrencies/index.html.md): This module adds all common currencies to your application and allows you to retrieve them.
-- [Support Currencies in Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/currency/links-to-other-modules/index.html.md): Other commerce modules use currency codes in their data models or operations. Use the Currency Module to retrieve a currency code and its details.
-
-***
-
-## How to Use the Currency Module
-
-In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
-
-You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
-
-For example:
-
-```ts title="src/workflows/retrieve-price-with-currency.ts" highlights={highlights}
-import {
- createWorkflow,
- WorkflowResponse,
- createStep,
- StepResponse,
- transform,
-} from "@medusajs/framework/workflows-sdk"
-import { Modules } from "@medusajs/framework/utils"
-
-const retrieveCurrencyStep = createStep(
- "retrieve-currency",
- async ({}, { container }) => {
- const currencyModuleService = container.resolve(Modules.CURRENCY)
-
- const currency = await currencyModuleService
- .retrieveCurrency("usd")
-
- return new StepResponse({ currency })
- }
-)
-
-type Input = {
- price: number
-}
-
-export const retrievePriceWithCurrency = createWorkflow(
- "create-currency",
- (input: Input) => {
- const { currency } = retrieveCurrencyStep()
-
- const formattedPrice = transform({
- input,
- currency,
- }, (data) => {
- return `${data.currency.symbol}${data.input.price}`
- })
-
- return new WorkflowResponse({
- formattedPrice,
- })
- }
-)
-```
-
-You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
-
-### API Route
-
-```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"], ["13"], ["14"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import { retrievePriceWithCurrency } from "../../workflows/retrieve-price-with-currency"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await retrievePriceWithCurrency(req.scope)
- .run({
- price: 10,
- })
-
- res.send(result)
-}
-```
-
-### Subscriber
-
-```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"], ["13"], ["14"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import {
- type SubscriberConfig,
- type SubscriberArgs,
-} from "@medusajs/framework"
-import { retrievePriceWithCurrency } from "../workflows/retrieve-price-with-currency"
-
-export default async function handleUserCreated({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- const { result } = await retrievePriceWithCurrency(container)
- .run({
- price: 10,
- })
-
- console.log(result)
-}
-
-export const config: SubscriberConfig = {
- event: "user.created",
-}
-```
-
-### Scheduled Job
-
-```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"], ["9"], ["10"]]}
-import { MedusaContainer } from "@medusajs/framework/types"
-import { retrievePriceWithCurrency } from "../workflows/retrieve-price-with-currency"
-
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const { result } = await retrievePriceWithCurrency(container)
- .run({
- price: 10,
- })
-
- console.log(result)
-}
-
-export const config = {
- name: "run-once-a-day",
- schedule: `0 0 * * *`,
-}
-```
-
-Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
-
-***
-
-
-# Cart Module
-
-In this section of the documentation, you will find resources to learn more about the Cart Module and how to use it in your application.
-
-Medusa has cart related features available out-of-the-box through the Cart Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Cart Module.
-
-Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-
-## Cart Features
-
-- [Cart Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/concepts/index.html.md): Store and manage carts, including their addresses, line items, shipping methods, and more.
-- [Apply Promotion Adjustments](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/promotions/index.html.md): Apply promotions or discounts to line items and shipping methods by adding adjustment lines that are factored into their subtotals.
-- [Apply Tax Lines](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/tax-lines/index.html.md): Apply tax lines to line items and shipping methods.
-- [Cart Scoping](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/links-to-other-modules/index.html.md): When used in the Medusa application, Medusa creates links to other commerce modules, scoping a cart to a sales channel, region, and a customer.
-
-***
-
-## How to Use the Cart Module
-
-In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
-
-You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
-
-For example:
-
-```ts title="src/workflows/create-cart.ts" highlights={highlights}
-import {
- createWorkflow,
- WorkflowResponse,
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { Modules } from "@medusajs/framework/utils"
-
-const createCartStep = createStep(
- "create-cart",
- async ({}, { container }) => {
- const cartModuleService = container.resolve(Modules.CART)
-
- const cart = await cartModuleService.createCarts({
- currency_code: "usd",
- shipping_address: {
- address_1: "1512 Barataria Blvd",
- country_code: "us",
- },
- items: [
- {
- title: "Shirt",
- unit_price: 1000,
- quantity: 1,
- },
- ],
- })
-
- return new StepResponse({ cart }, cart.id)
- },
- async (cartId, { container }) => {
- if (!cartId) {
- return
- }
- const cartModuleService = container.resolve(Modules.CART)
-
- await cartModuleService.deleteCarts([cartId])
- }
-)
-
-export const createCartWorkflow = createWorkflow(
- "create-cart",
- () => {
- const { cart } = createCartStep()
-
- return new WorkflowResponse({
- cart,
- })
- }
-)
-```
-
-You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
-
-### API Route
-
-```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import { createCartWorkflow } from "../../workflows/create-cart"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await createCartWorkflow(req.scope)
- .run()
-
- res.send(result)
-}
-```
-
-### Subscriber
-
-```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import {
- type SubscriberConfig,
- type SubscriberArgs,
-} from "@medusajs/framework"
-import { createCartWorkflow } from "../workflows/create-cart"
-
-export default async function handleUserCreated({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- const { result } = await createCartWorkflow(container)
- .run()
-
- console.log(result)
-}
-
-export const config: SubscriberConfig = {
- event: "user.created",
-}
-```
-
-### Scheduled Job
-
-```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
-import { MedusaContainer } from "@medusajs/framework/types"
-import { createCartWorkflow } from "../workflows/create-cart"
-
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const { result } = await createCartWorkflow(container)
- .run()
-
- console.log(result)
-}
-
-export const config = {
- name: "run-once-a-day",
- schedule: `0 0 * * *`,
-}
-```
-
-Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
-
-***
-
-
# Auth Module
In this section of the documentation, you will find resources to learn more about the Auth Module and how to use it in your application.
@@ -17750,150 +17690,6 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
-# Inventory Module
-
-In this section of the documentation, you will find resources to learn more about the Inventory Module and how to use it in your application.
-
-Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/inventory/index.html.md) to learn how to manage inventory and related features using the dashboard.
-
-Medusa has inventory related features available out-of-the-box through the Inventory Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Inventory Module.
-
-Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-
-## Inventory Features
-
-- [Inventory Items Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts/index.html.md): Store and manage inventory of any stock-kept item, such as product variants.
-- [Inventory Across Locations](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts#inventorylevel/index.html.md): Manage inventory levels across different locations, such as warehouses.
-- [Reservation Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts#reservationitem/index.html.md): Reserve quantities of inventory items at specific locations for orders or other purposes.
-- [Check Inventory Availability](https://docs.medusajs.com/references/inventory-next/confirmInventory/index.html.md): Check whether an inventory item has the necessary quantity for purchase.
-- [Inventory Kits](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-kit/index.html.md): Create and manage inventory kits for a single product, allowing you to implement use cases like bundled or multi-part products.
-
-***
-
-## How to Use the Inventory Module
-
-In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
-
-You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
-
-For example:
-
-```ts title="src/workflows/create-inventory-item.ts" highlights={highlights}
-import {
- createWorkflow,
- WorkflowResponse,
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { Modules } from "@medusajs/framework/utils"
-
-const createInventoryItemStep = createStep(
- "create-inventory-item",
- async ({}, { container }) => {
- const inventoryModuleService = container.resolve(Modules.INVENTORY)
-
- const inventoryItem = await inventoryModuleService.createInventoryItems({
- sku: "SHIRT",
- title: "Green Medusa Shirt",
- requires_shipping: true,
- })
-
- return new StepResponse({ inventoryItem }, inventoryItem.id)
- },
- async (inventoryItemId, { container }) => {
- if (!inventoryItemId) {
- return
- }
- const inventoryModuleService = container.resolve(Modules.INVENTORY)
-
- await inventoryModuleService.deleteInventoryItems([inventoryItemId])
- }
-)
-
-export const createInventoryItemWorkflow = createWorkflow(
- "create-inventory-item-workflow",
- () => {
- const { inventoryItem } = createInventoryItemStep()
-
- return new WorkflowResponse({
- inventoryItem,
- })
- }
-)
-```
-
-You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
-
-### API Route
-
-```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import { createInventoryItemWorkflow } from "../../workflows/create-inventory-item"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await createInventoryItemWorkflow(req.scope)
- .run()
-
- res.send(result)
-}
-```
-
-### Subscriber
-
-```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import {
- type SubscriberConfig,
- type SubscriberArgs,
-} from "@medusajs/framework"
-import { createInventoryItemWorkflow } from "../workflows/create-inventory-item"
-
-export default async function handleUserCreated({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- const { result } = await createInventoryItemWorkflow(container)
- .run()
-
- console.log(result)
-}
-
-export const config: SubscriberConfig = {
- event: "user.created",
-}
-```
-
-### Scheduled Job
-
-```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
-import { MedusaContainer } from "@medusajs/framework/types"
-import { createInventoryItemWorkflow } from "../workflows/create-inventory-item"
-
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const { result } = await createInventoryItemWorkflow(container)
- .run()
-
- console.log(result)
-}
-
-export const config = {
- name: "run-once-a-day",
- schedule: `0 0 * * *`,
-}
-```
-
-Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
-
-***
-
-
# Order Module
In this section of the documentation, you will find resources to learn more about the Order Module and how to use it in your application.
@@ -18050,6 +17846,154 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
+# Currency Module
+
+In this section of the documentation, you will find resources to learn more about the Currency Module and how to use it in your application.
+
+Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/store/index.html.md) to learn how to manage your store's currencies using the dashboard.
+
+Medusa has currency related features available out-of-the-box through the Currency Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Currency Module.
+
+Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
+
+## Currency Features
+
+- [Currency Management and Retrieval](https://docs.medusajs.com/references/currency/listAndCountCurrencies/index.html.md): This module adds all common currencies to your application and allows you to retrieve them.
+- [Support Currencies in Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/currency/links-to-other-modules/index.html.md): Other commerce modules use currency codes in their data models or operations. Use the Currency Module to retrieve a currency code and its details.
+
+***
+
+## How to Use the Currency Module
+
+In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
+
+You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
+
+For example:
+
+```ts title="src/workflows/retrieve-price-with-currency.ts" highlights={highlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+ createStep,
+ StepResponse,
+ transform,
+} from "@medusajs/framework/workflows-sdk"
+import { Modules } from "@medusajs/framework/utils"
+
+const retrieveCurrencyStep = createStep(
+ "retrieve-currency",
+ async ({}, { container }) => {
+ const currencyModuleService = container.resolve(Modules.CURRENCY)
+
+ const currency = await currencyModuleService
+ .retrieveCurrency("usd")
+
+ return new StepResponse({ currency })
+ }
+)
+
+type Input = {
+ price: number
+}
+
+export const retrievePriceWithCurrency = createWorkflow(
+ "create-currency",
+ (input: Input) => {
+ const { currency } = retrieveCurrencyStep()
+
+ const formattedPrice = transform({
+ input,
+ currency,
+ }, (data) => {
+ return `${data.currency.symbol}${data.input.price}`
+ })
+
+ return new WorkflowResponse({
+ formattedPrice,
+ })
+ }
+)
+```
+
+You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
+
+### API Route
+
+```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"], ["13"], ["14"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import { retrievePriceWithCurrency } from "../../workflows/retrieve-price-with-currency"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result } = await retrievePriceWithCurrency(req.scope)
+ .run({
+ price: 10,
+ })
+
+ res.send(result)
+}
+```
+
+### Subscriber
+
+```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"], ["13"], ["14"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import {
+ type SubscriberConfig,
+ type SubscriberArgs,
+} from "@medusajs/framework"
+import { retrievePriceWithCurrency } from "../workflows/retrieve-price-with-currency"
+
+export default async function handleUserCreated({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const { result } = await retrievePriceWithCurrency(container)
+ .run({
+ price: 10,
+ })
+
+ console.log(result)
+}
+
+export const config: SubscriberConfig = {
+ event: "user.created",
+}
+```
+
+### Scheduled Job
+
+```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"], ["9"], ["10"]]}
+import { MedusaContainer } from "@medusajs/framework/types"
+import { retrievePriceWithCurrency } from "../workflows/retrieve-price-with-currency"
+
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const { result } = await retrievePriceWithCurrency(container)
+ .run({
+ price: 10,
+ })
+
+ console.log(result)
+}
+
+export const config = {
+ name: "run-once-a-day",
+ schedule: `0 0 * * *`,
+}
+```
+
+Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
+
+***
+
+
# Payment Module
In this section of the documentation, you will find resources to learn more about the Payment Module and how to use it in your application.
@@ -18205,6 +18149,454 @@ Medusa provides the following payment providers out-of-the-box. You can use them
***
+# Cart Module
+
+In this section of the documentation, you will find resources to learn more about the Cart Module and how to use it in your application.
+
+Medusa has cart related features available out-of-the-box through the Cart Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Cart Module.
+
+Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
+
+## Cart Features
+
+- [Cart Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/concepts/index.html.md): Store and manage carts, including their addresses, line items, shipping methods, and more.
+- [Apply Promotion Adjustments](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/promotions/index.html.md): Apply promotions or discounts to line items and shipping methods by adding adjustment lines that are factored into their subtotals.
+- [Apply Tax Lines](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/tax-lines/index.html.md): Apply tax lines to line items and shipping methods.
+- [Cart Scoping](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/links-to-other-modules/index.html.md): When used in the Medusa application, Medusa creates links to other commerce modules, scoping a cart to a sales channel, region, and a customer.
+
+***
+
+## How to Use the Cart Module
+
+In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
+
+You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
+
+For example:
+
+```ts title="src/workflows/create-cart.ts" highlights={highlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { Modules } from "@medusajs/framework/utils"
+
+const createCartStep = createStep(
+ "create-cart",
+ async ({}, { container }) => {
+ const cartModuleService = container.resolve(Modules.CART)
+
+ const cart = await cartModuleService.createCarts({
+ currency_code: "usd",
+ shipping_address: {
+ address_1: "1512 Barataria Blvd",
+ country_code: "us",
+ },
+ items: [
+ {
+ title: "Shirt",
+ unit_price: 1000,
+ quantity: 1,
+ },
+ ],
+ })
+
+ return new StepResponse({ cart }, cart.id)
+ },
+ async (cartId, { container }) => {
+ if (!cartId) {
+ return
+ }
+ const cartModuleService = container.resolve(Modules.CART)
+
+ await cartModuleService.deleteCarts([cartId])
+ }
+)
+
+export const createCartWorkflow = createWorkflow(
+ "create-cart",
+ () => {
+ const { cart } = createCartStep()
+
+ return new WorkflowResponse({
+ cart,
+ })
+ }
+)
+```
+
+You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
+
+### API Route
+
+```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import { createCartWorkflow } from "../../workflows/create-cart"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result } = await createCartWorkflow(req.scope)
+ .run()
+
+ res.send(result)
+}
+```
+
+### Subscriber
+
+```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import {
+ type SubscriberConfig,
+ type SubscriberArgs,
+} from "@medusajs/framework"
+import { createCartWorkflow } from "../workflows/create-cart"
+
+export default async function handleUserCreated({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const { result } = await createCartWorkflow(container)
+ .run()
+
+ console.log(result)
+}
+
+export const config: SubscriberConfig = {
+ event: "user.created",
+}
+```
+
+### Scheduled Job
+
+```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
+import { MedusaContainer } from "@medusajs/framework/types"
+import { createCartWorkflow } from "../workflows/create-cart"
+
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const { result } = await createCartWorkflow(container)
+ .run()
+
+ console.log(result)
+}
+
+export const config = {
+ name: "run-once-a-day",
+ schedule: `0 0 * * *`,
+}
+```
+
+Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
+
+***
+
+
+# Inventory Module
+
+In this section of the documentation, you will find resources to learn more about the Inventory Module and how to use it in your application.
+
+Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/inventory/index.html.md) to learn how to manage inventory and related features using the dashboard.
+
+Medusa has inventory related features available out-of-the-box through the Inventory Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Inventory Module.
+
+Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
+
+## Inventory Features
+
+- [Inventory Items Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts/index.html.md): Store and manage inventory of any stock-kept item, such as product variants.
+- [Inventory Across Locations](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts#inventorylevel/index.html.md): Manage inventory levels across different locations, such as warehouses.
+- [Reservation Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts#reservationitem/index.html.md): Reserve quantities of inventory items at specific locations for orders or other purposes.
+- [Check Inventory Availability](https://docs.medusajs.com/references/inventory-next/confirmInventory/index.html.md): Check whether an inventory item has the necessary quantity for purchase.
+- [Inventory Kits](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-kit/index.html.md): Create and manage inventory kits for a single product, allowing you to implement use cases like bundled or multi-part products.
+
+***
+
+## How to Use the Inventory Module
+
+In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
+
+You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
+
+For example:
+
+```ts title="src/workflows/create-inventory-item.ts" highlights={highlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { Modules } from "@medusajs/framework/utils"
+
+const createInventoryItemStep = createStep(
+ "create-inventory-item",
+ async ({}, { container }) => {
+ const inventoryModuleService = container.resolve(Modules.INVENTORY)
+
+ const inventoryItem = await inventoryModuleService.createInventoryItems({
+ sku: "SHIRT",
+ title: "Green Medusa Shirt",
+ requires_shipping: true,
+ })
+
+ return new StepResponse({ inventoryItem }, inventoryItem.id)
+ },
+ async (inventoryItemId, { container }) => {
+ if (!inventoryItemId) {
+ return
+ }
+ const inventoryModuleService = container.resolve(Modules.INVENTORY)
+
+ await inventoryModuleService.deleteInventoryItems([inventoryItemId])
+ }
+)
+
+export const createInventoryItemWorkflow = createWorkflow(
+ "create-inventory-item-workflow",
+ () => {
+ const { inventoryItem } = createInventoryItemStep()
+
+ return new WorkflowResponse({
+ inventoryItem,
+ })
+ }
+)
+```
+
+You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
+
+### API Route
+
+```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import { createInventoryItemWorkflow } from "../../workflows/create-inventory-item"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result } = await createInventoryItemWorkflow(req.scope)
+ .run()
+
+ res.send(result)
+}
+```
+
+### Subscriber
+
+```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import {
+ type SubscriberConfig,
+ type SubscriberArgs,
+} from "@medusajs/framework"
+import { createInventoryItemWorkflow } from "../workflows/create-inventory-item"
+
+export default async function handleUserCreated({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const { result } = await createInventoryItemWorkflow(container)
+ .run()
+
+ console.log(result)
+}
+
+export const config: SubscriberConfig = {
+ event: "user.created",
+}
+```
+
+### Scheduled Job
+
+```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
+import { MedusaContainer } from "@medusajs/framework/types"
+import { createInventoryItemWorkflow } from "../workflows/create-inventory-item"
+
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const { result } = await createInventoryItemWorkflow(container)
+ .run()
+
+ console.log(result)
+}
+
+export const config = {
+ name: "run-once-a-day",
+ schedule: `0 0 * * *`,
+}
+```
+
+Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
+
+***
+
+
+# Pricing Module
+
+In this section of the documentation, you will find resources to learn more about the Pricing Module and how to use it in your application.
+
+Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/price-lists/index.html.md) to learn how to manage price lists using the dashboard.
+
+Medusa has pricing related features available out-of-the-box through the Pricing Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Pricing Module.
+
+Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
+
+## Pricing Features
+
+- [Price Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/concepts/index.html.md): Store and manage prices of a resource, such as a product or a variant.
+- [Advanced Rule Engine](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-rules/index.html.md): Create prices with custom rules to condition prices based on different contexts.
+- [Price Lists](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/concepts#price-list/index.html.md): Group prices and apply them only in specific conditions with price lists.
+- [Price Calculation Strategy](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation/index.html.md): Retrieve the best price in a given context and for the specified rule values.
+- [Tax-Inclusive Pricing](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/tax-inclusive-pricing/index.html.md): Calculate prices with taxes included in the price, and Medusa will handle calculating the taxes automatically.
+
+***
+
+## How to Use the Pricing Module
+
+In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
+
+You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
+
+For example:
+
+```ts title="src/workflows/create-price-set.ts" highlights={highlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { Modules } from "@medusajs/framework/utils"
+
+const createPriceSetStep = createStep(
+ "create-price-set",
+ async ({}, { container }) => {
+ const pricingModuleService = container.resolve(Modules.PRICING)
+
+ const priceSet = await pricingModuleService.createPriceSets({
+ prices: [
+ {
+ amount: 500,
+ currency_code: "USD",
+ },
+ {
+ amount: 400,
+ currency_code: "EUR",
+ min_quantity: 0,
+ max_quantity: 4,
+ rules: {},
+ },
+ ],
+ })
+
+ return new StepResponse({ priceSet }, priceSet.id)
+ },
+ async (priceSetId, { container }) => {
+ if (!priceSetId) {
+ return
+ }
+ const pricingModuleService = container.resolve(Modules.PRICING)
+
+ await pricingModuleService.deletePriceSets([priceSetId])
+ }
+)
+
+export const createPriceSetWorkflow = createWorkflow(
+ "create-price-set",
+ () => {
+ const { priceSet } = createPriceSetStep()
+
+ return new WorkflowResponse({
+ priceSet,
+ })
+ }
+)
+```
+
+You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
+
+### API Route
+
+```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import { createPriceSetWorkflow } from "../../workflows/create-price-set"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result } = await createPriceSetWorkflow(req.scope)
+ .run()
+
+ res.send(result)
+}
+```
+
+### Subscriber
+
+```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import {
+ type SubscriberConfig,
+ type SubscriberArgs,
+} from "@medusajs/framework"
+import { createPriceSetWorkflow } from "../workflows/create-price-set"
+
+export default async function handleUserCreated({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const { result } = await createPriceSetWorkflow(container)
+ .run()
+
+ console.log(result)
+}
+
+export const config: SubscriberConfig = {
+ event: "user.created",
+}
+```
+
+### Scheduled Job
+
+```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
+import { MedusaContainer } from "@medusajs/framework/types"
+import { createPriceSetWorkflow } from "../workflows/create-price-set"
+
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const { result } = await createPriceSetWorkflow(container)
+ .run()
+
+ console.log(result)
+}
+
+export const config = {
+ name: "run-once-a-day",
+ schedule: `0 0 * * *`,
+}
+```
+
+Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
+
+***
+
+
# Region Module
In this section of the documentation, you will find resources to learn more about the Region Module and how to use it in your application.
@@ -18348,6 +18740,308 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
+# Product Module
+
+In this section of the documentation, you will find resources to learn more about the Product Module and how to use it in your application.
+
+Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/products/index.html.md) to learn how to manage products using the dashboard.
+
+Medusa has product related features available out-of-the-box through the Product Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Product Module.
+
+Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
+
+## Product Features
+
+- [Products Management](https://docs.medusajs.com/references/product/models/Product/index.html.md): Store and manage products. Products have custom options, such as color or size, and each variant in the product sets the value for these options.
+- [Product Organization](https://docs.medusajs.com/references/product/models/index.html.md): The Product Module provides different data models used to organize products, including categories, collections, tags, and more.
+- [Bundled and Multi-Part Products](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-kit/index.html.md): Create and manage inventory kits for a single product, allowing you to implement use cases like bundled or multi-part products.
+
+***
+
+## How to Use the Product Module
+
+In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
+
+You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
+
+For example:
+
+```ts title="src/workflows/create-product.ts" highlights={highlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { Modules } from "@medusajs/framework/utils"
+
+const createProductStep = createStep(
+ "create-product",
+ async ({}, { container }) => {
+ const productService = container.resolve(Modules.PRODUCT)
+
+ const product = await productService.createProducts({
+ title: "Medusa Shirt",
+ options: [
+ {
+ title: "Color",
+ values: ["Black", "White"],
+ },
+ ],
+ variants: [
+ {
+ title: "Black Shirt",
+ options: {
+ Color: "Black",
+ },
+ },
+ ],
+ })
+
+ return new StepResponse({ product }, product.id)
+ },
+ async (productId, { container }) => {
+ if (!productId) {
+ return
+ }
+ const productService = container.resolve(Modules.PRODUCT)
+
+ await productService.deleteProducts([productId])
+ }
+)
+
+export const createProductWorkflow = createWorkflow(
+ "create-product",
+ () => {
+ const { product } = createProductStep()
+
+ return new WorkflowResponse({
+ product,
+ })
+ }
+)
+```
+
+You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
+
+### API Route
+
+```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import { createProductWorkflow } from "../../workflows/create-product"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result } = await createProductWorkflow(req.scope)
+ .run()
+
+ res.send(result)
+}
+```
+
+### Subscriber
+
+```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import {
+ type SubscriberConfig,
+ type SubscriberArgs,
+} from "@medusajs/framework"
+import { createProductWorkflow } from "../workflows/create-product"
+
+export default async function handleUserCreated({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const { result } = await createProductWorkflow(container)
+ .run()
+
+ console.log(result)
+}
+
+export const config: SubscriberConfig = {
+ event: "user.created",
+}
+```
+
+### Scheduled Job
+
+```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
+import { MedusaContainer } from "@medusajs/framework/types"
+import { createProductWorkflow } from "../workflows/create-product"
+
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const { result } = await createProductWorkflow(container)
+ .run()
+
+ console.log(result)
+}
+
+export const config = {
+ name: "run-once-a-day",
+ schedule: `0 0 * * *`,
+}
+```
+
+Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
+
+***
+
+
+# Promotion Module
+
+In this section of the documentation, you will find resources to learn more about the Promotion Module and how to use it in your application.
+
+Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/promotions/index.html.md) to learn how to manage promotions using the dashboard.
+
+Medusa has promotion related features available out-of-the-box through the Promotion Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Promotion Module.
+
+Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
+
+## Promotion Features
+
+- [Discount Functionalities](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/concepts/index.html.md): A promotion discounts an amount or percentage of a cart's items, shipping methods, or the entire order.
+- [Flexible Promotion Rules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/concepts#flexible-rules/index.html.md): A promotion has rules that restricts when the promotion is applied.
+- [Campaign Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/campaign/index.html.md): A campaign combines promotions under the same conditions, such as start and end dates, and budget configurations.
+- [Apply Promotion on Carts and Orders](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/actions/index.html.md): Apply promotions on carts and orders to discount items, shipping methods, or the entire order.
+
+***
+
+## How to Use the Promotion Module
+
+In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
+
+You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
+
+For example:
+
+```ts title="src/workflows/create-promotion.ts" highlights={highlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { Modules } from "@medusajs/framework/utils"
+
+const createPromotionStep = createStep(
+ "create-promotion",
+ async ({}, { container }) => {
+ const promotionModuleService = container.resolve(Modules.PROMOTION)
+
+ const promotion = await promotionModuleService.createPromotions({
+ code: "10%OFF",
+ type: "standard",
+ application_method: {
+ type: "percentage",
+ target_type: "order",
+ value: 10,
+ currency_code: "usd",
+ },
+ })
+
+ return new StepResponse({ promotion }, promotion.id)
+ },
+ async (promotionId, { container }) => {
+ if (!promotionId) {
+ return
+ }
+ const promotionModuleService = container.resolve(Modules.PROMOTION)
+
+ await promotionModuleService.deletePromotions(promotionId)
+ }
+)
+
+export const createPromotionWorkflow = createWorkflow(
+ "create-promotion",
+ () => {
+ const { promotion } = createPromotionStep()
+
+ return new WorkflowResponse({
+ promotion,
+ })
+ }
+)
+```
+
+You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
+
+### API Route
+
+```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import { createPromotionWorkflow } from "../../workflows/create-cart"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result } = await createPromotionWorkflow(req.scope)
+ .run()
+
+ res.send(result)
+}
+```
+
+### Subscriber
+
+```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import {
+ type SubscriberConfig,
+ type SubscriberArgs,
+} from "@medusajs/framework"
+import { createPromotionWorkflow } from "../workflows/create-cart"
+
+export default async function handleUserCreated({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const { result } = await createPromotionWorkflow(container)
+ .run()
+
+ console.log(result)
+}
+
+export const config: SubscriberConfig = {
+ event: "user.created",
+}
+```
+
+### Scheduled Job
+
+```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
+import { MedusaContainer } from "@medusajs/framework/types"
+import { createPromotionWorkflow } from "../workflows/create-cart"
+
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const { result } = await createPromotionWorkflow(container)
+ .run()
+
+ console.log(result)
+}
+
+export const config = {
+ name: "run-once-a-day",
+ schedule: `0 0 * * *`,
+}
+```
+
+Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
+
+***
+
+
# Sales Channel Module
In this section of the documentation, you will find resources to learn more about the Sales Channel Module and how to use it in your application.
@@ -18508,314 +19202,6 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
-# Product Module
-
-In this section of the documentation, you will find resources to learn more about the Product Module and how to use it in your application.
-
-Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/products/index.html.md) to learn how to manage products using the dashboard.
-
-Medusa has product related features available out-of-the-box through the Product Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Product Module.
-
-Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-
-## Product Features
-
-- [Products Management](https://docs.medusajs.com/references/product/models/Product/index.html.md): Store and manage products. Products have custom options, such as color or size, and each variant in the product sets the value for these options.
-- [Product Organization](https://docs.medusajs.com/references/product/models/index.html.md): The Product Module provides different data models used to organize products, including categories, collections, tags, and more.
-- [Bundled and Multi-Part Products](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-kit/index.html.md): Create and manage inventory kits for a single product, allowing you to implement use cases like bundled or multi-part products.
-
-***
-
-## How to Use the Product Module
-
-In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
-
-You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
-
-For example:
-
-```ts title="src/workflows/create-product.ts" highlights={highlights}
-import {
- createWorkflow,
- WorkflowResponse,
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { Modules } from "@medusajs/framework/utils"
-
-const createProductStep = createStep(
- "create-product",
- async ({}, { container }) => {
- const productService = container.resolve(Modules.PRODUCT)
-
- const product = await productService.createProducts({
- title: "Medusa Shirt",
- options: [
- {
- title: "Color",
- values: ["Black", "White"],
- },
- ],
- variants: [
- {
- title: "Black Shirt",
- options: {
- Color: "Black",
- },
- },
- ],
- })
-
- return new StepResponse({ product }, product.id)
- },
- async (productId, { container }) => {
- if (!productId) {
- return
- }
- const productService = container.resolve(Modules.PRODUCT)
-
- await productService.deleteProducts([productId])
- }
-)
-
-export const createProductWorkflow = createWorkflow(
- "create-product",
- () => {
- const { product } = createProductStep()
-
- return new WorkflowResponse({
- product,
- })
- }
-)
-```
-
-You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
-
-### API Route
-
-```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import { createProductWorkflow } from "../../workflows/create-product"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await createProductWorkflow(req.scope)
- .run()
-
- res.send(result)
-}
-```
-
-### Subscriber
-
-```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import {
- type SubscriberConfig,
- type SubscriberArgs,
-} from "@medusajs/framework"
-import { createProductWorkflow } from "../workflows/create-product"
-
-export default async function handleUserCreated({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- const { result } = await createProductWorkflow(container)
- .run()
-
- console.log(result)
-}
-
-export const config: SubscriberConfig = {
- event: "user.created",
-}
-```
-
-### Scheduled Job
-
-```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
-import { MedusaContainer } from "@medusajs/framework/types"
-import { createProductWorkflow } from "../workflows/create-product"
-
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const { result } = await createProductWorkflow(container)
- .run()
-
- console.log(result)
-}
-
-export const config = {
- name: "run-once-a-day",
- schedule: `0 0 * * *`,
-}
-```
-
-Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
-
-***
-
-
-# Pricing Module
-
-In this section of the documentation, you will find resources to learn more about the Pricing Module and how to use it in your application.
-
-Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/price-lists/index.html.md) to learn how to manage price lists using the dashboard.
-
-Medusa has pricing related features available out-of-the-box through the Pricing Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Pricing Module.
-
-Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-
-## Pricing Features
-
-- [Price Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/concepts/index.html.md): Store and manage prices of a resource, such as a product or a variant.
-- [Advanced Rule Engine](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-rules/index.html.md): Create prices with custom rules to condition prices based on different contexts.
-- [Price Lists](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/concepts#price-list/index.html.md): Group prices and apply them only in specific conditions with price lists.
-- [Price Calculation Strategy](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation/index.html.md): Retrieve the best price in a given context and for the specified rule values.
-- [Tax-Inclusive Pricing](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/tax-inclusive-pricing/index.html.md): Calculate prices with taxes included in the price, and Medusa will handle calculating the taxes automatically.
-
-***
-
-## How to Use the Pricing Module
-
-In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
-
-You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
-
-For example:
-
-```ts title="src/workflows/create-price-set.ts" highlights={highlights}
-import {
- createWorkflow,
- WorkflowResponse,
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { Modules } from "@medusajs/framework/utils"
-
-const createPriceSetStep = createStep(
- "create-price-set",
- async ({}, { container }) => {
- const pricingModuleService = container.resolve(Modules.PRICING)
-
- const priceSet = await pricingModuleService.createPriceSets({
- prices: [
- {
- amount: 500,
- currency_code: "USD",
- },
- {
- amount: 400,
- currency_code: "EUR",
- min_quantity: 0,
- max_quantity: 4,
- rules: {},
- },
- ],
- })
-
- return new StepResponse({ priceSet }, priceSet.id)
- },
- async (priceSetId, { container }) => {
- if (!priceSetId) {
- return
- }
- const pricingModuleService = container.resolve(Modules.PRICING)
-
- await pricingModuleService.deletePriceSets([priceSetId])
- }
-)
-
-export const createPriceSetWorkflow = createWorkflow(
- "create-price-set",
- () => {
- const { priceSet } = createPriceSetStep()
-
- return new WorkflowResponse({
- priceSet,
- })
- }
-)
-```
-
-You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
-
-### API Route
-
-```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import { createPriceSetWorkflow } from "../../workflows/create-price-set"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await createPriceSetWorkflow(req.scope)
- .run()
-
- res.send(result)
-}
-```
-
-### Subscriber
-
-```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import {
- type SubscriberConfig,
- type SubscriberArgs,
-} from "@medusajs/framework"
-import { createPriceSetWorkflow } from "../workflows/create-price-set"
-
-export default async function handleUserCreated({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- const { result } = await createPriceSetWorkflow(container)
- .run()
-
- console.log(result)
-}
-
-export const config: SubscriberConfig = {
- event: "user.created",
-}
-```
-
-### Scheduled Job
-
-```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
-import { MedusaContainer } from "@medusajs/framework/types"
-import { createPriceSetWorkflow } from "../workflows/create-price-set"
-
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const { result } = await createPriceSetWorkflow(container)
- .run()
-
- console.log(result)
-}
-
-export const config = {
- name: "run-once-a-day",
- schedule: `0 0 * * *`,
-}
-```
-
-Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
-
-***
-
-
# Stock Location Module
In this section of the documentation, you will find resources to learn more about the Stock Location Module and how to use it in your application.
@@ -18953,26 +19339,25 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
-# Promotion Module
+# Tax Module
-In this section of the documentation, you will find resources to learn more about the Promotion Module and how to use it in your application.
+In this section of the documentation, you will find resources to learn more about the Tax Module and how to use it in your application.
-Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/promotions/index.html.md) to learn how to manage promotions using the dashboard.
+Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/tax-regions/index.html.md) to learn how to manage tax regions using the dashboard.
-Medusa has promotion related features available out-of-the-box through the Promotion Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Promotion Module.
+Medusa has tax related features available out-of-the-box through the Tax Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Tax Module.
Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-## Promotion Features
+## Tax Features
-- [Discount Functionalities](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/concepts/index.html.md): A promotion discounts an amount or percentage of a cart's items, shipping methods, or the entire order.
-- [Flexible Promotion Rules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/concepts#flexible-rules/index.html.md): A promotion has rules that restricts when the promotion is applied.
-- [Campaign Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/campaign/index.html.md): A campaign combines promotions under the same conditions, such as start and end dates, and budget configurations.
-- [Apply Promotion on Carts and Orders](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/actions/index.html.md): Apply promotions on carts and orders to discount items, shipping methods, or the entire order.
+- [Tax Settings Per Region](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/tax/tax-region/index.html.md): Set different tax settings for each tax region.
+- [Tax Rates and Rules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/tax/tax-rates-and-rules/index.html.md): Manage each region's default tax rates and override them with conditioned tax rates.
+- [Retrieve Tax Lines for carts and orders](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/tax/tax-calculation-with-provider/index.html.md): Calculate and retrieve the tax lines of a cart or order's line items and shipping methods with tax providers.
***
-## How to Use the Promotion Module
+## How to Use Tax Module's Service
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
@@ -18980,7 +19365,7 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows
For example:
-```ts title="src/workflows/create-promotion.ts" highlights={highlights}
+```ts title="src/workflows/create-tax-region.ts" highlights={highlights}
import {
createWorkflow,
WorkflowResponse,
@@ -18989,42 +19374,33 @@ import {
} from "@medusajs/framework/workflows-sdk"
import { Modules } from "@medusajs/framework/utils"
-const createPromotionStep = createStep(
- "create-promotion",
+const createTaxRegionStep = createStep(
+ "create-tax-region",
async ({}, { container }) => {
- const promotionModuleService = container.resolve(Modules.PROMOTION)
+ const taxModuleService = container.resolve(Modules.TAX)
- const promotion = await promotionModuleService.createPromotions({
- code: "10%OFF",
- type: "standard",
- application_method: {
- type: "percentage",
- target_type: "order",
- value: 10,
- currency_code: "usd",
- },
+ const taxRegion = await taxModuleService.createTaxRegions({
+ country_code: "us",
})
- return new StepResponse({ promotion }, promotion.id)
+ return new StepResponse({ taxRegion }, taxRegion.id)
},
- async (promotionId, { container }) => {
- if (!promotionId) {
+ async (taxRegionId, { container }) => {
+ if (!taxRegionId) {
return
}
- const promotionModuleService = container.resolve(Modules.PROMOTION)
+ const taxModuleService = container.resolve(Modules.TAX)
- await promotionModuleService.deletePromotions(promotionId)
+ await taxModuleService.deleteTaxRegions([taxRegionId])
}
)
-export const createPromotionWorkflow = createWorkflow(
- "create-promotion",
+export const createTaxRegionWorkflow = createWorkflow(
+ "create-tax-region",
() => {
- const { promotion } = createPromotionStep()
+ const { taxRegion } = createTaxRegionStep()
- return new WorkflowResponse({
- promotion,
- })
+ return new WorkflowResponse({ taxRegion })
}
)
```
@@ -19038,13 +19414,13 @@ import type {
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
-import { createPromotionWorkflow } from "../../workflows/create-cart"
+import { createTaxRegionWorkflow } from "../../workflows/create-tax-region"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
- const { result } = await createPromotionWorkflow(req.scope)
+ const { result } = await createTaxRegionWorkflow(req.scope)
.run()
res.send(result)
@@ -19058,13 +19434,13 @@ import {
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/framework"
-import { createPromotionWorkflow } from "../workflows/create-cart"
+import { createTaxRegionWorkflow } from "../workflows/create-tax-region"
export default async function handleUserCreated({
event: { data },
container,
}: SubscriberArgs<{ id: string }>) {
- const { result } = await createPromotionWorkflow(container)
+ const { result } = await createTaxRegionWorkflow(container)
.run()
console.log(result)
@@ -19079,12 +19455,12 @@ export const config: SubscriberConfig = {
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
import { MedusaContainer } from "@medusajs/framework/types"
-import { createPromotionWorkflow } from "../workflows/create-cart"
+import { createTaxRegionWorkflow } from "../workflows/create-tax-region"
export default async function myCustomJob(
container: MedusaContainer
) {
- const { result } = await createPromotionWorkflow(container)
+ const { result } = await createTaxRegionWorkflow(container)
.run()
console.log(result)
@@ -19100,6 +19476,12 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
+## Configure Tax Module
+
+The Tax Module accepts options for further configurations. Refer to [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/tax/module-options/index.html.md) for details on the module's options.
+
+***
+
# Store Module
@@ -19389,6 +19771,34 @@ The User Module accepts options for further configurations. Refer to [this docum
***
+# API Key Concepts
+
+In this document, you’ll learn about the different types of API keys, their expiration and verification.
+
+## API Key Types
+
+There are two types of API keys:
+
+- `publishable`: A public key used in client applications, such as a storefront.
+- `secret`: A secret key used for authentication and verification purposes, such as an admin user’s authentication token or a password reset token.
+
+The API key’s type is stored in the `type` property of the [ApiKey data model](https://docs.medusajs.com/references/api-key/models/ApiKey/index.html.md).
+
+***
+
+## API Key Expiration
+
+An API key expires when it’s revoked using the [revoke method of the module’s main service](https://docs.medusajs.com/references/api-key/revoke/index.html.md).
+
+The associated token is no longer usable or verifiable.
+
+***
+
+## Token Verification
+
+To verify a token received as an input or in a request, use the [authenticate method of the module’s main service](https://docs.medusajs.com/references/api-key/authenticate/index.html.md) which validates the token against all non-expired tokens.
+
+
# Links between API Key Module and Other Modules
This document showcases the module links defined between the API Key Module and other commerce modules.
@@ -19487,786 +19897,6 @@ createRemoteLinkStep({
```
-# Tax Module
-
-In this section of the documentation, you will find resources to learn more about the Tax Module and how to use it in your application.
-
-Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/tax-regions/index.html.md) to learn how to manage tax regions using the dashboard.
-
-Medusa has tax related features available out-of-the-box through the Tax Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Tax Module.
-
-Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-
-## Tax Features
-
-- [Tax Settings Per Region](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/tax/tax-region/index.html.md): Set different tax settings for each tax region.
-- [Tax Rates and Rules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/tax/tax-rates-and-rules/index.html.md): Manage each region's default tax rates and override them with conditioned tax rates.
-- [Retrieve Tax Lines for carts and orders](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/tax/tax-calculation-with-provider/index.html.md): Calculate and retrieve the tax lines of a cart or order's line items and shipping methods with tax providers.
-
-***
-
-## How to Use Tax Module's Service
-
-In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
-
-You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
-
-For example:
-
-```ts title="src/workflows/create-tax-region.ts" highlights={highlights}
-import {
- createWorkflow,
- WorkflowResponse,
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { Modules } from "@medusajs/framework/utils"
-
-const createTaxRegionStep = createStep(
- "create-tax-region",
- async ({}, { container }) => {
- const taxModuleService = container.resolve(Modules.TAX)
-
- const taxRegion = await taxModuleService.createTaxRegions({
- country_code: "us",
- })
-
- return new StepResponse({ taxRegion }, taxRegion.id)
- },
- async (taxRegionId, { container }) => {
- if (!taxRegionId) {
- return
- }
- const taxModuleService = container.resolve(Modules.TAX)
-
- await taxModuleService.deleteTaxRegions([taxRegionId])
- }
-)
-
-export const createTaxRegionWorkflow = createWorkflow(
- "create-tax-region",
- () => {
- const { taxRegion } = createTaxRegionStep()
-
- return new WorkflowResponse({ taxRegion })
- }
-)
-```
-
-You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
-
-### API Route
-
-```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import { createTaxRegionWorkflow } from "../../workflows/create-tax-region"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await createTaxRegionWorkflow(req.scope)
- .run()
-
- res.send(result)
-}
-```
-
-### Subscriber
-
-```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import {
- type SubscriberConfig,
- type SubscriberArgs,
-} from "@medusajs/framework"
-import { createTaxRegionWorkflow } from "../workflows/create-tax-region"
-
-export default async function handleUserCreated({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- const { result } = await createTaxRegionWorkflow(container)
- .run()
-
- console.log(result)
-}
-
-export const config: SubscriberConfig = {
- event: "user.created",
-}
-```
-
-### Scheduled Job
-
-```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
-import { MedusaContainer } from "@medusajs/framework/types"
-import { createTaxRegionWorkflow } from "../workflows/create-tax-region"
-
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const { result } = await createTaxRegionWorkflow(container)
- .run()
-
- console.log(result)
-}
-
-export const config = {
- name: "run-once-a-day",
- schedule: `0 0 * * *`,
-}
-```
-
-Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
-
-***
-
-## Configure Tax Module
-
-The Tax Module accepts options for further configurations. Refer to [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/tax/module-options/index.html.md) for details on the module's options.
-
-***
-
-
-# Cart Concepts
-
-In this document, you’ll get an overview of the main concepts of a cart.
-
-## Shipping and Billing Addresses
-
-A cart has a shipping and billing address. Both of these addresses are represented by the [Address data model](https://docs.medusajs.com/references/cart/models/Address/index.html.md).
-
-
-
-***
-
-## Line Items
-
-A line item, represented by the [LineItem](https://docs.medusajs.com/references/cart/models/LineItem/index.html.md) data model, is a quantity of a product variant added to the cart. A cart has multiple line items.
-
-A line item stores some of the product variant’s properties, such as the `product_title` and `product_description`. It also stores data related to the item’s quantity and price.
-
-In the Medusa application, a product variant is implemented in the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md).
-
-***
-
-## Shipping Methods
-
-A shipping method, represented by the [ShippingMethod data model](https://docs.medusajs.com/references/cart/models/ShippingMethod/index.html.md), is used to fulfill the items in the cart after the order is placed. A cart can have more than one shipping method.
-
-In the Medusa application, the shipping method is created from a shipping option, available through the [Fulfillment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/index.html.md). Its ID is stored in the `shipping_option_id` property of the method.
-
-### data Property
-
-After an order is placed, you can use a third-party fulfillment provider to fulfill its shipments.
-
-If the fulfillment provider requires additional custom data to be passed along from the checkout process, set this data in the `ShippingMethod`'s `data` property.
-
-The `data` property is an object used to store custom data relevant later for fulfillment.
-
-
-# Links between Currency Module and Other Modules
-
-This document showcases the module links defined between the Currency Module and other commerce modules.
-
-## Summary
-
-The Currency Module has the following links to other modules:
-
-Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
-
-|First Data Model|Second Data Model|Type|Description|
-|---|---|---|---|
-| in ||Read-only||
-
-***
-
-## Store Module
-
-The Store Module has a `Currency` data model that stores the supported currencies of a store. However, these currencies don't hold all the details of a currency, such as its name or symbol.
-
-Instead, Medusa defines a read-only link between the [Store Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/store/index.html.md)'s `Currency` data model and the Currency Module's `Currency` data model. Because the link is read-only from the `Store`'s side, you can only retrieve the details of a store's supported currencies, and not the other way around.
-
-### Retrieve with Query
-
-To retrieve the details of a store's currencies with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `supported_currencies.currency.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: stores } = await query.graph({
- entity: "store",
- fields: [
- "supported_currencies.currency.*",
- ],
-})
-
-// stores.supported_currencies
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: stores } = useQueryGraphStep({
- entity: "store",
- fields: [
- "supported_currencies.currency.*",
- ],
-})
-
-// stores.supported_currencies
-```
-
-
-# API Key Concepts
-
-In this document, you’ll learn about the different types of API keys, their expiration and verification.
-
-## API Key Types
-
-There are two types of API keys:
-
-- `publishable`: A public key used in client applications, such as a storefront.
-- `secret`: A secret key used for authentication and verification purposes, such as an admin user’s authentication token or a password reset token.
-
-The API key’s type is stored in the `type` property of the [ApiKey data model](https://docs.medusajs.com/references/api-key/models/ApiKey/index.html.md).
-
-***
-
-## API Key Expiration
-
-An API key expires when it’s revoked using the [revoke method of the module’s main service](https://docs.medusajs.com/references/api-key/revoke/index.html.md).
-
-The associated token is no longer usable or verifiable.
-
-***
-
-## Token Verification
-
-To verify a token received as an input or in a request, use the [authenticate method of the module’s main service](https://docs.medusajs.com/references/api-key/authenticate/index.html.md) which validates the token against all non-expired tokens.
-
-
-# Links between Cart Module and Other Modules
-
-This document showcases the module links defined between the Cart Module and other commerce modules.
-
-## Summary
-
-The Cart Module has the following links to other modules:
-
-Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
-
-|First Data Model|Second Data Model|Type|Description|
-|---|---|---|---|
-|| in |Read-only||
-| in ||Stored||
-|| in |Stored||
-|| in |Read-only||
-|| in |Read-only||
-|| in |Stored||
-|| in |Read-only||
-|| in |Read-only||
-
-***
-
-## Customer Module
-
-Medusa defines a read-only link between the `Cart` data model and the [Customer Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/customer/index.html.md)'s `Customer` data model. This means you can retrieve the details of a cart's customer, but you don't manage the links in a pivot table in the database. The customer of a cart is determined by the `customer_id` property of the `Cart` data model.
-
-### Retrieve with Query
-
-To retrieve the customer of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: carts } = await query.graph({
- entity: "cart",
- fields: [
- "customer.*",
- ],
-})
-
-// carts.order
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: carts } = useQueryGraphStep({
- entity: "cart",
- fields: [
- "customer.*",
- ],
-})
-
-// carts.order
-```
-
-***
-
-## Order Module
-
-The [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md) provides order-management features.
-
-Medusa defines a link between the `Cart` and `Order` data models. The cart is linked to the order created once the cart is completed.
-
-
-
-### Retrieve with Query
-
-To retrieve the order of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: carts } = await query.graph({
- entity: "cart",
- fields: [
- "order.*",
- ],
-})
-
-// carts.order
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: carts } = useQueryGraphStep({
- entity: "cart",
- fields: [
- "order.*",
- ],
-})
-
-// carts.order
-```
-
-### Manage with Link
-
-To manage the order of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.CART]: {
- cart_id: "cart_123",
- },
- [Modules.ORDER]: {
- order_id: "order_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.CART]: {
- cart_id: "cart_123",
- },
- [Modules.ORDER]: {
- order_id: "order_123",
- },
-})
-```
-
-***
-
-## Payment Module
-
-The [Payment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/index.html.md) handles payment processing and management.
-
-Medusa defines a link between the `Cart` and `PaymentCollection` data models. A cart has a payment collection which holds all the authorized payment sessions and payments made related to the cart.
-
-
-
-### Retrieve with Query
-
-To retrieve the payment collection of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `payment_collection.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: carts } = await query.graph({
- entity: "cart",
- fields: [
- "payment_collection.*",
- ],
-})
-
-// carts.payment_collection
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: carts } = useQueryGraphStep({
- entity: "cart",
- fields: [
- "payment_collection.*",
- ],
-})
-
-// carts.payment_collection
-```
-
-### Manage with Link
-
-To manage the payment collection of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.CART]: {
- cart_id: "cart_123",
- },
- [Modules.PAYMENT]: {
- payment_collection_id: "paycol_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.CART]: {
- cart_id: "cart_123",
- },
- [Modules.PAYMENT]: {
- payment_collection_id: "paycol_123",
- },
-})
-```
-
-***
-
-## Product Module
-
-Medusa defines read-only links between:
-
-- the `LineItem` data model and the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md)'s `Product` data model. This means you can retrieve the details of a line item's product, but you don't manage the links in a pivot table in the database. The product of a line item is determined by the `product_id` property of the `LineItem` data model.
-- the `LineItem` data model and the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md)'s `ProductVariant` data model. This means you can retrieve the details of a line item's variant, but you don't manage the links in a pivot table in the database. The variant of a line item is determined by the `variant_id` property of the `LineItem` data model.
-
-### Retrieve with Query
-
-To retrieve the variant of a line item with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variant.*` in `fields`:
-
-To retrieve the product, pass `product.*` in `fields`.
-
-### query.graph
-
-```ts
-const { data: lineItems } = await query.graph({
- entity: "line_item",
- fields: [
- "variant.*",
- ],
-})
-
-// lineItems.variant
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: lineItems } = useQueryGraphStep({
- entity: "line_item",
- fields: [
- "variant.*",
- ],
-})
-
-// lineItems.variant
-```
-
-***
-
-## Promotion Module
-
-The [Promotion Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/index.html.md) provides discount features.
-
-Medusa defines a link between the `Cart` and `Promotion` data models. This indicates the promotions applied on a cart.
-
-
-
-Medusa also defines a read-only link between the `LineItemAdjustment` and `Promotion` data models. This means you can retrieve the details of the promotion applied on a line item, but you don't manage the links in a pivot table in the database. The promotion of a line item is determined by the `promotion_id` property of the `LineItemAdjustment` data model.
-
-### Retrieve with Query
-
-To retrieve the promotions of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `promotions.*` in `fields`:
-
-To retrieve the promotion of a line item adjustment, pass `promotion.*` in `fields`.
-
-### query.graph
-
-```ts
-const { data: carts } = await query.graph({
- entity: "cart",
- fields: [
- "promotions.*",
- ],
-})
-
-// carts.promotions
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: carts } = useQueryGraphStep({
- entity: "cart",
- fields: [
- "promotions.*",
- ],
-})
-
-// carts.promotions
-```
-
-### Manage with Link
-
-To manage the promotions of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.CART]: {
- cart_id: "cart_123",
- },
- [Modules.PROMOTION]: {
- promotion_id: "promo_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.CART]: {
- cart_id: "cart_123",
- },
- [Modules.PROMOTION]: {
- promotion_id: "promo_123",
- },
-})
-```
-
-***
-
-## Region Module
-
-Medusa defines a read-only link between the `Cart` data model and the [Region Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/region/index.html.md)'s `Region` data model. This means you can retrieve the details of a cart's region, but you don't manage the links in a pivot table in the database. The region of a cart is determined by the `region_id` property of the `Cart` data model.
-
-### Retrieve with Query
-
-To retrieve the region of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `region.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: carts } = await query.graph({
- entity: "cart",
- fields: [
- "region.*",
- ],
-})
-
-// carts.region
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: carts } = useQueryGraphStep({
- entity: "cart",
- fields: [
- "region.*",
- ],
-})
-
-// carts.region
-```
-
-***
-
-## Sales Channel Module
-
-Medusa defines a read-only link between the `Cart` data model and the [Sales Channel Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/index.html.md)'s `SalesChannel` data model. This means you can retrieve the details of a cart's sales channel, but you don't manage the links in a pivot table in the database. The sales channel of a cart is determined by the `sales_channel_id` property of the `Cart` data model.
-
-### Retrieve with Query
-
-To retrieve the sales channel of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channel.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: carts } = await query.graph({
- entity: "cart",
- fields: [
- "sales_channel.*",
- ],
-})
-
-// carts.sales_channel
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: carts } = useQueryGraphStep({
- entity: "cart",
- fields: [
- "sales_channel.*",
- ],
-})
-
-// carts.sales_channel
-```
-
-
-# Tax Lines in Cart Module
-
-In this document, you’ll learn about tax lines in a cart and how to retrieve tax lines with the Tax Module.
-
-## What are Tax Lines?
-
-A tax line indicates the tax rate of a line item or a shipping method. The [LineItemTaxLine data model](https://docs.medusajs.com/references/cart/models/LineItemTaxLine/index.html.md) represents a line item’s tax line, and the [ShippingMethodTaxLine data model](https://docs.medusajs.com/references/cart/models/ShippingMethodTaxLine/index.html.md) represents a shipping method’s tax line.
-
-
-
-***
-
-## Tax Inclusivity
-
-By default, the tax amount is calculated by taking the tax rate from the line item or shipping method’s amount, and then adding them to the item/method’s subtotal.
-
-However, line items and shipping methods have an `is_tax_inclusive` property that, when enabled, indicates that the item or method’s price already includes taxes.
-
-So, instead of calculating the tax rate and adding it to the item/method’s subtotal, it’s calculated as part of the subtotal.
-
-The following diagram is a simplified showcase of how a subtotal is calculated from the taxes perspective.
-
-
-
-For example, if a line item's amount is `5000`, the tax rate is `10`, and tax inclusivity is enabled, the tax amount is 10% of `5000`, which is `500`, making the unit price of the line item `4500`.
-
-***
-
-## Retrieve Tax Lines
-
-When using the Cart and Tax modules together, you can use the `getTaxLines` method of the Tax Module’s main service. It retrieves the tax lines for a cart’s line items and shipping methods.
-
-```ts
-// retrieve the cart
-const cart = await cartModuleService.retrieveCart("cart_123", {
- relations: [
- "items.tax_lines",
- "shipping_methods.tax_lines",
- "shipping_address",
- ],
-})
-
-// retrieve the tax lines
-const taxLines = await taxModuleService.getTaxLines(
- [
- ...(cart.items as TaxableItemDTO[]),
- ...(cart.shipping_methods as TaxableShippingDTO[]),
- ],
- {
- address: {
- ...cart.shipping_address,
- country_code:
- cart.shipping_address.country_code || "us",
- },
- }
-)
-```
-
-Then, use the returned tax lines to set the line items and shipping methods’ tax lines:
-
-```ts
-// set line item tax lines
-await cartModuleService.setLineItemTaxLines(
- cart.id,
- taxLines.filter((line) => "line_item_id" in line)
-)
-
-// set shipping method tax lines
-await cartModuleService.setLineItemTaxLines(
- cart.id,
- taxLines.filter((line) => "shipping_line_id" in line)
-)
-```
-
-
# Auth Identity and Actor Types
In this document, you’ll learn about concepts related to identity and actors in the Auth Module.
@@ -20538,122 +20168,52 @@ In the example above, you use the `emailpass` provider, so you have to pass an o
If the returned `success` property is `true`, the password has reset successfully.
-# Promotions Adjustments in Carts
+# Auth Providers
-In this document, you’ll learn how a promotion is applied to a cart’s line items and shipping methods using adjustment lines.
+In this document, you’ll learn how the Auth Module handles authentication using providers.
-## What are Adjustment Lines?
+## What's an Auth Module Provider?
-An adjustment line indicates a change to an item or a shipping method’s amount. It’s used to apply promotions or discounts on a cart.
+An auth module provider handles authenticating customers and users, either using custom logic or by integrating a third-party service.
-The [LineItemAdjustment](https://docs.medusajs.com/references/cart/models/LineItemAdjustment/index.html.md) data model represents changes on a line item, and the [ShippingMethodAdjustment](https://docs.medusajs.com/references/cart/models/ShippingMethodAdjustment/index.html.md) data model represents changes on a shipping method.
+For example, the EmailPass Auth Module Provider authenticates a user using their email and password, whereas the Google Auth Module Provider authenticates users using their Google account.
-
+### Auth Providers List
-The `amount` property of the adjustment line indicates the amount to be discounted from the original amount. Also, the ID of the applied promotion is stored in the `promotion_id` property of the adjustment line.
+- [Emailpass](https://docs.medusajs.com/commerce-modules/auth/auth-providers/emailpass/index.html.md)
+- [Google](https://docs.medusajs.com/commerce-modules/auth/auth-providers/google/index.html.md)
+- [GitHub](https://docs.medusajs.com/commerce-modules/auth/auth-providers/github/index.html.md)
***
-## Discountable Option
+## Configure Allowed Auth Providers of Actor Types
-The [LineItem](https://docs.medusajs.com/references/cart/models/LineItem/index.html.md) data model has an `is_discountable` property that indicates whether promotions can be applied to the line item. It’s enabled by default.
+By default, users of all actor types can authenticate with all installed auth module providers.
-When disabled, a promotion can’t be applied to a line item. In the context of the Promotion Module, the promotion isn’t applied to the line item even if it matches its rules.
+To restrict the auth providers used for actor types, use the [authMethodsPerActor option](https://docs.medusajs.com/docs/learn/configurations/medusa-config#httpauthMethodsPerActor/index.html.md) in Medusa's configurations:
+
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ projectConfig: {
+ http: {
+ authMethodsPerActor: {
+ user: ["google"],
+ customer: ["emailpass"],
+ },
+ // ...
+ },
+ // ...
+ },
+})
+```
+
+When you specify the `authMethodsPerActor` configuration, it overrides the default. So, if you don't specify any providers for an actor type, users of that actor type can't authenticate with any provider.
***
-## Promotion Actions
+## How to Create an Auth Module Provider
-When using the Cart and Promotion modules together, such as in the Medusa application, use the [computeActions method of the Promotion Module’s main service](https://docs.medusajs.com/references/promotion/computeActions/index.html.md). It retrieves the actions of line items and shipping methods.
-
-Learn more about actions in the [Promotion Module’s documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/actions/index.html.md).
-
-For example:
-
-```ts collapsibleLines="1-8" expandButtonLabel="Show Imports"
-import {
- ComputeActionAdjustmentLine,
- ComputeActionItemLine,
- ComputeActionShippingLine,
- // ...
-} from "@medusajs/framework/types"
-
-// retrieve the cart
-const cart = await cartModuleService.retrieveCart("cart_123", {
- relations: [
- "items.adjustments",
- "shipping_methods.adjustments",
- ],
-})
-
-// retrieve line item adjustments
-const lineItemAdjustments: ComputeActionItemLine[] = []
-cart.items.forEach((item) => {
- const filteredAdjustments = item.adjustments?.filter(
- (adjustment) => adjustment.code !== undefined
- ) as unknown as ComputeActionAdjustmentLine[]
- if (filteredAdjustments.length) {
- lineItemAdjustments.push({
- ...item,
- adjustments: filteredAdjustments,
- })
- }
-})
-
-// retrieve shipping method adjustments
-const shippingMethodAdjustments: ComputeActionShippingLine[] =
- []
-cart.shipping_methods.forEach((shippingMethod) => {
- const filteredAdjustments =
- shippingMethod.adjustments?.filter(
- (adjustment) => adjustment.code !== undefined
- ) as unknown as ComputeActionAdjustmentLine[]
- if (filteredAdjustments.length) {
- shippingMethodAdjustments.push({
- ...shippingMethod,
- adjustments: filteredAdjustments,
- })
- }
-})
-
-// compute actions
-const actions = await promotionModuleService.computeActions(
- ["promo_123"],
- {
- items: lineItemAdjustments,
- shipping_methods: shippingMethodAdjustments,
- }
-)
-```
-
-The `computeActions` method accepts the existing adjustments of line items and shipping methods to compute the actions accurately.
-
-Then, use the returned `addItemAdjustment` and `addShippingMethodAdjustment` actions to set the cart’s line item and the shipping method’s adjustments.
-
-```ts collapsibleLines="1-8" expandButtonLabel="Show Imports"
-import {
- AddItemAdjustmentAction,
- AddShippingMethodAdjustment,
- // ...
-} from "@medusajs/framework/types"
-
-// ...
-
-await cartModuleService.setLineItemAdjustments(
- cart.id,
- actions.filter(
- (action) => action.action === "addItemAdjustment"
- ) as AddItemAdjustmentAction[]
-)
-
-await cartModuleService.setShippingMethodAdjustments(
- cart.id,
- actions.filter(
- (action) =>
- action.action === "addShippingMethodAdjustment"
- ) as AddShippingMethodAdjustment[]
-)
-```
+Refer to [this guide](https://docs.medusajs.com/references/auth/provider/index.html.md) to learn how to create an auth module provider.
# How to Use Authentication Routes
@@ -21000,54 +20560,6 @@ If the authentication is successful, the request returns an object with a `succe
```
-# Auth Providers
-
-In this document, you’ll learn how the Auth Module handles authentication using providers.
-
-## What's an Auth Module Provider?
-
-An auth module provider handles authenticating customers and users, either using custom logic or by integrating a third-party service.
-
-For example, the EmailPass Auth Module Provider authenticates a user using their email and password, whereas the Google Auth Module Provider authenticates users using their Google account.
-
-### Auth Providers List
-
-- [Emailpass](https://docs.medusajs.com/commerce-modules/auth/auth-providers/emailpass/index.html.md)
-- [Google](https://docs.medusajs.com/commerce-modules/auth/auth-providers/google/index.html.md)
-- [GitHub](https://docs.medusajs.com/commerce-modules/auth/auth-providers/github/index.html.md)
-
-***
-
-## Configure Allowed Auth Providers of Actor Types
-
-By default, users of all actor types can authenticate with all installed auth module providers.
-
-To restrict the auth providers used for actor types, use the [authMethodsPerActor option](https://docs.medusajs.com/docs/learn/configurations/medusa-config#httpauthMethodsPerActor/index.html.md) in Medusa's configurations:
-
-```ts title="medusa-config.ts"
-module.exports = defineConfig({
- projectConfig: {
- http: {
- authMethodsPerActor: {
- user: ["google"],
- customer: ["emailpass"],
- },
- // ...
- },
- // ...
- },
-})
-```
-
-When you specify the `authMethodsPerActor` configuration, it overrides the default. So, if you don't specify any providers for an actor type, users of that actor type can't authenticate with any provider.
-
-***
-
-## How to Create an Auth Module Provider
-
-Refer to [this guide](https://docs.medusajs.com/references/auth/provider/index.html.md) to learn how to create an auth module provider.
-
-
# How to Create an Actor Type
In this document, learn how to create an actor type and authenticate its associated data model.
@@ -21621,33 +21133,6 @@ The page shows the user password fields to enter their new password, then submit
- [Storefront Guide: Reset Customer Password](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/reset-password/index.html.md)
-# Fulfillment Module Provider
-
-In this document, you’ll learn what a fulfillment module provider is.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/locations-and-shipping/locations#manage-fulfillment-providers/index.html.md) to learn how to add a fulfillment provider to a location using the dashboard.
-
-## What’s a Fulfillment Module Provider?
-
-A fulfillment module provider handles fulfilling items, typically using a third-party integration.
-
-Fulfillment module providers registered in the Fulfillment Module's [options](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/module-options/index.html.md) are stored and represented by the [FulfillmentProvider data model](https://docs.medusajs.com/references/fulfillment/models/FulfillmentProvider/index.html.md).
-
-***
-
-## Configure Fulfillment Providers
-
-The Fulfillment Module accepts a `providers` option that allows you to register providers in your application.
-
-Learn more about the `providers` option in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/module-options/index.html.md).
-
-***
-
-## How to Create a Fulfillment Provider?
-
-Refer to [this guide](https://docs.medusajs.com/references/fulfillment/provider/index.html.md) to learn how to create a fulfillment module provider.
-
-
# Fulfillment Concepts
In this document, you’ll learn about some basic fulfillment concepts.
@@ -21694,6 +21179,86 @@ A shipping profile defines a type of items that are shipped in a similar manner.
A shipping profile is represented by the [ShippingProfile data model](https://docs.medusajs.com/references/fulfillment/models/ShippingProfile/index.html.md). It only defines the profile’s details, but it’s associated with the shipping options available for the item type.
+# Fulfillment Module Provider
+
+In this document, you’ll learn what a fulfillment module provider is.
+
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/locations-and-shipping/locations#manage-fulfillment-providers/index.html.md) to learn how to add a fulfillment provider to a location using the dashboard.
+
+## What’s a Fulfillment Module Provider?
+
+A fulfillment module provider handles fulfilling items, typically using a third-party integration.
+
+Fulfillment module providers registered in the Fulfillment Module's [options](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/module-options/index.html.md) are stored and represented by the [FulfillmentProvider data model](https://docs.medusajs.com/references/fulfillment/models/FulfillmentProvider/index.html.md).
+
+***
+
+## Configure Fulfillment Providers
+
+The Fulfillment Module accepts a `providers` option that allows you to register providers in your application.
+
+Learn more about the `providers` option in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/module-options/index.html.md).
+
+***
+
+## How to Create a Fulfillment Provider?
+
+Refer to [this guide](https://docs.medusajs.com/references/fulfillment/provider/index.html.md) to learn how to create a fulfillment module provider.
+
+
+# Item Fulfillment
+
+In this document, you’ll learn about the concepts of item fulfillment.
+
+## Fulfillment Data Model
+
+A fulfillment is the shipping and delivery of one or more items to the customer. It’s represented by the [Fulfillment data model](https://docs.medusajs.com/references/fulfillment/models/Fulfillment/index.html.md).
+
+***
+
+## Fulfillment Processing by a Fulfillment Provider
+
+A fulfillment is associated with a fulfillment provider that handles all its processing, such as creating a shipment for the fulfillment’s items.
+
+The fulfillment is also associated with a shipping option of that provider, which determines how the item is shipped.
+
+
+
+***
+
+## data Property
+
+The `Fulfillment` data model has a `data` property that holds any necessary data for the third-party fulfillment provider to process the fulfillment.
+
+For example, the `data` property can hold the ID of the fulfillment in the third-party provider. The associated fulfillment provider then uses it whenever it retrieves the fulfillment’s details.
+
+***
+
+## Fulfillment Items
+
+A fulfillment is used to fulfill one or more items. Each item is represented by the `FulfillmentItem` data model.
+
+The fulfillment item holds details relevant to fulfilling the item, such as barcode, SKU, and quantity to fulfill.
+
+
+
+***
+
+## Fulfillment Label
+
+Once a shipment is created for the fulfillment, you can store its tracking number, URL, or other related details as a label, represented by the `FulfillmentLabel` data model.
+
+***
+
+## Fulfillment Status
+
+The `Fulfillment` data model has three properties to keep track of the current status of the fulfillment:
+
+- `packed_at`: The date the fulfillment was packed. If set, then the fulfillment has been packed.
+- `shipped_at`: The date the fulfillment was shipped. If set, then the fulfillment has been shipped.
+- `delivered_at`: The date the fulfillment was delivered. If set, then the fulfillment has been delivered.
+
+
# Links between Fulfillment Module and Other Modules
This document showcases the module links defined between the Fulfillment Module and other commerce modules.
@@ -22099,6 +21664,29 @@ The `providers` option is an array of objects that accept the following properti
- `options`: An optional object of the module provider's options.
+# Customer Accounts
+
+In this document, you’ll learn how registered and unregistered accounts are distinguished in the Medusa application.
+
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/customers/index.html.md) to learn how to manage customers using the dashboard.
+
+## `has_account` Property
+
+The [Customer data model](https://docs.medusajs.com/references/customer/models/Customer/index.html.md) has a `has_account` property, which is a boolean that indicates whether a customer is registered.
+
+When a guest customer places an order, a new `Customer` record is created with `has_account` set to `false`.
+
+When this or another guest customer registers an account with the same email, a new `Customer` record is created with `has_account` set to `true`.
+
+***
+
+## Email Uniqueness
+
+The above behavior means that two `Customer` records may exist with the same email. However, the main difference is the `has_account` property's value.
+
+So, there can only be one guest customer (having `has_account=false`) and one registered customer (having `has_account=true`) with the same email.
+
+
# Shipping Option
In this document, you’ll learn about shipping options and their rules.
@@ -22163,82 +21751,6 @@ When fulfilling an item, you might use a third-party fulfillment provider that r
The `ShippingOption` data model has a `data` property. It's an object that stores custom data relevant later when creating and processing a fulfillment.
-# Item Fulfillment
-
-In this document, you’ll learn about the concepts of item fulfillment.
-
-## Fulfillment Data Model
-
-A fulfillment is the shipping and delivery of one or more items to the customer. It’s represented by the [Fulfillment data model](https://docs.medusajs.com/references/fulfillment/models/Fulfillment/index.html.md).
-
-***
-
-## Fulfillment Processing by a Fulfillment Provider
-
-A fulfillment is associated with a fulfillment provider that handles all its processing, such as creating a shipment for the fulfillment’s items.
-
-The fulfillment is also associated with a shipping option of that provider, which determines how the item is shipped.
-
-
-
-***
-
-## data Property
-
-The `Fulfillment` data model has a `data` property that holds any necessary data for the third-party fulfillment provider to process the fulfillment.
-
-For example, the `data` property can hold the ID of the fulfillment in the third-party provider. The associated fulfillment provider then uses it whenever it retrieves the fulfillment’s details.
-
-***
-
-## Fulfillment Items
-
-A fulfillment is used to fulfill one or more items. Each item is represented by the `FulfillmentItem` data model.
-
-The fulfillment item holds details relevant to fulfilling the item, such as barcode, SKU, and quantity to fulfill.
-
-
-
-***
-
-## Fulfillment Label
-
-Once a shipment is created for the fulfillment, you can store its tracking number, URL, or other related details as a label, represented by the `FulfillmentLabel` data model.
-
-***
-
-## Fulfillment Status
-
-The `Fulfillment` data model has three properties to keep track of the current status of the fulfillment:
-
-- `packed_at`: The date the fulfillment was packed. If set, then the fulfillment has been packed.
-- `shipped_at`: The date the fulfillment was shipped. If set, then the fulfillment has been shipped.
-- `delivered_at`: The date the fulfillment was delivered. If set, then the fulfillment has been delivered.
-
-
-# Customer Accounts
-
-In this document, you’ll learn how registered and unregistered accounts are distinguished in the Medusa application.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/customers/index.html.md) to learn how to manage customers using the dashboard.
-
-## `has_account` Property
-
-The [Customer data model](https://docs.medusajs.com/references/customer/models/Customer/index.html.md) has a `has_account` property, which is a boolean that indicates whether a customer is registered.
-
-When a guest customer places an order, a new `Customer` record is created with `has_account` set to `false`.
-
-When this or another guest customer registers an account with the same email, a new `Customer` record is created with `has_account` set to `true`.
-
-***
-
-## Email Uniqueness
-
-The above behavior means that two `Customer` records may exist with the same email. However, the main difference is the `has_account` property's value.
-
-So, there can only be one guest customer (having `has_account=false`) and one registered customer (having `has_account=true`) with the same email.
-
-
# Links between Customer Module and Other Modules
This document showcases the module links defined between the Customer Module and other commerce modules.
@@ -22416,6 +21928,2599 @@ const { data: orders } = useQueryGraphStep({
```
+# Order Concepts
+
+In this document, you’ll learn about orders and related concepts
+
+## Order Items
+
+The items purchased in the order are represented by the [OrderItem data model](https://docs.medusajs.com/references/order/models/OrderItem/index.html.md). An order can have multiple items.
+
+
+
+### Item’s Product Details
+
+The details of the purchased products are represented by the [LineItem data model](https://docs.medusajs.com/references/order/models/OrderLineItem/index.html.md). Not only does a line item hold the details of the product, but also details related to its price, adjustments due to promotions, and taxes.
+
+***
+
+## Order’s Shipping Method
+
+An order has one or more shipping methods used to handle item shipment.
+
+Each shipping method is represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderShippingMethod/index.html.md) that holds its details. The shipping method is linked to the order through the [OrderShipping data model](https://docs.medusajs.com/references/order/models/OrderShipping/index.html.md).
+
+
+
+### data Property
+
+When fulfilling the order, you can use a third-party fulfillment provider that requires additional custom data to be passed along from the order creation process.
+
+The `OrderShippingMethod` data model has a `data` property. It’s an object used to store custom data relevant later for fulfillment.
+
+The Medusa application passes the `data` property to the Fulfillment Module when fulfilling items.
+
+***
+
+## Order Totals
+
+The order’s total amounts (including tax total, total after an item is returned, etc…) are represented by the [OrderSummary data model](https://docs.medusajs.com/references/order/models/OrderSummary/index.html.md).
+
+***
+
+## Order Payments
+
+Payments made on an order, whether they’re capture or refund payments, are recorded as transactions represented by the [OrderTransaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md).
+
+An order can have multiple transactions. The sum of these transactions must be equal to the order summary’s total. Otherwise, there’s an outstanding amount.
+
+Learn more about transactions in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/transactions/index.html.md).
+
+
+# Order Claim
+
+In this document, you’ll learn about order claims.
+
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/orders/claims/index.html.md) to learn how to manage an order's claims using the dashboard.
+
+## What is a Claim?
+
+When a customer receives a defective or incorrect item, the merchant can create a claim to refund or replace the item.
+
+The [OrderClaim data model](https://docs.medusajs.com/references/order/models/OrderClaim/index.html.md) represents a claim.
+
+***
+
+## Claim Type
+
+The `Claim` data model has a `type` property whose value indicates the type of the claim:
+
+- `refund`: the items are returned, and the customer is refunded.
+- `replace`: the items are returned, and the customer receives new items.
+
+***
+
+## Old and Replacement Items
+
+When the claim is created, a return, represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md), is also created to handle receiving the old items from the customer.
+
+Learn more about returns in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md).
+
+If the claim’s type is `replace`, replacement items are represented by the [ClaimItem data model](https://docs.medusajs.com/references/order/models/OrderClaimItem/index.html.md).
+
+***
+
+## Claim Shipping Methods
+
+A claim uses shipping methods to send the replacement items to the customer. These methods are represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderShippingMethod/index.html.md).
+
+The shipping methods for the returned items are associated with the claim's return, as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return#return-shipping-methods/index.html.md).
+
+***
+
+## Claim Refund
+
+If the claim’s type is `refund`, the amount to be refunded is stored in the `refund_amount` property.
+
+The [Transaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md) represents the refunds made for the claim.
+
+***
+
+## How Claims Impact an Order’s Version
+
+When a claim is confirmed, the order’s version is incremented.
+
+
+# Order Edit
+
+In this document, you'll learn about order edits.
+
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/orders/edit/index.html.md) to learn how to edit an order's items using the dashboard.
+
+## What is an Order Edit?
+
+A merchant can edit an order to add new items or change the quantity of existing items in the order.
+
+An order edit is represented by the [OrderChange data model](https://docs.medusajs.com/references/order/models/OrderChange/index.html.md).
+
+The `OrderChange` data model is associated with any type of change, including a return or exchange. However, its `change_type` property distinguishes the type of change it's making.
+
+In the case of an order edit, the `OrderChange`'s type is `edit`.
+
+***
+
+## Add Items in an Order Edit
+
+When the merchant adds new items to the order in the order edit, the item is added as an [OrderItem](https://docs.medusajs.com/references/order/models/OrderItem/index.html.md).
+
+Also, an `OrderChangeAction` is created. The [OrderChangeAction data model](https://docs.medusajs.com/references/order/models/OrderChangeAction/index.html.md) represents a change made by an `OrderChange`, such as an item added.
+
+So, when an item is added, an `OrderChangeAction` is created with the type `ITEM_ADD`. In its `details` property, the item's ID, price, and quantity are stored.
+
+***
+
+## Update Items in an Order Edit
+
+A merchant can update an existing item's quantity or price.
+
+This change is added as an `OrderChangeAction` with the type `ITEM_UPDATE`. In its `details` property, the item's ID, new price, and new quantity are stored.
+
+***
+
+## Shipping Methods of New Items in the Edit
+
+Adding new items to the order requires adding shipping methods for those items.
+
+These shipping methods are represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderItem/index.html.md). Also, an `OrderChangeAction` is created with the type `SHIPPING_ADD`
+
+***
+
+## How Order Edits Impact an Order’s Version
+
+When an order edit is confirmed, the order’s version is incremented.
+
+***
+
+## Payments and Refunds for Order Edit Changes
+
+Once the Order Edit is confirmed, any additional payment or refund required can be made on the original order.
+
+This is determined by the comparison between the `OrderSummary` and the order's transactions, as mentioned in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/transactions#checking-outstanding-amount/index.html.md).
+
+
+# Order Exchange
+
+In this document, you’ll learn about order exchanges.
+
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/orders/exchanges/index.html.md) to learn how to manage an order's exchanges using the dashboard.
+
+## What is an Exchange?
+
+An exchange is the replacement of an item that the customer ordered with another.
+
+A merchant creates the exchange, specifying the items to be replaced and the new items to be sent.
+
+The [OrderExchange data model](https://docs.medusajs.com/references/order/models/OrderExchange/index.html.md) represents an exchange.
+
+***
+
+## Returned and New Items
+
+When the exchange is created, a return, represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md), is created to handle receiving the items back from the customer.
+
+Learn more about returns in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md).
+
+The [OrderExchangeItem data model](https://docs.medusajs.com/references/order/models/OrderExchangeItem/index.html.md) represents the new items to be sent to the customer.
+
+***
+
+## Exchange Shipping Methods
+
+An exchange has shipping methods used to send the new items to the customer. They’re represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderShippingMethod/index.html.md).
+
+The shipping methods for the returned items are associated with the exchange's return, as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return#return-shipping-methods/index.html.md).
+
+***
+
+## Exchange Payment
+
+The `Exchange` data model has a `difference_due` property that stores the outstanding amount.
+
+|Condition|Result|
+|---|---|---|
+|\`difference\_due \< 0\`|Merchant owes the customer a refund of the |
+|\`difference\_due > 0\`|Merchant requires additional payment from the customer of the |
+|\`difference\_due = 0\`|No payment processing is required.|
+
+Any payment or refund made is stored in the [Transaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md).
+
+***
+
+## How Exchanges Impact an Order’s Version
+
+When an exchange is confirmed, the order’s version is incremented.
+
+
+# Order Change
+
+In this document, you'll learn about the Order Change data model and possible actions in it.
+
+## OrderChange Data Model
+
+The [OrderChange data model](https://docs.medusajs.com/references/order/models/OrderChange/index.html.md) represents any kind of change to an order, such as a return, exchange, or edit.
+
+Its `change_type` property indicates what the order change is created for:
+
+1. `edit`: The order change is making edits to the order, as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/edit/index.html.md).
+2. `exchange`: The order change is associated with an exchange, which you can learn about in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/exchange/index.html.md).
+3. `claim`: The order change is associated with a claim, which you can learn about in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/claim/index.html.md).
+4. `return_request` or `return_receive`: The order change is associated with a return, which you can learn about in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md).
+
+Once the order change is confirmed, its changes are applied on the order.
+
+***
+
+## Order Change Actions
+
+The actions to perform on the original order by a change, such as adding an item, are represented by the [OrderChangeAction data model](https://docs.medusajs.com/references/order/models/OrderChangeAction/index.html.md).
+
+The `OrderChangeAction` has an `action` property that indicates the type of action to perform on the order, and a `details` property that holds more details related to the action.
+
+The following table lists the possible `action` values that Medusa uses and what `details` they carry.
+
+|Action|Description|Details|
+|---|---|---|---|---|
+|\`ITEM\_ADD\`|Add an item to the order.|\`details\`|
+|\`ITEM\_UPDATE\`|Update an item in the order.|\`details\`|
+|\`RETURN\_ITEM\`|Set an item to be returned.|\`details\`|
+|\`RECEIVE\_RETURN\_ITEM\`|Mark a return item as received.|\`details\`|
+|\`RECEIVE\_DAMAGED\_RETURN\_ITEM\`|Mark a return item that's damaged as received.|\`details\`|
+|\`SHIPPING\_ADD\`|Add a shipping method for new or returned items.|No details added. The ID to the shipping method is added in the |
+|\`SHIPPING\_ADD\`|Add a shipping method for new or returned items.|No details added. The ID to the shipping method is added in the |
+|\`WRITE\_OFF\_ITEM\`|Remove an item's quantity as part of the claim, without adding the quantity back to the item variant's inventory.|\`details\`|
+
+
+# Links between Order Module and Other Modules
+
+This document showcases the module links defined between the Order Module and other commerce modules.
+
+## Summary
+
+The Order Module has the following links to other modules:
+
+Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
+
+|First Data Model|Second Data Model|Type|Description|
+|---|---|---|---|
+|| in |Read-only||
+|| in |Stored||
+|| in |Stored||
+|| in |Stored||
+|| in |Stored||
+|| in |Stored||
+|| in |Stored||
+|| in |Read-only||
+|| in |Stored||
+|| in |Read-only||
+|| in |Read-only||
+
+***
+
+## Customer Module
+
+Medusa defines a read-only link between the `Order` data model and the [Customer Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/customer/index.html.md)'s `Customer` data model. This means you can retrieve the details of an order's customer, but you don't manage the links in a pivot table in the database. The customer of an order is determined by the `customer_id` property of the `Order` data model.
+
+### Retrieve with Query
+
+To retrieve the customer of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: orders } = await query.graph({
+ entity: "order",
+ fields: [
+ "customer.*",
+ ],
+})
+
+// orders.customer
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: orders } = useQueryGraphStep({
+ entity: "order",
+ fields: [
+ "customer.*",
+ ],
+})
+
+// orders.customer
+```
+
+***
+
+## Cart Module
+
+The [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md) provides cart-management features.
+
+Medusa defines a link between the `Order` and `Cart` data models. The order is linked to the cart used for the purchased.
+
+
+
+### Retrieve with Query
+
+To retrieve the cart of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `cart.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: orders } = await query.graph({
+ entity: "order",
+ fields: [
+ "cart.*",
+ ],
+})
+
+// orders.cart
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: orders } = useQueryGraphStep({
+ entity: "order",
+ fields: [
+ "cart.*",
+ ],
+})
+
+// orders.cart
+```
+
+### Manage with Link
+
+To manage the cart of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.CART]: {
+ cart_id: "cart_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.CART]: {
+ cart_id: "cart_123",
+ },
+})
+```
+
+***
+
+## Fulfillment Module
+
+A fulfillment is created for an orders' items. Medusa defines a link between the `Fulfillment` and `Order` data models.
+
+
+
+A fulfillment is also created for a return's items. So, Medusa defines a link between the `Fulfillment` and `Return` data models.
+
+
+
+### Retrieve with Query
+
+To retrieve the fulfillments of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `fulfillments.*` in `fields`:
+
+To retrieve the fulfillments of a return, pass `fulfillments.*` in `fields`.
+
+### query.graph
+
+```ts
+const { data: orders } = await query.graph({
+ entity: "order",
+ fields: [
+ "fulfillments.*",
+ ],
+})
+
+// orders.fulfillments
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: orders } = useQueryGraphStep({
+ entity: "order",
+ fields: [
+ "fulfillments.*",
+ ],
+})
+
+// orders.fulfillments
+```
+
+### Manage with Link
+
+To manage the fulfillments of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.FULFILLMENT]: {
+ fulfillment_id: "ful_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.FULFILLMENT]: {
+ fulfillment_id: "ful_123",
+ },
+})
+```
+
+***
+
+## Payment Module
+
+An order's payment details are stored in a payment collection. This also applies for claims and exchanges.
+
+So, Medusa defines links between the `PaymentCollection` data model and the `Order`, `OrderClaim`, and `OrderExchange` data models.
+
+
+
+### Retrieve with Query
+
+To retrieve the payment collections of an order, order exchange, or order claim with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `payment_collections.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: orders } = await query.graph({
+ entity: "order",
+ fields: [
+ "payment_collections.*",
+ ],
+})
+
+// orders.payment_collections
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: orders } = useQueryGraphStep({
+ entity: "order",
+ fields: [
+ "payment_collections.*",
+ ],
+})
+
+// orders.payment_collections
+```
+
+### Manage with Link
+
+To manage the payment collections of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_collection_id: "paycol_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_collection_id: "paycol_123",
+ },
+})
+```
+
+***
+
+## Product Module
+
+Medusa defines read-only links between:
+
+- the `OrderLineItem` data model and the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md)'s `Product` data model. This means you can retrieve the details of a line item's product, but you don't manage the links in a pivot table in the database. The product of a line item is determined by the `product_id` property of the `OrderLineItem` data model.
+- the `OrderLineItem` data model and the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md)'s `ProductVariant` data model. This means you can retrieve the details of a line item's variant, but you don't manage the links in a pivot table in the database. The variant of a line item is determined by the `variant_id` property of the `OrderLineItem` data model.
+
+### Retrieve with Query
+
+To retrieve the variant of a line item with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variant.*` in `fields`:
+
+To retrieve the product, pass `product.*` in `fields`.
+
+### query.graph
+
+```ts
+const { data: lineItems } = await query.graph({
+ entity: "order_line_item",
+ fields: [
+ "variant.*",
+ ],
+})
+
+// lineItems.variant
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: lineItems } = useQueryGraphStep({
+ entity: "order_line_item",
+ fields: [
+ "variant.*",
+ ],
+})
+
+// lineItems.variant
+```
+
+***
+
+## Promotion Module
+
+An order is associated with the promotion applied on it. Medusa defines a link between the `Order` and `Promotion` data models.
+
+
+
+### Retrieve with Query
+
+To retrieve the promotion applied on an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `promotion.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: orders } = await query.graph({
+ entity: "order",
+ fields: [
+ "promotion.*",
+ ],
+})
+
+// orders.promotion
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: orders } = useQueryGraphStep({
+ entity: "order",
+ fields: [
+ "promotion.*",
+ ],
+})
+
+// orders.promotion
+```
+
+### Manage with Link
+
+To manage the promotion of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.PROMOTION]: {
+ promotion_id: "promo_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.PROMOTION]: {
+ promotion_id: "promo_123",
+ },
+})
+```
+
+***
+
+## Region Module
+
+Medusa defines a read-only link between the `Order` data model and the [Region Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/region/index.html.md)'s `Region` data model. This means you can retrieve the details of an order's region, but you don't manage the links in a pivot table in the database. The region of an order is determined by the `region_id` property of the `Order` data model.
+
+### Retrieve with Query
+
+To retrieve the region of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `region.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: orders } = await query.graph({
+ entity: "order",
+ fields: [
+ "region.*",
+ ],
+})
+
+// orders.region
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: orders } = useQueryGraphStep({
+ entity: "order",
+ fields: [
+ "region.*",
+ ],
+})
+
+// orders.region
+```
+
+***
+
+## Sales Channel Module
+
+Medusa defines a read-only link between the `Order` data model and the [Sales Channel Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/index.html.md)'s `SalesChannel` data model. This means you can retrieve the details of an order's sales channel, but you don't manage the links in a pivot table in the database. The sales channel of an order is determined by the `sales_channel_id` property of the `Order` data model.
+
+### Retrieve with Query
+
+To retrieve the sales channel of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channel.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: orders } = await query.graph({
+ entity: "order",
+ fields: [
+ "sales_channel.*",
+ ],
+})
+
+// orders.sales_channel
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: orders } = useQueryGraphStep({
+ entity: "order",
+ fields: [
+ "sales_channel.*",
+ ],
+})
+
+// orders.sales_channel
+```
+
+
+# Promotions Adjustments in Orders
+
+In this document, you’ll learn how a promotion is applied to an order’s items and shipping methods using adjustment lines.
+
+## What are Adjustment Lines?
+
+An adjustment line indicates a change to a line item or a shipping method’s amount. It’s used to apply promotions or discounts on an order.
+
+The [OrderLineItemAdjustment data model](https://docs.medusajs.com/references/order/models/OrderLineItemAdjustment/index.html.md) represents changes on a line item, and the [OrderShippingMethodAdjustment data model](https://docs.medusajs.com/references/order/models/OrderShippingMethodAdjustment/index.html.md) represents changes on a shipping method.
+
+
+
+The `amount` property of the adjustment line indicates the amount to be discounted from the original amount.
+
+The ID of the applied promotion is stored in the `promotion_id` property of the adjustment line.
+
+***
+
+## Discountable Option
+
+The `OrderLineItem` data model has an `is_discountable` property that indicates whether promotions can be applied to the line item. It’s enabled by default.
+
+When disabled, a promotion can’t be applied to a line item. In the context of the Promotion Module, the promotion isn’t applied to the line item even if it matches its rules.
+
+***
+
+## Promotion Actions
+
+When using the Order and Promotion modules together, use the [computeActions method of the Promotion Module’s main service](https://docs.medusajs.com/references/promotion/computeActions/index.html.md). It retrieves the actions of line items and shipping methods.
+
+Learn more about actions in the [Promotion Module’s documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/actions/index.html.md).
+
+```ts collapsibleLines="1-10" expandButtonLabel="Show Imports"
+import {
+ ComputeActionAdjustmentLine,
+ ComputeActionItemLine,
+ ComputeActionShippingLine,
+ // ...
+} from "@medusajs/framework/types"
+
+// ...
+
+// retrieve the order
+const order = await orderModuleService.retrieveOrder("ord_123", {
+ relations: [
+ "items.item.adjustments",
+ "shipping_methods.shipping_method.adjustments",
+ ],
+})
+// retrieve the line item adjustments
+const lineItemAdjustments: ComputeActionItemLine[] = []
+order.items.forEach((item) => {
+ const filteredAdjustments = item.adjustments?.filter(
+ (adjustment) => adjustment.code !== undefined
+ ) as unknown as ComputeActionAdjustmentLine[]
+ if (filteredAdjustments.length) {
+ lineItemAdjustments.push({
+ ...item,
+ ...item.detail,
+ adjustments: filteredAdjustments,
+ })
+ }
+})
+
+//retrieve shipping method adjustments
+const shippingMethodAdjustments: ComputeActionShippingLine[] =
+ []
+order.shipping_methods.forEach((shippingMethod) => {
+ const filteredAdjustments =
+ shippingMethod.adjustments?.filter(
+ (adjustment) => adjustment.code !== undefined
+ ) as unknown as ComputeActionAdjustmentLine[]
+ if (filteredAdjustments.length) {
+ shippingMethodAdjustments.push({
+ ...shippingMethod,
+ adjustments: filteredAdjustments,
+ })
+ }
+})
+
+// compute actions
+const actions = await promotionModuleService.computeActions(
+ ["promo_123"],
+ {
+ items: lineItemAdjustments,
+ shipping_methods: shippingMethodAdjustments,
+ // TODO infer from cart or region
+ currency_code: "usd",
+ }
+)
+```
+
+The `computeActions` method accepts the existing adjustments of line items and shipping methods to compute the actions accurately.
+
+Then, use the returned `addItemAdjustment` and `addShippingMethodAdjustment` actions to set the order’s line items and the shipping method’s adjustments.
+
+```ts collapsibleLines="1-9" expandButtonLabel="Show Imports"
+import {
+ AddItemAdjustmentAction,
+ AddShippingMethodAdjustment,
+ // ...
+} from "@medusajs/framework/types"
+
+// ...
+
+await orderModuleService.setOrderLineItemAdjustments(
+ order.id,
+ actions.filter(
+ (action) => action.action === "addItemAdjustment"
+ ) as AddItemAdjustmentAction[]
+)
+
+await orderModuleService.setOrderShippingMethodAdjustments(
+ order.id,
+ actions.filter(
+ (action) =>
+ action.action === "addShippingMethodAdjustment"
+ ) as AddShippingMethodAdjustment[]
+)
+```
+
+
+# Order Versioning
+
+In this document, you’ll learn how an order and its details are versioned.
+
+## What's Versioning?
+
+Versioning means assigning a version number to a record, such as an order and its items. This is useful to view the different versions of the order following changes in its lifetime.
+
+When changes are made on an order, such as an item is added or returned, the order's version changes.
+
+***
+
+## version Property
+
+The `Order` and `OrderSummary` data models have a `version` property that indicates the current version. By default, its value is `1`.
+
+Other order-related data models, such as `OrderItem`, also has a `version` property, but it indicates the version it belongs to.
+
+***
+
+## How the Version Changes
+
+When the order is changed, such as an item is exchanged, this changes the version of the order and its related data:
+
+1. The version of the order and its summary is incremented.
+2. Related order data that have a `version` property, such as the `OrderItem`, are duplicated. The duplicated item has the new version, whereas the original item has the previous version.
+
+When the order is retrieved, only the related data having the same version is retrieved.
+
+
+# Transactions
+
+In this document, you’ll learn about an order’s transactions and its use.
+
+## What is a Transaction?
+
+A transaction represents any order payment process, such as capturing or refunding an amount. It’s represented by the [OrderTransaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md).
+
+The transaction’s main purpose is to ensure a correct balance between paid and outstanding amounts.
+
+Transactions are also associated with returns, claims, and exchanges if additional payment or refund is required.
+
+***
+
+## Checking Outstanding Amount
+
+The order’s total amounts are stored in the `OrderSummary`'s `totals` property, which is a JSON object holding the total details of the order.
+
+```json
+{
+ "totals": {
+ "total": 30,
+ "subtotal": 30,
+ // ...
+ }
+}
+```
+
+To check the outstanding amount of the order, its transaction amounts are summed. Then, the following conditions are checked:
+
+|Condition|Result|
+|---|---|---|
+|summary’s total - transaction amounts total = 0|There’s no outstanding amount.|
+|summary’s total - transaction amounts total > 0|The customer owes additional payment to the merchant.|
+|summary’s total - transaction amounts total \< 0|The merchant owes the customer a refund.|
+
+***
+
+## Transaction Reference
+
+The Order Module doesn’t provide payment processing functionalities, so it doesn’t store payments that can be processed. Payment functionalities are provided by the [Payment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/index.html.md).
+
+The `OrderTransaction` data model has two properties that determine which data model and record holds the actual payment’s details:
+
+- `reference`: indicates the table’s name in the database. For example, `payment` from the Payment Module.
+- `reference_id`: indicates the ID of the record in the table. For example, `pay_123`.
+
+
+# Order Return
+
+In this document, you’ll learn about order returns.
+
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/orders/returns/index.html.md) to learn how to manage an order's returns using the dashboard.
+
+## What is a Return?
+
+A return is the return of items delivered from the customer back to the merchant. It is represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md).
+
+A return is requested either by the customer from the storefront, or the merchant from the admin. Medusa supports an automated Return Merchandise Authorization (RMA) flow.
+
+
+
+Once the merchant receives the returned items, they mark the return as received.
+
+***
+
+## Returned Items
+
+The items to be returned are represented by the [ReturnItem data model](references/order/models/ReturnItem).
+
+The `ReturnItem` model has two properties storing the item's quantity:
+
+1. `received_quantity`: The quantity of the item that's received and can be added to the item's inventory quantity.
+2. `damaged_quantity`: The quantity of the item that's damaged, meaning it can't be sold again or added to the item's inventory quantity.
+
+***
+
+## Return Shipping Methods
+
+A return has shipping methods used to return the items to the merchant. The shipping methods are represented by the [OrderShippingMethod data model](references/order/models/OrderShippingMethod).
+
+In the Medusa application, the shipping method for a return is created only from a shipping option, provided by the Fulfillment Module, that has the rule `is_return` enabled.
+
+***
+
+## Refund Payment
+
+The `refund_amount` property of the `Return` data model holds the amount a merchant must refund the customer.
+
+The [OrderTransaction data model](references/order/models/OrderTransaction) represents the refunds made for the return.
+
+***
+
+## Returns in Exchanges and Claims
+
+When a merchant creates an exchange or a claim, it includes returning items from the customer.
+
+The `Return` data model also represents the return of these items. In this case, the return is associated with the exchange or claim it was created for.
+
+***
+
+## How Returns Impact an Order’s Version
+
+The order’s version is incremented when:
+
+1. A return is requested.
+2. A return is marked as received.
+
+
+# Tax Lines in Order Module
+
+In this document, you’ll learn about tax lines in an order.
+
+## What are Tax Lines?
+
+A tax line indicates the tax rate of a line item or a shipping method.
+
+The [OrderLineItemTaxLine data model](https://docs.medusajs.com/references/order/models/OrderLineItemTaxLine/index.html.md) represents a line item’s tax line, and the [OrderShippingMethodTaxLine data model](https://docs.medusajs.com/references/order/models/OrderShippingMethodTaxLine/index.html.md) represents a shipping method’s tax line.
+
+
+
+***
+
+## Tax Inclusivity
+
+By default, the tax amount is calculated by taking the tax rate from the line item or shipping method’s amount and then adding it to the item/method’s subtotal.
+
+However, line items and shipping methods have an `is_tax_inclusive` property that, when enabled, indicates that the item or method’s price already includes taxes.
+
+So, instead of calculating the tax rate and adding it to the item/method’s subtotal, it’s calculated as part of the subtotal.
+
+The following diagram is a simplified showcase of how a subtotal is calculated from the tax perspective.
+
+
+
+For example, if a line item's amount is `5000`, the tax rate is `10`, and `is_tax_inclusive` is enabled, the tax amount is 10% of `5000`, which is `500`. The item's unit price becomes `4500`.
+
+
+# Links between Currency Module and Other Modules
+
+This document showcases the module links defined between the Currency Module and other commerce modules.
+
+## Summary
+
+The Currency Module has the following links to other modules:
+
+Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
+
+|First Data Model|Second Data Model|Type|Description|
+|---|---|---|---|
+| in ||Read-only||
+
+***
+
+## Store Module
+
+The Store Module has a `Currency` data model that stores the supported currencies of a store. However, these currencies don't hold all the details of a currency, such as its name or symbol.
+
+Instead, Medusa defines a read-only link between the [Store Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/store/index.html.md)'s `Currency` data model and the Currency Module's `Currency` data model. Because the link is read-only from the `Store`'s side, you can only retrieve the details of a store's supported currencies, and not the other way around.
+
+### Retrieve with Query
+
+To retrieve the details of a store's currencies with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `supported_currencies.currency.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: stores } = await query.graph({
+ entity: "store",
+ fields: [
+ "supported_currencies.currency.*",
+ ],
+})
+
+// stores.supported_currencies
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: stores } = useQueryGraphStep({
+ entity: "store",
+ fields: [
+ "supported_currencies.currency.*",
+ ],
+})
+
+// stores.supported_currencies
+```
+
+
+# Account Holders and Saved Payment Methods
+
+In this documentation, you'll learn about account holders, and how they're used to save payment methods in third-party payment providers.
+
+Account holders are available starting from Medusa `v2.5.0`.
+
+## What's an Account Holder?
+
+An account holder represents a customer that can have saved payment methods in a third-party service. It's represented by the `AccountHolder` data model.
+
+It holds fields retrieved from the third-party provider, such as:
+
+- `external_id`: The ID of the equivalent customer or account holder in the third-party provider.
+- `data`: Data returned by the payment provider when the account holder is created.
+
+A payment provider that supports saving payment methods for customers would create the equivalent of an account holder in the third-party provider. Then, whenever a payment method is saved, it would be saved under the account holder in the third-party provider.
+
+***
+
+## Save Payment Methods
+
+If a payment provider supports saving payment methods for a customer, they must implement the following methods:
+
+- `createAccountHolder`: Creates an account holder in the payment provider. The Payment Module uses this method before creating the account holder in Medusa, and uses the returned data to set fields like `external_id` and `data` in the created `AccountHolder` record.
+- `deleteAccountHolder`: Deletes an account holder in the payment provider. The Payment Module uses this method when an account holder is deleted in Medusa.
+- `savePaymentMethod`: Saves a payment method for an account holder in the payment provider.
+- `listPaymentMethods`: Lists saved payment methods in the third-party service for an account holder. This is useful when displaying the customer's saved payment methods in the storefront.
+
+Learn more about implementing these methods in the [Create Payment Provider guide](https://docs.medusajs.com/references/payment/provider/index.html.md).
+
+***
+
+## Account Holder in Medusa Payment Flows
+
+In the Medusa application, when a payment session is created for a registered customer, the Medusa application uses the Payment Module to create an account holder for the customer.
+
+Consequently, the Payment Module uses the payment provider to create an account holder in the third-party service, then creates the account holder in Medusa.
+
+This flow is only supported if the chosen payment provider has implemented the necessary [save payment methods](#save-payment-methods).
+
+
+# Links between Payment Module and Other Modules
+
+This document showcases the module links defined between the Payment Module and other commerce modules.
+
+## Summary
+
+The Payment Module has the following links to other modules:
+
+|First Data Model|Second Data Model|Type|Description|
+|---|---|---|---|
+| in ||Stored||
+| in ||Stored||
+| in ||Stored||
+| in ||Stored||
+| in ||Stored||
+| in ||Stored||
+
+***
+
+## Cart Module
+
+The Cart Module provides cart-related features, but not payment processing.
+
+Medusa defines a link between the `Cart` and `PaymentCollection` data models. A cart has a payment collection which holds all the authorized payment sessions and payments made related to the cart.
+
+Learn more about this relation in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-collection#usage-with-the-cart-module/index.html.md).
+
+### Retrieve with Query
+
+To retrieve the cart associated with the payment collection with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `cart.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: paymentCollections } = await query.graph({
+ entity: "payment_collection",
+ fields: [
+ "cart.*",
+ ],
+})
+
+// paymentCollections.cart
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: paymentCollections } = useQueryGraphStep({
+ entity: "payment_collection",
+ fields: [
+ "cart.*",
+ ],
+})
+
+// paymentCollections.cart
+```
+
+### Manage with Link
+
+To manage the payment collection of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.CART]: {
+ cart_id: "cart_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_collection_id: "paycol_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.CART]: {
+ cart_id: "cart_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_collection_id: "paycol_123",
+ },
+})
+```
+
+***
+
+## Customer Module
+
+Medusa defines a link between the `Customer` and `AccountHolder` data models, allowing payment providers to save payment methods for a customer, if the payment provider supports it.
+
+This link is available starting from Medusa `v2.5.0`.
+
+### Retrieve with Query
+
+To retrieve the customer associated with an account holder with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: accountHolders } = await query.graph({
+ entity: "account_holder",
+ fields: [
+ "customer.*",
+ ],
+})
+
+// accountHolders.customer
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: accountHolders } = useQueryGraphStep({
+ entity: "account_holder",
+ fields: [
+ "customer.*",
+ ],
+})
+
+// accountHolders.customer
+```
+
+### Manage with Link
+
+To manage the account holders of a customer, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.CUSTOMER]: {
+ customer_id: "cus_123",
+ },
+ [Modules.PAYMENT]: {
+ account_holder_id: "acchld_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.CUSTOMER]: {
+ customer_id: "cus_123",
+ },
+ [Modules.PAYMENT]: {
+ account_holder_id: "acchld_123",
+ },
+})
+```
+
+***
+
+## Order Module
+
+An order's payment details are stored in a payment collection. This also applies for claims and exchanges.
+
+So, Medusa defines links between the `PaymentCollection` data model and the `Order`, `OrderClaim`, and `OrderExchange` data models.
+
+
+
+### Retrieve with Query
+
+To retrieve the order of a payment collection with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: paymentCollections } = await query.graph({
+ entity: "payment_collection",
+ fields: [
+ "order.*",
+ ],
+})
+
+// paymentCollections.order
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: paymentCollections } = useQueryGraphStep({
+ entity: "payment_collection",
+ fields: [
+ "order.*",
+ ],
+})
+
+// paymentCollections.order
+```
+
+### Manage with Link
+
+To manage the payment collections of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_collection_id: "paycol_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_collection_id: "paycol_123",
+ },
+})
+```
+
+***
+
+## Region Module
+
+You can specify for each region which payment providers are available. The Medusa application defines a link between the `PaymentProvider` and the `Region` data models.
+
+
+
+This increases the flexibility of your store. For example, you only show during checkout the payment providers associated with the cart's region.
+
+### Retrieve with Query
+
+To retrieve the regions of a payment provider with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `regions.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: paymentProviders } = await query.graph({
+ entity: "payment_provider",
+ fields: [
+ "regions.*",
+ ],
+})
+
+// paymentProviders.regions
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: paymentProviders } = useQueryGraphStep({
+ entity: "payment_provider",
+ fields: [
+ "regions.*",
+ ],
+})
+
+// paymentProviders.regions
+```
+
+### Manage with Link
+
+To manage the payment providers in a region, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.REGION]: {
+ region_id: "reg_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_provider_id: "pp_stripe_stripe",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.REGION]: {
+ region_id: "reg_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_provider_id: "pp_stripe_stripe",
+ },
+})
+```
+
+
+# Payment Module Options
+
+In this document, you'll learn about the options of the Payment Module.
+
+## All Module Options
+
+|Option|Description|Required|Default|
+|---|---|---|---|---|---|---|
+|\`webhook\_delay\`|A number indicating the delay in milliseconds before processing a webhook event.|No|\`5000\`|
+|\`webhook\_retries\`|The number of times to retry the webhook event processing in case of an error.|No|\`3\`|
+|\`providers\`|An array of payment providers to install and register. Learn more |No|-|
+
+***
+
+## providers Option
+
+The `providers` option is an array of payment module providers.
+
+When the Medusa application starts, these providers are registered and can be used to process payments.
+
+For example:
+
+```ts title="medusa-config.ts"
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+module.exports = defineConfig({
+ // ...
+ modules: [
+ {
+ resolve: "@medusajs/medusa/payment",
+ options: {
+ providers: [
+ {
+ resolve: "@medusajs/medusa/payment-stripe",
+ id: "stripe",
+ options: {
+ // ...
+ },
+ },
+ ],
+ },
+ },
+ ],
+})
+```
+
+The `providers` option is an array of objects that accept the following properties:
+
+- `resolve`: A string indicating the package name of the module provider or the path to it relative to the `src` directory.
+- `id`: A string indicating the provider's unique name or ID.
+- `options`: An optional object of the module provider's options.
+
+
+# Payment Collection
+
+In this document, you’ll learn what a payment collection is and how the Medusa application uses it with the Cart Module.
+
+## What's a Payment Collection?
+
+A payment collection stores payment details related to a resource, such as a cart or an order. It’s represented by the [PaymentCollection data model](https://docs.medusajs.com/references/payment/models/PaymentCollection/index.html.md).
+
+Every purchase or request for payment starts with a payment collection. The collection holds details necessary to complete the payment, including:
+
+- The [payment sessions](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-session/index.html.md) that represents the payment amount to authorize.
+- The [payments](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment/index.html.md) that are created when a payment session is authorized. They can be captured and refunded.
+- The [payment providers](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/index.html.md) that handle the processing of each payment session, including the authorization, capture, and refund.
+
+***
+
+## Multiple Payments
+
+The payment collection supports multiple payment sessions and payments.
+
+You can use this to accept payments in increments or split payments across payment providers.
+
+
+
+***
+
+## Usage with the Cart Module
+
+The Cart Module provides cart management features. However, it doesn’t provide any features related to accepting payment.
+
+During checkout, the Medusa application links a cart to a payment collection, which will be used for further payment processing.
+
+It also implements the payment flow during checkout as explained in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-flow/index.html.md).
+
+
+
+
+# Payment
+
+In this document, you’ll learn what a payment is and how it's created, captured, and refunded.
+
+## What's a Payment?
+
+When a payment session is authorized, a payment, represented by the [Payment data model](https://docs.medusajs.com/references/payment/models/Payment/index.html.md), is created. This payment can later be captured or refunded.
+
+A payment carries many of the data and relations of a payment session:
+
+- It belongs to the same payment collection.
+- It’s associated with the same payment provider, which handles further payment processing.
+- It stores the payment session’s `data` property in its `data` property, as it’s still useful for the payment provider’s processing.
+
+***
+
+## Capture Payments
+
+When a payment is captured, a capture, represented by the [Capture data model](https://docs.medusajs.com/references/payment/models/Capture/index.html.md), is created. It holds details related to the capture, such as the amount, the capture date, and more.
+
+The payment can also be captured incrementally, each time a capture record is created for that amount.
+
+
+
+***
+
+## Refund Payments
+
+When a payment is refunded, a refund, represented by the [Refund data model](https://docs.medusajs.com/references/payment/models/Refund/index.html.md), is created. It holds details related to the refund, such as the amount, refund date, and more.
+
+A payment can be refunded multiple times, and each time a refund record is created.
+
+
+
+
+# Accept Payment Flow
+
+In this document, you’ll learn how to implement an accept-payment flow using workflows or the Payment Module's main service.
+
+It's highly recommended to use Medusa's workflows to implement this flow. Use the Payment Module's main service for more complex cases.
+
+For a guide on how to implement this flow in the storefront, check out [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/checkout/payment/index.html.md).
+
+## Flow Overview
+
+
+
+***
+
+## 1. Create a Payment Collection
+
+A payment collection holds all details related to a resource’s payment operations. So, you start off by creating a payment collection.
+
+For example:
+
+### Using Workflow
+
+```ts
+import { createPaymentCollectionForCartWorkflow } from "@medusajs/medusa/core-flows"
+
+// ...
+
+await createPaymentCollectionForCartWorkflow(req.scope)
+ .run({
+ input: {
+ cart_id: "cart_123",
+ },
+ })
+```
+
+### Using Service
+
+```ts
+const paymentCollection =
+ await paymentModuleService.createPaymentCollections({
+ currency_code: "usd",
+ amount: 5000,
+ })
+```
+
+***
+
+## 2. Create Payment Sessions
+
+The payment collection has one or more payment sessions, each being a payment amount to be authorized by a payment provider.
+
+So, after creating the payment collection, create at least one payment session for a provider.
+
+For example:
+
+### Using Workflow
+
+```ts
+import { createPaymentSessionsWorkflow } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { result: paymentSesion } = await createPaymentSessionsWorkflow(req.scope)
+ .run({
+ input: {
+ payment_collection_id: "paycol_123",
+ provider_id: "stripe",
+ },
+ })
+```
+
+### Using Service
+
+```ts
+const paymentSession =
+ await paymentModuleService.createPaymentSession(
+ paymentCollection.id,
+ {
+ provider_id: "stripe",
+ currency_code: "usd",
+ amount: 5000,
+ data: {
+ // any necessary data for the
+ // payment provider
+ },
+ }
+ )
+```
+
+***
+
+## 3. Authorize Payment Session
+
+Once the customer chooses a payment session, start the authorization process. This may involve some action performed by the third-party payment provider, such as entering a 3DS code.
+
+For example:
+
+### Using Step
+
+```ts
+import { authorizePaymentSessionStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+authorizePaymentSessionStep({
+ id: "payses_123",
+ context: {},
+})
+```
+
+### Using Service
+
+```ts
+const payment = authorizePaymentSessionStep({
+ id: "payses_123",
+ context: {},
+})
+```
+
+When the payment authorization is successful, a payment is created and returned.
+
+### Handling Additional Action
+
+If you used the `authorizePaymentSessionStep`, you don't need to implement this logic as it's implemented in the step.
+
+If the payment authorization isn’t successful, whether because it requires additional action or for another reason, the method updates the payment session with the new status and throws an error.
+
+In that case, you can catch that error and, if the session's `status` property is `requires_more`, handle the additional action, then retry the authorization.
+
+For example:
+
+```ts
+try {
+ const payment =
+ await paymentModuleService.authorizePaymentSession(
+ paymentSession.id,
+ {}
+ )
+} catch (e) {
+ // retrieve the payment session again
+ const updatedPaymentSession = (
+ await paymentModuleService.listPaymentSessions({
+ id: [paymentSession.id],
+ })
+ )[0]
+
+ if (updatedPaymentSession.status === "requires_more") {
+ // TODO perform required action
+ // TODO authorize payment again.
+ }
+}
+```
+
+***
+
+## 4. Payment Flow Complete
+
+The payment flow is complete once the payment session is authorized and the payment is created.
+
+You can then:
+
+- Capture the payment either using the [capturePaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/capturePaymentWorkflow/index.html.md) or [capturePayment method](https://docs.medusajs.com/references/payment/capturePayment/index.html.md).
+- Refund captured amounts using the [refundPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentWorkflow/index.html.md) or [refundPayment method](https://docs.medusajs.com/references/payment/refundPayment/index.html.md).
+
+Some payment providers allow capturing the payment automatically once it’s authorized. In that case, you don’t need to do it manually.
+
+
+# Payment Session
+
+In this document, you’ll learn what a payment session is.
+
+## What's a Payment Session?
+
+A payment session, represented by the [PaymentSession data model](https://docs.medusajs.com/references/payment/models/PaymentSession/index.html.md), is a payment amount to be authorized. It’s associated with a payment provider that handles authorizing it.
+
+A payment collection can have multiple payment sessions. Using this feature, you can implement payment in installments or payments using multiple providers.
+
+
+
+***
+
+## data Property
+
+Payment providers may need additional data to process the payment later. The `PaymentSession` data model has a `data` property used to store that data.
+
+For example, the customer's ID in Stripe is stored in the `data` property.
+
+***
+
+## Payment Session Status
+
+The `status` property of a payment session indicates its current status. Its value can be:
+
+- `pending`: The payment session is awaiting authorization.
+- `requires_more`: The payment session requires an action before it’s authorized. For example, to enter a 3DS code.
+- `authorized`: The payment session is authorized.
+- `error`: An error occurred while authorizing the payment.
+- `canceled`: The authorization of the payment session has been canceled.
+
+
+# Webhook Events
+
+In this document, you’ll learn how the Payment Module supports listening to webhook events.
+
+## What's a Webhook Event?
+
+A webhook event is sent from a third-party payment provider to your application. It indicates a change in a payment’s status.
+
+This is useful in many cases such as when a payment is being processed asynchronously or when a request is interrupted and the payment provider is sending details on the process later.
+
+***
+
+## getWebhookActionAndData Method
+
+The Payment Module’s main service has a [getWebhookActionAndData method](https://docs.medusajs.com/references/payment/getWebhookActionAndData/index.html.md) used to handle incoming webhook events from third-party payment services. The method delegates the handling to the associated payment provider, which returns the event's details.
+
+Medusa implements a webhook listener route at the `/hooks/payment/[identifier]_[provider]` API route, where:
+
+- `[identifier]` is the `identifier` static property defined in the payment provider. For example, `stripe`.
+- `[provider]` is the ID of the provider. For example, `stripe`.
+
+For example, when integrating basic Stripe payments with the [Stripe Module Provider](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/stripe/index.html.md), the webhook listener route is `/hooks/payment/stripe_stripe`. If you're integrating Stripe's Bancontact payments, the webhook listener route is `/hooks/payment/stripe-bancontact_stripe`.
+
+Use that webhook listener in your third-party payment provider's configurations.
+
+
+
+If the event's details indicate that the payment should be authorized, then the [authorizePaymentSession method of the main service](https://docs.medusajs.com/references/payment/authorizePaymentSession/index.html.md) is executed on the specified payment session.
+
+If the event's details indicate that the payment should be captured, then the [capturePayment method of the main service](https://docs.medusajs.com/references/payment/capturePayment/index.html.md) is executed on the payment of the specified payment session.
+
+### Actions After Webhook Payment Processing
+
+After the payment webhook actions are processed and the payment is authorized or captured, the Medusa application completes the cart associated with the payment's collection if it's not completed yet.
+
+
+# Payment Module Provider
+
+In this document, you’ll learn what a payment module provider is.
+
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/regions/index.html.md) to learn how to manage the payment providers available in a region using the dashboard.
+
+## What's a Payment Module Provider?
+
+A payment module provider registers a payment provider that handles payment processing in the Medusa application. It integrates third-party payment providers, such as Stripe.
+
+To authorize a payment amount with a payment provider, a payment session is created and associated with that payment provider. The payment provider is then used to handle the authorization.
+
+After the payment session is authorized, the payment provider is associated with the resulting payment and handles its payment processing, such as to capture or refund payment.
+
+### List of Payment Module Providers
+
+- [Stripe](https://docs.medusajs.com/commerce-modules/payment/payment-provider/stripe/index.html.md)
+
+***
+
+## System Payment Provider
+
+The Payment Module provides a `system` payment provider that acts as a placeholder payment provider.
+
+It doesn’t handle payment processing and delegates that to the merchant. It acts similarly to a cash-on-delivery (COD) payment method.
+
+***
+
+## How are Payment Providers Created?
+
+A payment provider is a module whose main service extends the `AbstractPaymentProvider` imported from `@medusajs/framework/utils`.
+
+Refer to [this guide](https://docs.medusajs.com/references/payment/provider/index.html.md) on how to create a payment provider for the Payment Module.
+
+***
+
+## Configure Payment Providers
+
+The Payment Module accepts a `providers` option that allows you to register providers in your application.
+
+Learn more about this option in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/module-options#providers/index.html.md).
+
+***
+
+## PaymentProvider Data Model
+
+When the Medusa application starts and registers the payment providers, it also creates a record of the `PaymentProvider` data model if none exists.
+
+This data model is used to reference a payment provider and determine whether it’s installed in the application.
+
+
+# Cart Concepts
+
+In this document, you’ll get an overview of the main concepts of a cart.
+
+## Shipping and Billing Addresses
+
+A cart has a shipping and billing address. Both of these addresses are represented by the [Address data model](https://docs.medusajs.com/references/cart/models/Address/index.html.md).
+
+
+
+***
+
+## Line Items
+
+A line item, represented by the [LineItem](https://docs.medusajs.com/references/cart/models/LineItem/index.html.md) data model, is a quantity of a product variant added to the cart. A cart has multiple line items.
+
+A line item stores some of the product variant’s properties, such as the `product_title` and `product_description`. It also stores data related to the item’s quantity and price.
+
+In the Medusa application, a product variant is implemented in the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md).
+
+***
+
+## Shipping Methods
+
+A shipping method, represented by the [ShippingMethod data model](https://docs.medusajs.com/references/cart/models/ShippingMethod/index.html.md), is used to fulfill the items in the cart after the order is placed. A cart can have more than one shipping method.
+
+In the Medusa application, the shipping method is created from a shipping option, available through the [Fulfillment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/index.html.md). Its ID is stored in the `shipping_option_id` property of the method.
+
+### data Property
+
+After an order is placed, you can use a third-party fulfillment provider to fulfill its shipments.
+
+If the fulfillment provider requires additional custom data to be passed along from the checkout process, set this data in the `ShippingMethod`'s `data` property.
+
+The `data` property is an object used to store custom data relevant later for fulfillment.
+
+
+# Links between Cart Module and Other Modules
+
+This document showcases the module links defined between the Cart Module and other commerce modules.
+
+## Summary
+
+The Cart Module has the following links to other modules:
+
+Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
+
+|First Data Model|Second Data Model|Type|Description|
+|---|---|---|---|
+|| in |Read-only||
+| in ||Stored||
+|| in |Stored||
+|| in |Read-only||
+|| in |Read-only||
+|| in |Stored||
+|| in |Read-only||
+|| in |Read-only||
+
+***
+
+## Customer Module
+
+Medusa defines a read-only link between the `Cart` data model and the [Customer Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/customer/index.html.md)'s `Customer` data model. This means you can retrieve the details of a cart's customer, but you don't manage the links in a pivot table in the database. The customer of a cart is determined by the `customer_id` property of the `Cart` data model.
+
+### Retrieve with Query
+
+To retrieve the customer of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: carts } = await query.graph({
+ entity: "cart",
+ fields: [
+ "customer.*",
+ ],
+})
+
+// carts.order
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: carts } = useQueryGraphStep({
+ entity: "cart",
+ fields: [
+ "customer.*",
+ ],
+})
+
+// carts.order
+```
+
+***
+
+## Order Module
+
+The [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md) provides order-management features.
+
+Medusa defines a link between the `Cart` and `Order` data models. The cart is linked to the order created once the cart is completed.
+
+
+
+### Retrieve with Query
+
+To retrieve the order of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: carts } = await query.graph({
+ entity: "cart",
+ fields: [
+ "order.*",
+ ],
+})
+
+// carts.order
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: carts } = useQueryGraphStep({
+ entity: "cart",
+ fields: [
+ "order.*",
+ ],
+})
+
+// carts.order
+```
+
+### Manage with Link
+
+To manage the order of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.CART]: {
+ cart_id: "cart_123",
+ },
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.CART]: {
+ cart_id: "cart_123",
+ },
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+})
+```
+
+***
+
+## Payment Module
+
+The [Payment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/index.html.md) handles payment processing and management.
+
+Medusa defines a link between the `Cart` and `PaymentCollection` data models. A cart has a payment collection which holds all the authorized payment sessions and payments made related to the cart.
+
+
+
+### Retrieve with Query
+
+To retrieve the payment collection of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `payment_collection.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: carts } = await query.graph({
+ entity: "cart",
+ fields: [
+ "payment_collection.*",
+ ],
+})
+
+// carts.payment_collection
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: carts } = useQueryGraphStep({
+ entity: "cart",
+ fields: [
+ "payment_collection.*",
+ ],
+})
+
+// carts.payment_collection
+```
+
+### Manage with Link
+
+To manage the payment collection of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.CART]: {
+ cart_id: "cart_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_collection_id: "paycol_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.CART]: {
+ cart_id: "cart_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_collection_id: "paycol_123",
+ },
+})
+```
+
+***
+
+## Product Module
+
+Medusa defines read-only links between:
+
+- the `LineItem` data model and the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md)'s `Product` data model. This means you can retrieve the details of a line item's product, but you don't manage the links in a pivot table in the database. The product of a line item is determined by the `product_id` property of the `LineItem` data model.
+- the `LineItem` data model and the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md)'s `ProductVariant` data model. This means you can retrieve the details of a line item's variant, but you don't manage the links in a pivot table in the database. The variant of a line item is determined by the `variant_id` property of the `LineItem` data model.
+
+### Retrieve with Query
+
+To retrieve the variant of a line item with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variant.*` in `fields`:
+
+To retrieve the product, pass `product.*` in `fields`.
+
+### query.graph
+
+```ts
+const { data: lineItems } = await query.graph({
+ entity: "line_item",
+ fields: [
+ "variant.*",
+ ],
+})
+
+// lineItems.variant
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: lineItems } = useQueryGraphStep({
+ entity: "line_item",
+ fields: [
+ "variant.*",
+ ],
+})
+
+// lineItems.variant
+```
+
+***
+
+## Promotion Module
+
+The [Promotion Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/index.html.md) provides discount features.
+
+Medusa defines a link between the `Cart` and `Promotion` data models. This indicates the promotions applied on a cart.
+
+
+
+Medusa also defines a read-only link between the `LineItemAdjustment` and `Promotion` data models. This means you can retrieve the details of the promotion applied on a line item, but you don't manage the links in a pivot table in the database. The promotion of a line item is determined by the `promotion_id` property of the `LineItemAdjustment` data model.
+
+### Retrieve with Query
+
+To retrieve the promotions of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `promotions.*` in `fields`:
+
+To retrieve the promotion of a line item adjustment, pass `promotion.*` in `fields`.
+
+### query.graph
+
+```ts
+const { data: carts } = await query.graph({
+ entity: "cart",
+ fields: [
+ "promotions.*",
+ ],
+})
+
+// carts.promotions
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: carts } = useQueryGraphStep({
+ entity: "cart",
+ fields: [
+ "promotions.*",
+ ],
+})
+
+// carts.promotions
+```
+
+### Manage with Link
+
+To manage the promotions of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.CART]: {
+ cart_id: "cart_123",
+ },
+ [Modules.PROMOTION]: {
+ promotion_id: "promo_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.CART]: {
+ cart_id: "cart_123",
+ },
+ [Modules.PROMOTION]: {
+ promotion_id: "promo_123",
+ },
+})
+```
+
+***
+
+## Region Module
+
+Medusa defines a read-only link between the `Cart` data model and the [Region Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/region/index.html.md)'s `Region` data model. This means you can retrieve the details of a cart's region, but you don't manage the links in a pivot table in the database. The region of a cart is determined by the `region_id` property of the `Cart` data model.
+
+### Retrieve with Query
+
+To retrieve the region of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `region.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: carts } = await query.graph({
+ entity: "cart",
+ fields: [
+ "region.*",
+ ],
+})
+
+// carts.region
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: carts } = useQueryGraphStep({
+ entity: "cart",
+ fields: [
+ "region.*",
+ ],
+})
+
+// carts.region
+```
+
+***
+
+## Sales Channel Module
+
+Medusa defines a read-only link between the `Cart` data model and the [Sales Channel Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/index.html.md)'s `SalesChannel` data model. This means you can retrieve the details of a cart's sales channel, but you don't manage the links in a pivot table in the database. The sales channel of a cart is determined by the `sales_channel_id` property of the `Cart` data model.
+
+### Retrieve with Query
+
+To retrieve the sales channel of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channel.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: carts } = await query.graph({
+ entity: "cart",
+ fields: [
+ "sales_channel.*",
+ ],
+})
+
+// carts.sales_channel
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: carts } = useQueryGraphStep({
+ entity: "cart",
+ fields: [
+ "sales_channel.*",
+ ],
+})
+
+// carts.sales_channel
+```
+
+
+# Tax Lines in Cart Module
+
+In this document, you’ll learn about tax lines in a cart and how to retrieve tax lines with the Tax Module.
+
+## What are Tax Lines?
+
+A tax line indicates the tax rate of a line item or a shipping method. The [LineItemTaxLine data model](https://docs.medusajs.com/references/cart/models/LineItemTaxLine/index.html.md) represents a line item’s tax line, and the [ShippingMethodTaxLine data model](https://docs.medusajs.com/references/cart/models/ShippingMethodTaxLine/index.html.md) represents a shipping method’s tax line.
+
+
+
+***
+
+## Tax Inclusivity
+
+By default, the tax amount is calculated by taking the tax rate from the line item or shipping method’s amount, and then adding them to the item/method’s subtotal.
+
+However, line items and shipping methods have an `is_tax_inclusive` property that, when enabled, indicates that the item or method’s price already includes taxes.
+
+So, instead of calculating the tax rate and adding it to the item/method’s subtotal, it’s calculated as part of the subtotal.
+
+The following diagram is a simplified showcase of how a subtotal is calculated from the taxes perspective.
+
+
+
+For example, if a line item's amount is `5000`, the tax rate is `10`, and tax inclusivity is enabled, the tax amount is 10% of `5000`, which is `500`, making the unit price of the line item `4500`.
+
+***
+
+## Retrieve Tax Lines
+
+When using the Cart and Tax modules together, you can use the `getTaxLines` method of the Tax Module’s main service. It retrieves the tax lines for a cart’s line items and shipping methods.
+
+```ts
+// retrieve the cart
+const cart = await cartModuleService.retrieveCart("cart_123", {
+ relations: [
+ "items.tax_lines",
+ "shipping_methods.tax_lines",
+ "shipping_address",
+ ],
+})
+
+// retrieve the tax lines
+const taxLines = await taxModuleService.getTaxLines(
+ [
+ ...(cart.items as TaxableItemDTO[]),
+ ...(cart.shipping_methods as TaxableShippingDTO[]),
+ ],
+ {
+ address: {
+ ...cart.shipping_address,
+ country_code:
+ cart.shipping_address.country_code || "us",
+ },
+ }
+)
+```
+
+Then, use the returned tax lines to set the line items and shipping methods’ tax lines:
+
+```ts
+// set line item tax lines
+await cartModuleService.setLineItemTaxLines(
+ cart.id,
+ taxLines.filter((line) => "line_item_id" in line)
+)
+
+// set shipping method tax lines
+await cartModuleService.setLineItemTaxLines(
+ cart.id,
+ taxLines.filter((line) => "shipping_line_id" in line)
+)
+```
+
+
+# Promotions Adjustments in Carts
+
+In this document, you’ll learn how a promotion is applied to a cart’s line items and shipping methods using adjustment lines.
+
+## What are Adjustment Lines?
+
+An adjustment line indicates a change to an item or a shipping method’s amount. It’s used to apply promotions or discounts on a cart.
+
+The [LineItemAdjustment](https://docs.medusajs.com/references/cart/models/LineItemAdjustment/index.html.md) data model represents changes on a line item, and the [ShippingMethodAdjustment](https://docs.medusajs.com/references/cart/models/ShippingMethodAdjustment/index.html.md) data model represents changes on a shipping method.
+
+
+
+The `amount` property of the adjustment line indicates the amount to be discounted from the original amount. Also, the ID of the applied promotion is stored in the `promotion_id` property of the adjustment line.
+
+***
+
+## Discountable Option
+
+The [LineItem](https://docs.medusajs.com/references/cart/models/LineItem/index.html.md) data model has an `is_discountable` property that indicates whether promotions can be applied to the line item. It’s enabled by default.
+
+When disabled, a promotion can’t be applied to a line item. In the context of the Promotion Module, the promotion isn’t applied to the line item even if it matches its rules.
+
+***
+
+## Promotion Actions
+
+When using the Cart and Promotion modules together, such as in the Medusa application, use the [computeActions method of the Promotion Module’s main service](https://docs.medusajs.com/references/promotion/computeActions/index.html.md). It retrieves the actions of line items and shipping methods.
+
+Learn more about actions in the [Promotion Module’s documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/actions/index.html.md).
+
+For example:
+
+```ts collapsibleLines="1-8" expandButtonLabel="Show Imports"
+import {
+ ComputeActionAdjustmentLine,
+ ComputeActionItemLine,
+ ComputeActionShippingLine,
+ // ...
+} from "@medusajs/framework/types"
+
+// retrieve the cart
+const cart = await cartModuleService.retrieveCart("cart_123", {
+ relations: [
+ "items.adjustments",
+ "shipping_methods.adjustments",
+ ],
+})
+
+// retrieve line item adjustments
+const lineItemAdjustments: ComputeActionItemLine[] = []
+cart.items.forEach((item) => {
+ const filteredAdjustments = item.adjustments?.filter(
+ (adjustment) => adjustment.code !== undefined
+ ) as unknown as ComputeActionAdjustmentLine[]
+ if (filteredAdjustments.length) {
+ lineItemAdjustments.push({
+ ...item,
+ adjustments: filteredAdjustments,
+ })
+ }
+})
+
+// retrieve shipping method adjustments
+const shippingMethodAdjustments: ComputeActionShippingLine[] =
+ []
+cart.shipping_methods.forEach((shippingMethod) => {
+ const filteredAdjustments =
+ shippingMethod.adjustments?.filter(
+ (adjustment) => adjustment.code !== undefined
+ ) as unknown as ComputeActionAdjustmentLine[]
+ if (filteredAdjustments.length) {
+ shippingMethodAdjustments.push({
+ ...shippingMethod,
+ adjustments: filteredAdjustments,
+ })
+ }
+})
+
+// compute actions
+const actions = await promotionModuleService.computeActions(
+ ["promo_123"],
+ {
+ items: lineItemAdjustments,
+ shipping_methods: shippingMethodAdjustments,
+ }
+)
+```
+
+The `computeActions` method accepts the existing adjustments of line items and shipping methods to compute the actions accurately.
+
+Then, use the returned `addItemAdjustment` and `addShippingMethodAdjustment` actions to set the cart’s line item and the shipping method’s adjustments.
+
+```ts collapsibleLines="1-8" expandButtonLabel="Show Imports"
+import {
+ AddItemAdjustmentAction,
+ AddShippingMethodAdjustment,
+ // ...
+} from "@medusajs/framework/types"
+
+// ...
+
+await cartModuleService.setLineItemAdjustments(
+ cart.id,
+ actions.filter(
+ (action) => action.action === "addItemAdjustment"
+ ) as AddItemAdjustmentAction[]
+)
+
+await cartModuleService.setShippingMethodAdjustments(
+ cart.id,
+ actions.filter(
+ (action) =>
+ action.action === "addShippingMethodAdjustment"
+ ) as AddShippingMethodAdjustment[]
+)
+```
+
+
# Inventory Concepts
In this document, you’ll learn about the main concepts in the Inventory Module, and how data is stored and related.
@@ -23049,395 +25154,67 @@ const { data: inventoryLevels } = useQueryGraphStep({
```
-# Order Concepts
+# Pricing Concepts
-In this document, you’ll learn about orders and related concepts
+In this document, you’ll learn about the main concepts in the Pricing Module.
-## Order Items
+## Price Set
-The items purchased in the order are represented by the [OrderItem data model](https://docs.medusajs.com/references/order/models/OrderItem/index.html.md). An order can have multiple items.
+A [PriceSet](https://docs.medusajs.com/references/pricing/models/PriceSet/index.html.md) represents a collection of prices that are linked to a resource (for example, a product or a shipping option).
-
+Each of these prices are represented by the [Price data module](https://docs.medusajs.com/references/pricing/models/Price/index.html.md).
-### Item’s Product Details
-
-The details of the purchased products are represented by the [LineItem data model](https://docs.medusajs.com/references/order/models/OrderLineItem/index.html.md). Not only does a line item hold the details of the product, but also details related to its price, adjustments due to promotions, and taxes.
+
***
-## Order’s Shipping Method
+## Price List
-An order has one or more shipping methods used to handle item shipment.
+A [PriceList](https://docs.medusajs.com/references/pricing/models/PriceList/index.html.md) is a group of prices only enabled if their conditions and rules are satisfied.
-Each shipping method is represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderShippingMethod/index.html.md) that holds its details. The shipping method is linked to the order through the [OrderShipping data model](https://docs.medusajs.com/references/order/models/OrderShipping/index.html.md).
+A price list has optional `start_date` and `end_date` properties that indicate the date range in which a price list can be applied.
-
+Its associated prices are represented by the `Price` data model.
-### data Property
-When fulfilling the order, you can use a third-party fulfillment provider that requires additional custom data to be passed along from the order creation process.
+# Links between Pricing Module and Other Modules
-The `OrderShippingMethod` data model has a `data` property. It’s an object used to store custom data relevant later for fulfillment.
-
-The Medusa application passes the `data` property to the Fulfillment Module when fulfilling items.
-
-***
-
-## Order Totals
-
-The order’s total amounts (including tax total, total after an item is returned, etc…) are represented by the [OrderSummary data model](https://docs.medusajs.com/references/order/models/OrderSummary/index.html.md).
-
-***
-
-## Order Payments
-
-Payments made on an order, whether they’re capture or refund payments, are recorded as transactions represented by the [OrderTransaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md).
-
-An order can have multiple transactions. The sum of these transactions must be equal to the order summary’s total. Otherwise, there’s an outstanding amount.
-
-Learn more about transactions in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/transactions/index.html.md).
-
-
-# Order Claim
-
-In this document, you’ll learn about order claims.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/orders/claims/index.html.md) to learn how to manage an order's claims using the dashboard.
-
-## What is a Claim?
-
-When a customer receives a defective or incorrect item, the merchant can create a claim to refund or replace the item.
-
-The [OrderClaim data model](https://docs.medusajs.com/references/order/models/OrderClaim/index.html.md) represents a claim.
-
-***
-
-## Claim Type
-
-The `Claim` data model has a `type` property whose value indicates the type of the claim:
-
-- `refund`: the items are returned, and the customer is refunded.
-- `replace`: the items are returned, and the customer receives new items.
-
-***
-
-## Old and Replacement Items
-
-When the claim is created, a return, represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md), is also created to handle receiving the old items from the customer.
-
-Learn more about returns in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md).
-
-If the claim’s type is `replace`, replacement items are represented by the [ClaimItem data model](https://docs.medusajs.com/references/order/models/OrderClaimItem/index.html.md).
-
-***
-
-## Claim Shipping Methods
-
-A claim uses shipping methods to send the replacement items to the customer. These methods are represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderShippingMethod/index.html.md).
-
-The shipping methods for the returned items are associated with the claim's return, as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return#return-shipping-methods/index.html.md).
-
-***
-
-## Claim Refund
-
-If the claim’s type is `refund`, the amount to be refunded is stored in the `refund_amount` property.
-
-The [Transaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md) represents the refunds made for the claim.
-
-***
-
-## How Claims Impact an Order’s Version
-
-When a claim is confirmed, the order’s version is incremented.
-
-
-# Order Edit
-
-In this document, you'll learn about order edits.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/orders/edit/index.html.md) to learn how to edit an order's items using the dashboard.
-
-## What is an Order Edit?
-
-A merchant can edit an order to add new items or change the quantity of existing items in the order.
-
-An order edit is represented by the [OrderChange data model](https://docs.medusajs.com/references/order/models/OrderChange/index.html.md).
-
-The `OrderChange` data model is associated with any type of change, including a return or exchange. However, its `change_type` property distinguishes the type of change it's making.
-
-In the case of an order edit, the `OrderChange`'s type is `edit`.
-
-***
-
-## Add Items in an Order Edit
-
-When the merchant adds new items to the order in the order edit, the item is added as an [OrderItem](https://docs.medusajs.com/references/order/models/OrderItem/index.html.md).
-
-Also, an `OrderChangeAction` is created. The [OrderChangeAction data model](https://docs.medusajs.com/references/order/models/OrderChangeAction/index.html.md) represents a change made by an `OrderChange`, such as an item added.
-
-So, when an item is added, an `OrderChangeAction` is created with the type `ITEM_ADD`. In its `details` property, the item's ID, price, and quantity are stored.
-
-***
-
-## Update Items in an Order Edit
-
-A merchant can update an existing item's quantity or price.
-
-This change is added as an `OrderChangeAction` with the type `ITEM_UPDATE`. In its `details` property, the item's ID, new price, and new quantity are stored.
-
-***
-
-## Shipping Methods of New Items in the Edit
-
-Adding new items to the order requires adding shipping methods for those items.
-
-These shipping methods are represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderItem/index.html.md). Also, an `OrderChangeAction` is created with the type `SHIPPING_ADD`
-
-***
-
-## How Order Edits Impact an Order’s Version
-
-When an order edit is confirmed, the order’s version is incremented.
-
-***
-
-## Payments and Refunds for Order Edit Changes
-
-Once the Order Edit is confirmed, any additional payment or refund required can be made on the original order.
-
-This is determined by the comparison between the `OrderSummary` and the order's transactions, as mentioned in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/transactions#checking-outstanding-amount/index.html.md).
-
-
-# Order Exchange
-
-In this document, you’ll learn about order exchanges.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/orders/exchanges/index.html.md) to learn how to manage an order's exchanges using the dashboard.
-
-## What is an Exchange?
-
-An exchange is the replacement of an item that the customer ordered with another.
-
-A merchant creates the exchange, specifying the items to be replaced and the new items to be sent.
-
-The [OrderExchange data model](https://docs.medusajs.com/references/order/models/OrderExchange/index.html.md) represents an exchange.
-
-***
-
-## Returned and New Items
-
-When the exchange is created, a return, represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md), is created to handle receiving the items back from the customer.
-
-Learn more about returns in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md).
-
-The [OrderExchangeItem data model](https://docs.medusajs.com/references/order/models/OrderExchangeItem/index.html.md) represents the new items to be sent to the customer.
-
-***
-
-## Exchange Shipping Methods
-
-An exchange has shipping methods used to send the new items to the customer. They’re represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderShippingMethod/index.html.md).
-
-The shipping methods for the returned items are associated with the exchange's return, as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return#return-shipping-methods/index.html.md).
-
-***
-
-## Exchange Payment
-
-The `Exchange` data model has a `difference_due` property that stores the outstanding amount.
-
-|Condition|Result|
-|---|---|---|
-|\`difference\_due \< 0\`|Merchant owes the customer a refund of the |
-|\`difference\_due > 0\`|Merchant requires additional payment from the customer of the |
-|\`difference\_due = 0\`|No payment processing is required.|
-
-Any payment or refund made is stored in the [Transaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md).
-
-***
-
-## How Exchanges Impact an Order’s Version
-
-When an exchange is confirmed, the order’s version is incremented.
-
-
-# Links between Order Module and Other Modules
-
-This document showcases the module links defined between the Order Module and other commerce modules.
+This document showcases the module links defined between the Pricing Module and other commerce modules.
## Summary
-The Order Module has the following links to other modules:
-
-Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
+The Pricing Module has the following links to other modules:
|First Data Model|Second Data Model|Type|Description|
|---|---|---|---|
-|| in |Read-only||
-|| in |Stored||
-|| in |Stored||
-|| in |Stored||
-|| in |Stored||
-|| in |Stored||
-|| in |Stored||
-|| in |Read-only||
-|| in |Stored||
-|| in |Read-only||
-|| in |Read-only||
-
-***
-
-## Customer Module
-
-Medusa defines a read-only link between the `Order` data model and the [Customer Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/customer/index.html.md)'s `Customer` data model. This means you can retrieve the details of an order's customer, but you don't manage the links in a pivot table in the database. The customer of an order is determined by the `customer_id` property of the `Order` data model.
-
-### Retrieve with Query
-
-To retrieve the customer of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: orders } = await query.graph({
- entity: "order",
- fields: [
- "customer.*",
- ],
-})
-
-// orders.customer
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: orders } = useQueryGraphStep({
- entity: "order",
- fields: [
- "customer.*",
- ],
-})
-
-// orders.customer
-```
-
-***
-
-## Cart Module
-
-The [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md) provides cart-management features.
-
-Medusa defines a link between the `Order` and `Cart` data models. The order is linked to the cart used for the purchased.
-
-
-
-### Retrieve with Query
-
-To retrieve the cart of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `cart.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: orders } = await query.graph({
- entity: "order",
- fields: [
- "cart.*",
- ],
-})
-
-// orders.cart
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: orders } = useQueryGraphStep({
- entity: "order",
- fields: [
- "cart.*",
- ],
-})
-
-// orders.cart
-```
-
-### Manage with Link
-
-To manage the cart of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
- [Modules.CART]: {
- cart_id: "cart_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
- [Modules.CART]: {
- cart_id: "cart_123",
- },
-})
-```
+| in ||Stored||
+| in ||Stored||
***
## Fulfillment Module
-A fulfillment is created for an orders' items. Medusa defines a link between the `Fulfillment` and `Order` data models.
+The Fulfillment Module provides fulfillment-related functionalities, including shipping options that the customer chooses from when they place their order. However, it doesn't provide pricing-related functionalities for these options.
-
+Medusa defines a link between the `PriceSet` and `ShippingOption` data models. A shipping option's price is stored as a price set.
-A fulfillment is also created for a return's items. So, Medusa defines a link between the `Fulfillment` and `Return` data models.
-
-
+
### Retrieve with Query
-To retrieve the fulfillments of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `fulfillments.*` in `fields`:
-
-To retrieve the fulfillments of a return, pass `fulfillments.*` in `fields`.
+To retrieve the shipping option of a price set with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `shipping_option.*` in `fields`:
### query.graph
```ts
-const { data: orders } = await query.graph({
- entity: "order",
+const { data: priceSets } = await query.graph({
+ entity: "price_set",
fields: [
- "fulfillments.*",
+ "shipping_option.*",
],
})
-// orders.fulfillments
+// priceSets.shipping_option
```
### useQueryGraphStep
@@ -23447,19 +25224,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: orders } = useQueryGraphStep({
- entity: "order",
+const { data: priceSets } = useQueryGraphStep({
+ entity: "price_set",
fields: [
- "fulfillments.*",
+ "shipping_option.*",
],
})
-// orders.fulfillments
+// priceSets.shipping_option
```
### Manage with Link
-To manage the fulfillments of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+To manage the price set of a shipping option, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
### link.create
@@ -23469,11 +25246,11 @@ import { Modules } from "@medusajs/framework/utils"
// ...
await link.create({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
[Modules.FULFILLMENT]: {
- fulfillment_id: "ful_123",
+ shipping_option_id: "so_123",
+ },
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
},
})
```
@@ -23487,94 +25264,11 @@ import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
// ...
createRemoteLinkStep({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
[Modules.FULFILLMENT]: {
- fulfillment_id: "ful_123",
+ shipping_option_id: "so_123",
},
-})
-```
-
-***
-
-## Payment Module
-
-An order's payment details are stored in a payment collection. This also applies for claims and exchanges.
-
-So, Medusa defines links between the `PaymentCollection` data model and the `Order`, `OrderClaim`, and `OrderExchange` data models.
-
-
-
-### Retrieve with Query
-
-To retrieve the payment collections of an order, order exchange, or order claim with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `payment_collections.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: orders } = await query.graph({
- entity: "order",
- fields: [
- "payment_collections.*",
- ],
-})
-
-// orders.payment_collections
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: orders } = useQueryGraphStep({
- entity: "order",
- fields: [
- "payment_collections.*",
- ],
-})
-
-// orders.payment_collections
-```
-
-### Manage with Link
-
-To manage the payment collections of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
- [Modules.PAYMENT]: {
- payment_collection_id: "paycol_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
- [Modules.PAYMENT]: {
- payment_collection_id: "paycol_123",
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
},
})
```
@@ -23583,28 +25277,31 @@ createRemoteLinkStep({
## Product Module
-Medusa defines read-only links between:
+The Product Module doesn't store or manage the prices of product variants.
-- the `OrderLineItem` data model and the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md)'s `Product` data model. This means you can retrieve the details of a line item's product, but you don't manage the links in a pivot table in the database. The product of a line item is determined by the `product_id` property of the `OrderLineItem` data model.
-- the `OrderLineItem` data model and the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md)'s `ProductVariant` data model. This means you can retrieve the details of a line item's variant, but you don't manage the links in a pivot table in the database. The variant of a line item is determined by the `variant_id` property of the `OrderLineItem` data model.
+Medusa defines a link between the `ProductVariant` and the `PriceSet`. A product variant’s prices are stored as prices belonging to a price set.
+
+
+
+So, when you want to add prices for a product variant, you create a price set and add the prices to it.
+
+You can then benefit from adding rules to prices or using the `calculatePrices` method to retrieve the price of a product variant within a specified context.
### Retrieve with Query
-To retrieve the variant of a line item with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variant.*` in `fields`:
-
-To retrieve the product, pass `product.*` in `fields`.
+To retrieve the variant of a price set with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variant.*` in `fields`:
### query.graph
```ts
-const { data: lineItems } = await query.graph({
- entity: "order_line_item",
+const { data: priceSets } = await query.graph({
+ entity: "price_set",
fields: [
"variant.*",
],
})
-// lineItems.variant
+// priceSets.variant
```
### useQueryGraphStep
@@ -23614,61 +25311,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: lineItems } = useQueryGraphStep({
- entity: "order_line_item",
+const { data: priceSets } = useQueryGraphStep({
+ entity: "price_set",
fields: [
"variant.*",
],
})
-// lineItems.variant
-```
-
-***
-
-## Promotion Module
-
-An order is associated with the promotion applied on it. Medusa defines a link between the `Order` and `Promotion` data models.
-
-
-
-### Retrieve with Query
-
-To retrieve the promotion applied on an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `promotion.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: orders } = await query.graph({
- entity: "order",
- fields: [
- "promotion.*",
- ],
-})
-
-// orders.promotion
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: orders } = useQueryGraphStep({
- entity: "order",
- fields: [
- "promotion.*",
- ],
-})
-
-// orders.promotion
+// priceSets.variant
```
### Manage with Link
-To manage the promotion of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+To manage the price set of a variant, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
### link.create
@@ -23678,11 +25333,11 @@ import { Modules } from "@medusajs/framework/utils"
// ...
await link.create({
- [Modules.ORDER]: {
- order_id: "order_123",
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
},
- [Modules.PROMOTION]: {
- promotion_id: "promo_123",
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
},
})
```
@@ -23696,1224 +25351,306 @@ import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
// ...
createRemoteLinkStep({
- [Modules.ORDER]: {
- order_id: "order_123",
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
},
- [Modules.PROMOTION]: {
- promotion_id: "promo_123",
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
},
})
```
-***
-## Region Module
+# Prices Calculation
-Medusa defines a read-only link between the `Order` data model and the [Region Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/region/index.html.md)'s `Region` data model. This means you can retrieve the details of an order's region, but you don't manage the links in a pivot table in the database. The region of an order is determined by the `region_id` property of the `Order` data model.
+In this document, you'll learn how prices are calculated when you use the [calculatePrices method](https://docs.medusajs.com/references/pricing/calculatePrices/index.html.md) of the Pricing Module's main service.
-### Retrieve with Query
+## calculatePrices Method
-To retrieve the region of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `region.*` in `fields`:
+The [calculatePrices method](https://docs.medusajs.com/references/pricing/calculatePrices/index.html.md) accepts as parameters the ID of one or more price sets and a context.
-### query.graph
+It returns a price object with the best matching price for each price set.
+
+### Calculation Context
+
+The calculation context is an optional object passed as a second parameter to the `calculatePrices` method. It accepts rules to restrict the selected prices in the price set.
+
+For example:
```ts
-const { data: orders } = await query.graph({
- entity: "order",
- fields: [
- "region.*",
- ],
-})
-
-// orders.region
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: orders } = useQueryGraphStep({
- entity: "order",
- fields: [
- "region.*",
- ],
-})
-
-// orders.region
-```
-
-***
-
-## Sales Channel Module
-
-Medusa defines a read-only link between the `Order` data model and the [Sales Channel Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/index.html.md)'s `SalesChannel` data model. This means you can retrieve the details of an order's sales channel, but you don't manage the links in a pivot table in the database. The sales channel of an order is determined by the `sales_channel_id` property of the `Order` data model.
-
-### Retrieve with Query
-
-To retrieve the sales channel of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channel.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: orders } = await query.graph({
- entity: "order",
- fields: [
- "sales_channel.*",
- ],
-})
-
-// orders.sales_channel
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: orders } = useQueryGraphStep({
- entity: "order",
- fields: [
- "sales_channel.*",
- ],
-})
-
-// orders.sales_channel
-```
-
-
-# Order Change
-
-In this document, you'll learn about the Order Change data model and possible actions in it.
-
-## OrderChange Data Model
-
-The [OrderChange data model](https://docs.medusajs.com/references/order/models/OrderChange/index.html.md) represents any kind of change to an order, such as a return, exchange, or edit.
-
-Its `change_type` property indicates what the order change is created for:
-
-1. `edit`: The order change is making edits to the order, as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/edit/index.html.md).
-2. `exchange`: The order change is associated with an exchange, which you can learn about in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/exchange/index.html.md).
-3. `claim`: The order change is associated with a claim, which you can learn about in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/claim/index.html.md).
-4. `return_request` or `return_receive`: The order change is associated with a return, which you can learn about in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md).
-
-Once the order change is confirmed, its changes are applied on the order.
-
-***
-
-## Order Change Actions
-
-The actions to perform on the original order by a change, such as adding an item, are represented by the [OrderChangeAction data model](https://docs.medusajs.com/references/order/models/OrderChangeAction/index.html.md).
-
-The `OrderChangeAction` has an `action` property that indicates the type of action to perform on the order, and a `details` property that holds more details related to the action.
-
-The following table lists the possible `action` values that Medusa uses and what `details` they carry.
-
-|Action|Description|Details|
-|---|---|---|---|---|
-|\`ITEM\_ADD\`|Add an item to the order.|\`details\`|
-|\`ITEM\_UPDATE\`|Update an item in the order.|\`details\`|
-|\`RETURN\_ITEM\`|Set an item to be returned.|\`details\`|
-|\`RECEIVE\_RETURN\_ITEM\`|Mark a return item as received.|\`details\`|
-|\`RECEIVE\_DAMAGED\_RETURN\_ITEM\`|Mark a return item that's damaged as received.|\`details\`|
-|\`SHIPPING\_ADD\`|Add a shipping method for new or returned items.|No details added. The ID to the shipping method is added in the |
-|\`SHIPPING\_ADD\`|Add a shipping method for new or returned items.|No details added. The ID to the shipping method is added in the |
-|\`WRITE\_OFF\_ITEM\`|Remove an item's quantity as part of the claim, without adding the quantity back to the item variant's inventory.|\`details\`|
-
-
-# Order Versioning
-
-In this document, you’ll learn how an order and its details are versioned.
-
-## What's Versioning?
-
-Versioning means assigning a version number to a record, such as an order and its items. This is useful to view the different versions of the order following changes in its lifetime.
-
-When changes are made on an order, such as an item is added or returned, the order's version changes.
-
-***
-
-## version Property
-
-The `Order` and `OrderSummary` data models have a `version` property that indicates the current version. By default, its value is `1`.
-
-Other order-related data models, such as `OrderItem`, also has a `version` property, but it indicates the version it belongs to.
-
-***
-
-## How the Version Changes
-
-When the order is changed, such as an item is exchanged, this changes the version of the order and its related data:
-
-1. The version of the order and its summary is incremented.
-2. Related order data that have a `version` property, such as the `OrderItem`, are duplicated. The duplicated item has the new version, whereas the original item has the previous version.
-
-When the order is retrieved, only the related data having the same version is retrieved.
-
-
-# Tax Lines in Order Module
-
-In this document, you’ll learn about tax lines in an order.
-
-## What are Tax Lines?
-
-A tax line indicates the tax rate of a line item or a shipping method.
-
-The [OrderLineItemTaxLine data model](https://docs.medusajs.com/references/order/models/OrderLineItemTaxLine/index.html.md) represents a line item’s tax line, and the [OrderShippingMethodTaxLine data model](https://docs.medusajs.com/references/order/models/OrderShippingMethodTaxLine/index.html.md) represents a shipping method’s tax line.
-
-
-
-***
-
-## Tax Inclusivity
-
-By default, the tax amount is calculated by taking the tax rate from the line item or shipping method’s amount and then adding it to the item/method’s subtotal.
-
-However, line items and shipping methods have an `is_tax_inclusive` property that, when enabled, indicates that the item or method’s price already includes taxes.
-
-So, instead of calculating the tax rate and adding it to the item/method’s subtotal, it’s calculated as part of the subtotal.
-
-The following diagram is a simplified showcase of how a subtotal is calculated from the tax perspective.
-
-
-
-For example, if a line item's amount is `5000`, the tax rate is `10`, and `is_tax_inclusive` is enabled, the tax amount is 10% of `5000`, which is `500`. The item's unit price becomes `4500`.
-
-
-# Order Return
-
-In this document, you’ll learn about order returns.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/orders/returns/index.html.md) to learn how to manage an order's returns using the dashboard.
-
-## What is a Return?
-
-A return is the return of items delivered from the customer back to the merchant. It is represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md).
-
-A return is requested either by the customer from the storefront, or the merchant from the admin. Medusa supports an automated Return Merchandise Authorization (RMA) flow.
-
-
-
-Once the merchant receives the returned items, they mark the return as received.
-
-***
-
-## Returned Items
-
-The items to be returned are represented by the [ReturnItem data model](references/order/models/ReturnItem).
-
-The `ReturnItem` model has two properties storing the item's quantity:
-
-1. `received_quantity`: The quantity of the item that's received and can be added to the item's inventory quantity.
-2. `damaged_quantity`: The quantity of the item that's damaged, meaning it can't be sold again or added to the item's inventory quantity.
-
-***
-
-## Return Shipping Methods
-
-A return has shipping methods used to return the items to the merchant. The shipping methods are represented by the [OrderShippingMethod data model](references/order/models/OrderShippingMethod).
-
-In the Medusa application, the shipping method for a return is created only from a shipping option, provided by the Fulfillment Module, that has the rule `is_return` enabled.
-
-***
-
-## Refund Payment
-
-The `refund_amount` property of the `Return` data model holds the amount a merchant must refund the customer.
-
-The [OrderTransaction data model](references/order/models/OrderTransaction) represents the refunds made for the return.
-
-***
-
-## Returns in Exchanges and Claims
-
-When a merchant creates an exchange or a claim, it includes returning items from the customer.
-
-The `Return` data model also represents the return of these items. In this case, the return is associated with the exchange or claim it was created for.
-
-***
-
-## How Returns Impact an Order’s Version
-
-The order’s version is incremented when:
-
-1. A return is requested.
-2. A return is marked as received.
-
-
-# Promotions Adjustments in Orders
-
-In this document, you’ll learn how a promotion is applied to an order’s items and shipping methods using adjustment lines.
-
-## What are Adjustment Lines?
-
-An adjustment line indicates a change to a line item or a shipping method’s amount. It’s used to apply promotions or discounts on an order.
-
-The [OrderLineItemAdjustment data model](https://docs.medusajs.com/references/order/models/OrderLineItemAdjustment/index.html.md) represents changes on a line item, and the [OrderShippingMethodAdjustment data model](https://docs.medusajs.com/references/order/models/OrderShippingMethodAdjustment/index.html.md) represents changes on a shipping method.
-
-
-
-The `amount` property of the adjustment line indicates the amount to be discounted from the original amount.
-
-The ID of the applied promotion is stored in the `promotion_id` property of the adjustment line.
-
-***
-
-## Discountable Option
-
-The `OrderLineItem` data model has an `is_discountable` property that indicates whether promotions can be applied to the line item. It’s enabled by default.
-
-When disabled, a promotion can’t be applied to a line item. In the context of the Promotion Module, the promotion isn’t applied to the line item even if it matches its rules.
-
-***
-
-## Promotion Actions
-
-When using the Order and Promotion modules together, use the [computeActions method of the Promotion Module’s main service](https://docs.medusajs.com/references/promotion/computeActions/index.html.md). It retrieves the actions of line items and shipping methods.
-
-Learn more about actions in the [Promotion Module’s documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/actions/index.html.md).
-
-```ts collapsibleLines="1-10" expandButtonLabel="Show Imports"
-import {
- ComputeActionAdjustmentLine,
- ComputeActionItemLine,
- ComputeActionShippingLine,
- // ...
-} from "@medusajs/framework/types"
-
-// ...
-
-// retrieve the order
-const order = await orderModuleService.retrieveOrder("ord_123", {
- relations: [
- "items.item.adjustments",
- "shipping_methods.shipping_method.adjustments",
- ],
-})
-// retrieve the line item adjustments
-const lineItemAdjustments: ComputeActionItemLine[] = []
-order.items.forEach((item) => {
- const filteredAdjustments = item.adjustments?.filter(
- (adjustment) => adjustment.code !== undefined
- ) as unknown as ComputeActionAdjustmentLine[]
- if (filteredAdjustments.length) {
- lineItemAdjustments.push({
- ...item,
- ...item.detail,
- adjustments: filteredAdjustments,
- })
- }
-})
-
-//retrieve shipping method adjustments
-const shippingMethodAdjustments: ComputeActionShippingLine[] =
- []
-order.shipping_methods.forEach((shippingMethod) => {
- const filteredAdjustments =
- shippingMethod.adjustments?.filter(
- (adjustment) => adjustment.code !== undefined
- ) as unknown as ComputeActionAdjustmentLine[]
- if (filteredAdjustments.length) {
- shippingMethodAdjustments.push({
- ...shippingMethod,
- adjustments: filteredAdjustments,
- })
- }
-})
-
-// compute actions
-const actions = await promotionModuleService.computeActions(
- ["promo_123"],
+const price = await pricingModuleService.calculatePrices(
+ { id: [priceSetId] },
{
- items: lineItemAdjustments,
- shipping_methods: shippingMethodAdjustments,
- // TODO infer from cart or region
- currency_code: "usd",
+ context: {
+ currency_code: currencyCode,
+ region_id: "reg_123",
+ },
}
)
```
-The `computeActions` method accepts the existing adjustments of line items and shipping methods to compute the actions accurately.
+In this example, you retrieve the prices in a price set for the specified currency code and region ID.
-Then, use the returned `addItemAdjustment` and `addShippingMethodAdjustment` actions to set the order’s line items and the shipping method’s adjustments.
+### Returned Price Object
-```ts collapsibleLines="1-9" expandButtonLabel="Show Imports"
-import {
- AddItemAdjustmentAction,
- AddShippingMethodAdjustment,
- // ...
-} from "@medusajs/framework/types"
+For each price set, the `calculatePrices` method selects two prices:
-// ...
+- A calculated price: Either a price that belongs to a price list and best matches the specified context, or the same as the original price.
+- An original price, which is either:
+ - The same price as the calculated price if the price list it belongs to is of type `override`;
+ - Or a price that doesn't belong to a price list and best matches the specified context.
-await orderModuleService.setOrderLineItemAdjustments(
- order.id,
- actions.filter(
- (action) => action.action === "addItemAdjustment"
- ) as AddItemAdjustmentAction[]
-)
+Both prices are returned in an object that has the following properties:
-await orderModuleService.setOrderShippingMethodAdjustments(
- order.id,
- actions.filter(
- (action) =>
- action.action === "addShippingMethodAdjustment"
- ) as AddShippingMethodAdjustment[]
-)
-```
+- id: (\`string\`) The ID of the price set from which the price was selected.
+- is\_calculated\_price\_price\_list: (\`boolean\`) Whether the calculated price belongs to a price list.
+- calculated\_amount: (\`number\`) The amount of the calculated price, or \`null\` if there isn't a calculated price. This is the amount shown to the customer.
+- is\_original\_price\_price\_list: (\`boolean\`) Whether the original price belongs to a price list.
+- original\_amount: (\`number\`) The amount of the original price, or \`null\` if there isn't an original price. This amount is useful to compare with the \`calculated\_amount\`, such as to check for discounted value.
+- currency\_code: (\`string\`) The currency code of the calculated price, or \`null\` if there isn't a calculated price.
+- is\_calculated\_price\_tax\_inclusive: (\`boolean\`) Whether the calculated price is tax inclusive. Learn more about tax-inclusivity in \[this document]\(../tax-inclusive-pricing/page.mdx)
+- is\_original\_price\_tax\_inclusive: (\`boolean\`) Whether the original price is tax inclusive. Learn more about tax-inclusivity in \[this document]\(../tax-inclusive-pricing/page.mdx)
+- calculated\_price: (\`object\`) The calculated price's price details.
+ - id: (\`string\`) The ID of the price.
-# Transactions
+ - price\_list\_id: (\`string\`) The ID of the associated price list.
-In this document, you’ll learn about an order’s transactions and its use.
+ - price\_list\_type: (\`string\`) The price list's type. For example, \`sale\`.
-## What is a Transaction?
+ - min\_quantity: (\`number\`) The price's min quantity condition.
-A transaction represents any order payment process, such as capturing or refunding an amount. It’s represented by the [OrderTransaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md).
+ - max\_quantity: (\`number\`) The price's max quantity condition.
+- original\_price: (\`object\`) The original price's price details.
-The transaction’s main purpose is to ensure a correct balance between paid and outstanding amounts.
+ - id: (\`string\`) The ID of the price.
-Transactions are also associated with returns, claims, and exchanges if additional payment or refund is required.
+ - price\_list\_id: (\`string\`) The ID of the associated price list.
+
+ - price\_list\_type: (\`string\`) The price list's type. For example, \`sale\`.
+
+ - min\_quantity: (\`number\`) The price's min quantity condition.
+
+ - max\_quantity: (\`number\`) The price's max quantity condition.
***
-## Checking Outstanding Amount
+## Examples
-The order’s total amounts are stored in the `OrderSummary`'s `totals` property, which is a JSON object holding the total details of the order.
+Consider the following price set:
+
+```ts
+const priceSet = await pricingModuleService.createPriceSets({
+ prices: [
+ // default price
+ {
+ amount: 500,
+ currency_code: "EUR",
+ rules: {},
+ },
+ // prices with rules
+ {
+ amount: 400,
+ currency_code: "EUR",
+ rules: {
+ region_id: "reg_123",
+ },
+ },
+ {
+ amount: 450,
+ currency_code: "EUR",
+ rules: {
+ city: "krakow",
+ },
+ },
+ {
+ amount: 500,
+ currency_code: "EUR",
+ rules: {
+ city: "warsaw",
+ region_id: "reg_123",
+ },
+ },
+ ],
+})
+```
+
+### Default Price Selection
+
+### Code
+
+```ts
+const price = await pricingModuleService.calculatePrices(
+ { id: [priceSet.id] },
+ {
+ context: {
+ currency_code: "EUR"
+ }
+ }
+)
+```
+
+### Result
+
+### Calculate Prices with Rules
+
+### Code
+
+```ts
+const price = await pricingModuleService.calculatePrices(
+ { id: [priceSet.id] },
+ {
+ context: {
+ currency_code: "EUR",
+ region_id: "reg_123",
+ city: "krakow"
+ }
+ }
+)
+```
+
+### Result
+
+### Price Selection with Price List
+
+### Code
+
+```ts
+const priceList = pricingModuleService.createPriceLists([{
+ title: "Summer Price List",
+ description: "Price list for summer sale",
+ starts_at: Date.parse("01/10/2023").toString(),
+ ends_at: Date.parse("31/10/2023").toString(),
+ rules: {
+ region_id: ['PL']
+ },
+ type: "sale",
+ prices: [
+ {
+ amount: 400,
+ currency_code: "EUR",
+ price_set_id: priceSet.id,
+ },
+ {
+ amount: 450,
+ currency_code: "EUR",
+ price_set_id: priceSet.id,
+ },
+ ],
+}]);
+
+const price = await pricingModuleService.calculatePrices(
+ { id: [priceSet.id] },
+ {
+ context: {
+ currency_code: "EUR",
+ region_id: "PL",
+ city: "krakow"
+ }
+ }
+)
+```
+
+### Result
+
+
+# Tax-Inclusive Pricing
+
+In this document, you’ll learn about tax-inclusive pricing and how it's used when calculating prices.
+
+## What is Tax-Inclusive Pricing?
+
+A tax-inclusive price is a price of a resource that includes taxes. Medusa calculates the tax amount from the price rather than adds the amount to it.
+
+For example, if a product’s price is $50, the tax rate is 2%, and tax-inclusive pricing is enabled, then the product's price is $49, and the applied tax amount is $1.
+
+***
+
+## How is Tax-Inclusive Pricing Set?
+
+The [PricePreference data model](https://docs.medusajs.com/references/pricing/models/PricePreference/index.html.md) holds the tax-inclusive setting for a context. It has two properties that indicate the context:
+
+- `attribute`: The name of the attribute to compare against. For example, `region_id` or `currency_code`.
+- `value`: The attribute’s value. For example, `reg_123` or `usd`.
+
+Only `region_id` and `currency_code` are supported as an `attribute` at the moment.
+
+The `is_tax_inclusive` property indicates whether tax-inclusivity is enabled in the specified context.
+
+For example:
```json
{
- "totals": {
- "total": 30,
- "subtotal": 30,
- // ...
- }
+ "attribute": "currency_code",
+ "value": "USD",
+ "is_tax_inclusive": true,
}
```
-To check the outstanding amount of the order, its transaction amounts are summed. Then, the following conditions are checked:
-
-|Condition|Result|
-|---|---|---|
-|summary’s total - transaction amounts total = 0|There’s no outstanding amount.|
-|summary’s total - transaction amounts total > 0|The customer owes additional payment to the merchant.|
-|summary’s total - transaction amounts total \< 0|The merchant owes the customer a refund.|
+In this example, tax-inclusivity is enabled for the `USD` currency code.
***
-## Transaction Reference
+## Tax-Inclusive Pricing in Price Calculation
-The Order Module doesn’t provide payment processing functionalities, so it doesn’t store payments that can be processed. Payment functionalities are provided by the [Payment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/index.html.md).
+### Tax Context
-The `OrderTransaction` data model has two properties that determine which data model and record holds the actual payment’s details:
+As mentioned in the [Price Calculation documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#calculation-context/index.html.md), The `calculatePrices` method accepts as a parameter a calculation context.
-- `reference`: indicates the table’s name in the database. For example, `payment` from the Payment Module.
-- `reference_id`: indicates the ID of the record in the table. For example, `pay_123`.
+To get accurate tax results, pass the `region_id` and / or `currency_code` in the calculation context.
+
+### Returned Tax Properties
+
+The `calculatePrices` method returns two properties related to tax-inclusivity:
+
+Learn more about the returned properties in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#returned-price-object/index.html.md).
+
+- `is_calculated_price_tax_inclusive`: Whether the selected `calculated_price` is tax-inclusive.
+- `is_original_price_tax_inclusive` : Whether the selected `original_price` is tax-inclusive.
+
+A price is considered tax-inclusive if:
+
+1. It belongs to the region or currency code specified in the calculation context;
+2. and the region or currency code has a price preference with `is_tax_inclusive` enabled.
+
+### Tax Context Precedence
+
+A region’s price preference’s `is_tax_inclusive`'s value takes higher precedence in determining whether a price is tax-inclusive if:
+
+- both the `region_id` and `currency_code` are provided in the calculation context;
+- the selected price belongs to the region;
+- and the region has a price preference
-# Account Holders and Saved Payment Methods
+# Price Rules
-In this documentation, you'll learn about account holders, and how they're used to save payment methods in third-party payment providers.
+In this document, you'll learn about price rules for price sets and price lists.
-Account holders are available starting from Medusa `v2.5.0`.
+## Price Rule
-## What's an Account Holder?
+You can restrict prices by rules. Each rule of a price is represented by the [PriceRule data model](https://docs.medusajs.com/references/pricing/models/PriceRule/index.html.md).
-An account holder represents a customer that can have saved payment methods in a third-party service. It's represented by the `AccountHolder` data model.
+The `Price` data model has a `rules_count` property, which indicates how many rules, represented by `PriceRule`, are applied to the price.
-It holds fields retrieved from the third-party provider, such as:
+For exmaple, you create a price restricted to `10557` zip codes.
-- `external_id`: The ID of the equivalent customer or account holder in the third-party provider.
-- `data`: Data returned by the payment provider when the account holder is created.
+
-A payment provider that supports saving payment methods for customers would create the equivalent of an account holder in the third-party provider. Then, whenever a payment method is saved, it would be saved under the account holder in the third-party provider.
+A price can have multiple price rules.
+
+For example, a price can be restricted by a region and a zip code.
+
+
***
-## Save Payment Methods
+## Price List Rules
-If a payment provider supports saving payment methods for a customer, they must implement the following methods:
+Rules applied to a price list are represented by the [PriceListRule data model](https://docs.medusajs.com/references/pricing/models/PriceListRule/index.html.md).
-- `createAccountHolder`: Creates an account holder in the payment provider. The Payment Module uses this method before creating the account holder in Medusa, and uses the returned data to set fields like `external_id` and `data` in the created `AccountHolder` record.
-- `deleteAccountHolder`: Deletes an account holder in the payment provider. The Payment Module uses this method when an account holder is deleted in Medusa.
-- `savePaymentMethod`: Saves a payment method for an account holder in the payment provider.
-- `listPaymentMethods`: Lists saved payment methods in the third-party service for an account holder. This is useful when displaying the customer's saved payment methods in the storefront.
+The `rules_count` property of a `PriceList` indicates how many rules are applied to it.
-Learn more about implementing these methods in the [Create Payment Provider guide](https://docs.medusajs.com/references/payment/provider/index.html.md).
-
-***
-
-## Account Holder in Medusa Payment Flows
-
-In the Medusa application, when a payment session is created for a registered customer, the Medusa application uses the Payment Module to create an account holder for the customer.
-
-Consequently, the Payment Module uses the payment provider to create an account holder in the third-party service, then creates the account holder in Medusa.
-
-This flow is only supported if the chosen payment provider has implemented the necessary [save payment methods](#save-payment-methods).
-
-
-# Payment Module Options
-
-In this document, you'll learn about the options of the Payment Module.
-
-## All Module Options
-
-|Option|Description|Required|Default|
-|---|---|---|---|---|---|---|
-|\`webhook\_delay\`|A number indicating the delay in milliseconds before processing a webhook event.|No|\`5000\`|
-|\`webhook\_retries\`|The number of times to retry the webhook event processing in case of an error.|No|\`3\`|
-|\`providers\`|An array of payment providers to install and register. Learn more |No|-|
-
-***
-
-## providers Option
-
-The `providers` option is an array of payment module providers.
-
-When the Medusa application starts, these providers are registered and can be used to process payments.
-
-For example:
-
-```ts title="medusa-config.ts"
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "@medusajs/medusa/payment",
- options: {
- providers: [
- {
- resolve: "@medusajs/medusa/payment-stripe",
- id: "stripe",
- options: {
- // ...
- },
- },
- ],
- },
- },
- ],
-})
-```
-
-The `providers` option is an array of objects that accept the following properties:
-
-- `resolve`: A string indicating the package name of the module provider or the path to it relative to the `src` directory.
-- `id`: A string indicating the provider's unique name or ID.
-- `options`: An optional object of the module provider's options.
-
-
-# Links between Payment Module and Other Modules
-
-This document showcases the module links defined between the Payment Module and other commerce modules.
-
-## Summary
-
-The Payment Module has the following links to other modules:
-
-|First Data Model|Second Data Model|Type|Description|
-|---|---|---|---|
-| in ||Stored||
-| in ||Stored||
-| in ||Stored||
-| in ||Stored||
-| in ||Stored||
-| in ||Stored||
-
-***
-
-## Cart Module
-
-The Cart Module provides cart-related features, but not payment processing.
-
-Medusa defines a link between the `Cart` and `PaymentCollection` data models. A cart has a payment collection which holds all the authorized payment sessions and payments made related to the cart.
-
-Learn more about this relation in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-collection#usage-with-the-cart-module/index.html.md).
-
-### Retrieve with Query
-
-To retrieve the cart associated with the payment collection with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `cart.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: paymentCollections } = await query.graph({
- entity: "payment_collection",
- fields: [
- "cart.*",
- ],
-})
-
-// paymentCollections.cart
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: paymentCollections } = useQueryGraphStep({
- entity: "payment_collection",
- fields: [
- "cart.*",
- ],
-})
-
-// paymentCollections.cart
-```
-
-### Manage with Link
-
-To manage the payment collection of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.CART]: {
- cart_id: "cart_123",
- },
- [Modules.PAYMENT]: {
- payment_collection_id: "paycol_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.CART]: {
- cart_id: "cart_123",
- },
- [Modules.PAYMENT]: {
- payment_collection_id: "paycol_123",
- },
-})
-```
-
-***
-
-## Customer Module
-
-Medusa defines a link between the `Customer` and `AccountHolder` data models, allowing payment providers to save payment methods for a customer, if the payment provider supports it.
-
-This link is available starting from Medusa `v2.5.0`.
-
-### Retrieve with Query
-
-To retrieve the customer associated with an account holder with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: accountHolders } = await query.graph({
- entity: "account_holder",
- fields: [
- "customer.*",
- ],
-})
-
-// accountHolders.customer
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: accountHolders } = useQueryGraphStep({
- entity: "account_holder",
- fields: [
- "customer.*",
- ],
-})
-
-// accountHolders.customer
-```
-
-### Manage with Link
-
-To manage the account holders of a customer, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.CUSTOMER]: {
- customer_id: "cus_123",
- },
- [Modules.PAYMENT]: {
- account_holder_id: "acchld_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.CUSTOMER]: {
- customer_id: "cus_123",
- },
- [Modules.PAYMENT]: {
- account_holder_id: "acchld_123",
- },
-})
-```
-
-***
-
-## Order Module
-
-An order's payment details are stored in a payment collection. This also applies for claims and exchanges.
-
-So, Medusa defines links between the `PaymentCollection` data model and the `Order`, `OrderClaim`, and `OrderExchange` data models.
-
-
-
-### Retrieve with Query
-
-To retrieve the order of a payment collection with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: paymentCollections } = await query.graph({
- entity: "payment_collection",
- fields: [
- "order.*",
- ],
-})
-
-// paymentCollections.order
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: paymentCollections } = useQueryGraphStep({
- entity: "payment_collection",
- fields: [
- "order.*",
- ],
-})
-
-// paymentCollections.order
-```
-
-### Manage with Link
-
-To manage the payment collections of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
- [Modules.PAYMENT]: {
- payment_collection_id: "paycol_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
- [Modules.PAYMENT]: {
- payment_collection_id: "paycol_123",
- },
-})
-```
-
-***
-
-## Region Module
-
-You can specify for each region which payment providers are available. The Medusa application defines a link between the `PaymentProvider` and the `Region` data models.
-
-
-
-This increases the flexibility of your store. For example, you only show during checkout the payment providers associated with the cart's region.
-
-### Retrieve with Query
-
-To retrieve the regions of a payment provider with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `regions.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: paymentProviders } = await query.graph({
- entity: "payment_provider",
- fields: [
- "regions.*",
- ],
-})
-
-// paymentProviders.regions
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: paymentProviders } = useQueryGraphStep({
- entity: "payment_provider",
- fields: [
- "regions.*",
- ],
-})
-
-// paymentProviders.regions
-```
-
-### Manage with Link
-
-To manage the payment providers in a region, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.REGION]: {
- region_id: "reg_123",
- },
- [Modules.PAYMENT]: {
- payment_provider_id: "pp_stripe_stripe",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.REGION]: {
- region_id: "reg_123",
- },
- [Modules.PAYMENT]: {
- payment_provider_id: "pp_stripe_stripe",
- },
-})
-```
-
-
-# Payment
-
-In this document, you’ll learn what a payment is and how it's created, captured, and refunded.
-
-## What's a Payment?
-
-When a payment session is authorized, a payment, represented by the [Payment data model](https://docs.medusajs.com/references/payment/models/Payment/index.html.md), is created. This payment can later be captured or refunded.
-
-A payment carries many of the data and relations of a payment session:
-
-- It belongs to the same payment collection.
-- It’s associated with the same payment provider, which handles further payment processing.
-- It stores the payment session’s `data` property in its `data` property, as it’s still useful for the payment provider’s processing.
-
-***
-
-## Capture Payments
-
-When a payment is captured, a capture, represented by the [Capture data model](https://docs.medusajs.com/references/payment/models/Capture/index.html.md), is created. It holds details related to the capture, such as the amount, the capture date, and more.
-
-The payment can also be captured incrementally, each time a capture record is created for that amount.
-
-
-
-***
-
-## Refund Payments
-
-When a payment is refunded, a refund, represented by the [Refund data model](https://docs.medusajs.com/references/payment/models/Refund/index.html.md), is created. It holds details related to the refund, such as the amount, refund date, and more.
-
-A payment can be refunded multiple times, and each time a refund record is created.
-
-
-
-
-# Payment Collection
-
-In this document, you’ll learn what a payment collection is and how the Medusa application uses it with the Cart Module.
-
-## What's a Payment Collection?
-
-A payment collection stores payment details related to a resource, such as a cart or an order. It’s represented by the [PaymentCollection data model](https://docs.medusajs.com/references/payment/models/PaymentCollection/index.html.md).
-
-Every purchase or request for payment starts with a payment collection. The collection holds details necessary to complete the payment, including:
-
-- The [payment sessions](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-session/index.html.md) that represents the payment amount to authorize.
-- The [payments](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment/index.html.md) that are created when a payment session is authorized. They can be captured and refunded.
-- The [payment providers](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/index.html.md) that handle the processing of each payment session, including the authorization, capture, and refund.
-
-***
-
-## Multiple Payments
-
-The payment collection supports multiple payment sessions and payments.
-
-You can use this to accept payments in increments or split payments across payment providers.
-
-
-
-***
-
-## Usage with the Cart Module
-
-The Cart Module provides cart management features. However, it doesn’t provide any features related to accepting payment.
-
-During checkout, the Medusa application links a cart to a payment collection, which will be used for further payment processing.
-
-It also implements the payment flow during checkout as explained in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-flow/index.html.md).
-
-
-
-
-# Accept Payment Flow
-
-In this document, you’ll learn how to implement an accept-payment flow using workflows or the Payment Module's main service.
-
-It's highly recommended to use Medusa's workflows to implement this flow. Use the Payment Module's main service for more complex cases.
-
-For a guide on how to implement this flow in the storefront, check out [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/checkout/payment/index.html.md).
-
-## Flow Overview
-
-
-
-***
-
-## 1. Create a Payment Collection
-
-A payment collection holds all details related to a resource’s payment operations. So, you start off by creating a payment collection.
-
-For example:
-
-### Using Workflow
-
-```ts
-import { createPaymentCollectionForCartWorkflow } from "@medusajs/medusa/core-flows"
-
-// ...
-
-await createPaymentCollectionForCartWorkflow(req.scope)
- .run({
- input: {
- cart_id: "cart_123",
- },
- })
-```
-
-### Using Service
-
-```ts
-const paymentCollection =
- await paymentModuleService.createPaymentCollections({
- currency_code: "usd",
- amount: 5000,
- })
-```
-
-***
-
-## 2. Create Payment Sessions
-
-The payment collection has one or more payment sessions, each being a payment amount to be authorized by a payment provider.
-
-So, after creating the payment collection, create at least one payment session for a provider.
-
-For example:
-
-### Using Workflow
-
-```ts
-import { createPaymentSessionsWorkflow } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { result: paymentSesion } = await createPaymentSessionsWorkflow(req.scope)
- .run({
- input: {
- payment_collection_id: "paycol_123",
- provider_id: "stripe",
- },
- })
-```
-
-### Using Service
-
-```ts
-const paymentSession =
- await paymentModuleService.createPaymentSession(
- paymentCollection.id,
- {
- provider_id: "stripe",
- currency_code: "usd",
- amount: 5000,
- data: {
- // any necessary data for the
- // payment provider
- },
- }
- )
-```
-
-***
-
-## 3. Authorize Payment Session
-
-Once the customer chooses a payment session, start the authorization process. This may involve some action performed by the third-party payment provider, such as entering a 3DS code.
-
-For example:
-
-### Using Step
-
-```ts
-import { authorizePaymentSessionStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-authorizePaymentSessionStep({
- id: "payses_123",
- context: {},
-})
-```
-
-### Using Service
-
-```ts
-const payment = authorizePaymentSessionStep({
- id: "payses_123",
- context: {},
-})
-```
-
-When the payment authorization is successful, a payment is created and returned.
-
-### Handling Additional Action
-
-If you used the `authorizePaymentSessionStep`, you don't need to implement this logic as it's implemented in the step.
-
-If the payment authorization isn’t successful, whether because it requires additional action or for another reason, the method updates the payment session with the new status and throws an error.
-
-In that case, you can catch that error and, if the session's `status` property is `requires_more`, handle the additional action, then retry the authorization.
-
-For example:
-
-```ts
-try {
- const payment =
- await paymentModuleService.authorizePaymentSession(
- paymentSession.id,
- {}
- )
-} catch (e) {
- // retrieve the payment session again
- const updatedPaymentSession = (
- await paymentModuleService.listPaymentSessions({
- id: [paymentSession.id],
- })
- )[0]
-
- if (updatedPaymentSession.status === "requires_more") {
- // TODO perform required action
- // TODO authorize payment again.
- }
-}
-```
-
-***
-
-## 4. Payment Flow Complete
-
-The payment flow is complete once the payment session is authorized and the payment is created.
-
-You can then:
-
-- Capture the payment either using the [capturePaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/capturePaymentWorkflow/index.html.md) or [capturePayment method](https://docs.medusajs.com/references/payment/capturePayment/index.html.md).
-- Refund captured amounts using the [refundPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentWorkflow/index.html.md) or [refundPayment method](https://docs.medusajs.com/references/payment/refundPayment/index.html.md).
-
-Some payment providers allow capturing the payment automatically once it’s authorized. In that case, you don’t need to do it manually.
-
-
-# Payment Session
-
-In this document, you’ll learn what a payment session is.
-
-## What's a Payment Session?
-
-A payment session, represented by the [PaymentSession data model](https://docs.medusajs.com/references/payment/models/PaymentSession/index.html.md), is a payment amount to be authorized. It’s associated with a payment provider that handles authorizing it.
-
-A payment collection can have multiple payment sessions. Using this feature, you can implement payment in installments or payments using multiple providers.
-
-
-
-***
-
-## data Property
-
-Payment providers may need additional data to process the payment later. The `PaymentSession` data model has a `data` property used to store that data.
-
-For example, the customer's ID in Stripe is stored in the `data` property.
-
-***
-
-## Payment Session Status
-
-The `status` property of a payment session indicates its current status. Its value can be:
-
-- `pending`: The payment session is awaiting authorization.
-- `requires_more`: The payment session requires an action before it’s authorized. For example, to enter a 3DS code.
-- `authorized`: The payment session is authorized.
-- `error`: An error occurred while authorizing the payment.
-- `canceled`: The authorization of the payment session has been canceled.
-
-
-# Payment Module Provider
-
-In this document, you’ll learn what a payment module provider is.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/regions/index.html.md) to learn how to manage the payment providers available in a region using the dashboard.
-
-## What's a Payment Module Provider?
-
-A payment module provider registers a payment provider that handles payment processing in the Medusa application. It integrates third-party payment providers, such as Stripe.
-
-To authorize a payment amount with a payment provider, a payment session is created and associated with that payment provider. The payment provider is then used to handle the authorization.
-
-After the payment session is authorized, the payment provider is associated with the resulting payment and handles its payment processing, such as to capture or refund payment.
-
-### List of Payment Module Providers
-
-- [Stripe](https://docs.medusajs.com/commerce-modules/payment/payment-provider/stripe/index.html.md)
-
-***
-
-## System Payment Provider
-
-The Payment Module provides a `system` payment provider that acts as a placeholder payment provider.
-
-It doesn’t handle payment processing and delegates that to the merchant. It acts similarly to a cash-on-delivery (COD) payment method.
-
-***
-
-## How are Payment Providers Created?
-
-A payment provider is a module whose main service extends the `AbstractPaymentProvider` imported from `@medusajs/framework/utils`.
-
-Refer to [this guide](https://docs.medusajs.com/references/payment/provider/index.html.md) on how to create a payment provider for the Payment Module.
-
-***
-
-## Configure Payment Providers
-
-The Payment Module accepts a `providers` option that allows you to register providers in your application.
-
-Learn more about this option in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/module-options#providers/index.html.md).
-
-***
-
-## PaymentProvider Data Model
-
-When the Medusa application starts and registers the payment providers, it also creates a record of the `PaymentProvider` data model if none exists.
-
-This data model is used to reference a payment provider and determine whether it’s installed in the application.
-
-
-# Webhook Events
-
-In this document, you’ll learn how the Payment Module supports listening to webhook events.
-
-## What's a Webhook Event?
-
-A webhook event is sent from a third-party payment provider to your application. It indicates a change in a payment’s status.
-
-This is useful in many cases such as when a payment is being processed asynchronously or when a request is interrupted and the payment provider is sending details on the process later.
-
-***
-
-## getWebhookActionAndData Method
-
-The Payment Module’s main service has a [getWebhookActionAndData method](https://docs.medusajs.com/references/payment/getWebhookActionAndData/index.html.md) used to handle incoming webhook events from third-party payment services. The method delegates the handling to the associated payment provider, which returns the event's details.
-
-Medusa implements a webhook listener route at the `/hooks/payment/[identifier]_[provider]` API route, where:
-
-- `[identifier]` is the `identifier` static property defined in the payment provider. For example, `stripe`.
-- `[provider]` is the ID of the provider. For example, `stripe`.
-
-For example, when integrating basic Stripe payments with the [Stripe Module Provider](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/stripe/index.html.md), the webhook listener route is `/hooks/payment/stripe_stripe`. If you're integrating Stripe's Bancontact payments, the webhook listener route is `/hooks/payment/stripe-bancontact_stripe`.
-
-Use that webhook listener in your third-party payment provider's configurations.
-
-
-
-If the event's details indicate that the payment should be authorized, then the [authorizePaymentSession method of the main service](https://docs.medusajs.com/references/payment/authorizePaymentSession/index.html.md) is executed on the specified payment session.
-
-If the event's details indicate that the payment should be captured, then the [capturePayment method of the main service](https://docs.medusajs.com/references/payment/capturePayment/index.html.md) is executed on the payment of the specified payment session.
-
-### Actions After Webhook Payment Processing
-
-After the payment webhook actions are processed and the payment is authorized or captured, the Medusa application completes the cart associated with the payment's collection if it's not completed yet.
+
# Links between Region Module and Other Modules
@@ -25096,376 +25833,232 @@ createRemoteLinkStep({
```
-# Links between Sales Channel Module and Other Modules
+# Product Variant Inventory
-This document showcases the module links defined between the Sales Channel Module and other commerce modules.
+# Product Variant Inventory
-## Summary
+In this guide, you'll learn about the inventory management features related to product variants.
-The Sales Channel Module has the following links to other modules:
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/products/variants#manage-product-variant-inventory/index.html.md) to learn how to manage inventory of product variants.
-Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
+## Configure Inventory Management of Product Variants
-|First Data Model|Second Data Model|Type|Description|
-|---|---|---|---|
-| in ||Stored||
-| in ||Read-only||
-| in ||Read-only||
-| in ||Stored||
-|| in |Stored||
+A product variant, represented by the [ProductVariant](https://docs.medusajs.com/references/product/models/ProductVariant/index.html.md) data model, has a `manage_inventory` field that's disabled by default. This field indicates whether you'll manage the inventory quantity of the product variant in the Medusa application. You can also keep `manage_inventory` disabled if you manage the product's inventory in an external system, such as an ERP.
+
+The Product Module doesn't provide inventory-management features. Instead, the Medusa application uses the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md) to manage inventory for products and variants. When `manage_inventory` is disabled, the Medusa application always considers the product variant to be in stock. This is useful if your product's variants aren't items that can be stocked, such as digital products, or they don't have a limited stock quantity.
+
+When `manage_inventory` is enabled, the Medusa application tracks the inventory of the product variant using the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md). For example, when a customer purchases a product variant, the Medusa application decrements the stocked quantity of the product variant.
***
-## API Key Module
+## How the Medusa Application Manages Inventory
-A publishable API key allows you to easily specify the sales channel scope in a client request.
+When a product variant has `manage_inventory` enabled, the Medusa application creates an inventory item using the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md) and links it to the product variant.
-Medusa defines a link between the `ApiKey` and the `SalesChannel` data models.
+
-
+The inventory item has one or more locations, called inventory levels, that represent the stock quantity of the product variant at a specific location. This allows you to manage inventory across multiple warehouses, such as a warehouse in the US and another in Europe.
-### Retrieve with Query
+
-To retrieve the API keys associated with a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `publishable_api_keys.*` in `fields`:
+Learn more about inventory concepts in the [Inventory Module's documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts/index.html.md).
-### query.graph
+The Medusa application represents and manages stock locations using the [Stock Location Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/index.html.md). It creates a read-only link between the `InventoryLevel` and `StockLocation` data models so that it can retrieve the stock location of an inventory level.
-```ts
-const { data: salesChannels } = await query.graph({
- entity: "sales_channel",
- fields: [
- "publishable_api_keys.*",
- ],
-})
+
-// salesChannels.publishable_api_keys
-```
+Learn more about the Stock Location Module in the [Stock Location Module's documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/concepts/index.html.md).
-### useQueryGraphStep
+### Product Inventory in Storefronts
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+When a storefront sends a request to the Medusa application, it must always pass a [publishable API key](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/publishable-api-keys/index.html.md) in the request header. This API key specifies the sales channels, available through the [Sales Channel Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/index.html.md), of the storefront.
-// ...
+The Medusa application links sales channels to stock locations, indicating the locations available for a specific sales channel. So, all inventory-related operations are scoped by the sales channel and its associated stock locations.
-const { data: salesChannels } = useQueryGraphStep({
- entity: "sales_channel",
- fields: [
- "publishable_api_keys.*",
- ],
-})
+For example, the availability of a product variant is determined by the `stocked_quantity` of its inventory level at the stock location linked to the storefront's sales channel.
-// salesChannels.publishable_api_keys
-```
-
-### Manage with Link
-
-To manage the sales channels of an API key, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.API_KEY]: {
- api_key_id: "apk_123",
- },
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.API_KEY]: {
- api_key_id: "apk_123",
- },
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
- },
-})
-```
+
***
-## Cart Module
+## Variant Back Orders
-Medusa defines a read-only link between the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model and the `SalesChannel` data model. Because the link is read-only from the `Cart`'s side, you can only retrieve the sales channel of a cart, and not the other way around.
+Product variants have an `allow_backorder` field that's disabled by default. When enabled, the Medusa application allows customers to purchase the product variant even when it's out of stock. Use this when your product variant is available through on-demand or pre-order purchase.
-### Retrieve with Query
-
-To retrieve the sales channel of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channel.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: carts } = await query.graph({
- entity: "cart",
- fields: [
- "sales_channel.*",
- ],
-})
-
-// carts.sales_channel
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: carts } = useQueryGraphStep({
- entity: "cart",
- fields: [
- "sales_channel.*",
- ],
-})
-
-// carts.sales_channel
-```
+You can also allow customers to subscribe to restock notifications of a product variant as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/recipes/commerce-automation/restock-notification/index.html.md).
***
-## Order Module
+## Additional Resources
-Medusa defines a read-only link between the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `Order` data model and the `SalesChannel` data model. Because the link is read-only from the `Order`'s side, you can only retrieve the sales channel of an order, and not the other way around.
+The following guides provide more details on inventory management in the Medusa application:
-### Retrieve with Query
+- [Inventory Kits in the Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-kit/index.html.md): Learn how you can implement bundled or multi-part products through the Inventory Module.
+- [Configure Selling Products](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/selling-products/index.html.md): Learn how to use inventory management to support different use cases when selling products.
+- [Inventory in Flows](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-in-flows/index.html.md): Learn how Medusa utilizes inventory management in different flows.
+- [Storefront guide: how to retrieve a product variant's inventory details](https://docs.medusajs.com/resources/storefront-development/products/inventory/index.html.md).
-To retrieve the sales channel of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channel.*` in `fields`:
-### query.graph
+# Configure Selling Products
-```ts
-const { data: orders } = await query.graph({
- entity: "order",
- fields: [
- "sales_channel.*",
- ],
-})
+In this guide, you'll learn how to set up and configure your products based on their shipping and inventory requirements, the product type, how you want to sell them, or your commerce ecosystem.
-// orders.sales_channel
-```
+The concepts in this guide are applicable starting from Medusa v2.5.1.
-### useQueryGraphStep
+## Scenario
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+Businesses can have different selling requirements:
-// ...
+1. They may sell physical or digital items.
+2. They may sell items that don't require shipping or inventory management, such as selling digital products, services, or booking appointments.
+3. They may sell items whose inventory is managed by an external system, such as an ERP.
-const { data: orders } = useQueryGraphStep({
- entity: "order",
- fields: [
- "sales_channel.*",
- ],
-})
-
-// orders.sales_channel
-```
+Medusa supports these different selling requirements by allowing you to configure shipping and inventory requirements for products and their variants. This guide explains how these configurations work, then provides examples of setting up different use cases.
***
-## Product Module
+## Configuring Shipping Requirements
-A product has different availability for different sales channels. Medusa defines a link between the `Product` and the `SalesChannel` data models.
+The Medusa application defines a link between the `Product` data model and a [ShippingProfile](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/concepts#shipping-profile/index.html.md) in the [Fulfillment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/index.html.md), allowing you to associate a product with a shipping profile.
-
+When a product is associated with a shipping profile, its variants require shipping and fulfillment when purchased. This is useful for physical products or digital products that require custom fulfillment.
-A product can be available in more than one sales channel. You can retrieve only the products of a sales channel.
+If a product doesn't have an associated shipping profile, its variants don't require shipping and fulfillment when purchased. This is useful for digital products, for example, that don't require shipping.
-### Retrieve with Query
+### Overriding Shipping Requirements for Variants
-To retrieve the products of a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `products.*` in `fields`:
+A product variant whose inventory is managed by Medusa (its `manage_inventory` property is enabled) has an [inventory item](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts#inventoryitem/index.html.md). The inventory item has a `requires_shipping` property that can be used to override its shipping requirement. This is useful if the product has an associated shipping profile but you want to disable shipping for a specific variant, or vice versa.
-### query.graph
+Learn more about product variant's inventory in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/variant-inventory/index.html.md).
-```ts
-const { data: salesChannels } = await query.graph({
- entity: "sales_channel",
- fields: [
- "products.*",
- ],
-})
+When a product variant is purchased, the Medusa application decides whether the purchased item requires shipping in the following order:
-// salesChannels.products
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: salesChannels } = useQueryGraphStep({
- entity: "sales_channel",
- fields: [
- "products.*",
- ],
-})
-
-// salesChannels.products
-```
-
-### Manage with Link
-
-To manage the sales channels of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
- },
-})
-```
+1. The product variant has an inventory item. In this case, the Medusa application uses the inventory item's `requires_shipping` property to determine if the item requires shipping.
+2. If the product variant doesn't have an inventory item, the Medusa application checks whether the product has an associated shipping profile to determine if the item requires shipping.
***
-## Stock Location Module
+## Use Case Examples
-A stock location is associated with a sales channel. This scopes inventory quantities associated with that stock location by the associated sales channel.
+By combining configurations of shipment requirements and inventory management, you can set up your products to support your use case:
-Medusa defines a link between the `SalesChannel` and `StockLocation` data models.
-
-
-
-### Retrieve with Query
-
-To retrieve the stock locations of a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `stock_locations.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: salesChannels } = await query.graph({
- entity: "sales_channel",
- fields: [
- "stock_locations.*",
- ],
-})
-
-// salesChannels.stock_locations
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: salesChannels } = useQueryGraphStep({
- entity: "sales_channel",
- fields: [
- "stock_locations.*",
- ],
-})
-
-// salesChannels.stock_locations
-```
-
-### Manage with Link
-
-To manage the stock locations of a sales channel, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
- },
- [Modules.STOCK_LOCATION]: {
- sales_channel_id: "sloc_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
- },
- [Modules.STOCK_LOCATION]: {
- sales_channel_id: "sloc_123",
- },
-})
-```
+|Use Case|Configurations|Example|
+|---|---|---|---|---|
+|Item that's shipped on purchase, and its variant inventory is managed by the Medusa application.||Any stock-kept item (clothing, for example), whose inventory is managed in the Medusa application.|
+|Item that's shipped on purchase, but its variant inventory is managed externally (not by Medusa) or it has infinite stock.||Any stock-kept item (clothing, for example), whose inventory is managed in an ERP or has infinite stock.|
+|Item that's not shipped on purchase, but its variant inventory is managed by Medusa.||Digital products, such as licenses, that don't require shipping but have a limited quantity.|
+|Item that doesn't require shipping and its variant inventory isn't managed by Medusa.|||
-# Publishable API Keys with Sales Channels
+# Promotion Actions
-In this document, you’ll learn what publishable API keys are and how to use them with sales channels.
+In this document, you’ll learn about promotion actions and how they’re computed using the [computeActions method](https://docs.medusajs.com/references/promotion/computeActions/index.html.md).
-## Publishable API Keys with Sales Channels
+## computeActions Method
-A publishable API key, provided by the API Key Module, is a client key scoped to one or more sales channels.
+The Promotion Module's main service has a [computeActions method](https://docs.medusajs.com/references/promotion/computeActions/index.html.md) that returns an array of actions to perform on a cart when one or more promotions are applied.
-When sending a request to a Store API route, you must pass a publishable API key in the header of the request:
-
-```bash
-curl http://localhost:9000/store/products \
- x-publishable-api-key: {your_publishable_api_key}
-```
-
-The Medusa application infers the associated sales channels and ensures that only data relevant to the sales channel are used.
+Actions inform you what adjustment must be made to a cart item or shipping method. Each action is an object having the `action` property indicating the type of action.
***
-## How to Create a Publishable API Key?
+## Action Types
-To create a publishable API key, either use the [Medusa Admin](https://docs.medusajs.com/user-guide/settings/developer/publishable-api-keys/index.html.md) or the [Admin API Routes](https://docs.medusajs.com/api/admin#publishable-api-keys).
+### `addItemAdjustment` Action
+
+The `addItemAdjustment` action indicates that an adjustment must be made to an item. For example, removing $5 off its amount.
+
+This action has the following format:
+
+```ts
+export interface AddItemAdjustmentAction {
+ action: "addItemAdjustment"
+ item_id: string
+ amount: number
+ code: string
+ description?: string
+}
+```
+
+This action means that a new record should be created of the `LineItemAdjustment` data model in the Cart Module, or `OrderLineItemAdjustment` data model in the Order Module.
+
+Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.AddItemAdjustmentAction/index.html.md) for details on the object’s properties.
+
+### `removeItemAdjustment` Action
+
+The `removeItemAdjustment` action indicates that an adjustment must be removed from a line item. For example, remove the $5 discount.
+
+The `computeActions` method accepts any previous item adjustments in the `items` property of the second parameter.
+
+This action has the following format:
+
+```ts
+export interface RemoveItemAdjustmentAction {
+ action: "removeItemAdjustment"
+ adjustment_id: string
+ description?: string
+ code: string
+}
+```
+
+This action means that a new record should be removed of the `LineItemAdjustment` (or `OrderLineItemAdjustment`) with the specified ID in the `adjustment_id` property.
+
+Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.RemoveItemAdjustmentAction/index.html.md) for details on the object’s properties.
+
+### `addShippingMethodAdjustment` Action
+
+The `addShippingMethodAdjustment` action indicates that an adjustment must be made on a shipping method. For example, make the shipping method free.
+
+This action has the following format:
+
+```ts
+export interface AddShippingMethodAdjustment {
+ action: "addShippingMethodAdjustment"
+ shipping_method_id: string
+ amount: number
+ code: string
+ description?: string
+}
+```
+
+This action means that a new record should be created of the `ShippingMethodAdjustment` data model in the Cart Module, or `OrderShippingMethodAdjustment` data model in the Order Module.
+
+Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.AddShippingMethodAdjustment/index.html.md) for details on the object’s properties.
+
+### `removeShippingMethodAdjustment` Action
+
+The `removeShippingMethodAdjustment` action indicates that an adjustment must be removed from a shipping method. For example, remove the free shipping discount.
+
+The `computeActions` method accepts any previous shipping method adjustments in the `shipping_methods` property of the second parameter.
+
+This action has the following format:
+
+```ts
+export interface RemoveShippingMethodAdjustment {
+ action: "removeShippingMethodAdjustment"
+ adjustment_id: string
+ code: string
+}
+```
+
+When the Medusa application receives this action type, it removes the `ShippingMethodAdjustment` (or `OrderShippingMethodAdjustment`) with the specified ID in the `adjustment_id` property.
+
+Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.RemoveShippingMethodAdjustment/index.html.md) for details on the object’s properties.
+
+### `campaignBudgetExceeded` Action
+
+When the `campaignBudgetExceeded` action is returned, the promotions within a campaign can no longer be used as the campaign budget has been exceeded.
+
+This action has the following format:
+
+```ts
+export interface CampaignBudgetExceededAction {
+ action: "campaignBudgetExceeded"
+ code: string
+}
+```
+
+Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.CampaignBudgetExceededAction/index.html.md) for details on the object’s properties.
# Links between Product Module and Other Modules
@@ -25914,980 +26507,6 @@ createRemoteLinkStep({
```
-# Configure Selling Products
-
-In this guide, you'll learn how to set up and configure your products based on their shipping and inventory requirements, the product type, how you want to sell them, or your commerce ecosystem.
-
-The concepts in this guide are applicable starting from Medusa v2.5.1.
-
-## Scenario
-
-Businesses can have different selling requirements:
-
-1. They may sell physical or digital items.
-2. They may sell items that don't require shipping or inventory management, such as selling digital products, services, or booking appointments.
-3. They may sell items whose inventory is managed by an external system, such as an ERP.
-
-Medusa supports these different selling requirements by allowing you to configure shipping and inventory requirements for products and their variants. This guide explains how these configurations work, then provides examples of setting up different use cases.
-
-***
-
-## Configuring Shipping Requirements
-
-The Medusa application defines a link between the `Product` data model and a [ShippingProfile](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/concepts#shipping-profile/index.html.md) in the [Fulfillment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/index.html.md), allowing you to associate a product with a shipping profile.
-
-When a product is associated with a shipping profile, its variants require shipping and fulfillment when purchased. This is useful for physical products or digital products that require custom fulfillment.
-
-If a product doesn't have an associated shipping profile, its variants don't require shipping and fulfillment when purchased. This is useful for digital products, for example, that don't require shipping.
-
-### Overriding Shipping Requirements for Variants
-
-A product variant whose inventory is managed by Medusa (its `manage_inventory` property is enabled) has an [inventory item](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts#inventoryitem/index.html.md). The inventory item has a `requires_shipping` property that can be used to override its shipping requirement. This is useful if the product has an associated shipping profile but you want to disable shipping for a specific variant, or vice versa.
-
-Learn more about product variant's inventory in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/variant-inventory/index.html.md).
-
-When a product variant is purchased, the Medusa application decides whether the purchased item requires shipping in the following order:
-
-1. The product variant has an inventory item. In this case, the Medusa application uses the inventory item's `requires_shipping` property to determine if the item requires shipping.
-2. If the product variant doesn't have an inventory item, the Medusa application checks whether the product has an associated shipping profile to determine if the item requires shipping.
-
-***
-
-## Use Case Examples
-
-By combining configurations of shipment requirements and inventory management, you can set up your products to support your use case:
-
-|Use Case|Configurations|Example|
-|---|---|---|---|---|
-|Item that's shipped on purchase, and its variant inventory is managed by the Medusa application.||Any stock-kept item (clothing, for example), whose inventory is managed in the Medusa application.|
-|Item that's shipped on purchase, but its variant inventory is managed externally (not by Medusa) or it has infinite stock.||Any stock-kept item (clothing, for example), whose inventory is managed in an ERP or has infinite stock.|
-|Item that's not shipped on purchase, but its variant inventory is managed by Medusa.||Digital products, such as licenses, that don't require shipping but have a limited quantity.|
-|Item that doesn't require shipping and its variant inventory isn't managed by Medusa.|||
-
-
-# Pricing Concepts
-
-In this document, you’ll learn about the main concepts in the Pricing Module.
-
-## Price Set
-
-A [PriceSet](https://docs.medusajs.com/references/pricing/models/PriceSet/index.html.md) represents a collection of prices that are linked to a resource (for example, a product or a shipping option).
-
-Each of these prices are represented by the [Price data module](https://docs.medusajs.com/references/pricing/models/Price/index.html.md).
-
-
-
-***
-
-## Price List
-
-A [PriceList](https://docs.medusajs.com/references/pricing/models/PriceList/index.html.md) is a group of prices only enabled if their conditions and rules are satisfied.
-
-A price list has optional `start_date` and `end_date` properties that indicate the date range in which a price list can be applied.
-
-Its associated prices are represented by the `Price` data model.
-
-
-# Product Variant Inventory
-
-# Product Variant Inventory
-
-In this guide, you'll learn about the inventory management features related to product variants.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/products/variants#manage-product-variant-inventory/index.html.md) to learn how to manage inventory of product variants.
-
-## Configure Inventory Management of Product Variants
-
-A product variant, represented by the [ProductVariant](https://docs.medusajs.com/references/product/models/ProductVariant/index.html.md) data model, has a `manage_inventory` field that's disabled by default. This field indicates whether you'll manage the inventory quantity of the product variant in the Medusa application. You can also keep `manage_inventory` disabled if you manage the product's inventory in an external system, such as an ERP.
-
-The Product Module doesn't provide inventory-management features. Instead, the Medusa application uses the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md) to manage inventory for products and variants. When `manage_inventory` is disabled, the Medusa application always considers the product variant to be in stock. This is useful if your product's variants aren't items that can be stocked, such as digital products, or they don't have a limited stock quantity.
-
-When `manage_inventory` is enabled, the Medusa application tracks the inventory of the product variant using the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md). For example, when a customer purchases a product variant, the Medusa application decrements the stocked quantity of the product variant.
-
-***
-
-## How the Medusa Application Manages Inventory
-
-When a product variant has `manage_inventory` enabled, the Medusa application creates an inventory item using the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md) and links it to the product variant.
-
-
-
-The inventory item has one or more locations, called inventory levels, that represent the stock quantity of the product variant at a specific location. This allows you to manage inventory across multiple warehouses, such as a warehouse in the US and another in Europe.
-
-
-
-Learn more about inventory concepts in the [Inventory Module's documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts/index.html.md).
-
-The Medusa application represents and manages stock locations using the [Stock Location Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/index.html.md). It creates a read-only link between the `InventoryLevel` and `StockLocation` data models so that it can retrieve the stock location of an inventory level.
-
-
-
-Learn more about the Stock Location Module in the [Stock Location Module's documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/concepts/index.html.md).
-
-### Product Inventory in Storefronts
-
-When a storefront sends a request to the Medusa application, it must always pass a [publishable API key](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/publishable-api-keys/index.html.md) in the request header. This API key specifies the sales channels, available through the [Sales Channel Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/index.html.md), of the storefront.
-
-The Medusa application links sales channels to stock locations, indicating the locations available for a specific sales channel. So, all inventory-related operations are scoped by the sales channel and its associated stock locations.
-
-For example, the availability of a product variant is determined by the `stocked_quantity` of its inventory level at the stock location linked to the storefront's sales channel.
-
-
-
-***
-
-## Variant Back Orders
-
-Product variants have an `allow_backorder` field that's disabled by default. When enabled, the Medusa application allows customers to purchase the product variant even when it's out of stock. Use this when your product variant is available through on-demand or pre-order purchase.
-
-You can also allow customers to subscribe to restock notifications of a product variant as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/recipes/commerce-automation/restock-notification/index.html.md).
-
-***
-
-## Additional Resources
-
-The following guides provide more details on inventory management in the Medusa application:
-
-- [Inventory Kits in the Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-kit/index.html.md): Learn how you can implement bundled or multi-part products through the Inventory Module.
-- [Configure Selling Products](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/selling-products/index.html.md): Learn how to use inventory management to support different use cases when selling products.
-- [Inventory in Flows](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-in-flows/index.html.md): Learn how Medusa utilizes inventory management in different flows.
-- [Storefront guide: how to retrieve a product variant's inventory details](https://docs.medusajs.com/resources/storefront-development/products/inventory/index.html.md).
-
-
-# Links between Pricing Module and Other Modules
-
-This document showcases the module links defined between the Pricing Module and other commerce modules.
-
-## Summary
-
-The Pricing Module has the following links to other modules:
-
-|First Data Model|Second Data Model|Type|Description|
-|---|---|---|---|
-| in ||Stored||
-| in ||Stored||
-
-***
-
-## Fulfillment Module
-
-The Fulfillment Module provides fulfillment-related functionalities, including shipping options that the customer chooses from when they place their order. However, it doesn't provide pricing-related functionalities for these options.
-
-Medusa defines a link between the `PriceSet` and `ShippingOption` data models. A shipping option's price is stored as a price set.
-
-
-
-### Retrieve with Query
-
-To retrieve the shipping option of a price set with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `shipping_option.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: priceSets } = await query.graph({
- entity: "price_set",
- fields: [
- "shipping_option.*",
- ],
-})
-
-// priceSets.shipping_option
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: priceSets } = useQueryGraphStep({
- entity: "price_set",
- fields: [
- "shipping_option.*",
- ],
-})
-
-// priceSets.shipping_option
-```
-
-### Manage with Link
-
-To manage the price set of a shipping option, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.FULFILLMENT]: {
- shipping_option_id: "so_123",
- },
- [Modules.PRICING]: {
- price_set_id: "pset_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.FULFILLMENT]: {
- shipping_option_id: "so_123",
- },
- [Modules.PRICING]: {
- price_set_id: "pset_123",
- },
-})
-```
-
-***
-
-## Product Module
-
-The Product Module doesn't store or manage the prices of product variants.
-
-Medusa defines a link between the `ProductVariant` and the `PriceSet`. A product variant’s prices are stored as prices belonging to a price set.
-
-
-
-So, when you want to add prices for a product variant, you create a price set and add the prices to it.
-
-You can then benefit from adding rules to prices or using the `calculatePrices` method to retrieve the price of a product variant within a specified context.
-
-### Retrieve with Query
-
-To retrieve the variant of a price set with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variant.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: priceSets } = await query.graph({
- entity: "price_set",
- fields: [
- "variant.*",
- ],
-})
-
-// priceSets.variant
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: priceSets } = useQueryGraphStep({
- entity: "price_set",
- fields: [
- "variant.*",
- ],
-})
-
-// priceSets.variant
-```
-
-### Manage with Link
-
-To manage the price set of a variant, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.PRODUCT]: {
- variant_id: "variant_123",
- },
- [Modules.PRICING]: {
- price_set_id: "pset_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.PRODUCT]: {
- variant_id: "variant_123",
- },
- [Modules.PRICING]: {
- price_set_id: "pset_123",
- },
-})
-```
-
-
-# Prices Calculation
-
-In this document, you'll learn how prices are calculated when you use the [calculatePrices method](https://docs.medusajs.com/references/pricing/calculatePrices/index.html.md) of the Pricing Module's main service.
-
-## calculatePrices Method
-
-The [calculatePrices method](https://docs.medusajs.com/references/pricing/calculatePrices/index.html.md) accepts as parameters the ID of one or more price sets and a context.
-
-It returns a price object with the best matching price for each price set.
-
-### Calculation Context
-
-The calculation context is an optional object passed as a second parameter to the `calculatePrices` method. It accepts rules to restrict the selected prices in the price set.
-
-For example:
-
-```ts
-const price = await pricingModuleService.calculatePrices(
- { id: [priceSetId] },
- {
- context: {
- currency_code: currencyCode,
- region_id: "reg_123",
- },
- }
-)
-```
-
-In this example, you retrieve the prices in a price set for the specified currency code and region ID.
-
-### Returned Price Object
-
-For each price set, the `calculatePrices` method selects two prices:
-
-- A calculated price: Either a price that belongs to a price list and best matches the specified context, or the same as the original price.
-- An original price, which is either:
- - The same price as the calculated price if the price list it belongs to is of type `override`;
- - Or a price that doesn't belong to a price list and best matches the specified context.
-
-Both prices are returned in an object that has the following properties:
-
-- id: (\`string\`) The ID of the price set from which the price was selected.
-- is\_calculated\_price\_price\_list: (\`boolean\`) Whether the calculated price belongs to a price list.
-- calculated\_amount: (\`number\`) The amount of the calculated price, or \`null\` if there isn't a calculated price. This is the amount shown to the customer.
-- is\_original\_price\_price\_list: (\`boolean\`) Whether the original price belongs to a price list.
-- original\_amount: (\`number\`) The amount of the original price, or \`null\` if there isn't an original price. This amount is useful to compare with the \`calculated\_amount\`, such as to check for discounted value.
-- currency\_code: (\`string\`) The currency code of the calculated price, or \`null\` if there isn't a calculated price.
-- is\_calculated\_price\_tax\_inclusive: (\`boolean\`) Whether the calculated price is tax inclusive. Learn more about tax-inclusivity in \[this document]\(../tax-inclusive-pricing/page.mdx)
-- is\_original\_price\_tax\_inclusive: (\`boolean\`) Whether the original price is tax inclusive. Learn more about tax-inclusivity in \[this document]\(../tax-inclusive-pricing/page.mdx)
-- calculated\_price: (\`object\`) The calculated price's price details.
-
- - id: (\`string\`) The ID of the price.
-
- - price\_list\_id: (\`string\`) The ID of the associated price list.
-
- - price\_list\_type: (\`string\`) The price list's type. For example, \`sale\`.
-
- - min\_quantity: (\`number\`) The price's min quantity condition.
-
- - max\_quantity: (\`number\`) The price's max quantity condition.
-- original\_price: (\`object\`) The original price's price details.
-
- - id: (\`string\`) The ID of the price.
-
- - price\_list\_id: (\`string\`) The ID of the associated price list.
-
- - price\_list\_type: (\`string\`) The price list's type. For example, \`sale\`.
-
- - min\_quantity: (\`number\`) The price's min quantity condition.
-
- - max\_quantity: (\`number\`) The price's max quantity condition.
-
-***
-
-## Examples
-
-Consider the following price set:
-
-```ts
-const priceSet = await pricingModuleService.createPriceSets({
- prices: [
- // default price
- {
- amount: 500,
- currency_code: "EUR",
- rules: {},
- },
- // prices with rules
- {
- amount: 400,
- currency_code: "EUR",
- rules: {
- region_id: "reg_123",
- },
- },
- {
- amount: 450,
- currency_code: "EUR",
- rules: {
- city: "krakow",
- },
- },
- {
- amount: 500,
- currency_code: "EUR",
- rules: {
- city: "warsaw",
- region_id: "reg_123",
- },
- },
- ],
-})
-```
-
-### Default Price Selection
-
-### Code
-
-```ts
-const price = await pricingModuleService.calculatePrices(
- { id: [priceSet.id] },
- {
- context: {
- currency_code: "EUR"
- }
- }
-)
-```
-
-### Result
-
-### Calculate Prices with Rules
-
-### Code
-
-```ts
-const price = await pricingModuleService.calculatePrices(
- { id: [priceSet.id] },
- {
- context: {
- currency_code: "EUR",
- region_id: "reg_123",
- city: "krakow"
- }
- }
-)
-```
-
-### Result
-
-### Price Selection with Price List
-
-### Code
-
-```ts
-const priceList = pricingModuleService.createPriceLists([{
- title: "Summer Price List",
- description: "Price list for summer sale",
- starts_at: Date.parse("01/10/2023").toString(),
- ends_at: Date.parse("31/10/2023").toString(),
- rules: {
- region_id: ['PL']
- },
- type: "sale",
- prices: [
- {
- amount: 400,
- currency_code: "EUR",
- price_set_id: priceSet.id,
- },
- {
- amount: 450,
- currency_code: "EUR",
- price_set_id: priceSet.id,
- },
- ],
-}]);
-
-const price = await pricingModuleService.calculatePrices(
- { id: [priceSet.id] },
- {
- context: {
- currency_code: "EUR",
- region_id: "PL",
- city: "krakow"
- }
- }
-)
-```
-
-### Result
-
-
-# Price Rules
-
-In this document, you'll learn about price rules for price sets and price lists.
-
-## Price Rule
-
-You can restrict prices by rules. Each rule of a price is represented by the [PriceRule data model](https://docs.medusajs.com/references/pricing/models/PriceRule/index.html.md).
-
-The `Price` data model has a `rules_count` property, which indicates how many rules, represented by `PriceRule`, are applied to the price.
-
-For exmaple, you create a price restricted to `10557` zip codes.
-
-
-
-A price can have multiple price rules.
-
-For example, a price can be restricted by a region and a zip code.
-
-
-
-***
-
-## Price List Rules
-
-Rules applied to a price list are represented by the [PriceListRule data model](https://docs.medusajs.com/references/pricing/models/PriceListRule/index.html.md).
-
-The `rules_count` property of a `PriceList` indicates how many rules are applied to it.
-
-
-
-
-# Tax-Inclusive Pricing
-
-In this document, you’ll learn about tax-inclusive pricing and how it's used when calculating prices.
-
-## What is Tax-Inclusive Pricing?
-
-A tax-inclusive price is a price of a resource that includes taxes. Medusa calculates the tax amount from the price rather than adds the amount to it.
-
-For example, if a product’s price is $50, the tax rate is 2%, and tax-inclusive pricing is enabled, then the product's price is $49, and the applied tax amount is $1.
-
-***
-
-## How is Tax-Inclusive Pricing Set?
-
-The [PricePreference data model](https://docs.medusajs.com/references/pricing/models/PricePreference/index.html.md) holds the tax-inclusive setting for a context. It has two properties that indicate the context:
-
-- `attribute`: The name of the attribute to compare against. For example, `region_id` or `currency_code`.
-- `value`: The attribute’s value. For example, `reg_123` or `usd`.
-
-Only `region_id` and `currency_code` are supported as an `attribute` at the moment.
-
-The `is_tax_inclusive` property indicates whether tax-inclusivity is enabled in the specified context.
-
-For example:
-
-```json
-{
- "attribute": "currency_code",
- "value": "USD",
- "is_tax_inclusive": true,
-}
-```
-
-In this example, tax-inclusivity is enabled for the `USD` currency code.
-
-***
-
-## Tax-Inclusive Pricing in Price Calculation
-
-### Tax Context
-
-As mentioned in the [Price Calculation documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#calculation-context/index.html.md), The `calculatePrices` method accepts as a parameter a calculation context.
-
-To get accurate tax results, pass the `region_id` and / or `currency_code` in the calculation context.
-
-### Returned Tax Properties
-
-The `calculatePrices` method returns two properties related to tax-inclusivity:
-
-Learn more about the returned properties in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#returned-price-object/index.html.md).
-
-- `is_calculated_price_tax_inclusive`: Whether the selected `calculated_price` is tax-inclusive.
-- `is_original_price_tax_inclusive` : Whether the selected `original_price` is tax-inclusive.
-
-A price is considered tax-inclusive if:
-
-1. It belongs to the region or currency code specified in the calculation context;
-2. and the region or currency code has a price preference with `is_tax_inclusive` enabled.
-
-### Tax Context Precedence
-
-A region’s price preference’s `is_tax_inclusive`'s value takes higher precedence in determining whether a price is tax-inclusive if:
-
-- both the `region_id` and `currency_code` are provided in the calculation context;
-- the selected price belongs to the region;
-- and the region has a price preference
-
-
-# Links between Stock Location Module and Other Modules
-
-This document showcases the module links defined between the Stock Location Module and other commerce modules.
-
-## Summary
-
-The Stock Location Module has the following links to other modules:
-
-Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
-
-|First Data Model|Second Data Model|Type|Description|
-|---|---|---|---|
-| in ||Stored||
-| in ||Stored||
-| in ||Stored||
-| in ||Stored||
-
-***
-
-## Fulfillment Module
-
-A fulfillment set can be conditioned to a specific stock location.
-
-Medusa defines a link between the `FulfillmentSet` and `StockLocation` data models.
-
-
-
-Medusa also defines a link between the `FulfillmentProvider` and `StockLocation` data models to indicate the providers that can be used in a location.
-
-
-
-### Retrieve with Query
-
-To retrieve the fulfillment sets of a stock location with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `fulfillment_sets.*` in `fields`:
-
-To retrieve the fulfillment providers, pass `fulfillment_providers.*` in `fields`.
-
-### query.graph
-
-```ts
-const { data: stockLocations } = await query.graph({
- entity: "stock_location",
- fields: [
- "fulfillment_sets.*",
- ],
-})
-
-// stockLocations.fulfillment_sets
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: stockLocations } = useQueryGraphStep({
- entity: "stock_location",
- fields: [
- "fulfillment_sets.*",
- ],
-})
-
-// stockLocations.fulfillment_sets
-```
-
-### Manage with Link
-
-To manage the stock location of a fulfillment set, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.STOCK_LOCATION]: {
- stock_location_id: "sloc_123",
- },
- [Modules.FULFILLMENT]: {
- fulfillment_set_id: "fset_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.STOCK_LOCATION]: {
- stock_location_id: "sloc_123",
- },
- [Modules.FULFILLMENT]: {
- fulfillment_set_id: "fset_123",
- },
-})
-```
-
-***
-
-## Inventory Module
-
-Medusa defines a read-only link between the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md)'s `InventoryLevel` data model and the `StockLocation` data model. Because the link is read-only from the `InventoryLevel`'s side, you can only retrieve the stock location of an inventory level, and not the other way around.
-
-### Retrieve with Query
-
-To retrieve the stock locations of an inventory level with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `stock_locations.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: inventoryLevels } = await query.graph({
- entity: "inventory_level",
- fields: [
- "stock_locations.*",
- ],
-})
-
-// inventoryLevels.stock_locations
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: inventoryLevels } = useQueryGraphStep({
- entity: "inventory_level",
- fields: [
- "stock_locations.*",
- ],
-})
-
-// inventoryLevels.stock_locations
-```
-
-***
-
-## Sales Channel Module
-
-A stock location is associated with a sales channel. This scopes inventory quantities in a stock location by the associated sales channel.
-
-Medusa defines a link between the `SalesChannel` and `StockLocation` data models.
-
-
-
-### Retrieve with Query
-
-To retrieve the sales channels of a stock location with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channels.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: stockLocations } = await query.graph({
- entity: "stock_location",
- fields: [
- "sales_channels.*",
- ],
-})
-
-// stockLocations.sales_channels
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: stockLocations } = useQueryGraphStep({
- entity: "stock_location",
- fields: [
- "sales_channels.*",
- ],
-})
-
-// stockLocations.sales_channels
-```
-
-### Manage with Link
-
-To manage the stock locations of a sales channel, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
- },
- [Modules.STOCK_LOCATION]: {
- sales_channel_id: "sloc_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
- },
- [Modules.STOCK_LOCATION]: {
- sales_channel_id: "sloc_123",
- },
-})
-```
-
-
-# Stock Location Concepts
-
-In this document, you’ll learn about the main concepts in the Stock Location Module.
-
-## Stock Location
-
-A stock location, represented by the `StockLocation` data model, represents a location where stock items are kept. For example, a warehouse.
-
-Medusa uses stock locations to provide inventory details, from the Inventory Module, per location.
-
-***
-
-## StockLocationAddress
-
-The `StockLocationAddress` data model belongs to the `StockLocation` data model. It provides more detailed information of the location, such as country code or street address.
-
-
-# Promotion Actions
-
-In this document, you’ll learn about promotion actions and how they’re computed using the [computeActions method](https://docs.medusajs.com/references/promotion/computeActions/index.html.md).
-
-## computeActions Method
-
-The Promotion Module's main service has a [computeActions method](https://docs.medusajs.com/references/promotion/computeActions/index.html.md) that returns an array of actions to perform on a cart when one or more promotions are applied.
-
-Actions inform you what adjustment must be made to a cart item or shipping method. Each action is an object having the `action` property indicating the type of action.
-
-***
-
-## Action Types
-
-### `addItemAdjustment` Action
-
-The `addItemAdjustment` action indicates that an adjustment must be made to an item. For example, removing $5 off its amount.
-
-This action has the following format:
-
-```ts
-export interface AddItemAdjustmentAction {
- action: "addItemAdjustment"
- item_id: string
- amount: number
- code: string
- description?: string
-}
-```
-
-This action means that a new record should be created of the `LineItemAdjustment` data model in the Cart Module, or `OrderLineItemAdjustment` data model in the Order Module.
-
-Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.AddItemAdjustmentAction/index.html.md) for details on the object’s properties.
-
-### `removeItemAdjustment` Action
-
-The `removeItemAdjustment` action indicates that an adjustment must be removed from a line item. For example, remove the $5 discount.
-
-The `computeActions` method accepts any previous item adjustments in the `items` property of the second parameter.
-
-This action has the following format:
-
-```ts
-export interface RemoveItemAdjustmentAction {
- action: "removeItemAdjustment"
- adjustment_id: string
- description?: string
- code: string
-}
-```
-
-This action means that a new record should be removed of the `LineItemAdjustment` (or `OrderLineItemAdjustment`) with the specified ID in the `adjustment_id` property.
-
-Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.RemoveItemAdjustmentAction/index.html.md) for details on the object’s properties.
-
-### `addShippingMethodAdjustment` Action
-
-The `addShippingMethodAdjustment` action indicates that an adjustment must be made on a shipping method. For example, make the shipping method free.
-
-This action has the following format:
-
-```ts
-export interface AddShippingMethodAdjustment {
- action: "addShippingMethodAdjustment"
- shipping_method_id: string
- amount: number
- code: string
- description?: string
-}
-```
-
-This action means that a new record should be created of the `ShippingMethodAdjustment` data model in the Cart Module, or `OrderShippingMethodAdjustment` data model in the Order Module.
-
-Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.AddShippingMethodAdjustment/index.html.md) for details on the object’s properties.
-
-### `removeShippingMethodAdjustment` Action
-
-The `removeShippingMethodAdjustment` action indicates that an adjustment must be removed from a shipping method. For example, remove the free shipping discount.
-
-The `computeActions` method accepts any previous shipping method adjustments in the `shipping_methods` property of the second parameter.
-
-This action has the following format:
-
-```ts
-export interface RemoveShippingMethodAdjustment {
- action: "removeShippingMethodAdjustment"
- adjustment_id: string
- code: string
-}
-```
-
-When the Medusa application receives this action type, it removes the `ShippingMethodAdjustment` (or `OrderShippingMethodAdjustment`) with the specified ID in the `adjustment_id` property.
-
-Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.RemoveShippingMethodAdjustment/index.html.md) for details on the object’s properties.
-
-### `campaignBudgetExceeded` Action
-
-When the `campaignBudgetExceeded` action is returned, the promotions within a campaign can no longer be used as the campaign budget has been exceeded.
-
-This action has the following format:
-
-```ts
-export interface CampaignBudgetExceededAction {
- action: "campaignBudgetExceeded"
- code: string
-}
-```
-
-Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.CampaignBudgetExceededAction/index.html.md) for details on the object’s properties.
-
-
# Application Method
In this document, you'll learn what an application method is.
@@ -27191,6 +26810,798 @@ createRemoteLinkStep({
```
+# Links between Sales Channel Module and Other Modules
+
+This document showcases the module links defined between the Sales Channel Module and other commerce modules.
+
+## Summary
+
+The Sales Channel Module has the following links to other modules:
+
+Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
+
+|First Data Model|Second Data Model|Type|Description|
+|---|---|---|---|
+| in ||Stored||
+| in ||Read-only||
+| in ||Read-only||
+| in ||Stored||
+|| in |Stored||
+
+***
+
+## API Key Module
+
+A publishable API key allows you to easily specify the sales channel scope in a client request.
+
+Medusa defines a link between the `ApiKey` and the `SalesChannel` data models.
+
+
+
+### Retrieve with Query
+
+To retrieve the API keys associated with a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `publishable_api_keys.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: salesChannels } = await query.graph({
+ entity: "sales_channel",
+ fields: [
+ "publishable_api_keys.*",
+ ],
+})
+
+// salesChannels.publishable_api_keys
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: salesChannels } = useQueryGraphStep({
+ entity: "sales_channel",
+ fields: [
+ "publishable_api_keys.*",
+ ],
+})
+
+// salesChannels.publishable_api_keys
+```
+
+### Manage with Link
+
+To manage the sales channels of an API key, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.API_KEY]: {
+ api_key_id: "apk_123",
+ },
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.API_KEY]: {
+ api_key_id: "apk_123",
+ },
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+})
+```
+
+***
+
+## Cart Module
+
+Medusa defines a read-only link between the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model and the `SalesChannel` data model. Because the link is read-only from the `Cart`'s side, you can only retrieve the sales channel of a cart, and not the other way around.
+
+### Retrieve with Query
+
+To retrieve the sales channel of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channel.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: carts } = await query.graph({
+ entity: "cart",
+ fields: [
+ "sales_channel.*",
+ ],
+})
+
+// carts.sales_channel
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: carts } = useQueryGraphStep({
+ entity: "cart",
+ fields: [
+ "sales_channel.*",
+ ],
+})
+
+// carts.sales_channel
+```
+
+***
+
+## Order Module
+
+Medusa defines a read-only link between the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `Order` data model and the `SalesChannel` data model. Because the link is read-only from the `Order`'s side, you can only retrieve the sales channel of an order, and not the other way around.
+
+### Retrieve with Query
+
+To retrieve the sales channel of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channel.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: orders } = await query.graph({
+ entity: "order",
+ fields: [
+ "sales_channel.*",
+ ],
+})
+
+// orders.sales_channel
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: orders } = useQueryGraphStep({
+ entity: "order",
+ fields: [
+ "sales_channel.*",
+ ],
+})
+
+// orders.sales_channel
+```
+
+***
+
+## Product Module
+
+A product has different availability for different sales channels. Medusa defines a link between the `Product` and the `SalesChannel` data models.
+
+
+
+A product can be available in more than one sales channel. You can retrieve only the products of a sales channel.
+
+### Retrieve with Query
+
+To retrieve the products of a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `products.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: salesChannels } = await query.graph({
+ entity: "sales_channel",
+ fields: [
+ "products.*",
+ ],
+})
+
+// salesChannels.products
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: salesChannels } = useQueryGraphStep({
+ entity: "sales_channel",
+ fields: [
+ "products.*",
+ ],
+})
+
+// salesChannels.products
+```
+
+### Manage with Link
+
+To manage the sales channels of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+})
+```
+
+***
+
+## Stock Location Module
+
+A stock location is associated with a sales channel. This scopes inventory quantities associated with that stock location by the associated sales channel.
+
+Medusa defines a link between the `SalesChannel` and `StockLocation` data models.
+
+
+
+### Retrieve with Query
+
+To retrieve the stock locations of a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `stock_locations.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: salesChannels } = await query.graph({
+ entity: "sales_channel",
+ fields: [
+ "stock_locations.*",
+ ],
+})
+
+// salesChannels.stock_locations
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: salesChannels } = useQueryGraphStep({
+ entity: "sales_channel",
+ fields: [
+ "stock_locations.*",
+ ],
+})
+
+// salesChannels.stock_locations
+```
+
+### Manage with Link
+
+To manage the stock locations of a sales channel, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+ [Modules.STOCK_LOCATION]: {
+ sales_channel_id: "sloc_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+ [Modules.STOCK_LOCATION]: {
+ sales_channel_id: "sloc_123",
+ },
+})
+```
+
+
+# Publishable API Keys with Sales Channels
+
+In this document, you’ll learn what publishable API keys are and how to use them with sales channels.
+
+## Publishable API Keys with Sales Channels
+
+A publishable API key, provided by the API Key Module, is a client key scoped to one or more sales channels.
+
+When sending a request to a Store API route, you must pass a publishable API key in the header of the request:
+
+```bash
+curl http://localhost:9000/store/products \
+ x-publishable-api-key: {your_publishable_api_key}
+```
+
+The Medusa application infers the associated sales channels and ensures that only data relevant to the sales channel are used.
+
+***
+
+## How to Create a Publishable API Key?
+
+To create a publishable API key, either use the [Medusa Admin](https://docs.medusajs.com/user-guide/settings/developer/publishable-api-keys/index.html.md) or the [Admin API Routes](https://docs.medusajs.com/api/admin#publishable-api-keys).
+
+
+# Stock Location Concepts
+
+In this document, you’ll learn about the main concepts in the Stock Location Module.
+
+## Stock Location
+
+A stock location, represented by the `StockLocation` data model, represents a location where stock items are kept. For example, a warehouse.
+
+Medusa uses stock locations to provide inventory details, from the Inventory Module, per location.
+
+***
+
+## StockLocationAddress
+
+The `StockLocationAddress` data model belongs to the `StockLocation` data model. It provides more detailed information of the location, such as country code or street address.
+
+
+# Links between Stock Location Module and Other Modules
+
+This document showcases the module links defined between the Stock Location Module and other commerce modules.
+
+## Summary
+
+The Stock Location Module has the following links to other modules:
+
+Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
+
+|First Data Model|Second Data Model|Type|Description|
+|---|---|---|---|
+| in ||Stored||
+| in ||Stored||
+| in ||Stored||
+| in ||Stored||
+
+***
+
+## Fulfillment Module
+
+A fulfillment set can be conditioned to a specific stock location.
+
+Medusa defines a link between the `FulfillmentSet` and `StockLocation` data models.
+
+
+
+Medusa also defines a link between the `FulfillmentProvider` and `StockLocation` data models to indicate the providers that can be used in a location.
+
+
+
+### Retrieve with Query
+
+To retrieve the fulfillment sets of a stock location with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `fulfillment_sets.*` in `fields`:
+
+To retrieve the fulfillment providers, pass `fulfillment_providers.*` in `fields`.
+
+### query.graph
+
+```ts
+const { data: stockLocations } = await query.graph({
+ entity: "stock_location",
+ fields: [
+ "fulfillment_sets.*",
+ ],
+})
+
+// stockLocations.fulfillment_sets
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: stockLocations } = useQueryGraphStep({
+ entity: "stock_location",
+ fields: [
+ "fulfillment_sets.*",
+ ],
+})
+
+// stockLocations.fulfillment_sets
+```
+
+### Manage with Link
+
+To manage the stock location of a fulfillment set, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.STOCK_LOCATION]: {
+ stock_location_id: "sloc_123",
+ },
+ [Modules.FULFILLMENT]: {
+ fulfillment_set_id: "fset_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.STOCK_LOCATION]: {
+ stock_location_id: "sloc_123",
+ },
+ [Modules.FULFILLMENT]: {
+ fulfillment_set_id: "fset_123",
+ },
+})
+```
+
+***
+
+## Inventory Module
+
+Medusa defines a read-only link between the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md)'s `InventoryLevel` data model and the `StockLocation` data model. Because the link is read-only from the `InventoryLevel`'s side, you can only retrieve the stock location of an inventory level, and not the other way around.
+
+### Retrieve with Query
+
+To retrieve the stock locations of an inventory level with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `stock_locations.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: inventoryLevels } = await query.graph({
+ entity: "inventory_level",
+ fields: [
+ "stock_locations.*",
+ ],
+})
+
+// inventoryLevels.stock_locations
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: inventoryLevels } = useQueryGraphStep({
+ entity: "inventory_level",
+ fields: [
+ "stock_locations.*",
+ ],
+})
+
+// inventoryLevels.stock_locations
+```
+
+***
+
+## Sales Channel Module
+
+A stock location is associated with a sales channel. This scopes inventory quantities in a stock location by the associated sales channel.
+
+Medusa defines a link between the `SalesChannel` and `StockLocation` data models.
+
+
+
+### Retrieve with Query
+
+To retrieve the sales channels of a stock location with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channels.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: stockLocations } = await query.graph({
+ entity: "stock_location",
+ fields: [
+ "sales_channels.*",
+ ],
+})
+
+// stockLocations.sales_channels
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: stockLocations } = useQueryGraphStep({
+ entity: "stock_location",
+ fields: [
+ "sales_channels.*",
+ ],
+})
+
+// stockLocations.sales_channels
+```
+
+### Manage with Link
+
+To manage the stock locations of a sales channel, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+ [Modules.STOCK_LOCATION]: {
+ sales_channel_id: "sloc_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+ [Modules.STOCK_LOCATION]: {
+ sales_channel_id: "sloc_123",
+ },
+})
+```
+
+
+# Tax Calculation with the Tax Provider
+
+In this document, you’ll learn how tax lines are calculated and what a tax provider is.
+
+## Tax Lines Calculation
+
+Tax lines are calculated and retrieved using the [getTaxLines method of the Tax Module’s main service](https://docs.medusajs.com/references/tax/getTaxLines/index.html.md). It accepts an array of line items and shipping methods, and the context of the calculation.
+
+For example:
+
+```ts
+const taxLines = await taxModuleService.getTaxLines(
+ [
+ {
+ id: "cali_123",
+ product_id: "prod_123",
+ unit_price: 1000,
+ quantity: 1,
+ },
+ {
+ id: "casm_123",
+ shipping_option_id: "so_123",
+ unit_price: 2000,
+ },
+ ],
+ {
+ address: {
+ country_code: "us",
+ },
+ }
+)
+```
+
+The context object is used to determine which tax regions and rates to use in the calculation. It includes properties related to the address and customer.
+
+The example above retrieves the tax lines based on the tax region for the United States.
+
+The method returns tax lines for the line item and shipping methods. For example:
+
+```json
+[
+ {
+ "line_item_id": "cali_123",
+ "rate_id": "txr_1",
+ "rate": 10,
+ "code": "XXX",
+ "name": "Tax Rate 1"
+ },
+ {
+ "shipping_line_id": "casm_123",
+ "rate_id": "txr_2",
+ "rate": 5,
+ "code": "YYY",
+ "name": "Tax Rate 2"
+ }
+]
+```
+
+***
+
+## Using the Tax Provider in the Calculation
+
+The tax lines retrieved by the `getTaxLines` method are actually retrieved from the tax region’s provider.
+
+A tax module provider whose main service implements the logic to shape tax lines. Each tax region has a tax provider.
+
+The Tax Module provides a `system` tax provider that only transforms calculated item and shipping tax rates into the required return type.
+
+{/* ---
+
+TODO add once tax provider guide is updated + add module providers match other modules.
+
+## Create Tax Provider
+
+Refer to [this guide](/modules/tax/provider) to learn more about creating a tax provider. */}
+
+
+# Tax Module Options
+
+In this document, you'll learn about the options of the Tax Module.
+
+## providers
+
+The `providers` option is an array of either tax module providers or path to a file that defines a tax provider.
+
+When the Medusa application starts, these providers are registered and can be used to retrieve tax lines.
+
+```ts title="medusa-config.ts"
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+module.exports = defineConfig({
+ // ...
+ modules: [
+ {
+ resolve: "@medusajs/tax",
+ options: {
+ providers: [
+ {
+ resolve: "./path/to/my-provider",
+ id: "my-provider",
+ options: {
+ // ...
+ },
+ },
+ ],
+ },
+ },
+ ],
+})
+```
+
+The objects in the array accept the following properties:
+
+- `resolve`: A string indicating the package name of the module provider or the path to it.
+- `id`: A string indicating the provider's unique name or ID.
+- `options`: An optional object of the module provider's options.
+
+
+# Tax Rates and Rules
+
+In this document, you’ll learn about tax rates and rules.
+
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/tax-regions#manage-tax-rate-overrides/index.html.md) to learn how to manage tax rates using the dashboard.
+
+## What are Tax Rates?
+
+A tax rate is a percentage amount used to calculate the tax amount for each taxable item’s price, such as line items or shipping methods, in a cart. The sum of all calculated tax amounts are then added to the cart’s total as a tax total.
+
+Each tax region has a default tax rate. This tax rate is applied to all taxable items of a cart in that region.
+
+### Combinable Tax Rates
+
+Tax regions can have parent tax regions. To inherit the tax rates of the parent tax region, set the `is_combinable` of the child’s tax rates to `true`.
+
+Then, when tax rates are retrieved for a taxable item in the child region, both the child and the parent tax regions’ applicable rates are returned.
+
+***
+
+## Override Tax Rates with Rules
+
+You can create tax rates that override the default for specific conditions or rules.
+
+For example, you can have a default tax rate is 10%, but for products of type “Shirt” is %15.
+
+A tax region can have multiple tax rates, and each tax rate can have multiple tax rules. The [TaxRateRule data model](https://docs.medusajs.com/references/tax/models/TaxRateRule/index.html.md) represents a tax rate’s rule.
+
+
+
+These two properties of the data model identify the rule’s target:
+
+- `reference`: the name of the table in the database that this rule points to. For example, `product_type`.
+- `reference_id`: the ID of the data model’s record that this points to. For example, a product type’s ID.
+
+So, to override the default tax rate for product types “Shirt”, you create a tax rate and associate with it a tax rule whose `reference` is `product_type` and `reference_id` the ID of the “Shirt” product type.
+
+
+# Tax Region
+
+In this document, you’ll learn about tax regions and how to use them with the Region Module.
+
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/tax-regions/index.html.md) to learn how to manage tax regions using the dashboard.
+
+## What is a Tax Region?
+
+A tax region, represented by the [TaxRegion data model](https://docs.medusajs.com/references/tax/models/TaxRegion/index.html.md), stores tax settings related to a region that your store serves.
+
+Tax regions can inherit settings and rules from a parent tax region.
+
+Each tax region has tax rules and a tax provider.
+
+
# Links between Store Module and Other Modules
This document showcases the module links defined between the Store Module and other commerce modules.
@@ -27365,179 +27776,6 @@ if (!count) {
```
-# Tax Module Options
-
-In this document, you'll learn about the options of the Tax Module.
-
-## providers
-
-The `providers` option is an array of either tax module providers or path to a file that defines a tax provider.
-
-When the Medusa application starts, these providers are registered and can be used to retrieve tax lines.
-
-```ts title="medusa-config.ts"
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "@medusajs/tax",
- options: {
- providers: [
- {
- resolve: "./path/to/my-provider",
- id: "my-provider",
- options: {
- // ...
- },
- },
- ],
- },
- },
- ],
-})
-```
-
-The objects in the array accept the following properties:
-
-- `resolve`: A string indicating the package name of the module provider or the path to it.
-- `id`: A string indicating the provider's unique name or ID.
-- `options`: An optional object of the module provider's options.
-
-
-# Tax Rates and Rules
-
-In this document, you’ll learn about tax rates and rules.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/tax-regions#manage-tax-rate-overrides/index.html.md) to learn how to manage tax rates using the dashboard.
-
-## What are Tax Rates?
-
-A tax rate is a percentage amount used to calculate the tax amount for each taxable item’s price, such as line items or shipping methods, in a cart. The sum of all calculated tax amounts are then added to the cart’s total as a tax total.
-
-Each tax region has a default tax rate. This tax rate is applied to all taxable items of a cart in that region.
-
-### Combinable Tax Rates
-
-Tax regions can have parent tax regions. To inherit the tax rates of the parent tax region, set the `is_combinable` of the child’s tax rates to `true`.
-
-Then, when tax rates are retrieved for a taxable item in the child region, both the child and the parent tax regions’ applicable rates are returned.
-
-***
-
-## Override Tax Rates with Rules
-
-You can create tax rates that override the default for specific conditions or rules.
-
-For example, you can have a default tax rate is 10%, but for products of type “Shirt” is %15.
-
-A tax region can have multiple tax rates, and each tax rate can have multiple tax rules. The [TaxRateRule data model](https://docs.medusajs.com/references/tax/models/TaxRateRule/index.html.md) represents a tax rate’s rule.
-
-
-
-These two properties of the data model identify the rule’s target:
-
-- `reference`: the name of the table in the database that this rule points to. For example, `product_type`.
-- `reference_id`: the ID of the data model’s record that this points to. For example, a product type’s ID.
-
-So, to override the default tax rate for product types “Shirt”, you create a tax rate and associate with it a tax rule whose `reference` is `product_type` and `reference_id` the ID of the “Shirt” product type.
-
-
-# Tax Calculation with the Tax Provider
-
-In this document, you’ll learn how tax lines are calculated and what a tax provider is.
-
-## Tax Lines Calculation
-
-Tax lines are calculated and retrieved using the [getTaxLines method of the Tax Module’s main service](https://docs.medusajs.com/references/tax/getTaxLines/index.html.md). It accepts an array of line items and shipping methods, and the context of the calculation.
-
-For example:
-
-```ts
-const taxLines = await taxModuleService.getTaxLines(
- [
- {
- id: "cali_123",
- product_id: "prod_123",
- unit_price: 1000,
- quantity: 1,
- },
- {
- id: "casm_123",
- shipping_option_id: "so_123",
- unit_price: 2000,
- },
- ],
- {
- address: {
- country_code: "us",
- },
- }
-)
-```
-
-The context object is used to determine which tax regions and rates to use in the calculation. It includes properties related to the address and customer.
-
-The example above retrieves the tax lines based on the tax region for the United States.
-
-The method returns tax lines for the line item and shipping methods. For example:
-
-```json
-[
- {
- "line_item_id": "cali_123",
- "rate_id": "txr_1",
- "rate": 10,
- "code": "XXX",
- "name": "Tax Rate 1"
- },
- {
- "shipping_line_id": "casm_123",
- "rate_id": "txr_2",
- "rate": 5,
- "code": "YYY",
- "name": "Tax Rate 2"
- }
-]
-```
-
-***
-
-## Using the Tax Provider in the Calculation
-
-The tax lines retrieved by the `getTaxLines` method are actually retrieved from the tax region’s provider.
-
-A tax module provider whose main service implements the logic to shape tax lines. Each tax region has a tax provider.
-
-The Tax Module provides a `system` tax provider that only transforms calculated item and shipping tax rates into the required return type.
-
-{/* ---
-
-TODO add once tax provider guide is updated + add module providers match other modules.
-
-## Create Tax Provider
-
-Refer to [this guide](/modules/tax/provider) to learn more about creating a tax provider. */}
-
-
-# Tax Region
-
-In this document, you’ll learn about tax regions and how to use them with the Region Module.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/tax-regions/index.html.md) to learn how to manage tax regions using the dashboard.
-
-## What is a Tax Region?
-
-A tax region, represented by the [TaxRegion data model](https://docs.medusajs.com/references/tax/models/TaxRegion/index.html.md), stores tax settings related to a region that your store serves.
-
-Tax regions can inherit settings and rules from a parent tax region.
-
-Each tax region has tax rules and a tax provider.
-
-
# Emailpass Auth Module Provider
In this document, you’ll learn about the Emailpass auth module provider and how to install and use it in the Auth Module.
@@ -27600,88 +27838,6 @@ const hashConfig = \{
- [How to register a customer using email and password](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/register/index.html.md)
-# GitHub Auth Module Provider
-
-In this document, you’ll learn about the GitHub Auth Module Provider and how to install and use it in the Auth Module.
-
-The Github Auth Module Provider authenticates users with their GitHub account.
-
-Learn about the authentication flow in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route/index.html.md).
-
-***
-
-## Register the Github Auth Module Provider
-
-### Prerequisites
-
-- [Register GitHub App. When setting the Callback URL, set it to a URL in your frontend that later uses Medusa's callback route to validate the authentication.](https://docs.github.com/en/apps/creating-github-apps/setting-up-a-github-app/creating-a-github-app)
-- [Retrieve the client ID and client secret of your GitHub App](https://docs.github.com/en/rest/authentication/authenticating-to-the-rest-api?apiVersion=2022-11-28#using-basic-authentication)
-
-Add the module to the array of providers passed to the Auth Module:
-
-```ts title="medusa-config.ts"
-import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils"
-
-// ...
-
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "@medusajs/medusa/auth",
- dependencies: [Modules.CACHE, ContainerRegistrationKeys.LOGGER],
- options: {
- providers: [
- // other providers...
- {
- resolve: "@medusajs/medusa/auth-github",
- id: "github",
- options: {
- clientId: process.env.GITHUB_CLIENT_ID,
- clientSecret: process.env.GITHUB_CLIENT_SECRET,
- callbackUrl: process.env.GITHUB_CALLBACK_URL,
- },
- },
- ],
- },
- },
- ],
-})
-```
-
-### Environment Variables
-
-Make sure to add the necessary environment variables for the above options in `.env`:
-
-```plain
-GITHUB_CLIENT_ID=
-GITHUB_CLIENT_SECRET=
-GITHUB_CALLBACK_URL=
-```
-
-### Module Options
-
-|Configuration|Description|Required|
-|---|---|---|---|---|
-|\`clientId\`|A string indicating the client ID of your GitHub app.|Yes|
-|\`clientSecret\`|A string indicating the client secret of your GitHub app.|Yes|
-|\`callbackUrl\`|A string indicating the URL to redirect to in your frontend after the user completes their authentication in GitHub.|Yes|
-
-***
-
-## Override Callback URL During Authentication
-
-In many cases, you may have different callback URL for actor types. For example, you may redirect admin users to a different URL than customers after authentication.
-
-The [Authenticate or Login API Route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#login-route/index.html.md) can accept a `callback_url` body parameter to override the provider's `callbackUrl` option. Learn more in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#login-route/index.html.md).
-
-***
-
-## Examples
-
-- [How to implement third-party / social login in the storefront.](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/third-party-login/index.html.md).
-
-
# Google Auth Module Provider
In this document, you’ll learn about the Google Auth Module Provider and how to install and use it in the Auth Module.
@@ -27769,6 +27925,88 @@ The [Authenticate or Login API Route](https://docs.medusajs.com/Users/shahednass
- [How to implement Google social login in the storefront](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/third-party-login/index.html.md).
+# GitHub Auth Module Provider
+
+In this document, you’ll learn about the GitHub Auth Module Provider and how to install and use it in the Auth Module.
+
+The Github Auth Module Provider authenticates users with their GitHub account.
+
+Learn about the authentication flow in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route/index.html.md).
+
+***
+
+## Register the Github Auth Module Provider
+
+### Prerequisites
+
+- [Register GitHub App. When setting the Callback URL, set it to a URL in your frontend that later uses Medusa's callback route to validate the authentication.](https://docs.github.com/en/apps/creating-github-apps/setting-up-a-github-app/creating-a-github-app)
+- [Retrieve the client ID and client secret of your GitHub App](https://docs.github.com/en/rest/authentication/authenticating-to-the-rest-api?apiVersion=2022-11-28#using-basic-authentication)
+
+Add the module to the array of providers passed to the Auth Module:
+
+```ts title="medusa-config.ts"
+import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils"
+
+// ...
+
+module.exports = defineConfig({
+ // ...
+ modules: [
+ {
+ resolve: "@medusajs/medusa/auth",
+ dependencies: [Modules.CACHE, ContainerRegistrationKeys.LOGGER],
+ options: {
+ providers: [
+ // other providers...
+ {
+ resolve: "@medusajs/medusa/auth-github",
+ id: "github",
+ options: {
+ clientId: process.env.GITHUB_CLIENT_ID,
+ clientSecret: process.env.GITHUB_CLIENT_SECRET,
+ callbackUrl: process.env.GITHUB_CALLBACK_URL,
+ },
+ },
+ ],
+ },
+ },
+ ],
+})
+```
+
+### Environment Variables
+
+Make sure to add the necessary environment variables for the above options in `.env`:
+
+```plain
+GITHUB_CLIENT_ID=
+GITHUB_CLIENT_SECRET=
+GITHUB_CALLBACK_URL=
+```
+
+### Module Options
+
+|Configuration|Description|Required|
+|---|---|---|---|---|
+|\`clientId\`|A string indicating the client ID of your GitHub app.|Yes|
+|\`clientSecret\`|A string indicating the client secret of your GitHub app.|Yes|
+|\`callbackUrl\`|A string indicating the URL to redirect to in your frontend after the user completes their authentication in GitHub.|Yes|
+
+***
+
+## Override Callback URL During Authentication
+
+In many cases, you may have different callback URL for actor types. For example, you may redirect admin users to a different URL than customers after authentication.
+
+The [Authenticate or Login API Route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#login-route/index.html.md) can accept a `callback_url` body parameter to override the provider's `callbackUrl` option. Learn more in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#login-route/index.html.md).
+
+***
+
+## Examples
+
+- [How to implement third-party / social login in the storefront.](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/third-party-login/index.html.md).
+
+
# Stripe Module Provider
In this document, you’ll learn about the Stripe Module Provider and how to configure it in the Payment Module.
@@ -28146,338 +28384,338 @@ For each product variant, you:
## Workflows
-- [generateResetPasswordTokenWorkflow](https://docs.medusajs.com/references/medusa-workflows/generateResetPasswordTokenWorkflow/index.html.md)
- [createApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/createApiKeysWorkflow/index.html.md)
-- [linkSalesChannelsToApiKeyWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkSalesChannelsToApiKeyWorkflow/index.html.md)
- [deleteApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteApiKeysWorkflow/index.html.md)
-- [updateApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateApiKeysWorkflow/index.html.md)
- [revokeApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/revokeApiKeysWorkflow/index.html.md)
+- [linkSalesChannelsToApiKeyWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkSalesChannelsToApiKeyWorkflow/index.html.md)
+- [updateApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateApiKeysWorkflow/index.html.md)
- [createCustomerAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerAccountWorkflow/index.html.md)
-- [createCustomerAddressesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerAddressesWorkflow/index.html.md)
- [createCustomersWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomersWorkflow/index.html.md)
-- [deleteCustomerAddressesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomerAddressesWorkflow/index.html.md)
- [deleteCustomersWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomersWorkflow/index.html.md)
-- [removeCustomerAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeCustomerAccountWorkflow/index.html.md)
+- [createCustomerAddressesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerAddressesWorkflow/index.html.md)
+- [deleteCustomerAddressesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomerAddressesWorkflow/index.html.md)
- [updateCustomerAddressesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomerAddressesWorkflow/index.html.md)
- [updateCustomersWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomersWorkflow/index.html.md)
+- [removeCustomerAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeCustomerAccountWorkflow/index.html.md)
- [deleteCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomerGroupsWorkflow/index.html.md)
- [createCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerGroupsWorkflow/index.html.md)
-- [linkCustomersToCustomerGroupWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkCustomersToCustomerGroupWorkflow/index.html.md)
-- [updateCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomerGroupsWorkflow/index.html.md)
- [linkCustomerGroupsToCustomerWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkCustomerGroupsToCustomerWorkflow/index.html.md)
+- [updateCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomerGroupsWorkflow/index.html.md)
+- [linkCustomersToCustomerGroupWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkCustomersToCustomerGroupWorkflow/index.html.md)
+- [batchLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinksWorkflow/index.html.md)
+- [createLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/createLinksWorkflow/index.html.md)
+- [dismissLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/dismissLinksWorkflow/index.html.md)
+- [updateLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateLinksWorkflow/index.html.md)
+- [createDefaultsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createDefaultsWorkflow/index.html.md)
- [deleteFilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteFilesWorkflow/index.html.md)
- [uploadFilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/uploadFilesWorkflow/index.html.md)
-- [createDefaultsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createDefaultsWorkflow/index.html.md)
+- [addToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addToCartWorkflow/index.html.md)
+- [generateResetPasswordTokenWorkflow](https://docs.medusajs.com/references/medusa-workflows/generateResetPasswordTokenWorkflow/index.html.md)
+- [addShippingMethodToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addShippingMethodToCartWorkflow/index.html.md)
+- [completeCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/completeCartWorkflow/index.html.md)
+- [createCartCreditLinesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCartCreditLinesWorkflow/index.html.md)
+- [confirmVariantInventoryWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmVariantInventoryWorkflow/index.html.md)
+- [createCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCartWorkflow/index.html.md)
+- [createPaymentCollectionForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPaymentCollectionForCartWorkflow/index.html.md)
+- [listShippingOptionsForCartWithPricingWorkflow](https://docs.medusajs.com/references/medusa-workflows/listShippingOptionsForCartWithPricingWorkflow/index.html.md)
+- [deleteCartCreditLinesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCartCreditLinesWorkflow/index.html.md)
+- [listShippingOptionsForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/listShippingOptionsForCartWorkflow/index.html.md)
+- [refreshCartItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshCartItemsWorkflow/index.html.md)
+- [refreshCartShippingMethodsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshCartShippingMethodsWorkflow/index.html.md)
+- [updateCartPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCartPromotionsWorkflow/index.html.md)
+- [refreshPaymentCollectionForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshPaymentCollectionForCartWorkflow/index.html.md)
+- [transferCartCustomerWorkflow](https://docs.medusajs.com/references/medusa-workflows/transferCartCustomerWorkflow/index.html.md)
+- [updateCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCartWorkflow/index.html.md)
+- [updateLineItemInCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateLineItemInCartWorkflow/index.html.md)
+- [updateTaxLinesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxLinesWorkflow/index.html.md)
+- [validateExistingPaymentCollectionStep](https://docs.medusajs.com/references/medusa-workflows/validateExistingPaymentCollectionStep/index.html.md)
- [batchShippingOptionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchShippingOptionRulesWorkflow/index.html.md)
- [calculateShippingOptionsPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/calculateShippingOptionsPricesWorkflow/index.html.md)
-- [cancelFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelFulfillmentWorkflow/index.html.md)
- [createFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createFulfillmentWorkflow/index.html.md)
-- [createReturnFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnFulfillmentWorkflow/index.html.md)
-- [createShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShippingOptionsWorkflow/index.html.md)
-- [createServiceZonesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createServiceZonesWorkflow/index.html.md)
-- [createShippingProfilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShippingProfilesWorkflow/index.html.md)
+- [cancelFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelFulfillmentWorkflow/index.html.md)
- [createShipmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShipmentWorkflow/index.html.md)
+- [createServiceZonesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createServiceZonesWorkflow/index.html.md)
+- [createReturnFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnFulfillmentWorkflow/index.html.md)
- [deleteFulfillmentSetsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteFulfillmentSetsWorkflow/index.html.md)
-- [deleteShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteShippingOptionsWorkflow/index.html.md)
-- [updateFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateFulfillmentWorkflow/index.html.md)
-- [updateServiceZonesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateServiceZonesWorkflow/index.html.md)
- [deleteServiceZonesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteServiceZonesWorkflow/index.html.md)
+- [createShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShippingOptionsWorkflow/index.html.md)
+- [createShippingProfilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShippingProfilesWorkflow/index.html.md)
+- [deleteShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteShippingOptionsWorkflow/index.html.md)
+- [updateServiceZonesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateServiceZonesWorkflow/index.html.md)
- [updateShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateShippingOptionsWorkflow/index.html.md)
- [markFulfillmentAsDeliveredWorkflow](https://docs.medusajs.com/references/medusa-workflows/markFulfillmentAsDeliveredWorkflow/index.html.md)
+- [updateFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateFulfillmentWorkflow/index.html.md)
- [updateShippingProfilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateShippingProfilesWorkflow/index.html.md)
- [validateFulfillmentDeliverabilityStep](https://docs.medusajs.com/references/medusa-workflows/validateFulfillmentDeliverabilityStep/index.html.md)
-- [addShippingMethodToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addShippingMethodToCartWorkflow/index.html.md)
-- [addToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addToCartWorkflow/index.html.md)
-- [createCartCreditLinesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCartCreditLinesWorkflow/index.html.md)
-- [completeCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/completeCartWorkflow/index.html.md)
-- [createPaymentCollectionForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPaymentCollectionForCartWorkflow/index.html.md)
-- [confirmVariantInventoryWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmVariantInventoryWorkflow/index.html.md)
-- [listShippingOptionsForCartWithPricingWorkflow](https://docs.medusajs.com/references/medusa-workflows/listShippingOptionsForCartWithPricingWorkflow/index.html.md)
-- [createCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCartWorkflow/index.html.md)
-- [listShippingOptionsForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/listShippingOptionsForCartWorkflow/index.html.md)
-- [deleteCartCreditLinesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCartCreditLinesWorkflow/index.html.md)
-- [refreshCartItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshCartItemsWorkflow/index.html.md)
-- [refreshPaymentCollectionForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshPaymentCollectionForCartWorkflow/index.html.md)
-- [updateCartPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCartPromotionsWorkflow/index.html.md)
-- [refreshCartShippingMethodsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshCartShippingMethodsWorkflow/index.html.md)
-- [updateLineItemInCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateLineItemInCartWorkflow/index.html.md)
-- [updateCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCartWorkflow/index.html.md)
-- [validateExistingPaymentCollectionStep](https://docs.medusajs.com/references/medusa-workflows/validateExistingPaymentCollectionStep/index.html.md)
-- [transferCartCustomerWorkflow](https://docs.medusajs.com/references/medusa-workflows/transferCartCustomerWorkflow/index.html.md)
-- [updateTaxLinesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxLinesWorkflow/index.html.md)
- [batchInventoryItemLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchInventoryItemLevelsWorkflow/index.html.md)
+- [deleteInventoryItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteInventoryItemWorkflow/index.html.md)
- [createInventoryItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createInventoryItemsWorkflow/index.html.md)
- [createInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createInventoryLevelsWorkflow/index.html.md)
- [bulkCreateDeleteLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/bulkCreateDeleteLevelsWorkflow/index.html.md)
-- [updateInventoryItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateInventoryItemsWorkflow/index.html.md)
- [deleteInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteInventoryLevelsWorkflow/index.html.md)
+- [updateInventoryItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateInventoryItemsWorkflow/index.html.md)
- [updateInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateInventoryLevelsWorkflow/index.html.md)
- [validateInventoryLevelsDelete](https://docs.medusajs.com/references/medusa-workflows/validateInventoryLevelsDelete/index.html.md)
-- [deleteInventoryItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteInventoryItemWorkflow/index.html.md)
-- [createInvitesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createInvitesWorkflow/index.html.md)
- [acceptInviteWorkflow](https://docs.medusajs.com/references/medusa-workflows/acceptInviteWorkflow/index.html.md)
+- [createInvitesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createInvitesWorkflow/index.html.md)
- [deleteInvitesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteInvitesWorkflow/index.html.md)
- [refreshInviteTokensWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshInviteTokensWorkflow/index.html.md)
-- [createLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/createLinksWorkflow/index.html.md)
-- [batchLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinksWorkflow/index.html.md)
-- [dismissLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/dismissLinksWorkflow/index.html.md)
-- [updateLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateLinksWorkflow/index.html.md)
- [deleteLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteLineItemsWorkflow/index.html.md)
- [createPaymentSessionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPaymentSessionsWorkflow/index.html.md)
- [createRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createRefundReasonsWorkflow/index.html.md)
-- [deletePaymentSessionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePaymentSessionsWorkflow/index.html.md)
- [deleteRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteRefundReasonsWorkflow/index.html.md)
- [updateRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRefundReasonsWorkflow/index.html.md)
-- [batchPriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchPriceListPricesWorkflow/index.html.md)
-- [createPriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPriceListPricesWorkflow/index.html.md)
-- [deletePriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePriceListsWorkflow/index.html.md)
-- [removePriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/removePriceListPricesWorkflow/index.html.md)
-- [createPriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPriceListsWorkflow/index.html.md)
-- [updatePriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePriceListsWorkflow/index.html.md)
-- [updatePriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePriceListPricesWorkflow/index.html.md)
-- [acceptOrderTransferValidationStep](https://docs.medusajs.com/references/medusa-workflows/acceptOrderTransferValidationStep/index.html.md)
+- [deletePaymentSessionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePaymentSessionsWorkflow/index.html.md)
+- [capturePaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/capturePaymentWorkflow/index.html.md)
+- [refundPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentWorkflow/index.html.md)
+- [processPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/processPaymentWorkflow/index.html.md)
+- [validateRefundStep](https://docs.medusajs.com/references/medusa-workflows/validateRefundStep/index.html.md)
+- [refundPaymentsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentsWorkflow/index.html.md)
+- [validatePaymentsRefundStep](https://docs.medusajs.com/references/medusa-workflows/validatePaymentsRefundStep/index.html.md)
+- [acceptOrderTransferWorkflow](https://docs.medusajs.com/references/medusa-workflows/acceptOrderTransferWorkflow/index.html.md)
- [addOrderLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/addOrderLineItemsWorkflow/index.html.md)
+- [archiveOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/archiveOrderWorkflow/index.html.md)
- [beginClaimOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginClaimOrderValidationStep/index.html.md)
- [beginClaimOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginClaimOrderWorkflow/index.html.md)
-- [acceptOrderTransferWorkflow](https://docs.medusajs.com/references/medusa-workflows/acceptOrderTransferWorkflow/index.html.md)
-- [archiveOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/archiveOrderWorkflow/index.html.md)
+- [acceptOrderTransferValidationStep](https://docs.medusajs.com/references/medusa-workflows/acceptOrderTransferValidationStep/index.html.md)
- [beginExchangeOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginExchangeOrderWorkflow/index.html.md)
- [beginOrderEditOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginOrderEditOrderWorkflow/index.html.md)
-- [beginOrderExchangeValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginOrderExchangeValidationStep/index.html.md)
-- [beginReceiveReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginReceiveReturnWorkflow/index.html.md)
- [beginOrderEditValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginOrderEditValidationStep/index.html.md)
- [beginReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginReceiveReturnValidationStep/index.html.md)
+- [beginOrderExchangeValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginOrderExchangeValidationStep/index.html.md)
+- [beginReceiveReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginReceiveReturnWorkflow/index.html.md)
- [beginReturnOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginReturnOrderValidationStep/index.html.md)
-- [cancelBeginOrderClaimValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderClaimValidationStep/index.html.md)
-- [cancelBeginOrderClaimWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderClaimWorkflow/index.html.md)
- [beginReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginReturnOrderWorkflow/index.html.md)
-- [cancelBeginOrderExchangeValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderExchangeValidationStep/index.html.md)
- [cancelBeginOrderEditValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderEditValidationStep/index.html.md)
+- [cancelBeginOrderClaimValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderClaimValidationStep/index.html.md)
- [cancelBeginOrderEditWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderEditWorkflow/index.html.md)
+- [cancelBeginOrderClaimWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderClaimWorkflow/index.html.md)
+- [cancelBeginOrderExchangeValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderExchangeValidationStep/index.html.md)
+- [cancelBeginOrderExchangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderExchangeWorkflow/index.html.md)
- [cancelClaimValidateOrderStep](https://docs.medusajs.com/references/medusa-workflows/cancelClaimValidateOrderStep/index.html.md)
- [cancelOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderChangeWorkflow/index.html.md)
+- [cancelExchangeValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelExchangeValidateOrder/index.html.md)
- [cancelOrderClaimWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderClaimWorkflow/index.html.md)
- [cancelOrderExchangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderExchangeWorkflow/index.html.md)
-- [cancelExchangeValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelExchangeValidateOrder/index.html.md)
-- [cancelBeginOrderExchangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderExchangeWorkflow/index.html.md)
- [cancelOrderFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderFulfillmentWorkflow/index.html.md)
- [cancelOrderFulfillmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelOrderFulfillmentValidateOrder/index.html.md)
-- [cancelOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderWorkflow/index.html.md)
-- [cancelReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelReceiveReturnValidationStep/index.html.md)
-- [cancelRequestReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelRequestReturnValidationStep/index.html.md)
-- [cancelReturnReceiveWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnReceiveWorkflow/index.html.md)
-- [cancelReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnRequestWorkflow/index.html.md)
-- [cancelReturnValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelReturnValidateOrder/index.html.md)
- [cancelOrderTransferRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderTransferRequestWorkflow/index.html.md)
+- [cancelRequestReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelRequestReturnValidationStep/index.html.md)
+- [cancelReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelReceiveReturnValidationStep/index.html.md)
+- [cancelOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderWorkflow/index.html.md)
+- [cancelReturnReceiveWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnReceiveWorkflow/index.html.md)
+- [cancelReturnValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelReturnValidateOrder/index.html.md)
- [cancelReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnWorkflow/index.html.md)
-- [cancelValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelValidateOrder/index.html.md)
- [cancelTransferOrderRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelTransferOrderRequestValidationStep/index.html.md)
+- [cancelReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnRequestWorkflow/index.html.md)
+- [cancelValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelValidateOrder/index.html.md)
+- [completeOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/completeOrderWorkflow/index.html.md)
- [confirmClaimRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmClaimRequestValidationStep/index.html.md)
- [confirmClaimRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmClaimRequestWorkflow/index.html.md)
-- [confirmExchangeRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmExchangeRequestWorkflow/index.html.md)
- [confirmOrderEditRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmOrderEditRequestValidationStep/index.html.md)
-- [confirmExchangeRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmExchangeRequestValidationStep/index.html.md)
-- [completeOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/completeOrderWorkflow/index.html.md)
-- [confirmReturnReceiveWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmReturnReceiveWorkflow/index.html.md)
-- [confirmReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmReceiveReturnValidationStep/index.html.md)
+- [confirmExchangeRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmExchangeRequestWorkflow/index.html.md)
- [confirmOrderEditRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmOrderEditRequestWorkflow/index.html.md)
+- [confirmReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmReceiveReturnValidationStep/index.html.md)
+- [confirmReturnReceiveWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmReturnReceiveWorkflow/index.html.md)
+- [confirmExchangeRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmExchangeRequestValidationStep/index.html.md)
- [confirmReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmReturnRequestWorkflow/index.html.md)
-- [confirmReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmReturnRequestValidationStep/index.html.md)
- [createAndCompleteReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/createAndCompleteReturnOrderWorkflow/index.html.md)
+- [confirmReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmReturnRequestValidationStep/index.html.md)
- [createCompleteReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/createCompleteReturnValidationStep/index.html.md)
-- [createExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createExchangeShippingMethodValidationStep/index.html.md)
- [createClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createClaimShippingMethodWorkflow/index.html.md)
- [createClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createClaimShippingMethodValidationStep/index.html.md)
- [createFulfillmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/createFulfillmentValidateOrder/index.html.md)
+- [createExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createExchangeShippingMethodValidationStep/index.html.md)
- [createExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createExchangeShippingMethodWorkflow/index.html.md)
-- [createOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderChangeActionsWorkflow/index.html.md)
+- [createOrUpdateOrderPaymentCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrUpdateOrderPaymentCollectionWorkflow/index.html.md)
- [createOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderChangeWorkflow/index.html.md)
+- [createOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderChangeActionsWorkflow/index.html.md)
- [createOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderEditShippingMethodWorkflow/index.html.md)
- [createOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createOrderEditShippingMethodValidationStep/index.html.md)
-- [createOrUpdateOrderPaymentCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrUpdateOrderPaymentCollectionWorkflow/index.html.md)
- [createOrderFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderFulfillmentWorkflow/index.html.md)
-- [createOrderShipmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderShipmentWorkflow/index.html.md)
- [createOrderPaymentCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderPaymentCollectionWorkflow/index.html.md)
+- [createOrderShipmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderShipmentWorkflow/index.html.md)
- [createOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderWorkflow/index.html.md)
- [createOrdersWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrdersWorkflow/index.html.md)
+- [createReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnShippingMethodWorkflow/index.html.md)
- [createReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createReturnShippingMethodValidationStep/index.html.md)
- [createShipmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/createShipmentValidateOrder/index.html.md)
-- [createReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnShippingMethodWorkflow/index.html.md)
- [declineOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/declineOrderChangeWorkflow/index.html.md)
-- [deleteOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteOrderChangeActionsWorkflow/index.html.md)
- [declineOrderTransferRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/declineOrderTransferRequestWorkflow/index.html.md)
- [declineTransferOrderRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/declineTransferOrderRequestValidationStep/index.html.md)
-- [deleteOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteOrderChangeWorkflow/index.html.md)
-- [deleteOrderPaymentCollections](https://docs.medusajs.com/references/medusa-workflows/deleteOrderPaymentCollections/index.html.md)
+- [deleteOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteOrderChangeActionsWorkflow/index.html.md)
- [dismissItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/dismissItemReturnRequestValidationStep/index.html.md)
- [dismissItemReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/dismissItemReturnRequestWorkflow/index.html.md)
-- [exchangeAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/exchangeAddNewItemValidationStep/index.html.md)
-- [getOrdersListWorkflow](https://docs.medusajs.com/references/medusa-workflows/getOrdersListWorkflow/index.html.md)
+- [deleteOrderPaymentCollections](https://docs.medusajs.com/references/medusa-workflows/deleteOrderPaymentCollections/index.html.md)
+- [deleteOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteOrderChangeWorkflow/index.html.md)
- [exchangeRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/exchangeRequestItemReturnValidationStep/index.html.md)
- [getOrderDetailWorkflow](https://docs.medusajs.com/references/medusa-workflows/getOrderDetailWorkflow/index.html.md)
+- [exchangeAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/exchangeAddNewItemValidationStep/index.html.md)
+- [getOrdersListWorkflow](https://docs.medusajs.com/references/medusa-workflows/getOrdersListWorkflow/index.html.md)
- [markOrderFulfillmentAsDeliveredWorkflow](https://docs.medusajs.com/references/medusa-workflows/markOrderFulfillmentAsDeliveredWorkflow/index.html.md)
-- [orderClaimAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimAddNewItemValidationStep/index.html.md)
- [markPaymentCollectionAsPaid](https://docs.medusajs.com/references/medusa-workflows/markPaymentCollectionAsPaid/index.html.md)
-- [orderClaimAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimAddNewItemWorkflow/index.html.md)
+- [orderClaimAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimAddNewItemValidationStep/index.html.md)
- [orderClaimItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimItemValidationStep/index.html.md)
+- [orderClaimAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimAddNewItemWorkflow/index.html.md)
- [orderClaimItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimItemWorkflow/index.html.md)
-- [orderClaimRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimRequestItemReturnValidationStep/index.html.md)
-- [orderEditAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderEditAddNewItemValidationStep/index.html.md)
- [orderClaimRequestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimRequestItemReturnWorkflow/index.html.md)
+- [orderEditAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderEditAddNewItemValidationStep/index.html.md)
+- [orderClaimRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimRequestItemReturnValidationStep/index.html.md)
- [orderEditAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderEditAddNewItemWorkflow/index.html.md)
-- [orderEditUpdateItemQuantityWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderEditUpdateItemQuantityWorkflow/index.html.md)
-- [orderExchangeAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderExchangeAddNewItemWorkflow/index.html.md)
-- [orderFulfillmentDeliverablilityValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderFulfillmentDeliverablilityValidationStep/index.html.md)
-- [orderExchangeRequestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderExchangeRequestItemReturnWorkflow/index.html.md)
- [orderEditUpdateItemQuantityValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderEditUpdateItemQuantityValidationStep/index.html.md)
+- [orderExchangeAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderExchangeAddNewItemWorkflow/index.html.md)
+- [orderEditUpdateItemQuantityWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderEditUpdateItemQuantityWorkflow/index.html.md)
+- [orderExchangeRequestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderExchangeRequestItemReturnWorkflow/index.html.md)
+- [orderFulfillmentDeliverablilityValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderFulfillmentDeliverablilityValidationStep/index.html.md)
+- [receiveCompleteReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/receiveCompleteReturnValidationStep/index.html.md)
- [receiveAndCompleteReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/receiveAndCompleteReturnOrderWorkflow/index.html.md)
- [receiveItemReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/receiveItemReturnRequestWorkflow/index.html.md)
-- [receiveItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/receiveItemReturnRequestValidationStep/index.html.md)
-- [receiveCompleteReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/receiveCompleteReturnValidationStep/index.html.md)
-- [removeClaimAddItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimAddItemActionValidationStep/index.html.md)
- [removeAddItemClaimActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeAddItemClaimActionWorkflow/index.html.md)
+- [removeClaimAddItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimAddItemActionValidationStep/index.html.md)
+- [receiveItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/receiveItemReturnRequestValidationStep/index.html.md)
- [removeClaimItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimItemActionValidationStep/index.html.md)
- [removeClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimShippingMethodValidationStep/index.html.md)
-- [removeExchangeItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeExchangeItemActionValidationStep/index.html.md)
- [removeClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeClaimShippingMethodWorkflow/index.html.md)
-- [removeExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeExchangeShippingMethodWorkflow/index.html.md)
-- [removeExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeExchangeShippingMethodValidationStep/index.html.md)
- [removeItemClaimActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemClaimActionWorkflow/index.html.md)
-- [removeItemOrderEditActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemOrderEditActionWorkflow/index.html.md)
+- [removeExchangeItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeExchangeItemActionValidationStep/index.html.md)
+- [removeExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeExchangeShippingMethodValidationStep/index.html.md)
+- [removeExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeExchangeShippingMethodWorkflow/index.html.md)
- [removeItemExchangeActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemExchangeActionWorkflow/index.html.md)
+- [removeItemReturnActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemReturnActionWorkflow/index.html.md)
- [removeItemReceiveReturnActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeItemReceiveReturnActionValidationStep/index.html.md)
- [removeItemReceiveReturnActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemReceiveReturnActionWorkflow/index.html.md)
-- [removeItemReturnActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemReturnActionWorkflow/index.html.md)
-- [removeOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditShippingMethodValidationStep/index.html.md)
-- [removeReturnItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeReturnItemActionValidationStep/index.html.md)
-- [removeOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditShippingMethodWorkflow/index.html.md)
- [removeOrderEditItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditItemActionValidationStep/index.html.md)
+- [removeItemOrderEditActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemOrderEditActionWorkflow/index.html.md)
+- [removeOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditShippingMethodValidationStep/index.html.md)
+- [removeOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditShippingMethodWorkflow/index.html.md)
+- [removeReturnItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeReturnItemActionValidationStep/index.html.md)
- [removeReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeReturnShippingMethodValidationStep/index.html.md)
- [removeReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeReturnShippingMethodWorkflow/index.html.md)
- [requestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/requestItemReturnValidationStep/index.html.md)
- [requestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/requestItemReturnWorkflow/index.html.md)
-- [requestOrderTransferValidationStep](https://docs.medusajs.com/references/medusa-workflows/requestOrderTransferValidationStep/index.html.md)
-- [requestOrderEditRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/requestOrderEditRequestWorkflow/index.html.md)
- [requestOrderEditRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/requestOrderEditRequestValidationStep/index.html.md)
+- [requestOrderEditRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/requestOrderEditRequestWorkflow/index.html.md)
+- [requestOrderTransferValidationStep](https://docs.medusajs.com/references/medusa-workflows/requestOrderTransferValidationStep/index.html.md)
- [throwUnlessPaymentCollectionNotPaid](https://docs.medusajs.com/references/medusa-workflows/throwUnlessPaymentCollectionNotPaid/index.html.md)
-- [updateClaimAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimAddItemValidationStep/index.html.md)
- [requestOrderTransferWorkflow](https://docs.medusajs.com/references/medusa-workflows/requestOrderTransferWorkflow/index.html.md)
-- [updateClaimItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimItemValidationStep/index.html.md)
-- [updateClaimAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimAddItemWorkflow/index.html.md)
- [throwUnlessStatusIsNotPaid](https://docs.medusajs.com/references/medusa-workflows/throwUnlessStatusIsNotPaid/index.html.md)
-- [updateClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimShippingMethodValidationStep/index.html.md)
-- [updateClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimShippingMethodWorkflow/index.html.md)
+- [updateClaimAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimAddItemValidationStep/index.html.md)
+- [updateClaimAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimAddItemWorkflow/index.html.md)
+- [updateClaimItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimItemValidationStep/index.html.md)
- [updateClaimItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimItemWorkflow/index.html.md)
-- [updateExchangeAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateExchangeAddItemWorkflow/index.html.md)
+- [updateClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimShippingMethodWorkflow/index.html.md)
- [updateExchangeAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateExchangeAddItemValidationStep/index.html.md)
+- [updateClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimShippingMethodValidationStep/index.html.md)
- [updateExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateExchangeShippingMethodValidationStep/index.html.md)
+- [updateExchangeAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateExchangeAddItemWorkflow/index.html.md)
- [updateExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateExchangeShippingMethodWorkflow/index.html.md)
-- [updateOrderChangesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderChangesWorkflow/index.html.md)
- [updateOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderChangeActionsWorkflow/index.html.md)
-- [updateOrderEditAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditAddItemValidationStep/index.html.md)
- [updateOrderEditAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditAddItemWorkflow/index.html.md)
+- [updateOrderChangesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderChangesWorkflow/index.html.md)
+- [updateOrderEditAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditAddItemValidationStep/index.html.md)
- [updateOrderEditItemQuantityValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditItemQuantityValidationStep/index.html.md)
-- [updateOrderEditItemQuantityWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditItemQuantityWorkflow/index.html.md)
- [updateOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditShippingMethodValidationStep/index.html.md)
- [updateOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditShippingMethodWorkflow/index.html.md)
+- [updateOrderEditItemQuantityWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditItemQuantityWorkflow/index.html.md)
- [updateOrderTaxLinesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderTaxLinesWorkflow/index.html.md)
-- [updateOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderWorkflow/index.html.md)
-- [updateOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderValidationStep/index.html.md)
- [updateReceiveItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReceiveItemReturnRequestValidationStep/index.html.md)
+- [updateOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderValidationStep/index.html.md)
+- [updateOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderWorkflow/index.html.md)
- [updateReceiveItemReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReceiveItemReturnRequestWorkflow/index.html.md)
- [updateRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateRequestItemReturnValidationStep/index.html.md)
+- [updateReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReturnShippingMethodValidationStep/index.html.md)
- [updateRequestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRequestItemReturnWorkflow/index.html.md)
- [updateReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnShippingMethodWorkflow/index.html.md)
-- [updateReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReturnValidationStep/index.html.md)
+- [batchPriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchPriceListPricesWorkflow/index.html.md)
- [updateReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnWorkflow/index.html.md)
-- [updateReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReturnShippingMethodValidationStep/index.html.md)
-- [deletePricePreferencesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePricePreferencesWorkflow/index.html.md)
-- [updatePricePreferencesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePricePreferencesWorkflow/index.html.md)
-- [createPricePreferencesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPricePreferencesWorkflow/index.html.md)
+- [updateReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReturnValidationStep/index.html.md)
+- [createPriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPriceListPricesWorkflow/index.html.md)
+- [deletePriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePriceListsWorkflow/index.html.md)
+- [removePriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/removePriceListPricesWorkflow/index.html.md)
+- [createPriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPriceListsWorkflow/index.html.md)
+- [updatePriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePriceListPricesWorkflow/index.html.md)
+- [updatePriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePriceListsWorkflow/index.html.md)
- [batchLinkProductsToCategoryWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinkProductsToCategoryWorkflow/index.html.md)
-- [batchProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchProductVariantsWorkflow/index.html.md)
-- [batchProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchProductsWorkflow/index.html.md)
- [batchLinkProductsToCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinkProductsToCollectionWorkflow/index.html.md)
+- [batchProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchProductVariantsWorkflow/index.html.md)
- [createCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCollectionsWorkflow/index.html.md)
-- [createProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductTypesWorkflow/index.html.md)
- [createProductOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductOptionsWorkflow/index.html.md)
-- [createProductTagsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductTagsWorkflow/index.html.md)
+- [batchProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchProductsWorkflow/index.html.md)
- [createProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductVariantsWorkflow/index.html.md)
-- [deleteCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCollectionsWorkflow/index.html.md)
-- [deleteProductOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductOptionsWorkflow/index.html.md)
+- [createProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductTypesWorkflow/index.html.md)
- [createProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductsWorkflow/index.html.md)
+- [createProductTagsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductTagsWorkflow/index.html.md)
+- [deleteCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCollectionsWorkflow/index.html.md)
- [deleteProductTagsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductTagsWorkflow/index.html.md)
-- [deleteProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductTypesWorkflow/index.html.md)
+- [deleteProductOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductOptionsWorkflow/index.html.md)
- [deleteProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductVariantsWorkflow/index.html.md)
- [deleteProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductsWorkflow/index.html.md)
+- [deleteProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductTypesWorkflow/index.html.md)
- [exportProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/exportProductsWorkflow/index.html.md)
- [importProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/importProductsWorkflow/index.html.md)
-- [updateProductOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductOptionsWorkflow/index.html.md)
- [updateCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCollectionsWorkflow/index.html.md)
+- [updateProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductTypesWorkflow/index.html.md)
+- [updateProductOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductOptionsWorkflow/index.html.md)
- [updateProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductVariantsWorkflow/index.html.md)
- [updateProductTagsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductTagsWorkflow/index.html.md)
- [updateProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductsWorkflow/index.html.md)
-- [updateProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductTypesWorkflow/index.html.md)
-- [upsertVariantPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/upsertVariantPricesWorkflow/index.html.md)
- [validateProductInputStep](https://docs.medusajs.com/references/medusa-workflows/validateProductInputStep/index.html.md)
-- [updateProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductCategoriesWorkflow/index.html.md)
+- [upsertVariantPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/upsertVariantPricesWorkflow/index.html.md)
+- [createPricePreferencesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPricePreferencesWorkflow/index.html.md)
+- [deletePricePreferencesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePricePreferencesWorkflow/index.html.md)
+- [updatePricePreferencesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePricePreferencesWorkflow/index.html.md)
- [createProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductCategoriesWorkflow/index.html.md)
- [deleteProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductCategoriesWorkflow/index.html.md)
+- [updateProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductCategoriesWorkflow/index.html.md)
- [addOrRemoveCampaignPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/addOrRemoveCampaignPromotionsWorkflow/index.html.md)
-- [createCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCampaignsWorkflow/index.html.md)
- [batchPromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchPromotionRulesWorkflow/index.html.md)
+- [createCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCampaignsWorkflow/index.html.md)
+- [deleteCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCampaignsWorkflow/index.html.md)
- [createPromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPromotionRulesWorkflow/index.html.md)
- [createPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPromotionsWorkflow/index.html.md)
-- [deleteCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCampaignsWorkflow/index.html.md)
- [deletePromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePromotionRulesWorkflow/index.html.md)
- [deletePromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePromotionsWorkflow/index.html.md)
+- [updateCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCampaignsWorkflow/index.html.md)
- [updatePromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionRulesWorkflow/index.html.md)
- [updatePromotionsStatusWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsStatusWorkflow/index.html.md)
-- [updateCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCampaignsWorkflow/index.html.md)
-- [updatePromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsWorkflow/index.html.md)
- [updatePromotionsValidationStep](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsValidationStep/index.html.md)
-- [refundPaymentsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentsWorkflow/index.html.md)
-- [processPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/processPaymentWorkflow/index.html.md)
-- [capturePaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/capturePaymentWorkflow/index.html.md)
-- [validatePaymentsRefundStep](https://docs.medusajs.com/references/medusa-workflows/validatePaymentsRefundStep/index.html.md)
-- [validateRefundStep](https://docs.medusajs.com/references/medusa-workflows/validateRefundStep/index.html.md)
-- [refundPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentWorkflow/index.html.md)
-- [deleteRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteRegionsWorkflow/index.html.md)
-- [createRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createRegionsWorkflow/index.html.md)
+- [updatePromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsWorkflow/index.html.md)
+- [createReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnReasonsWorkflow/index.html.md)
+- [deleteReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReturnReasonsWorkflow/index.html.md)
+- [updateReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnReasonsWorkflow/index.html.md)
+- [createReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReservationsWorkflow/index.html.md)
+- [deleteReservationsByLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReservationsByLineItemsWorkflow/index.html.md)
+- [deleteReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReservationsWorkflow/index.html.md)
+- [updateReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReservationsWorkflow/index.html.md)
- [updateRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRegionsWorkflow/index.html.md)
+- [createRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createRegionsWorkflow/index.html.md)
+- [deleteRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteRegionsWorkflow/index.html.md)
- [createSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createSalesChannelsWorkflow/index.html.md)
- [deleteSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteSalesChannelsWorkflow/index.html.md)
- [linkProductsToSalesChannelWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkProductsToSalesChannelWorkflow/index.html.md)
- [updateSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateSalesChannelsWorkflow/index.html.md)
-- [deleteReservationsByLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReservationsByLineItemsWorkflow/index.html.md)
-- [createReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReservationsWorkflow/index.html.md)
-- [updateReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReservationsWorkflow/index.html.md)
-- [deleteReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReservationsWorkflow/index.html.md)
-- [createReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnReasonsWorkflow/index.html.md)
-- [validateStepShippingProfileDelete](https://docs.medusajs.com/references/medusa-workflows/validateStepShippingProfileDelete/index.html.md)
-- [deleteReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReturnReasonsWorkflow/index.html.md)
+- [deleteStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteStoresWorkflow/index.html.md)
+- [createStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/createStoresWorkflow/index.html.md)
+- [updateStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateStoresWorkflow/index.html.md)
- [deleteShippingProfileWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteShippingProfileWorkflow/index.html.md)
-- [updateReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnReasonsWorkflow/index.html.md)
+- [validateStepShippingProfileDelete](https://docs.medusajs.com/references/medusa-workflows/validateStepShippingProfileDelete/index.html.md)
- [createLocationFulfillmentSetWorkflow](https://docs.medusajs.com/references/medusa-workflows/createLocationFulfillmentSetWorkflow/index.html.md)
- [createStockLocationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createStockLocationsWorkflow/index.html.md)
- [deleteStockLocationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteStockLocationsWorkflow/index.html.md)
-- [updateStockLocationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateStockLocationsWorkflow/index.html.md)
- [linkSalesChannelsToStockLocationWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkSalesChannelsToStockLocationWorkflow/index.html.md)
-- [createStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/createStoresWorkflow/index.html.md)
-- [updateStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateStoresWorkflow/index.html.md)
-- [deleteStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteStoresWorkflow/index.html.md)
-- [createTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRateRulesWorkflow/index.html.md)
-- [createTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRegionsWorkflow/index.html.md)
+- [updateStockLocationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateStockLocationsWorkflow/index.html.md)
- [createTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRatesWorkflow/index.html.md)
-- [deleteTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRatesWorkflow/index.html.md)
+- [createTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRegionsWorkflow/index.html.md)
- [deleteTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRateRulesWorkflow/index.html.md)
+- [createTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRateRulesWorkflow/index.html.md)
+- [deleteTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRatesWorkflow/index.html.md)
- [deleteTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRegionsWorkflow/index.html.md)
-- [setTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/setTaxRateRulesWorkflow/index.html.md)
-- [updateTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxRegionsWorkflow/index.html.md)
-- [updateTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxRatesWorkflow/index.html.md)
- [maybeListTaxRateRuleIdsStep](https://docs.medusajs.com/references/medusa-workflows/maybeListTaxRateRuleIdsStep/index.html.md)
-- [createUserAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/createUserAccountWorkflow/index.html.md)
+- [updateTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxRatesWorkflow/index.html.md)
+- [updateTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxRegionsWorkflow/index.html.md)
+- [setTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/setTaxRateRulesWorkflow/index.html.md)
- [createUsersWorkflow](https://docs.medusajs.com/references/medusa-workflows/createUsersWorkflow/index.html.md)
+- [createUserAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/createUserAccountWorkflow/index.html.md)
- [deleteUsersWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteUsersWorkflow/index.html.md)
- [updateUsersWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateUsersWorkflow/index.html.md)
- [removeUserAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeUserAccountWorkflow/index.html.md)
@@ -28489,176 +28727,187 @@ For each product variant, you:
- [deleteApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteApiKeysStep/index.html.md)
- [linkSalesChannelsToApiKeyStep](https://docs.medusajs.com/references/medusa-workflows/steps/linkSalesChannelsToApiKeyStep/index.html.md)
- [revokeApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/revokeApiKeysStep/index.html.md)
-- [validateSalesChannelsExistStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateSalesChannelsExistStep/index.html.md)
- [updateApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateApiKeysStep/index.html.md)
-- [setAuthAppMetadataStep](https://docs.medusajs.com/references/medusa-workflows/steps/setAuthAppMetadataStep/index.html.md)
-- [deleteCustomerGroupStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomerGroupStep/index.html.md)
-- [createCustomerGroupsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomerGroupsStep/index.html.md)
-- [updateCustomerGroupsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomerGroupsStep/index.html.md)
-- [linkCustomersToCustomerGroupStep](https://docs.medusajs.com/references/medusa-workflows/steps/linkCustomersToCustomerGroupStep/index.html.md)
-- [linkCustomerGroupsToCustomerStep](https://docs.medusajs.com/references/medusa-workflows/steps/linkCustomerGroupsToCustomerStep/index.html.md)
-- [createCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomerAddressesStep/index.html.md)
-- [createCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomersStep/index.html.md)
-- [deleteCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomersStep/index.html.md)
-- [maybeUnsetDefaultBillingAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/maybeUnsetDefaultBillingAddressesStep/index.html.md)
-- [maybeUnsetDefaultShippingAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/maybeUnsetDefaultShippingAddressesStep/index.html.md)
-- [updateCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomerAddressesStep/index.html.md)
-- [deleteCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomerAddressesStep/index.html.md)
-- [updateCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomersStep/index.html.md)
-- [validateCustomerAccountCreation](https://docs.medusajs.com/references/medusa-workflows/steps/validateCustomerAccountCreation/index.html.md)
+- [validateSalesChannelsExistStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateSalesChannelsExistStep/index.html.md)
+- [confirmInventoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/confirmInventoryStep/index.html.md)
+- [addShippingMethodToCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/addShippingMethodToCartStep/index.html.md)
+- [createCartsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCartsStep/index.html.md)
+- [createLineItemAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createLineItemAdjustmentsStep/index.html.md)
+- [createLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createLineItemsStep/index.html.md)
+- [createShippingMethodAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingMethodAdjustmentsStep/index.html.md)
+- [createPaymentCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentCollectionsStep/index.html.md)
+- [findOneOrAnyRegionStep](https://docs.medusajs.com/references/medusa-workflows/steps/findOneOrAnyRegionStep/index.html.md)
+- [getActionsToComputeFromPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getActionsToComputeFromPromotionsStep/index.html.md)
+- [findOrCreateCustomerStep](https://docs.medusajs.com/references/medusa-workflows/steps/findOrCreateCustomerStep/index.html.md)
+- [getLineItemActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getLineItemActionsStep/index.html.md)
+- [findSalesChannelStep](https://docs.medusajs.com/references/medusa-workflows/steps/findSalesChannelStep/index.html.md)
+- [getVariantPriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getVariantPriceSetsStep/index.html.md)
+- [getPromotionCodesToApply](https://docs.medusajs.com/references/medusa-workflows/steps/getPromotionCodesToApply/index.html.md)
+- [getVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getVariantsStep/index.html.md)
+- [removeLineItemAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeLineItemAdjustmentsStep/index.html.md)
+- [prepareAdjustmentsFromPromotionActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/prepareAdjustmentsFromPromotionActionsStep/index.html.md)
+- [removeShippingMethodAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeShippingMethodAdjustmentsStep/index.html.md)
+- [removeShippingMethodFromCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeShippingMethodFromCartStep/index.html.md)
+- [reserveInventoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/reserveInventoryStep/index.html.md)
+- [retrieveCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/retrieveCartStep/index.html.md)
+- [updateCartPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCartPromotionsStep/index.html.md)
+- [setTaxLinesForItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/setTaxLinesForItemsStep/index.html.md)
+- [updateLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateLineItemsStep/index.html.md)
+- [updateCartsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCartsStep/index.html.md)
+- [validateAndReturnShippingMethodsDataStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateAndReturnShippingMethodsDataStep/index.html.md)
+- [updateShippingMethodsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingMethodsStep/index.html.md)
+- [validateCartPaymentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartPaymentsStep/index.html.md)
+- [validateCartShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartShippingOptionsStep/index.html.md)
+- [validateCartShippingOptionsPriceStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartShippingOptionsPriceStep/index.html.md)
+- [validateCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartStep/index.html.md)
+- [validateVariantPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateVariantPricesStep/index.html.md)
+- [validateLineItemPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateLineItemPricesStep/index.html.md)
+- [validateShippingStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateShippingStep/index.html.md)
- [createRemoteLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRemoteLinkStep/index.html.md)
- [dismissRemoteLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/dismissRemoteLinkStep/index.html.md)
- [removeRemoteLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeRemoteLinkStep/index.html.md)
- [emitEventStep](https://docs.medusajs.com/references/medusa-workflows/steps/emitEventStep/index.html.md)
- [updateRemoteLinksStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRemoteLinksStep/index.html.md)
- [useQueryGraphStep](https://docs.medusajs.com/references/medusa-workflows/steps/useQueryGraphStep/index.html.md)
-- [validatePresenceOfStep](https://docs.medusajs.com/references/medusa-workflows/steps/validatePresenceOfStep/index.html.md)
- [useRemoteQueryStep](https://docs.medusajs.com/references/medusa-workflows/steps/useRemoteQueryStep/index.html.md)
-- [addShippingMethodToCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/addShippingMethodToCartStep/index.html.md)
-- [confirmInventoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/confirmInventoryStep/index.html.md)
-- [createLineItemAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createLineItemAdjustmentsStep/index.html.md)
-- [createCartsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCartsStep/index.html.md)
-- [createShippingMethodAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingMethodAdjustmentsStep/index.html.md)
-- [findOneOrAnyRegionStep](https://docs.medusajs.com/references/medusa-workflows/steps/findOneOrAnyRegionStep/index.html.md)
-- [createPaymentCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentCollectionsStep/index.html.md)
-- [createLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createLineItemsStep/index.html.md)
-- [getActionsToComputeFromPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getActionsToComputeFromPromotionsStep/index.html.md)
-- [findOrCreateCustomerStep](https://docs.medusajs.com/references/medusa-workflows/steps/findOrCreateCustomerStep/index.html.md)
-- [findSalesChannelStep](https://docs.medusajs.com/references/medusa-workflows/steps/findSalesChannelStep/index.html.md)
-- [getLineItemActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getLineItemActionsStep/index.html.md)
-- [getVariantPriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getVariantPriceSetsStep/index.html.md)
-- [getPromotionCodesToApply](https://docs.medusajs.com/references/medusa-workflows/steps/getPromotionCodesToApply/index.html.md)
-- [getVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getVariantsStep/index.html.md)
-- [prepareAdjustmentsFromPromotionActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/prepareAdjustmentsFromPromotionActionsStep/index.html.md)
-- [removeShippingMethodFromCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeShippingMethodFromCartStep/index.html.md)
-- [removeShippingMethodAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeShippingMethodAdjustmentsStep/index.html.md)
-- [removeLineItemAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeLineItemAdjustmentsStep/index.html.md)
-- [reserveInventoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/reserveInventoryStep/index.html.md)
-- [setTaxLinesForItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/setTaxLinesForItemsStep/index.html.md)
-- [retrieveCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/retrieveCartStep/index.html.md)
-- [updateCartPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCartPromotionsStep/index.html.md)
-- [updateCartsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCartsStep/index.html.md)
-- [updateShippingMethodsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingMethodsStep/index.html.md)
-- [validateAndReturnShippingMethodsDataStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateAndReturnShippingMethodsDataStep/index.html.md)
-- [updateLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateLineItemsStep/index.html.md)
-- [validateCartPaymentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartPaymentsStep/index.html.md)
-- [validateCartShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartShippingOptionsStep/index.html.md)
-- [validateCartShippingOptionsPriceStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartShippingOptionsPriceStep/index.html.md)
-- [validateCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartStep/index.html.md)
-- [validateLineItemPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateLineItemPricesStep/index.html.md)
-- [validateVariantPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateVariantPricesStep/index.html.md)
-- [validateShippingStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateShippingStep/index.html.md)
-- [createDefaultStoreStep](https://docs.medusajs.com/references/medusa-workflows/steps/createDefaultStoreStep/index.html.md)
+- [validatePresenceOfStep](https://docs.medusajs.com/references/medusa-workflows/steps/validatePresenceOfStep/index.html.md)
+- [createCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomerAddressesStep/index.html.md)
+- [deleteCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomerAddressesStep/index.html.md)
+- [createCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomersStep/index.html.md)
+- [deleteCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomersStep/index.html.md)
+- [maybeUnsetDefaultBillingAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/maybeUnsetDefaultBillingAddressesStep/index.html.md)
+- [updateCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomersStep/index.html.md)
+- [maybeUnsetDefaultShippingAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/maybeUnsetDefaultShippingAddressesStep/index.html.md)
+- [updateCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomerAddressesStep/index.html.md)
+- [validateCustomerAccountCreation](https://docs.medusajs.com/references/medusa-workflows/steps/validateCustomerAccountCreation/index.html.md)
+- [setAuthAppMetadataStep](https://docs.medusajs.com/references/medusa-workflows/steps/setAuthAppMetadataStep/index.html.md)
+- [createCustomerGroupsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomerGroupsStep/index.html.md)
+- [deleteCustomerGroupStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomerGroupStep/index.html.md)
+- [linkCustomerGroupsToCustomerStep](https://docs.medusajs.com/references/medusa-workflows/steps/linkCustomerGroupsToCustomerStep/index.html.md)
+- [linkCustomersToCustomerGroupStep](https://docs.medusajs.com/references/medusa-workflows/steps/linkCustomersToCustomerGroupStep/index.html.md)
+- [updateCustomerGroupsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomerGroupsStep/index.html.md)
+- [uploadFilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/uploadFilesStep/index.html.md)
+- [deleteFilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteFilesStep/index.html.md)
- [buildPriceSet](https://docs.medusajs.com/references/medusa-workflows/steps/buildPriceSet/index.html.md)
-- [cancelFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelFulfillmentStep/index.html.md)
- [calculateShippingOptionsPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/calculateShippingOptionsPricesStep/index.html.md)
-- [createFulfillmentSets](https://docs.medusajs.com/references/medusa-workflows/steps/createFulfillmentSets/index.html.md)
+- [cancelFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelFulfillmentStep/index.html.md)
- [createFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/createFulfillmentStep/index.html.md)
-- [createServiceZonesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createServiceZonesStep/index.html.md)
+- [createFulfillmentSets](https://docs.medusajs.com/references/medusa-workflows/steps/createFulfillmentSets/index.html.md)
- [createReturnFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnFulfillmentStep/index.html.md)
-- [createShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingOptionRulesStep/index.html.md)
- [createShippingOptionsPriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingOptionsPriceSetsStep/index.html.md)
- [createShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingProfilesStep/index.html.md)
+- [createServiceZonesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createServiceZonesStep/index.html.md)
+- [createShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingOptionRulesStep/index.html.md)
- [deleteFulfillmentSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteFulfillmentSetsStep/index.html.md)
- [deleteServiceZonesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteServiceZonesStep/index.html.md)
-- [deleteShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingOptionRulesStep/index.html.md)
- [deleteShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingOptionsStep/index.html.md)
- [setShippingOptionsPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/setShippingOptionsPricesStep/index.html.md)
+- [deleteShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingOptionRulesStep/index.html.md)
- [updateFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateFulfillmentStep/index.html.md)
-- [updateShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingProfilesStep/index.html.md)
-- [updateShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingOptionRulesStep/index.html.md)
-- [upsertShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/upsertShippingOptionsStep/index.html.md)
- [updateServiceZonesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateServiceZonesStep/index.html.md)
+- [updateShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingOptionRulesStep/index.html.md)
+- [updateShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingProfilesStep/index.html.md)
+- [upsertShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/upsertShippingOptionsStep/index.html.md)
- [validateShipmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateShipmentStep/index.html.md)
- [validateShippingOptionPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateShippingOptionPricesStep/index.html.md)
-- [deleteFilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteFilesStep/index.html.md)
-- [uploadFilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/uploadFilesStep/index.html.md)
-- [createInviteStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInviteStep/index.html.md)
-- [deleteInvitesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInvitesStep/index.html.md)
-- [refreshInviteTokensStep](https://docs.medusajs.com/references/medusa-workflows/steps/refreshInviteTokensStep/index.html.md)
-- [validateTokenStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateTokenStep/index.html.md)
-- [deleteLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteLineItemsStep/index.html.md)
-- [listLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listLineItemsStep/index.html.md)
-- [updateLineItemsStepWithSelector](https://docs.medusajs.com/references/medusa-workflows/steps/updateLineItemsStepWithSelector/index.html.md)
-- [notifyOnFailureStep](https://docs.medusajs.com/references/medusa-workflows/steps/notifyOnFailureStep/index.html.md)
-- [sendNotificationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/sendNotificationsStep/index.html.md)
-- [adjustInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/adjustInventoryLevelsStep/index.html.md)
- [attachInventoryItemToVariants](https://docs.medusajs.com/references/medusa-workflows/steps/attachInventoryItemToVariants/index.html.md)
-- [createInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInventoryLevelsStep/index.html.md)
-- [deleteInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInventoryLevelsStep/index.html.md)
-- [deleteInventoryItemStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInventoryItemStep/index.html.md)
- [createInventoryItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInventoryItemsStep/index.html.md)
-- [updateInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateInventoryLevelsStep/index.html.md)
+- [adjustInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/adjustInventoryLevelsStep/index.html.md)
+- [createInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInventoryLevelsStep/index.html.md)
+- [deleteInventoryItemStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInventoryItemStep/index.html.md)
+- [deleteInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInventoryLevelsStep/index.html.md)
- [updateInventoryItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateInventoryItemsStep/index.html.md)
+- [updateInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateInventoryLevelsStep/index.html.md)
- [validateInventoryDeleteStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryDeleteStep/index.html.md)
- [validateInventoryItemsForCreate](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryItemsForCreate/index.html.md)
- [validateInventoryLocationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryLocationsStep/index.html.md)
+- [createDefaultStoreStep](https://docs.medusajs.com/references/medusa-workflows/steps/createDefaultStoreStep/index.html.md)
+- [createInviteStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInviteStep/index.html.md)
+- [refreshInviteTokensStep](https://docs.medusajs.com/references/medusa-workflows/steps/refreshInviteTokensStep/index.html.md)
+- [deleteInvitesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInvitesStep/index.html.md)
+- [validateTokenStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateTokenStep/index.html.md)
+- [listLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listLineItemsStep/index.html.md)
+- [deleteLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteLineItemsStep/index.html.md)
+- [updateLineItemsStepWithSelector](https://docs.medusajs.com/references/medusa-workflows/steps/updateLineItemsStepWithSelector/index.html.md)
- [addOrderTransactionStep](https://docs.medusajs.com/references/medusa-workflows/steps/addOrderTransactionStep/index.html.md)
- [archiveOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/archiveOrdersStep/index.html.md)
- [cancelOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderChangeStep/index.html.md)
-- [cancelOrderExchangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderExchangeStep/index.html.md)
- [cancelOrderClaimStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderClaimStep/index.html.md)
+- [cancelOrderExchangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderExchangeStep/index.html.md)
- [cancelOrderFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderFulfillmentStep/index.html.md)
- [cancelOrderReturnStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderReturnStep/index.html.md)
-- [completeOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/completeOrdersStep/index.html.md)
- [cancelOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrdersStep/index.html.md)
+- [completeOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/completeOrdersStep/index.html.md)
- [createCompleteReturnStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCompleteReturnStep/index.html.md)
- [createOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderChangeStep/index.html.md)
+- [createOrderClaimItemsFromActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderClaimItemsFromActionsStep/index.html.md)
- [createOrderClaimsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderClaimsStep/index.html.md)
- [createOrderExchangeItemsFromActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderExchangeItemsFromActionsStep/index.html.md)
-- [createOrderClaimItemsFromActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderClaimItemsFromActionsStep/index.html.md)
- [createOrderExchangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderExchangesStep/index.html.md)
-- [createOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrdersStep/index.html.md)
- [createOrderLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderLineItemsStep/index.html.md)
+- [deleteClaimsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteClaimsStep/index.html.md)
+- [createOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrdersStep/index.html.md)
- [createReturnsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnsStep/index.html.md)
- [declineOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/declineOrderChangeStep/index.html.md)
-- [deleteClaimsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteClaimsStep/index.html.md)
- [deleteExchangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteExchangesStep/index.html.md)
- [deleteOrderChangeActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderChangeActionsStep/index.html.md)
- [deleteOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderChangesStep/index.html.md)
-- [deleteOrderLineItems](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderLineItems/index.html.md)
- [deleteOrderShippingMethods](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderShippingMethods/index.html.md)
+- [deleteOrderLineItems](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderLineItems/index.html.md)
- [deleteReturnsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReturnsStep/index.html.md)
- [previewOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/previewOrderChangeStep/index.html.md)
- [registerOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderChangesStep/index.html.md)
- [registerOrderFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderFulfillmentStep/index.html.md)
- [registerOrderShipmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderShipmentStep/index.html.md)
- [setOrderTaxLinesForItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/setOrderTaxLinesForItemsStep/index.html.md)
+- [updateOrderChangeActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrderChangeActionsStep/index.html.md)
- [updateOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrderChangesStep/index.html.md)
- [updateOrderShippingMethodsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrderShippingMethodsStep/index.html.md)
-- [updateOrderChangeActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrderChangeActionsStep/index.html.md)
- [updateOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrdersStep/index.html.md)
-- [authorizePaymentSessionStep](https://docs.medusajs.com/references/medusa-workflows/steps/authorizePaymentSessionStep/index.html.md)
- [updateReturnsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnsStep/index.html.md)
- [updateReturnItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnItemsStep/index.html.md)
-- [cancelPaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelPaymentStep/index.html.md)
-- [refundPaymentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/refundPaymentsStep/index.html.md)
-- [refundPaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/refundPaymentStep/index.html.md)
-- [capturePaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/capturePaymentStep/index.html.md)
+- [notifyOnFailureStep](https://docs.medusajs.com/references/medusa-workflows/steps/notifyOnFailureStep/index.html.md)
+- [sendNotificationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/sendNotificationsStep/index.html.md)
- [createPriceListPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceListPricesStep/index.html.md)
-- [createPriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceListsStep/index.html.md)
-- [deletePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePriceListsStep/index.html.md)
- [getExistingPriceListsPriceIdsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getExistingPriceListsPriceIdsStep/index.html.md)
+- [deletePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePriceListsStep/index.html.md)
+- [createPriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceListsStep/index.html.md)
- [removePriceListPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/removePriceListPricesStep/index.html.md)
+- [updatePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePriceListsStep/index.html.md)
- [updatePriceListPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePriceListPricesStep/index.html.md)
- [validatePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validatePriceListsStep/index.html.md)
-- [updatePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePriceListsStep/index.html.md)
- [validateVariantPriceLinksStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateVariantPriceLinksStep/index.html.md)
-- [createProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductCategoriesStep/index.html.md)
-- [deleteProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductCategoriesStep/index.html.md)
-- [updateProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductCategoriesStep/index.html.md)
+- [createPaymentAccountHolderStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentAccountHolderStep/index.html.md)
+- [createPaymentSessionStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentSessionStep/index.html.md)
+- [deleteRefundReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteRefundReasonsStep/index.html.md)
+- [deletePaymentSessionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePaymentSessionsStep/index.html.md)
+- [createRefundReasonStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRefundReasonStep/index.html.md)
+- [updatePaymentCollectionStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePaymentCollectionStep/index.html.md)
+- [validateDeletedPaymentSessionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateDeletedPaymentSessionsStep/index.html.md)
+- [updateRefundReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRefundReasonsStep/index.html.md)
+- [createPriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceSetsStep/index.html.md)
+- [updatePricePreferencesAsArrayStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePricePreferencesAsArrayStep/index.html.md)
+- [createPricePreferencesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPricePreferencesStep/index.html.md)
+- [deletePricePreferencesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePricePreferencesStep/index.html.md)
+- [updatePricePreferencesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePricePreferencesStep/index.html.md)
+- [updatePriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePriceSetsStep/index.html.md)
+- [cancelPaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelPaymentStep/index.html.md)
+- [authorizePaymentSessionStep](https://docs.medusajs.com/references/medusa-workflows/steps/authorizePaymentSessionStep/index.html.md)
+- [capturePaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/capturePaymentStep/index.html.md)
+- [refundPaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/refundPaymentStep/index.html.md)
+- [refundPaymentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/refundPaymentsStep/index.html.md)
- [batchLinkProductsToCategoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/batchLinkProductsToCategoryStep/index.html.md)
-- [batchLinkProductsToCollectionStep](https://docs.medusajs.com/references/medusa-workflows/steps/batchLinkProductsToCollectionStep/index.html.md)
-- [createCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCollectionsStep/index.html.md)
- [createProductOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductOptionsStep/index.html.md)
+- [createCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCollectionsStep/index.html.md)
+- [batchLinkProductsToCollectionStep](https://docs.medusajs.com/references/medusa-workflows/steps/batchLinkProductsToCollectionStep/index.html.md)
- [createProductTagsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductTagsStep/index.html.md)
- [createProductTypesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductTypesStep/index.html.md)
-- [createProductVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductVariantsStep/index.html.md)
- [createProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductsStep/index.html.md)
+- [createProductVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductVariantsStep/index.html.md)
- [createVariantPricingLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/createVariantPricingLinkStep/index.html.md)
- [deleteCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCollectionsStep/index.html.md)
-- [deleteProductOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductOptionsStep/index.html.md)
- [deleteProductTagsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductTagsStep/index.html.md)
+- [deleteProductOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductOptionsStep/index.html.md)
- [deleteProductTypesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductTypesStep/index.html.md)
- [deleteProductVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductVariantsStep/index.html.md)
- [deleteProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductsStep/index.html.md)
@@ -28667,81 +28916,70 @@ For each product variant, you:
- [getProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getProductsStep/index.html.md)
- [getVariantAvailabilityStep](https://docs.medusajs.com/references/medusa-workflows/steps/getVariantAvailabilityStep/index.html.md)
- [groupProductsForBatchStep](https://docs.medusajs.com/references/medusa-workflows/steps/groupProductsForBatchStep/index.html.md)
-- [parseProductCsvStep](https://docs.medusajs.com/references/medusa-workflows/steps/parseProductCsvStep/index.html.md)
-- [updateCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCollectionsStep/index.html.md)
- [updateProductOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductOptionsStep/index.html.md)
+- [updateCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCollectionsStep/index.html.md)
+- [parseProductCsvStep](https://docs.medusajs.com/references/medusa-workflows/steps/parseProductCsvStep/index.html.md)
- [updateProductTagsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductTagsStep/index.html.md)
-- [updateProductTypesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductTypesStep/index.html.md)
- [updateProductVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductVariantsStep/index.html.md)
-- [updateProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductsStep/index.html.md)
+- [updateProductTypesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductTypesStep/index.html.md)
- [waitConfirmationProductImportStep](https://docs.medusajs.com/references/medusa-workflows/steps/waitConfirmationProductImportStep/index.html.md)
-- [createPricePreferencesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPricePreferencesStep/index.html.md)
-- [deletePricePreferencesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePricePreferencesStep/index.html.md)
-- [updatePricePreferencesAsArrayStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePricePreferencesAsArrayStep/index.html.md)
-- [createPriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceSetsStep/index.html.md)
-- [updatePricePreferencesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePricePreferencesStep/index.html.md)
-- [updatePriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePriceSetsStep/index.html.md)
+- [updateProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductsStep/index.html.md)
+- [createProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductCategoriesStep/index.html.md)
+- [deleteProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductCategoriesStep/index.html.md)
+- [updateProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductCategoriesStep/index.html.md)
- [addCampaignPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/addCampaignPromotionsStep/index.html.md)
- [addRulesToPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/addRulesToPromotionsStep/index.html.md)
- [createCampaignsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCampaignsStep/index.html.md)
+- [deletePromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePromotionsStep/index.html.md)
- [createPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPromotionsStep/index.html.md)
- [deleteCampaignsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCampaignsStep/index.html.md)
-- [deletePromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePromotionsStep/index.html.md)
- [removeCampaignPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeCampaignPromotionsStep/index.html.md)
-- [removeRulesFromPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeRulesFromPromotionsStep/index.html.md)
-- [updateCampaignsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCampaignsStep/index.html.md)
- [updatePromotionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePromotionRulesStep/index.html.md)
+- [updateCampaignsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCampaignsStep/index.html.md)
+- [removeRulesFromPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeRulesFromPromotionsStep/index.html.md)
- [updatePromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePromotionsStep/index.html.md)
+- [setRegionsPaymentProvidersStep](https://docs.medusajs.com/references/medusa-workflows/steps/setRegionsPaymentProvidersStep/index.html.md)
- [createRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRegionsStep/index.html.md)
- [deleteRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteRegionsStep/index.html.md)
-- [setRegionsPaymentProvidersStep](https://docs.medusajs.com/references/medusa-workflows/steps/setRegionsPaymentProvidersStep/index.html.md)
- [updateRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRegionsStep/index.html.md)
-- [deleteReturnReasonStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReturnReasonStep/index.html.md)
-- [createReturnReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnReasonsStep/index.html.md)
-- [updateReturnReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnReasonsStep/index.html.md)
-- [deleteReservationsByLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReservationsByLineItemsStep/index.html.md)
-- [updateReservationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReservationsStep/index.html.md)
-- [deleteReservationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReservationsStep/index.html.md)
- [createReservationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReservationsStep/index.html.md)
-- [associateProductsWithSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/associateProductsWithSalesChannelsStep/index.html.md)
+- [deleteReservationsByLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReservationsByLineItemsStep/index.html.md)
+- [deleteReservationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReservationsStep/index.html.md)
+- [updateReservationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReservationsStep/index.html.md)
+- [createReturnReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnReasonsStep/index.html.md)
+- [deleteReturnReasonStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReturnReasonStep/index.html.md)
+- [updateReturnReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnReasonsStep/index.html.md)
- [associateLocationsWithSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/associateLocationsWithSalesChannelsStep/index.html.md)
+- [associateProductsWithSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/associateProductsWithSalesChannelsStep/index.html.md)
- [canDeleteSalesChannelsOrThrowStep](https://docs.medusajs.com/references/medusa-workflows/steps/canDeleteSalesChannelsOrThrowStep/index.html.md)
- [createDefaultSalesChannelStep](https://docs.medusajs.com/references/medusa-workflows/steps/createDefaultSalesChannelStep/index.html.md)
-- [deleteSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteSalesChannelsStep/index.html.md)
- [createSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createSalesChannelsStep/index.html.md)
-- [detachLocationsFromSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/detachLocationsFromSalesChannelsStep/index.html.md)
-- [detachProductsFromSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/detachProductsFromSalesChannelsStep/index.html.md)
+- [deleteSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteSalesChannelsStep/index.html.md)
- [updateSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateSalesChannelsStep/index.html.md)
-- [createPaymentAccountHolderStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentAccountHolderStep/index.html.md)
-- [createPaymentSessionStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentSessionStep/index.html.md)
-- [createRefundReasonStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRefundReasonStep/index.html.md)
-- [deletePaymentSessionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePaymentSessionsStep/index.html.md)
-- [deleteRefundReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteRefundReasonsStep/index.html.md)
-- [updatePaymentCollectionStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePaymentCollectionStep/index.html.md)
-- [validateDeletedPaymentSessionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateDeletedPaymentSessionsStep/index.html.md)
-- [updateRefundReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRefundReasonsStep/index.html.md)
+- [detachProductsFromSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/detachProductsFromSalesChannelsStep/index.html.md)
+- [detachLocationsFromSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/detachLocationsFromSalesChannelsStep/index.html.md)
- [listShippingOptionsForContextStep](https://docs.medusajs.com/references/medusa-workflows/steps/listShippingOptionsForContextStep/index.html.md)
-- [deleteShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingProfilesStep/index.html.md)
-- [createStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/createStoresStep/index.html.md)
-- [updateStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateStoresStep/index.html.md)
-- [deleteStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteStoresStep/index.html.md)
-- [createTaxRateRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createTaxRateRulesStep/index.html.md)
-- [createTaxRatesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createTaxRatesStep/index.html.md)
-- [createTaxRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createTaxRegionsStep/index.html.md)
-- [deleteTaxRateRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteTaxRateRulesStep/index.html.md)
-- [deleteTaxRatesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteTaxRatesStep/index.html.md)
-- [deleteTaxRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteTaxRegionsStep/index.html.md)
-- [listTaxRateIdsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listTaxRateIdsStep/index.html.md)
-- [getItemTaxLinesStep](https://docs.medusajs.com/references/medusa-workflows/steps/getItemTaxLinesStep/index.html.md)
-- [listTaxRateRuleIdsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listTaxRateRuleIdsStep/index.html.md)
-- [updateTaxRatesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateTaxRatesStep/index.html.md)
-- [updateTaxRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateTaxRegionsStep/index.html.md)
- [createStockLocations](https://docs.medusajs.com/references/medusa-workflows/steps/createStockLocations/index.html.md)
- [deleteStockLocationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteStockLocationsStep/index.html.md)
- [updateStockLocationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateStockLocationsStep/index.html.md)
+- [deleteShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingProfilesStep/index.html.md)
+- [createStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/createStoresStep/index.html.md)
+- [deleteStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteStoresStep/index.html.md)
+- [updateStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateStoresStep/index.html.md)
+- [updateUsersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateUsersStep/index.html.md)
- [createUsersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createUsersStep/index.html.md)
- [deleteUsersStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteUsersStep/index.html.md)
-- [updateUsersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateUsersStep/index.html.md)
+- [createTaxRatesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createTaxRatesStep/index.html.md)
+- [deleteTaxRateRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteTaxRateRulesStep/index.html.md)
+- [createTaxRateRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createTaxRateRulesStep/index.html.md)
+- [deleteTaxRatesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteTaxRatesStep/index.html.md)
+- [deleteTaxRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteTaxRegionsStep/index.html.md)
+- [createTaxRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createTaxRegionsStep/index.html.md)
+- [getItemTaxLinesStep](https://docs.medusajs.com/references/medusa-workflows/steps/getItemTaxLinesStep/index.html.md)
+- [updateTaxRatesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateTaxRatesStep/index.html.md)
+- [listTaxRateIdsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listTaxRateIdsStep/index.html.md)
+- [listTaxRateRuleIdsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listTaxRateRuleIdsStep/index.html.md)
+- [updateTaxRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateTaxRegionsStep/index.html.md)
# Medusa CLI Reference
@@ -28767,6 +29005,100 @@ npx medusa --help
***
+# build Command - Medusa CLI Reference
+
+Create a standalone build of the Medusa application.
+
+This creates a build that:
+
+- Doesn't rely on the source TypeScript files.
+- Can be copied to a production server reliably.
+
+The build is outputted to a new `.medusa/server` directory.
+
+```bash
+npx medusa build
+```
+
+Refer to [this section](#run-built-medusa-application) for next steps.
+
+## Options
+
+|Option|Description|
+|---|---|---|
+|\`--admin-only\`|Whether to only build the admin to host it separately. If this option is not passed, the admin is built to the |
+
+***
+
+## Run Built Medusa Application
+
+After running the `build` command, use the following step to run the built Medusa application:
+
+- Change to the `.medusa/server` directory and install the dependencies:
+
+```bash npm2yarn
+cd .medusa/server && npm install
+```
+
+- When running the application locally, make sure to copy the `.env` file from the root project's directory. In production, use system environment variables instead.
+
+```bash npm2yarn
+cp .env .medusa/server/.env.production
+```
+
+- In the system environment variables, set `NODE_ENV` to `production`:
+
+```bash
+NODE_ENV=production
+```
+
+- Use the `start` command to run the application:
+
+```bash npm2yarn
+cd .medusa/server && npm run start
+```
+
+***
+
+## Build Medusa Admin
+
+By default, the Medusa Admin is built to the `.medusa/server/public/admin` directory.
+
+If you want a separate build to host the admin standalone, such as on Vercel, pass the `--admin-only` option as explained in the [Options](#options) section. This outputs the admin to the `.medusa/admin` directory instead.
+
+
+# develop Command - Medusa CLI Reference
+
+Start Medusa application in development. This command watches files for any changes, then rebuilds the files and restarts the Medusa application.
+
+```bash
+npx medusa develop
+```
+
+## Options
+
+|Option|Description|Default|
+|---|---|---|---|---|
+|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
+|\`-p \\`|Set port of the Medusa server.|\`9000\`|
+
+
+# exec Command - Medusa CLI Reference
+
+Run a custom CLI script. Learn more about it in [this guide](https://docs.medusajs.com/docs/learn/fundamentals/custom-cli-scripts/index.html.md).
+
+```bash
+npx medusa exec [file] [args...]
+```
+
+## Arguments
+
+|Argument|Description|Required|
+|---|---|---|---|---|
+|\`file\`|The path to the TypeScript or JavaScript file holding the function to execute.|Yes|
+|\`args\`|A list of arguments to pass to the function. These arguments are passed in the |No|
+
+
# db Commands - Medusa CLI Reference
Commands starting with `db:` perform actions on the database.
@@ -28887,68 +29219,6 @@ npx medusa db:sync-links
|\`--execute-all\`|Skip prompts when syncing links and execute all (including unsafe) actions.|No|Prompts are shown for unsafe actions, by default.|
-# build Command - Medusa CLI Reference
-
-Create a standalone build of the Medusa application.
-
-This creates a build that:
-
-- Doesn't rely on the source TypeScript files.
-- Can be copied to a production server reliably.
-
-The build is outputted to a new `.medusa/server` directory.
-
-```bash
-npx medusa build
-```
-
-Refer to [this section](#run-built-medusa-application) for next steps.
-
-## Options
-
-|Option|Description|
-|---|---|---|
-|\`--admin-only\`|Whether to only build the admin to host it separately. If this option is not passed, the admin is built to the |
-
-***
-
-## Run Built Medusa Application
-
-After running the `build` command, use the following step to run the built Medusa application:
-
-- Change to the `.medusa/server` directory and install the dependencies:
-
-```bash npm2yarn
-cd .medusa/server && npm install
-```
-
-- When running the application locally, make sure to copy the `.env` file from the root project's directory. In production, use system environment variables instead.
-
-```bash npm2yarn
-cp .env .medusa/server/.env.production
-```
-
-- In the system environment variables, set `NODE_ENV` to `production`:
-
-```bash
-NODE_ENV=production
-```
-
-- Use the `start` command to run the application:
-
-```bash npm2yarn
-cd .medusa/server && npm run start
-```
-
-***
-
-## Build Medusa Admin
-
-By default, the Medusa Admin is built to the `.medusa/server/public/admin` directory.
-
-If you want a separate build to host the admin standalone, such as on Vercel, pass the `--admin-only` option as explained in the [Options](#options) section. This outputs the admin to the `.medusa/admin` directory instead.
-
-
# new Command - Medusa CLI Reference
Create a new Medusa application. Unlike the `create-medusa-app` CLI tool, this command provides more flexibility for experienced Medusa developers in creating and configuring their project.
@@ -28978,54 +29248,6 @@ medusa new [ []]
|\`--db-host \\`|The database host to use for database setup.|
-# develop Command - Medusa CLI Reference
-
-Start Medusa application in development. This command watches files for any changes, then rebuilds the files and restarts the Medusa application.
-
-```bash
-npx medusa develop
-```
-
-## Options
-
-|Option|Description|Default|
-|---|---|---|---|---|
-|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
-|\`-p \\`|Set port of the Medusa server.|\`9000\`|
-
-
-# exec Command - Medusa CLI Reference
-
-Run a custom CLI script. Learn more about it in [this guide](https://docs.medusajs.com/docs/learn/fundamentals/custom-cli-scripts/index.html.md).
-
-```bash
-npx medusa exec [file] [args...]
-```
-
-## Arguments
-
-|Argument|Description|Required|
-|---|---|---|---|---|
-|\`file\`|The path to the TypeScript or JavaScript file holding the function to execute.|Yes|
-|\`args\`|A list of arguments to pass to the function. These arguments are passed in the |No|
-
-
-# start Command - Medusa CLI Reference
-
-Start the Medusa application in production.
-
-```bash
-npx medusa start
-```
-
-## Options
-
-|Option|Description|Default|
-|---|---|---|---|---|
-|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
-|\`-p \\`|Set port of the Medusa server.|\`9000\`|
-
-
# plugin Commands - Medusa CLI Reference
Commands starting with `plugin:` perform actions related to [plugin](https://docs.medusajs.com/docs/learn/fundamentals/plugins/index.html.md) development.
@@ -29087,22 +29309,6 @@ npx medusa plugin:build
```
-# telemetry Command - Medusa CLI Reference
-
-Enable or disable the collection of anonymous data usage. If no option is provided, the command enables the collection of anonymous data usage.
-
-```bash
-npx medusa telemetry
-```
-
-#### Options
-
-|Option|Description|
-|---|---|---|
-|\`--enable\`|Enable telemetry (default).|
-|\`--disable\`|Disable telemetry.|
-
-
# start-cluster Command - Medusa CLI Reference
Starts the Medusa application in [cluster mode](https://expressjs.com/en/advanced/best-practice-performance.html#run-your-app-in-a-cluster).
@@ -29122,6 +29328,38 @@ npx medusa start-cluster
|\`-p \\`|Set port of the Medusa server.|\`9000\`|
+# start Command - Medusa CLI Reference
+
+Start the Medusa application in production.
+
+```bash
+npx medusa start
+```
+
+## Options
+
+|Option|Description|Default|
+|---|---|---|---|---|
+|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
+|\`-p \\`|Set port of the Medusa server.|\`9000\`|
+
+
+# telemetry Command - Medusa CLI Reference
+
+Enable or disable the collection of anonymous data usage. If no option is provided, the command enables the collection of anonymous data usage.
+
+```bash
+npx medusa telemetry
+```
+
+#### Options
+
+|Option|Description|
+|---|---|---|
+|\`--enable\`|Enable telemetry (default).|
+|\`--disable\`|Disable telemetry.|
+
+
# user Command - Medusa CLI Reference
Create a new admin user.
@@ -29164,6 +29402,113 @@ npx medusa --help
***
+# build Command - Medusa CLI Reference
+
+Create a standalone build of the Medusa application.
+
+This creates a build that:
+
+- Doesn't rely on the source TypeScript files.
+- Can be copied to a production server reliably.
+
+The build is outputted to a new `.medusa/server` directory.
+
+```bash
+npx medusa build
+```
+
+Refer to [this section](#run-built-medusa-application) for next steps.
+
+## Options
+
+|Option|Description|
+|---|---|---|
+|\`--admin-only\`|Whether to only build the admin to host it separately. If this option is not passed, the admin is built to the |
+
+***
+
+## Run Built Medusa Application
+
+After running the `build` command, use the following step to run the built Medusa application:
+
+- Change to the `.medusa/server` directory and install the dependencies:
+
+```bash npm2yarn
+cd .medusa/server && npm install
+```
+
+- When running the application locally, make sure to copy the `.env` file from the root project's directory. In production, use system environment variables instead.
+
+```bash npm2yarn
+cp .env .medusa/server/.env.production
+```
+
+- In the system environment variables, set `NODE_ENV` to `production`:
+
+```bash
+NODE_ENV=production
+```
+
+- Use the `start` command to run the application:
+
+```bash npm2yarn
+cd .medusa/server && npm run start
+```
+
+***
+
+## Build Medusa Admin
+
+By default, the Medusa Admin is built to the `.medusa/server/public/admin` directory.
+
+If you want a separate build to host the admin standalone, such as on Vercel, pass the `--admin-only` option as explained in the [Options](#options) section. This outputs the admin to the `.medusa/admin` directory instead.
+
+
+# exec Command - Medusa CLI Reference
+
+Run a custom CLI script. Learn more about it in [this guide](https://docs.medusajs.com/docs/learn/fundamentals/custom-cli-scripts/index.html.md).
+
+```bash
+npx medusa exec [file] [args...]
+```
+
+## Arguments
+
+|Argument|Description|Required|
+|---|---|---|---|---|
+|\`file\`|The path to the TypeScript or JavaScript file holding the function to execute.|Yes|
+|\`args\`|A list of arguments to pass to the function. These arguments are passed in the |No|
+
+
+# new Command - Medusa CLI Reference
+
+Create a new Medusa application. Unlike the `create-medusa-app` CLI tool, this command provides more flexibility for experienced Medusa developers in creating and configuring their project.
+
+```bash
+medusa new [ []]
+```
+
+## Arguments
+
+|Argument|Description|Required|Default|
+|---|---|---|---|---|---|---|
+|\`dir\_name\`|The name of the directory to create the Medusa application in.|Yes|-|
+|\`starter\_url\`|The URL of the starter repository to create the project from.|No|\`https://github.com/medusajs/medusa-starter-default\`|
+
+## Options
+
+|Option|Description|
+|---|---|---|
+|\`-y\`|Skip all prompts, such as databaes prompts. A database might not be created if default PostgreSQL credentials don't work.|
+|\`--skip-db\`|Skip database creation.|
+|\`--skip-env\`|Skip populating |
+|\`--db-user \\`|The database user to use for database setup.|
+|\`--db-database \\`|The name of the database used for database setup.|
+|\`--db-pass \\`|The database password to use for database setup.|
+|\`--db-port \\`|The database port to use for database setup.|
+|\`--db-host \\`|The database host to use for database setup.|
+
+
# db Commands - Medusa CLI Reference
Commands starting with `db:` perform actions on the database.
@@ -29284,68 +29629,6 @@ npx medusa db:sync-links
|\`--execute-all\`|Skip prompts when syncing links and execute all (including unsafe) actions.|No|Prompts are shown for unsafe actions, by default.|
-# build Command - Medusa CLI Reference
-
-Create a standalone build of the Medusa application.
-
-This creates a build that:
-
-- Doesn't rely on the source TypeScript files.
-- Can be copied to a production server reliably.
-
-The build is outputted to a new `.medusa/server` directory.
-
-```bash
-npx medusa build
-```
-
-Refer to [this section](#run-built-medusa-application) for next steps.
-
-## Options
-
-|Option|Description|
-|---|---|---|
-|\`--admin-only\`|Whether to only build the admin to host it separately. If this option is not passed, the admin is built to the |
-
-***
-
-## Run Built Medusa Application
-
-After running the `build` command, use the following step to run the built Medusa application:
-
-- Change to the `.medusa/server` directory and install the dependencies:
-
-```bash npm2yarn
-cd .medusa/server && npm install
-```
-
-- When running the application locally, make sure to copy the `.env` file from the root project's directory. In production, use system environment variables instead.
-
-```bash npm2yarn
-cp .env .medusa/server/.env.production
-```
-
-- In the system environment variables, set `NODE_ENV` to `production`:
-
-```bash
-NODE_ENV=production
-```
-
-- Use the `start` command to run the application:
-
-```bash npm2yarn
-cd .medusa/server && npm run start
-```
-
-***
-
-## Build Medusa Admin
-
-By default, the Medusa Admin is built to the `.medusa/server/public/admin` directory.
-
-If you want a separate build to host the admin standalone, such as on Vercel, pass the `--admin-only` option as explained in the [Options](#options) section. This outputs the admin to the `.medusa/admin` directory instead.
-
-
# develop Command - Medusa CLI Reference
Start Medusa application in development. This command watches files for any changes, then rebuilds the files and restarts the Medusa application.
@@ -29362,67 +29645,6 @@ npx medusa develop
|\`-p \\`|Set port of the Medusa server.|\`9000\`|
-# new Command - Medusa CLI Reference
-
-Create a new Medusa application. Unlike the `create-medusa-app` CLI tool, this command provides more flexibility for experienced Medusa developers in creating and configuring their project.
-
-```bash
-medusa new [ []]
-```
-
-## Arguments
-
-|Argument|Description|Required|Default|
-|---|---|---|---|---|---|---|
-|\`dir\_name\`|The name of the directory to create the Medusa application in.|Yes|-|
-|\`starter\_url\`|The URL of the starter repository to create the project from.|No|\`https://github.com/medusajs/medusa-starter-default\`|
-
-## Options
-
-|Option|Description|
-|---|---|---|
-|\`-y\`|Skip all prompts, such as databaes prompts. A database might not be created if default PostgreSQL credentials don't work.|
-|\`--skip-db\`|Skip database creation.|
-|\`--skip-env\`|Skip populating |
-|\`--db-user \\`|The database user to use for database setup.|
-|\`--db-database \\`|The name of the database used for database setup.|
-|\`--db-pass \\`|The database password to use for database setup.|
-|\`--db-port \\`|The database port to use for database setup.|
-|\`--db-host \\`|The database host to use for database setup.|
-
-
-# exec Command - Medusa CLI Reference
-
-Run a custom CLI script. Learn more about it in [this guide](https://docs.medusajs.com/docs/learn/fundamentals/custom-cli-scripts/index.html.md).
-
-```bash
-npx medusa exec [file] [args...]
-```
-
-## Arguments
-
-|Argument|Description|Required|
-|---|---|---|---|---|
-|\`file\`|The path to the TypeScript or JavaScript file holding the function to execute.|Yes|
-|\`args\`|A list of arguments to pass to the function. These arguments are passed in the |No|
-
-
-# start Command - Medusa CLI Reference
-
-Start the Medusa application in production.
-
-```bash
-npx medusa start
-```
-
-## Options
-
-|Option|Description|Default|
-|---|---|---|---|---|
-|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
-|\`-p \\`|Set port of the Medusa server.|\`9000\`|
-
-
# plugin Commands - Medusa CLI Reference
Commands starting with `plugin:` perform actions related to [plugin](https://docs.medusajs.com/docs/learn/fundamentals/plugins/index.html.md) development.
@@ -29484,6 +29706,22 @@ npx medusa plugin:build
```
+# start Command - Medusa CLI Reference
+
+Start the Medusa application in production.
+
+```bash
+npx medusa start
+```
+
+## Options
+
+|Option|Description|Default|
+|---|---|---|---|---|
+|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
+|\`-p \\`|Set port of the Medusa server.|\`9000\`|
+
+
# start-cluster Command - Medusa CLI Reference
Starts the Medusa application in [cluster mode](https://expressjs.com/en/advanced/best-practice-performance.html#run-your-app-in-a-cluster).
@@ -33659,795 +33897,6 @@ const user = await userModuleService.createUsers({
```
-# Implement Custom Line Item Pricing in Medusa
-
-In this guide, you'll learn how to add line items with custom prices to a cart in Medusa.
-
-When you install a Medusa application, you get a fully-fledged commerce platform with a framework for customization. The Medusa application's commerce features are built around [commerce modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md) which are available out-of-the-box. These features include managing carts and adding line items to them.
-
-By default, you can add product variants to the cart, where the price of its associated line item is based on the product variant's price. However, you can build customizations to add line items with custom prices to the cart. This is useful when integrating an Enterprise Resource Planning (ERP), Product Information Management (PIM), or other third-party services that provide real-time prices for your products.
-
-To showcase how to add line items with custom prices to the cart, this guide uses [GoldAPI.io](https://www.goldapi.io) as an example of a third-party system that you can integrate for real-time prices. You can follow the same approach for other third-party integrations that provide custom pricing.
-
-You can follow this guide whether you're new to Medusa or an advanced Medusa developer.
-
-### Summary
-
-This guide will teach you how to:
-
-- Install and set up Medusa.
-- Integrate the third-party service [GoldAPI.io](https://www.goldapi.io) that retrieves real-time prices for metals like Gold and Silver.
-- Add an API route to add a product variant that has metals, such as a gold ring, to the cart with the real-time price retrieved from the third-party service.
-
-
-
-- [Custom Item Price Repository](https://github.com/medusajs/examples/tree/main/custom-item-price): Find the full code for this guide in this repository.
-- [OpenApi Specs for Postman](https://res.cloudinary.com/dza7lstvk/raw/upload/v1738246728/OpenApi/Custom_Item_Price_gdfnl3.yaml): Import this OpenApi Specs file into tools like Postman.
-
-***
-
-## Step 1: Install a Medusa Application
-
-### Prerequisites
-
-- [Node.js v20+](https://nodejs.org/en/download)
-- [Git CLI tool](https://git-scm.com/downloads)
-- [PostgreSQL](https://www.postgresql.org/download/)
-
-Start by installing the Medusa application on your machine with the following command:
-
-```bash
-npx create-medusa-app@latest
-```
-
-You'll first be asked for the project's name. You can also optionally choose to install the [Next.js starter storefront](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/nextjs-starter/index.html.md).
-
-Afterwards, the installation process will start, which will install the Medusa application in a directory with your project's name. If you chose to install the Next.js starter, it'll be installed in a separate directory with the `{project-name}-storefront` name.
-
-The Medusa application is composed of a headless Node.js server and an admin dashboard. The storefront is installed or custom-built separately and connects to the Medusa application through its REST endpoints, called [API routes](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md). Learn more about Medusa's architecture in [this documentation](https://docs.medusajs.com/docs/learn/introduction/architecture/index.html.md).
-
-Once the installation finishes successfully, the Medusa Admin dashboard will open with a form to create a new user. Enter the user's credentials and submit the form. Afterwards, you can log in with the new user and explore the dashboard.
-
-Check out the [troubleshooting guides](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/troubleshooting/create-medusa-app-errors/index.html.md) for help.
-
-***
-
-## Step 2: Integrate GoldAPI.io
-
-### Prerequisites
-
-- [GoldAPI.io Account. You can create a free account.](https://www.goldapi.io)
-
-To integrate third-party services into Medusa, you create a custom module. A module is a reusable package with functionalities related to a single feature or domain. Medusa integrates the module into your application without implications or side effects on your setup.
-
-In this step, you'll create a Metal Price Module that uses the GoldAPI.io service to retrieve real-time prices for metals like Gold and Silver. You'll use this module later to retrieve the real-time price of a product variant based on the metals in it, and add it to the cart with that custom price.
-
-Learn more about modules in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md).
-
-### Create Module Directory
-
-A module is created under the `src/modules` directory of your Medusa application. So, create the directory `src/modules/metal-prices`.
-
-
-
-### Create Module's Service
-
-You define a module's functionalities in a service. A service is a TypeScript or JavaScript class that the module exports. In the service's methods, you can connect to the database, which is useful if your module defines tables in the database, or connect to a third-party service.
-
-In this section, you'll create the Metal Prices Module's service that connects to the GoldAPI.io service to retrieve real-time prices for metals.
-
-Start by creating the file `src/modules/metal-prices/service.ts` with the following content:
-
-
-
-```ts title="src/modules/metal-prices/service.ts"
-type Options = {
- accessToken: string
- sandbox?: boolean
-}
-
-export default class MetalPricesModuleService {
- protected options_: Options
-
- constructor({}, options: Options) {
- this.options_ = options
- }
-}
-```
-
-A module can accept options that are passed to its service. You define an `Options` type that indicates the options the module accepts. It accepts two options:
-
-- `accessToken`: The access token for the GoldAPI.io service.
-- `sandbox`: A boolean that indicates whether to simulate sending requests to the GoldAPI.io service. This is useful when running in a test environment.
-
-The service's constructor receives the module's options as a second parameter. You store the options in the service's `options_` property.
-
-A module has a container of Medusa framework tools and local resources in the module that you can access in the service constructor's first parameter. Learn more in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/container/index.html.md).
-
-#### Add Method to Retrieve Metal Prices
-
-Next, you'll add the method to retrieve the metal prices from the third-party service.
-
-First, add the following types at the beginning of `src/modules/metal-prices/service.ts`:
-
-```ts title="src/modules/metal-prices/service.ts"
-export enum MetalSymbols {
- Gold = "XAU",
- Silver = "XAG",
- Platinum = "XPT",
- Palladium = "XPD"
-}
-
-export type PriceResponse = {
- metal: MetalSymbols
- currency: string
- exchange: string
- symbol: string
- price: number
- [key: string]: unknown
-}
-
-```
-
-The `MetalSymbols` enum defines the symbols for metals like Gold, Silver, Platinum, and Palladium. The `PriceResponse` type defines the structure of the response from the GoldAPI.io's endpoint.
-
-Next, add the method `getMetalPrices` to the `MetalPricesModuleService` class:
-
-```ts title="src/modules/metal-prices/service.ts"
-import { MedusaError } from "@medusajs/framework/utils"
-
-// ...
-
-export default class MetalPricesModuleService {
- // ...
- async getMetalPrice(
- symbol: MetalSymbols,
- currency: string
- ): Promise {
- const upperCaseSymbol = symbol.toUpperCase()
- const upperCaseCurrency = currency.toUpperCase()
-
- return fetch(`https://www.goldapi.io/api/${upperCaseSymbol}/${upperCaseCurrency}`, {
- headers: {
- "x-access-token": this.options_.accessToken,
- "Content-Type": "application/json",
- },
- redirect: "follow",
- }).then((response) => response.json())
- .then((response) => {
- if (response.error) {
- throw new MedusaError(
- MedusaError.Types.INVALID_DATA,
- response.error
- )
- }
-
- return response
- })
- }
-}
-```
-
-The `getMetalPrice` method accepts the metal symbol and currency as parameters. You send a request to GoldAPI.io's `/api/{symbol}/{currency}` endpoint to retrieve the metal's price, also passing the access token in the request's headers.
-
-If the response contains an error, you throw a `MedusaError` with the error message. Otherwise, you return the response, which is of type `PriceResponse`.
-
-#### Add Helper Methods
-
-You'll also add two helper methods to the `MetalPricesModuleService`. The first one is `getMetalSymbols` that returns the metal symbols as an array of strings:
-
-```ts title="src/modules/metal-prices/service.ts"
-export default class MetalPricesModuleService {
- // ...
- async getMetalSymbols(): Promise {
- return Object.values(MetalSymbols)
- }
-}
-```
-
-The second is `getMetalSymbol` that receives a name like `gold` and returns the corresponding metal symbol:
-
-```ts title="src/modules/metal-prices/service.ts"
-export default class MetalPricesModuleService {
- // ...
- async getMetalSymbol(name: string): Promise {
- const formattedName = name.charAt(0).toUpperCase() + name.slice(1).toLowerCase()
- return MetalSymbols[formattedName as keyof typeof MetalSymbols]
- }
-}
-```
-
-You'll use these methods in later steps.
-
-### Export Module Definition
-
-The final piece to a module is its definition, which you export in an `index.ts` file at its root directory. This definition tells Medusa the name of the module and its service.
-
-So, create the file `src/modules/metal-prices/index.ts` with the following content:
-
-
-
-```ts title="src/modules/metal-prices/index.ts"
-import { Module } from "@medusajs/framework/utils"
-import MetalPricesModuleService from "./service"
-
-export const METAL_PRICES_MODULE = "metal-prices"
-
-export default Module(METAL_PRICES_MODULE, {
- service: MetalPricesModuleService,
-})
-```
-
-You use the `Module` function from the Modules SDK to create the module's definition. It accepts two parameters:
-
-1. The module's name, which is `metal-prices`.
-2. An object with a required property `service` indicating the module's service.
-
-### Add Module to Medusa's Configurations
-
-Once you finish building the module, add it to Medusa's configurations to start using it.
-
-In `medusa-config.ts`, add a `modules` property and pass an array with your custom module:
-
-```ts title="medusa-config.ts"
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "./src/modules/metal-prices",
- options: {
- accessToken: process.env.GOLD_API_TOKEN,
- sandbox: process.env.GOLD_API_SANDBOX === "true",
- },
- },
- ],
-})
-```
-
-Each object in the `modules` array has a `resolve` property, whose value is either a path to the module's directory, or an `npm` package’s name.
-
-The object also has an `options` property that accepts the module's options. You set the `accessToken` and `sandbox` options based on environment variables.
-
-You'll find the access token at the top of your GoldAPI.io dashboard.
-
-
-
-Set the access token as an environment variable in `.env`:
-
-```bash
-GOLD_API_TOKEN=
-```
-
-You'll start using the module in the next steps.
-
-***
-
-## Step 3: Add Custom Item to Cart Workflow
-
-In this section, you'll implement the logic to retrieve the real-time price of a variant based on the metals in it, then add the variant to the cart with the custom price. You'll implement this logic in a workflow.
-
-A workflow is a series of queries and actions, called steps, that complete a task. You construct a workflow like you construct a function, but it's a special function that allows you to track its executions' progress, define roll-back logic, and configure other advanced features. Then, you execute the workflow from other customizations, such as in an endpoint.
-
-Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md)
-
-The workflow you'll implement in this section has the following steps:
-
-- [useQueryGraphStep (Retrieve Cart)](https://docs.medusajs.com/references/helper-steps/useQueryGraphStep/index.html.md): Retrieve the cart's ID and currency using Query.
-- [useQueryGraphStep (Retrieve Variant)](https://docs.medusajs.com/references/helper-steps/useQueryGraphStep/index.html.md): Retrieve the variant's details using Query
-- [getVariantMetalPricesStep](#getvariantmetalpricesstep): Retrieve the variant's price using the third-party service.
-- [addToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addToCartWorkflow/index.html.md): Add the item with the custom price to the cart.
-- [useQueryGraphStep (Retrieve Cart)](https://docs.medusajs.com/references/helper-steps/useQueryGraphStep/index.html.md): Retrieve the updated cart's details using Query.
-
-`useQueryGraphStep` and `addToCartWorkflow` are available through Medusa's core workflows package. You'll only implement the `getVariantMetalPricesStep`.
-
-### getVariantMetalPricesStep
-
-The `getVariantMetalPricesStep` will retrieve the real-time metal price of a variant received as an input.
-
-To create the step, create the file `src/workflows/steps/get-variant-metal-prices.ts` with the following content:
-
-
-
-```ts title="src/workflows/steps/get-variant-metal-prices.ts"
-import { createStep } from "@medusajs/framework/workflows-sdk"
-import { ProductVariantDTO } from "@medusajs/framework/types"
-import { METAL_PRICES_MODULE } from "../../modules/metal-prices"
-import MetalPricesModuleService from "../../modules/metal-prices/service"
-
-export type GetVariantMetalPricesStepInput = {
- variant: ProductVariantDTO & {
- calculated_price?: {
- calculated_amount: number
- }
- }
- currencyCode: string
- quantity?: number
-}
-
-export const getVariantMetalPricesStep = createStep(
- "get-variant-metal-prices",
- async ({
- variant,
- currencyCode,
- quantity = 1,
- }: GetVariantMetalPricesStepInput, { container }) => {
- const metalPricesModuleService: MetalPricesModuleService =
- container.resolve(METAL_PRICES_MODULE)
-
- // TODO
- }
-)
-```
-
-You create a step with `createStep` from the Workflows SDK. It accepts two parameters:
-
-1. The step's unique name, which is `get-variant-metal-prices`.
-2. An async function that receives two parameters:
- - An input object with the variant, currency code, and quantity. The variant has a `calculated_price` property that holds the variant's fixed price in the Medusa application. This is useful when you want to add a fixed price to the real-time custom price, such as handling fees.
- - The [Medusa container](https://docs.medusajs.com/docs/learn/fundamentals/medusa-container/index.html.md), which is a registry of framework and commerce tools that you can access in the step.
-
-In the step function, so far you only resolve the Metal Prices Module's service from the Medusa container.
-
-Next, you'll validate that the specified variant can have its price calculated. Add the following import at the top of the file:
-
-```ts title="src/workflows/steps/get-variant-metal-prices.ts"
-import { MedusaError } from "@medusajs/framework/utils"
-```
-
-And replace the `TODO` in the step function with the following:
-
-```ts title="src/workflows/steps/get-variant-metal-prices.ts"
-const variantMetal = variant.options.find(
- (option) => option.option?.title === "Metal"
-)?.value
-const metalSymbol = await metalPricesModuleService
- .getMetalSymbol(variantMetal || "")
-
-if (!metalSymbol) {
- throw new MedusaError(
- MedusaError.Types.INVALID_DATA,
- "Variant doesn't have metal. Make sure the variant's SKU matches a metal symbol."
- )
-}
-
-if (!variant.weight) {
- throw new MedusaError(
- MedusaError.Types.INVALID_DATA,
- "Variant doesn't have weight. Make sure the variant has weight to calculate its price."
- )
-}
-
-// TODO retrieve custom price
-```
-
-In the code above, you first retrieve the metal option's value from the variant's options, assuming that a variant has metals if it has a `Metal` option. Then, you retrieve the metal symbol of the option's value using the `getMetalSymbol` method of the Metal Prices Module's service.
-
-If the variant doesn't have a metal in its options, the option's value is not valid, or the variant doesn't have a weight, you throw an error. The weight is necessary to calculate the price based on the metal's price per weight.
-
-Next, you'll retrieve the real-time price of the metal using the third-party service. Replace the `TODO` with the following:
-
-```ts title="src/workflows/steps/get-variant-metal-prices.ts"
-let price = variant.calculated_price?.calculated_amount || 0
-const weight = variant.weight
-const { price: metalPrice } = await metalPricesModuleService.getMetalPrice(
- metalSymbol as MetalSymbols, currencyCode
-)
-price += (metalPrice * weight * quantity)
-
-return new StepResponse(price)
-```
-
-In the code above, you first set the price to the variant's fixed price, if it has one. Then, you retrieve the metal's price using the `getMetalPrice` method of the Metal Prices Module's service.
-
-Finally, you calculate the price by multiplying the metal's price by the variant's weight and the quantity to add to the cart, then add the fixed price to it.
-
-Every step must return a `StepResponse` instance. The `StepResponse` constructor accepts the step's output as a parameter, which in this case is the variant's price.
-
-### Create addCustomToCartWorkflow
-
-Now that you have the `getVariantMetalPricesStep`, you can create the workflow that adds the item with custom pricing to the cart.
-
-Create the file `src/workflows/add-custom-to-cart.ts` with the following content:
-
-
-
-```ts title="src/workflows/add-custom-to-cart.ts" highlights={workflowHighlights}
-import { createWorkflow } from "@medusajs/framework/workflows-sdk"
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-import { QueryContext } from "@medusajs/framework/utils"
-
-type AddCustomToCartWorkflowInput = {
- cart_id: string
- item: {
- variant_id: string
- quantity: number
- metadata?: Record
- }
-}
-
-export const addCustomToCartWorkflow = createWorkflow(
- "add-custom-to-cart",
- ({ cart_id, item }: AddCustomToCartWorkflowInput) => {
- // @ts-ignore
- const { data: carts } = useQueryGraphStep({
- entity: "cart",
- filters: { id: cart_id },
- fields: ["id", "currency_code"],
- })
-
- const { data: variants } = useQueryGraphStep({
- entity: "variant",
- fields: [
- "*",
- "options.*",
- "options.option.*",
- "calculated_price.*",
- ],
- filters: {
- id: item.variant_id,
- },
- options: {
- throwIfKeyNotFound: true,
- },
- context: {
- calculated_price: QueryContext({
- currency_code: carts[0].currency_code,
- }),
- },
- }).config({ name: "retrieve-variant" })
-
- // TODO add more steps
- }
-)
-```
-
-You create a workflow with `createWorkflow` from the Workflows SDK. It accepts two parameters:
-
-1. The workflow's unique name, which is `add-custom-to-cart`.
-2. A function that receives an input object with the cart's ID and the item to add to the cart. The item has the variant's ID, quantity, and optional metadata.
-
-In the function, you first retrieve the cart's details using the `useQueryGraphStep` helper step. This step uses [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md) which is a Modules SDK tool that retrieves data across modules. You use it to retrieve the cart's ID and currency code.
-
-You also retrieve the variant's details using the `useQueryGraphStep` helper step. You pass the variant's ID to the step's filters and specify the fields to retrieve. To retrieve the variant's price based on the cart's context, you pass the cart's currency code to the `calculated_price` context.
-
-Next, you'll retrieve the variant's real-time price using the `getVariantMetalPricesStep` you created earlier. First, add the following import:
-
-```ts title="src/workflows/add-custom-to-cart.ts"
-import {
- getVariantMetalPricesStep,
- GetVariantMetalPricesStepInput,
-} from "./steps/get-variant-metal-prices"
-```
-
-Then, replace the `TODO` in the workflow with the following:
-
-```ts title="src/workflows/add-custom-to-cart.ts"
-const price = getVariantMetalPricesStep({
- variant: variants[0],
- currencyCode: carts[0].currency_code,
- quantity: item.quantity,
-} as unknown as GetVariantMetalPricesStepInput)
-
-// TODO add item with custom price to cart
-```
-
-You execute the `getVariantMetalPricesStep` passing it the variant's details, the cart's currency code, and the quantity of the item to add to the cart. The step returns the variant's custom price.
-
-Next, you'll add the item with the custom price to the cart. First, add the following imports at the top of the file:
-
-```ts title="src/workflows/add-custom-to-cart.ts"
-import { transform } from "@medusajs/framework/workflows-sdk"
-import { addToCartWorkflow } from "@medusajs/medusa/core-flows"
-```
-
-Then, replace the `TODO` in the workflow with the following:
-
-```ts title="src/workflows/add-custom-to-cart.ts"
-const itemToAdd = transform({
- item,
- price,
-}, (data) => {
- return [{
- ...data.item,
- unit_price: data.price,
- }]
-})
-
-addToCartWorkflow.runAsStep({
- input: {
- items: itemToAdd,
- cart_id,
- },
-})
-
-// TODO retrieve and return cart
-```
-
-You prepare the item to add to the cart using `transform` from the Workflows SDK. It allows you to manipulate and create variables in a workflow. After that, you use Medusa's `addToCartWorkflow` to add the item with the custom price to the cart.
-
-A workflow's constructor function has some constraints in implementation, which is why you need to use `transform` for variable manipulation. Learn more about these constraints in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/constructor-constraints/index.html.md).
-
-Lastly, you'll retrieve the cart's details again and return them. Add the following import at the beginning of the file:
-
-```ts title="src/workflows/add-custom-to-cart.ts"
-import { WorkflowResponse } from "@medusajs/framework/workflows-sdk"
-```
-
-And replace the last `TODO` in the workflow with the following:
-
-```ts title="src/workflows/add-custom-to-cart.ts"
-// @ts-ignore
-const { data: updatedCarts } = useQueryGraphStep({
- entity: "cart",
- filters: { id: cart_id },
- fields: ["id", "items.*"],
-}).config({ name: "refetch-cart" })
-
-return new WorkflowResponse({
- cart: updatedCarts[0],
-})
-```
-
-In the code above, you retrieve the updated cart's details using the `useQueryGraphStep` helper step. To return data from the workflow, you create and return a `WorkflowResponse` instance. It accepts as a parameter the data to return, which is the updated cart.
-
-In the next step, you'll use the workflow in a custom route to add an item with a custom price to the cart.
-
-***
-
-## Step 4: Create Add Custom Item to Cart API Route
-
-Now that you've implemented the logic to add an item with a custom price to the cart, you'll expose this functionality in an API route.
-
-An API Route is an endpoint that exposes commerce features to external applications and clients, such as storefronts. You'll create an API route at the path `/store/carts/:id/line-items-metals` that executes the workflow from the previous step to add a product variant with custom price to the cart.
-
-Learn more about API routes in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md).
-
-### Create API Route
-
-An API route is created in a `route.ts` file under a sub-directory of the `src/api` directory.
-
-The path of the API route is the file's path relative to `src/api`. So, to create the `/store/carts/:id/line-items-metals` API route, create the file `src/api/store/carts/[id]/line-items-metals/route.ts` with the following content:
-
-
-
-```ts title="src/api/store/carts/[id]/line-items-metals/route.ts"
-import { MedusaRequest, MedusaResponse } from "@medusajs/framework"
-import { HttpTypes } from "@medusajs/framework/types"
-import { addCustomToCartWorkflow } from "../../../../../workflows/add-custom-to-cart"
-
-export const POST = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- const { id } = req.params
- const item = req.validatedBody
-
- const { result } = await addCustomToCartWorkflow(req.scope)
- .run({
- input: {
- cart_id: id,
- item,
- },
- })
-
- res.status(200).json({ cart: result.cart })
-}
-```
-
-Since you export a `POST` function in this file, you're exposing a `POST` API route at `/store/carts/:id/line-items-metals`. The route handler function accepts two parameters:
-
-1. A request object with details and context on the request, such as path and body parameters.
-2. A response object to manipulate and send the response.
-
-In the function, you retrieve the cart's ID from the path parameter, and the item's details from the request body. This API route will accept the same request body parameters as Medusa's [Add Item to Cart API Route](https://docs.medusajs.com/api/store#carts_postcartsidlineitems).
-
-Then, you execute the `addCustomToCartWorkflow` by invoking it, passing it the Medusa container, which is available in the request's `scope` property, then executing its `run` method. You pass the workflow's input object with the cart's ID and the item to add to the cart.
-
-Finally, you return a response with the updated cart's details.
-
-### Add Request Body Validation Middleware
-
-To ensure that the request body contains the required parameters, you'll add a middleware that validates the incoming request's body based on a defined schema.
-
-A middleware is a function executed before the API route when a request is sent to it. You define middlewares in Medusa in the `src/api/middlewares.ts` directory.
-
-Learn more about middlewares in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/middlewares/index.html.md).
-
-To add a validation middleware to the custom API route, create the file `src/api/middlewares.ts` with the following content:
-
-
-
-```ts title="src/api/middlewares.ts"
-import {
- defineMiddlewares,
- validateAndTransformBody,
-} from "@medusajs/framework/http"
-import {
- StoreAddCartLineItem,
-} from "@medusajs/medusa/api/store/carts/validators"
-
-export default defineMiddlewares({
- routes: [
- {
- matcher: "/store/carts/:id/line-items-metals",
- method: "POST",
- middlewares: [
- validateAndTransformBody(
- StoreAddCartLineItem
- ),
- ],
- },
- ],
-})
-```
-
-In this file, you export the middlewares definition using `defineMiddlewares` from the Medusa Framework. This function accepts an object having a `routes` property, which is an array of middleware configurations to apply on routes.
-
-You pass in the `routes` array an object having the following properties:
-
-- `matcher`: The route to apply the middleware on.
-- `method`: The HTTP method to apply the middleware on for the specified API route.
-- `middlewares`: An array of the middlewares to apply. You apply the `validateAndTransformBody` middleware, which validates the request body based on the `StoreAddCartLineItem` schema. This validation schema is the same schema used for Medusa's [Add Item to Cart API Route](https://docs.medusajs.com/api/store#carts_postcartsidlineitems).
-
-Any request sent to the `/store/carts/:id/line-items-metals` API route will now fail if it doesn't have the required parameters.
-
-Learn more about API route validation in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/validation/index.html.md).
-
-### Prepare to Test API Route
-
-Before you test the API route, you'll prepare and retrieve the necessary data to add a product variant with a custom price to the cart.
-
-#### Create Product with Metal Variant
-
-You'll first create a product that has a `Metal` option, and variant(s) with values for this option.
-
-Start the Medusa application with the following command:
-
-```bash npm2yarn
-npm run dev
-```
-
-Then, open the Medusa Admin dashboard at `localhost:9000/app` and log in with the email and password you created when you installed the Medusa application in the first step.
-
-Once you log in, click on Products in the sidebar, then click the Create button at the top right.
-
-
-
-Then, in the Create Product form:
-
-1. Enter a name for the product, and optionally enter other details like description.
-2. Enable the "Yes, this is a product with variants" toggle.
-3. Under Product Options, enter "Metal" for the title, and enter "Gold" for the values.
-
-Once you're done, click the Continue button.
-
-
-
-You can skip the next two steps by clicking the Continue button again, then the Publish button.
-
-Once you're done, the product's page will open. You'll now add weight to the product's Gold variant. To do that:
-
-- Scroll to the Variants section and find the Gold variant.
-- Click on the three-dots icon at its right.
-- Choose "Edit" from the dropdown.
-
-
-
-In the side window that opens, find the Weight field, enter the weight, and click the Save button.
-
-
-
-Finally, you need to set fixed prices for the variant, even if they're just `0`. To do that:
-
-1. Click on the three-dots icon at the top right of the Variants section.
-2. Choose "Edit Prices" from the dropdown.
-
-
-
-For each cell in the table, either enter a fixed price for the specified currency or leave it as `0`. Once you're done, click the Save button.
-
-
-
-You'll use this variant to add it to the cart later. You can find its ID by clicking on the variant, opening its details page. Then, on the details page, click on the icon at the right of the JSON section, and copy the ID from the JSON data.
-
-
-
-#### Retrieve Publishable API Key
-
-All requests sent to API routes starting with `/store` must have a publishable API key in the header. This ensures the request's operations are scoped to the publishable API key's associated sales channels. For example, products that aren't available in a cart's sales channel can't be added to it.
-
-To retrieve the publishable API key, on the Medusa Admin:
-
-1. Click on Settings in the sidebar at the bottom left.
-2. Click on Publishable API Keys from the sidebar, then click on a publishable API key in the list.
-
-
-
-3. Click on the publishable API key to copy it.
-
-
-
-You'll use this key when you test the API route.
-
-### Test API Route
-
-To test out the API route, you need to create a cart. A cart must be associated with a region. So, to retrieve the ID of a region in your store, send a `GET` request to the `/store/regions` API route:
-
-```bash
-curl 'localhost:9000/store/regions' \
--H 'x-publishable-api-key: {api_key}'
-```
-
-Make sure to replace `{api_key}` with the publishable API key you copied earlier.
-
-This will return a list of regions. Copy the ID of one of the regions.
-
-Then, send a `POST` request to the `/store/carts` API route to create a cart:
-
-```bash
-curl -X POST 'localhost:9000/store/carts' \
--H 'x-publishable-api-key: {api_key}' \
--H 'Content-Type: application/json' \
---data '{
- "region_id": "{region_id}"
-}'
-```
-
-Make sure to replace `{api_key}` with the publishable API key you copied earlier, and `{region_id}` with the ID of a region from the previous request.
-
-This will return the created cart. Copy the ID of the cart to use it next.
-
-Finally, to add the Gold variant to the cart with a custom price, send a `POST` request to the `/store/carts/:id/line-items-metals` API route:
-
-```bash
-curl -X POST 'localhost:9000/store/carts/{cart_id}/line-items-metals' \
--H 'x-publishable-api-key: {api_key}' \
--H 'Content-Type: application/json' \
---data '{
- "variant_id": "{variant_id}",
- "quantity": 1
-}'
-```
-
-Make sure to replace:
-
-- `{api_key}` with the publishable API key you copied earlier.
-- `{cart_id}` with the ID of the cart you created.
-- `{variant_id}` with the ID of the Gold variant you created.
-
-This will return the cart's details, where you can see in its `items` array the item with the custom price:
-
-```json title="Example Response"
-{
- "cart": {
- "items": [
- {
- "variant_id": "{variant_id}",
- "quantity": 1,
- "is_custom_price": true,
- // example custom price
- "unit_price": 2000
- }
- ]
- }
-}
-```
-
-The price will be the result of the calculation you've implemented earlier, which is the fixed price of the variant plus the real-time price of the metal, multiplied by the weight of the variant and the quantity added to the cart.
-
-This price will be reflected in the cart's total price, and you can proceed to checkout with the custom-priced item.
-
-***
-
-## Next Steps
-
-You've now implemented custom item pricing in Medusa. You can also customize the [storefront](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/nextjs-starter/index.html.md) to use the new API route to add custom-priced items to the cart.
-
-If you're new to Medusa, check out the [main documentation](https://docs.medusajs.com/docs/learn/index.html.md), where you'll get a more in-depth learning of all the concepts you've used in this guide and more.
-
-To learn more about the commerce features that Medusa provides, check out Medusa's [Commerce Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md).
-
-
# Implement Quote Management in Medusa
In this guide, you'll learn how to implement quote management in Medusa.
@@ -38329,6 +37778,795 @@ If you're new to Medusa, check out the [main documentation](https://docs.medusaj
To learn more about the commerce features that Medusa provides, check out Medusa's [Commerce Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md).
+# Implement Custom Line Item Pricing in Medusa
+
+In this guide, you'll learn how to add line items with custom prices to a cart in Medusa.
+
+When you install a Medusa application, you get a fully-fledged commerce platform with a framework for customization. The Medusa application's commerce features are built around [commerce modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md) which are available out-of-the-box. These features include managing carts and adding line items to them.
+
+By default, you can add product variants to the cart, where the price of its associated line item is based on the product variant's price. However, you can build customizations to add line items with custom prices to the cart. This is useful when integrating an Enterprise Resource Planning (ERP), Product Information Management (PIM), or other third-party services that provide real-time prices for your products.
+
+To showcase how to add line items with custom prices to the cart, this guide uses [GoldAPI.io](https://www.goldapi.io) as an example of a third-party system that you can integrate for real-time prices. You can follow the same approach for other third-party integrations that provide custom pricing.
+
+You can follow this guide whether you're new to Medusa or an advanced Medusa developer.
+
+### Summary
+
+This guide will teach you how to:
+
+- Install and set up Medusa.
+- Integrate the third-party service [GoldAPI.io](https://www.goldapi.io) that retrieves real-time prices for metals like Gold and Silver.
+- Add an API route to add a product variant that has metals, such as a gold ring, to the cart with the real-time price retrieved from the third-party service.
+
+
+
+- [Custom Item Price Repository](https://github.com/medusajs/examples/tree/main/custom-item-price): Find the full code for this guide in this repository.
+- [OpenApi Specs for Postman](https://res.cloudinary.com/dza7lstvk/raw/upload/v1738246728/OpenApi/Custom_Item_Price_gdfnl3.yaml): Import this OpenApi Specs file into tools like Postman.
+
+***
+
+## Step 1: Install a Medusa Application
+
+### Prerequisites
+
+- [Node.js v20+](https://nodejs.org/en/download)
+- [Git CLI tool](https://git-scm.com/downloads)
+- [PostgreSQL](https://www.postgresql.org/download/)
+
+Start by installing the Medusa application on your machine with the following command:
+
+```bash
+npx create-medusa-app@latest
+```
+
+You'll first be asked for the project's name. You can also optionally choose to install the [Next.js starter storefront](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/nextjs-starter/index.html.md).
+
+Afterwards, the installation process will start, which will install the Medusa application in a directory with your project's name. If you chose to install the Next.js starter, it'll be installed in a separate directory with the `{project-name}-storefront` name.
+
+The Medusa application is composed of a headless Node.js server and an admin dashboard. The storefront is installed or custom-built separately and connects to the Medusa application through its REST endpoints, called [API routes](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md). Learn more about Medusa's architecture in [this documentation](https://docs.medusajs.com/docs/learn/introduction/architecture/index.html.md).
+
+Once the installation finishes successfully, the Medusa Admin dashboard will open with a form to create a new user. Enter the user's credentials and submit the form. Afterwards, you can log in with the new user and explore the dashboard.
+
+Check out the [troubleshooting guides](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/troubleshooting/create-medusa-app-errors/index.html.md) for help.
+
+***
+
+## Step 2: Integrate GoldAPI.io
+
+### Prerequisites
+
+- [GoldAPI.io Account. You can create a free account.](https://www.goldapi.io)
+
+To integrate third-party services into Medusa, you create a custom module. A module is a reusable package with functionalities related to a single feature or domain. Medusa integrates the module into your application without implications or side effects on your setup.
+
+In this step, you'll create a Metal Price Module that uses the GoldAPI.io service to retrieve real-time prices for metals like Gold and Silver. You'll use this module later to retrieve the real-time price of a product variant based on the metals in it, and add it to the cart with that custom price.
+
+Learn more about modules in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md).
+
+### Create Module Directory
+
+A module is created under the `src/modules` directory of your Medusa application. So, create the directory `src/modules/metal-prices`.
+
+
+
+### Create Module's Service
+
+You define a module's functionalities in a service. A service is a TypeScript or JavaScript class that the module exports. In the service's methods, you can connect to the database, which is useful if your module defines tables in the database, or connect to a third-party service.
+
+In this section, you'll create the Metal Prices Module's service that connects to the GoldAPI.io service to retrieve real-time prices for metals.
+
+Start by creating the file `src/modules/metal-prices/service.ts` with the following content:
+
+
+
+```ts title="src/modules/metal-prices/service.ts"
+type Options = {
+ accessToken: string
+ sandbox?: boolean
+}
+
+export default class MetalPricesModuleService {
+ protected options_: Options
+
+ constructor({}, options: Options) {
+ this.options_ = options
+ }
+}
+```
+
+A module can accept options that are passed to its service. You define an `Options` type that indicates the options the module accepts. It accepts two options:
+
+- `accessToken`: The access token for the GoldAPI.io service.
+- `sandbox`: A boolean that indicates whether to simulate sending requests to the GoldAPI.io service. This is useful when running in a test environment.
+
+The service's constructor receives the module's options as a second parameter. You store the options in the service's `options_` property.
+
+A module has a container of Medusa framework tools and local resources in the module that you can access in the service constructor's first parameter. Learn more in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/container/index.html.md).
+
+#### Add Method to Retrieve Metal Prices
+
+Next, you'll add the method to retrieve the metal prices from the third-party service.
+
+First, add the following types at the beginning of `src/modules/metal-prices/service.ts`:
+
+```ts title="src/modules/metal-prices/service.ts"
+export enum MetalSymbols {
+ Gold = "XAU",
+ Silver = "XAG",
+ Platinum = "XPT",
+ Palladium = "XPD"
+}
+
+export type PriceResponse = {
+ metal: MetalSymbols
+ currency: string
+ exchange: string
+ symbol: string
+ price: number
+ [key: string]: unknown
+}
+
+```
+
+The `MetalSymbols` enum defines the symbols for metals like Gold, Silver, Platinum, and Palladium. The `PriceResponse` type defines the structure of the response from the GoldAPI.io's endpoint.
+
+Next, add the method `getMetalPrices` to the `MetalPricesModuleService` class:
+
+```ts title="src/modules/metal-prices/service.ts"
+import { MedusaError } from "@medusajs/framework/utils"
+
+// ...
+
+export default class MetalPricesModuleService {
+ // ...
+ async getMetalPrice(
+ symbol: MetalSymbols,
+ currency: string
+ ): Promise {
+ const upperCaseSymbol = symbol.toUpperCase()
+ const upperCaseCurrency = currency.toUpperCase()
+
+ return fetch(`https://www.goldapi.io/api/${upperCaseSymbol}/${upperCaseCurrency}`, {
+ headers: {
+ "x-access-token": this.options_.accessToken,
+ "Content-Type": "application/json",
+ },
+ redirect: "follow",
+ }).then((response) => response.json())
+ .then((response) => {
+ if (response.error) {
+ throw new MedusaError(
+ MedusaError.Types.INVALID_DATA,
+ response.error
+ )
+ }
+
+ return response
+ })
+ }
+}
+```
+
+The `getMetalPrice` method accepts the metal symbol and currency as parameters. You send a request to GoldAPI.io's `/api/{symbol}/{currency}` endpoint to retrieve the metal's price, also passing the access token in the request's headers.
+
+If the response contains an error, you throw a `MedusaError` with the error message. Otherwise, you return the response, which is of type `PriceResponse`.
+
+#### Add Helper Methods
+
+You'll also add two helper methods to the `MetalPricesModuleService`. The first one is `getMetalSymbols` that returns the metal symbols as an array of strings:
+
+```ts title="src/modules/metal-prices/service.ts"
+export default class MetalPricesModuleService {
+ // ...
+ async getMetalSymbols(): Promise {
+ return Object.values(MetalSymbols)
+ }
+}
+```
+
+The second is `getMetalSymbol` that receives a name like `gold` and returns the corresponding metal symbol:
+
+```ts title="src/modules/metal-prices/service.ts"
+export default class MetalPricesModuleService {
+ // ...
+ async getMetalSymbol(name: string): Promise {
+ const formattedName = name.charAt(0).toUpperCase() + name.slice(1).toLowerCase()
+ return MetalSymbols[formattedName as keyof typeof MetalSymbols]
+ }
+}
+```
+
+You'll use these methods in later steps.
+
+### Export Module Definition
+
+The final piece to a module is its definition, which you export in an `index.ts` file at its root directory. This definition tells Medusa the name of the module and its service.
+
+So, create the file `src/modules/metal-prices/index.ts` with the following content:
+
+
+
+```ts title="src/modules/metal-prices/index.ts"
+import { Module } from "@medusajs/framework/utils"
+import MetalPricesModuleService from "./service"
+
+export const METAL_PRICES_MODULE = "metal-prices"
+
+export default Module(METAL_PRICES_MODULE, {
+ service: MetalPricesModuleService,
+})
+```
+
+You use the `Module` function from the Modules SDK to create the module's definition. It accepts two parameters:
+
+1. The module's name, which is `metal-prices`.
+2. An object with a required property `service` indicating the module's service.
+
+### Add Module to Medusa's Configurations
+
+Once you finish building the module, add it to Medusa's configurations to start using it.
+
+In `medusa-config.ts`, add a `modules` property and pass an array with your custom module:
+
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ // ...
+ modules: [
+ {
+ resolve: "./src/modules/metal-prices",
+ options: {
+ accessToken: process.env.GOLD_API_TOKEN,
+ sandbox: process.env.GOLD_API_SANDBOX === "true",
+ },
+ },
+ ],
+})
+```
+
+Each object in the `modules` array has a `resolve` property, whose value is either a path to the module's directory, or an `npm` package’s name.
+
+The object also has an `options` property that accepts the module's options. You set the `accessToken` and `sandbox` options based on environment variables.
+
+You'll find the access token at the top of your GoldAPI.io dashboard.
+
+
+
+Set the access token as an environment variable in `.env`:
+
+```bash
+GOLD_API_TOKEN=
+```
+
+You'll start using the module in the next steps.
+
+***
+
+## Step 3: Add Custom Item to Cart Workflow
+
+In this section, you'll implement the logic to retrieve the real-time price of a variant based on the metals in it, then add the variant to the cart with the custom price. You'll implement this logic in a workflow.
+
+A workflow is a series of queries and actions, called steps, that complete a task. You construct a workflow like you construct a function, but it's a special function that allows you to track its executions' progress, define roll-back logic, and configure other advanced features. Then, you execute the workflow from other customizations, such as in an endpoint.
+
+Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md)
+
+The workflow you'll implement in this section has the following steps:
+
+- [useQueryGraphStep (Retrieve Cart)](https://docs.medusajs.com/references/helper-steps/useQueryGraphStep/index.html.md): Retrieve the cart's ID and currency using Query.
+- [useQueryGraphStep (Retrieve Variant)](https://docs.medusajs.com/references/helper-steps/useQueryGraphStep/index.html.md): Retrieve the variant's details using Query
+- [getVariantMetalPricesStep](#getvariantmetalpricesstep): Retrieve the variant's price using the third-party service.
+- [addToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addToCartWorkflow/index.html.md): Add the item with the custom price to the cart.
+- [useQueryGraphStep (Retrieve Cart)](https://docs.medusajs.com/references/helper-steps/useQueryGraphStep/index.html.md): Retrieve the updated cart's details using Query.
+
+`useQueryGraphStep` and `addToCartWorkflow` are available through Medusa's core workflows package. You'll only implement the `getVariantMetalPricesStep`.
+
+### getVariantMetalPricesStep
+
+The `getVariantMetalPricesStep` will retrieve the real-time metal price of a variant received as an input.
+
+To create the step, create the file `src/workflows/steps/get-variant-metal-prices.ts` with the following content:
+
+
+
+```ts title="src/workflows/steps/get-variant-metal-prices.ts"
+import { createStep } from "@medusajs/framework/workflows-sdk"
+import { ProductVariantDTO } from "@medusajs/framework/types"
+import { METAL_PRICES_MODULE } from "../../modules/metal-prices"
+import MetalPricesModuleService from "../../modules/metal-prices/service"
+
+export type GetVariantMetalPricesStepInput = {
+ variant: ProductVariantDTO & {
+ calculated_price?: {
+ calculated_amount: number
+ }
+ }
+ currencyCode: string
+ quantity?: number
+}
+
+export const getVariantMetalPricesStep = createStep(
+ "get-variant-metal-prices",
+ async ({
+ variant,
+ currencyCode,
+ quantity = 1,
+ }: GetVariantMetalPricesStepInput, { container }) => {
+ const metalPricesModuleService: MetalPricesModuleService =
+ container.resolve(METAL_PRICES_MODULE)
+
+ // TODO
+ }
+)
+```
+
+You create a step with `createStep` from the Workflows SDK. It accepts two parameters:
+
+1. The step's unique name, which is `get-variant-metal-prices`.
+2. An async function that receives two parameters:
+ - An input object with the variant, currency code, and quantity. The variant has a `calculated_price` property that holds the variant's fixed price in the Medusa application. This is useful when you want to add a fixed price to the real-time custom price, such as handling fees.
+ - The [Medusa container](https://docs.medusajs.com/docs/learn/fundamentals/medusa-container/index.html.md), which is a registry of framework and commerce tools that you can access in the step.
+
+In the step function, so far you only resolve the Metal Prices Module's service from the Medusa container.
+
+Next, you'll validate that the specified variant can have its price calculated. Add the following import at the top of the file:
+
+```ts title="src/workflows/steps/get-variant-metal-prices.ts"
+import { MedusaError } from "@medusajs/framework/utils"
+```
+
+And replace the `TODO` in the step function with the following:
+
+```ts title="src/workflows/steps/get-variant-metal-prices.ts"
+const variantMetal = variant.options.find(
+ (option) => option.option?.title === "Metal"
+)?.value
+const metalSymbol = await metalPricesModuleService
+ .getMetalSymbol(variantMetal || "")
+
+if (!metalSymbol) {
+ throw new MedusaError(
+ MedusaError.Types.INVALID_DATA,
+ "Variant doesn't have metal. Make sure the variant's SKU matches a metal symbol."
+ )
+}
+
+if (!variant.weight) {
+ throw new MedusaError(
+ MedusaError.Types.INVALID_DATA,
+ "Variant doesn't have weight. Make sure the variant has weight to calculate its price."
+ )
+}
+
+// TODO retrieve custom price
+```
+
+In the code above, you first retrieve the metal option's value from the variant's options, assuming that a variant has metals if it has a `Metal` option. Then, you retrieve the metal symbol of the option's value using the `getMetalSymbol` method of the Metal Prices Module's service.
+
+If the variant doesn't have a metal in its options, the option's value is not valid, or the variant doesn't have a weight, you throw an error. The weight is necessary to calculate the price based on the metal's price per weight.
+
+Next, you'll retrieve the real-time price of the metal using the third-party service. Replace the `TODO` with the following:
+
+```ts title="src/workflows/steps/get-variant-metal-prices.ts"
+let price = variant.calculated_price?.calculated_amount || 0
+const weight = variant.weight
+const { price: metalPrice } = await metalPricesModuleService.getMetalPrice(
+ metalSymbol as MetalSymbols, currencyCode
+)
+price += (metalPrice * weight * quantity)
+
+return new StepResponse(price)
+```
+
+In the code above, you first set the price to the variant's fixed price, if it has one. Then, you retrieve the metal's price using the `getMetalPrice` method of the Metal Prices Module's service.
+
+Finally, you calculate the price by multiplying the metal's price by the variant's weight and the quantity to add to the cart, then add the fixed price to it.
+
+Every step must return a `StepResponse` instance. The `StepResponse` constructor accepts the step's output as a parameter, which in this case is the variant's price.
+
+### Create addCustomToCartWorkflow
+
+Now that you have the `getVariantMetalPricesStep`, you can create the workflow that adds the item with custom pricing to the cart.
+
+Create the file `src/workflows/add-custom-to-cart.ts` with the following content:
+
+
+
+```ts title="src/workflows/add-custom-to-cart.ts" highlights={workflowHighlights}
+import { createWorkflow } from "@medusajs/framework/workflows-sdk"
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+import { QueryContext } from "@medusajs/framework/utils"
+
+type AddCustomToCartWorkflowInput = {
+ cart_id: string
+ item: {
+ variant_id: string
+ quantity: number
+ metadata?: Record
+ }
+}
+
+export const addCustomToCartWorkflow = createWorkflow(
+ "add-custom-to-cart",
+ ({ cart_id, item }: AddCustomToCartWorkflowInput) => {
+ // @ts-ignore
+ const { data: carts } = useQueryGraphStep({
+ entity: "cart",
+ filters: { id: cart_id },
+ fields: ["id", "currency_code"],
+ })
+
+ const { data: variants } = useQueryGraphStep({
+ entity: "variant",
+ fields: [
+ "*",
+ "options.*",
+ "options.option.*",
+ "calculated_price.*",
+ ],
+ filters: {
+ id: item.variant_id,
+ },
+ options: {
+ throwIfKeyNotFound: true,
+ },
+ context: {
+ calculated_price: QueryContext({
+ currency_code: carts[0].currency_code,
+ }),
+ },
+ }).config({ name: "retrieve-variant" })
+
+ // TODO add more steps
+ }
+)
+```
+
+You create a workflow with `createWorkflow` from the Workflows SDK. It accepts two parameters:
+
+1. The workflow's unique name, which is `add-custom-to-cart`.
+2. A function that receives an input object with the cart's ID and the item to add to the cart. The item has the variant's ID, quantity, and optional metadata.
+
+In the function, you first retrieve the cart's details using the `useQueryGraphStep` helper step. This step uses [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md) which is a Modules SDK tool that retrieves data across modules. You use it to retrieve the cart's ID and currency code.
+
+You also retrieve the variant's details using the `useQueryGraphStep` helper step. You pass the variant's ID to the step's filters and specify the fields to retrieve. To retrieve the variant's price based on the cart's context, you pass the cart's currency code to the `calculated_price` context.
+
+Next, you'll retrieve the variant's real-time price using the `getVariantMetalPricesStep` you created earlier. First, add the following import:
+
+```ts title="src/workflows/add-custom-to-cart.ts"
+import {
+ getVariantMetalPricesStep,
+ GetVariantMetalPricesStepInput,
+} from "./steps/get-variant-metal-prices"
+```
+
+Then, replace the `TODO` in the workflow with the following:
+
+```ts title="src/workflows/add-custom-to-cart.ts"
+const price = getVariantMetalPricesStep({
+ variant: variants[0],
+ currencyCode: carts[0].currency_code,
+ quantity: item.quantity,
+} as unknown as GetVariantMetalPricesStepInput)
+
+// TODO add item with custom price to cart
+```
+
+You execute the `getVariantMetalPricesStep` passing it the variant's details, the cart's currency code, and the quantity of the item to add to the cart. The step returns the variant's custom price.
+
+Next, you'll add the item with the custom price to the cart. First, add the following imports at the top of the file:
+
+```ts title="src/workflows/add-custom-to-cart.ts"
+import { transform } from "@medusajs/framework/workflows-sdk"
+import { addToCartWorkflow } from "@medusajs/medusa/core-flows"
+```
+
+Then, replace the `TODO` in the workflow with the following:
+
+```ts title="src/workflows/add-custom-to-cart.ts"
+const itemToAdd = transform({
+ item,
+ price,
+}, (data) => {
+ return [{
+ ...data.item,
+ unit_price: data.price,
+ }]
+})
+
+addToCartWorkflow.runAsStep({
+ input: {
+ items: itemToAdd,
+ cart_id,
+ },
+})
+
+// TODO retrieve and return cart
+```
+
+You prepare the item to add to the cart using `transform` from the Workflows SDK. It allows you to manipulate and create variables in a workflow. After that, you use Medusa's `addToCartWorkflow` to add the item with the custom price to the cart.
+
+A workflow's constructor function has some constraints in implementation, which is why you need to use `transform` for variable manipulation. Learn more about these constraints in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/constructor-constraints/index.html.md).
+
+Lastly, you'll retrieve the cart's details again and return them. Add the following import at the beginning of the file:
+
+```ts title="src/workflows/add-custom-to-cart.ts"
+import { WorkflowResponse } from "@medusajs/framework/workflows-sdk"
+```
+
+And replace the last `TODO` in the workflow with the following:
+
+```ts title="src/workflows/add-custom-to-cart.ts"
+// @ts-ignore
+const { data: updatedCarts } = useQueryGraphStep({
+ entity: "cart",
+ filters: { id: cart_id },
+ fields: ["id", "items.*"],
+}).config({ name: "refetch-cart" })
+
+return new WorkflowResponse({
+ cart: updatedCarts[0],
+})
+```
+
+In the code above, you retrieve the updated cart's details using the `useQueryGraphStep` helper step. To return data from the workflow, you create and return a `WorkflowResponse` instance. It accepts as a parameter the data to return, which is the updated cart.
+
+In the next step, you'll use the workflow in a custom route to add an item with a custom price to the cart.
+
+***
+
+## Step 4: Create Add Custom Item to Cart API Route
+
+Now that you've implemented the logic to add an item with a custom price to the cart, you'll expose this functionality in an API route.
+
+An API Route is an endpoint that exposes commerce features to external applications and clients, such as storefronts. You'll create an API route at the path `/store/carts/:id/line-items-metals` that executes the workflow from the previous step to add a product variant with custom price to the cart.
+
+Learn more about API routes in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md).
+
+### Create API Route
+
+An API route is created in a `route.ts` file under a sub-directory of the `src/api` directory.
+
+The path of the API route is the file's path relative to `src/api`. So, to create the `/store/carts/:id/line-items-metals` API route, create the file `src/api/store/carts/[id]/line-items-metals/route.ts` with the following content:
+
+
+
+```ts title="src/api/store/carts/[id]/line-items-metals/route.ts"
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework"
+import { HttpTypes } from "@medusajs/framework/types"
+import { addCustomToCartWorkflow } from "../../../../../workflows/add-custom-to-cart"
+
+export const POST = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ const { id } = req.params
+ const item = req.validatedBody
+
+ const { result } = await addCustomToCartWorkflow(req.scope)
+ .run({
+ input: {
+ cart_id: id,
+ item,
+ },
+ })
+
+ res.status(200).json({ cart: result.cart })
+}
+```
+
+Since you export a `POST` function in this file, you're exposing a `POST` API route at `/store/carts/:id/line-items-metals`. The route handler function accepts two parameters:
+
+1. A request object with details and context on the request, such as path and body parameters.
+2. A response object to manipulate and send the response.
+
+In the function, you retrieve the cart's ID from the path parameter, and the item's details from the request body. This API route will accept the same request body parameters as Medusa's [Add Item to Cart API Route](https://docs.medusajs.com/api/store#carts_postcartsidlineitems).
+
+Then, you execute the `addCustomToCartWorkflow` by invoking it, passing it the Medusa container, which is available in the request's `scope` property, then executing its `run` method. You pass the workflow's input object with the cart's ID and the item to add to the cart.
+
+Finally, you return a response with the updated cart's details.
+
+### Add Request Body Validation Middleware
+
+To ensure that the request body contains the required parameters, you'll add a middleware that validates the incoming request's body based on a defined schema.
+
+A middleware is a function executed before the API route when a request is sent to it. You define middlewares in Medusa in the `src/api/middlewares.ts` directory.
+
+Learn more about middlewares in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/middlewares/index.html.md).
+
+To add a validation middleware to the custom API route, create the file `src/api/middlewares.ts` with the following content:
+
+
+
+```ts title="src/api/middlewares.ts"
+import {
+ defineMiddlewares,
+ validateAndTransformBody,
+} from "@medusajs/framework/http"
+import {
+ StoreAddCartLineItem,
+} from "@medusajs/medusa/api/store/carts/validators"
+
+export default defineMiddlewares({
+ routes: [
+ {
+ matcher: "/store/carts/:id/line-items-metals",
+ method: "POST",
+ middlewares: [
+ validateAndTransformBody(
+ StoreAddCartLineItem
+ ),
+ ],
+ },
+ ],
+})
+```
+
+In this file, you export the middlewares definition using `defineMiddlewares` from the Medusa Framework. This function accepts an object having a `routes` property, which is an array of middleware configurations to apply on routes.
+
+You pass in the `routes` array an object having the following properties:
+
+- `matcher`: The route to apply the middleware on.
+- `method`: The HTTP method to apply the middleware on for the specified API route.
+- `middlewares`: An array of the middlewares to apply. You apply the `validateAndTransformBody` middleware, which validates the request body based on the `StoreAddCartLineItem` schema. This validation schema is the same schema used for Medusa's [Add Item to Cart API Route](https://docs.medusajs.com/api/store#carts_postcartsidlineitems).
+
+Any request sent to the `/store/carts/:id/line-items-metals` API route will now fail if it doesn't have the required parameters.
+
+Learn more about API route validation in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/validation/index.html.md).
+
+### Prepare to Test API Route
+
+Before you test the API route, you'll prepare and retrieve the necessary data to add a product variant with a custom price to the cart.
+
+#### Create Product with Metal Variant
+
+You'll first create a product that has a `Metal` option, and variant(s) with values for this option.
+
+Start the Medusa application with the following command:
+
+```bash npm2yarn
+npm run dev
+```
+
+Then, open the Medusa Admin dashboard at `localhost:9000/app` and log in with the email and password you created when you installed the Medusa application in the first step.
+
+Once you log in, click on Products in the sidebar, then click the Create button at the top right.
+
+
+
+Then, in the Create Product form:
+
+1. Enter a name for the product, and optionally enter other details like description.
+2. Enable the "Yes, this is a product with variants" toggle.
+3. Under Product Options, enter "Metal" for the title, and enter "Gold" for the values.
+
+Once you're done, click the Continue button.
+
+
+
+You can skip the next two steps by clicking the Continue button again, then the Publish button.
+
+Once you're done, the product's page will open. You'll now add weight to the product's Gold variant. To do that:
+
+- Scroll to the Variants section and find the Gold variant.
+- Click on the three-dots icon at its right.
+- Choose "Edit" from the dropdown.
+
+
+
+In the side window that opens, find the Weight field, enter the weight, and click the Save button.
+
+
+
+Finally, you need to set fixed prices for the variant, even if they're just `0`. To do that:
+
+1. Click on the three-dots icon at the top right of the Variants section.
+2. Choose "Edit Prices" from the dropdown.
+
+
+
+For each cell in the table, either enter a fixed price for the specified currency or leave it as `0`. Once you're done, click the Save button.
+
+
+
+You'll use this variant to add it to the cart later. You can find its ID by clicking on the variant, opening its details page. Then, on the details page, click on the icon at the right of the JSON section, and copy the ID from the JSON data.
+
+
+
+#### Retrieve Publishable API Key
+
+All requests sent to API routes starting with `/store` must have a publishable API key in the header. This ensures the request's operations are scoped to the publishable API key's associated sales channels. For example, products that aren't available in a cart's sales channel can't be added to it.
+
+To retrieve the publishable API key, on the Medusa Admin:
+
+1. Click on Settings in the sidebar at the bottom left.
+2. Click on Publishable API Keys from the sidebar, then click on a publishable API key in the list.
+
+
+
+3. Click on the publishable API key to copy it.
+
+
+
+You'll use this key when you test the API route.
+
+### Test API Route
+
+To test out the API route, you need to create a cart. A cart must be associated with a region. So, to retrieve the ID of a region in your store, send a `GET` request to the `/store/regions` API route:
+
+```bash
+curl 'localhost:9000/store/regions' \
+-H 'x-publishable-api-key: {api_key}'
+```
+
+Make sure to replace `{api_key}` with the publishable API key you copied earlier.
+
+This will return a list of regions. Copy the ID of one of the regions.
+
+Then, send a `POST` request to the `/store/carts` API route to create a cart:
+
+```bash
+curl -X POST 'localhost:9000/store/carts' \
+-H 'x-publishable-api-key: {api_key}' \
+-H 'Content-Type: application/json' \
+--data '{
+ "region_id": "{region_id}"
+}'
+```
+
+Make sure to replace `{api_key}` with the publishable API key you copied earlier, and `{region_id}` with the ID of a region from the previous request.
+
+This will return the created cart. Copy the ID of the cart to use it next.
+
+Finally, to add the Gold variant to the cart with a custom price, send a `POST` request to the `/store/carts/:id/line-items-metals` API route:
+
+```bash
+curl -X POST 'localhost:9000/store/carts/{cart_id}/line-items-metals' \
+-H 'x-publishable-api-key: {api_key}' \
+-H 'Content-Type: application/json' \
+--data '{
+ "variant_id": "{variant_id}",
+ "quantity": 1
+}'
+```
+
+Make sure to replace:
+
+- `{api_key}` with the publishable API key you copied earlier.
+- `{cart_id}` with the ID of the cart you created.
+- `{variant_id}` with the ID of the Gold variant you created.
+
+This will return the cart's details, where you can see in its `items` array the item with the custom price:
+
+```json title="Example Response"
+{
+ "cart": {
+ "items": [
+ {
+ "variant_id": "{variant_id}",
+ "quantity": 1,
+ "is_custom_price": true,
+ // example custom price
+ "unit_price": 2000
+ }
+ ]
+ }
+}
+```
+
+The price will be the result of the calculation you've implemented earlier, which is the fixed price of the variant plus the real-time price of the metal, multiplied by the weight of the variant and the quantity added to the cart.
+
+This price will be reflected in the cart's total price, and you can proceed to checkout with the custom-priced item.
+
+***
+
+## Next Steps
+
+You've now implemented custom item pricing in Medusa. You can also customize the [storefront](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/nextjs-starter/index.html.md) to use the new API route to add custom-priced items to the cart.
+
+If you're new to Medusa, check out the [main documentation](https://docs.medusajs.com/docs/learn/index.html.md), where you'll get a more in-depth learning of all the concepts you've used in this guide and more.
+
+To learn more about the commerce features that Medusa provides, check out Medusa's [Commerce Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md).
+
+
# How-to & Tutorials
In this section of the documentation, you'll find how-to guides and tutorials that will help you customize the Medusa server and admin. These guides are useful after you've learned Medusa's main concepts in the [Get Started](https://docs.medusajs.com/docs/learn/index.html.md) section of the documentation.
@@ -38588,7 +38826,7 @@ export default defineLink(
},
ProductModule.linkable.product,
{
- readOnly: true
+ readOnly: true,
}
)
```
@@ -39743,7 +39981,7 @@ export default defineMiddlewares({
"first_name",
"last_name",
"content",
- "created_at"
+ "created_at",
],
}),
],
@@ -40295,1820 +40533,6 @@ A Payment Module Provider processes payments made in your Medusa store using a t
Learn how to create a payment provider in [this guide](https://docs.medusajs.com/references/payment/provider/index.html.md).
-# Integrate Medusa with Sanity
-
-In this guide, you'll learn how to integrate Medusa with Sanity.
-
-When you install a Medusa application, you get a fully-fledged commerce platform with support for customizations. While Medusa allows you to manage basic content, such as product description and images, you might need rich content-management features, such as localized content. Medusa's framework supports you in integrating a CMS with these features.
-
-Sanity is a CMS that simplifies managing content from third-party sources into a single interface. By integrating it with Medusa, you can manage your storefront and commerce-related content, such as product details, from a single interface. You also benefit from advanced content-management features, such as live-preview editing.
-
-This guide will teach you how to:
-
-- Install and set up Medusa.
-- Install and set up Sanity with Medusa's Next.js Starter storefront.
-- Sync product data from Medusa to Sanity when a product is created or updated.
-- Customize the Medusa Admin dashboard to check the sync status and trigger syncing products to Sanity.
-
-You can follow this guide whether you're new to Medusa or an advanced Medusa developer. This guide also assumes you're familiar with Sanity concepts, which you can learn about in [their documentation](https://www.sanity.io/docs).
-
-[Example Repository](https://github.com/medusajs/examples/tree/main/sanity-integration): Find the full code of the guide in this repository.
-
-***
-
-## Step 1: Install a Medusa Application
-
-### Prerequisites
-
-- [Node.js v20+](https://nodejs.org/en/download)
-- [Git CLI tool](https://git-scm.com/downloads)
-- [PostgreSQL](https://www.postgresql.org/download/)
-
-Start by installing the Medusa application on your machine with the following command:
-
-```bash
-npx create-medusa-app@latest
-```
-
-You'll first be asked for the project's name. Then, when you're asked whether you want to install the Next.js storefront, choose `Y` for yes.
-
-Afterwards, the installation process will start, which will install the Medusa application in a directory with your project's name, and the Next.js storefront in a directory with the `{project-name}-storefront` name.
-
-The Medusa application is composed of a headless Node.js server and an admin dashboard. The storefront is installed or custom-built separately and connects to the Medusa application through its REST endpoints, called [API routes](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md). Learn more about Medusa's architecture in [this documentation](https://docs.medusajs.com/docs/learn/introduction/architecture/index.html.md).
-
-Once the installation finishes successfully, the Medusa Admin dashboard will open with a form to create a new user. Enter the user's credential and submit the form.
-
-Afterwards, you can login with the new user and explore the dashboard. The Next.js storefront is also running at `http://localhost:8000`.
-
-Check out the [troubleshooting guides](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/troubleshooting/create-medusa-app-errors/index.html.md) for help.
-
-***
-
-## Step 2: Install Sanity Client SDK
-
-In this step, you'll install [Sanity's JavaScript client SDK](https://www.sanity.io/docs/js-client) in the Medusa application, which you'll use later in your code when sending requests to Sanity.
-
-In your terminal, move to the Medusa application's directory and run the following command:
-
-```bash npm2yarn
-cd project-name # replace with directory name
-npm install @sanity/client
-```
-
-***
-
-## Step 3: Create a Sanity Project
-
-When the Medusa application connects to Sanity, it must connect to a project in Sanity.
-
-So, before building the integration in Medusa, create a project in Sanity using their website:
-
-1. [Sign in or sign up on the Sanity website.](https://www.sanity.io/login)
-2. On your account's dashboard, click the "Create new project" button.
-
-
-
-3. Enter a project name and click "Create Project"
-
-
-
-You'll go back to the project's setting page in a later step.
-
-***
-
-## Step 4: Create Sanity Module
-
-To integrate third-party services into Medusa, you create a custom module. A module is a re-usable package with functionalities related to a single feature or domain. Medusa integrates the module into your application without implications or side effects on your setup.
-
-In this step, you'll create a Sanity Module that provides the interface to connect to and interact with Sanity. In later steps, you'll use the functionalities provided by this module to sync products to Sanity or retrieve documents from it.
-
-Learn more about modules in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md).
-
-### Create Module Directory
-
-A module is created under the `src/modules` directory of your Medusa application. So, create the directory `src/modules/sanity`.
-
-### Create Service
-
-You define a module's functionalities in a service. A service is a TypeScript or JavaScript class that the module exports. In the service's methods, you can connect to the database, which is useful if your module defines tables in the database, or connect to a third-party service.
-
-Medusa registers the module's service in the [Medusa container](https://docs.medusajs.com/docs/learn/fundamentals/medusa-container/index.html.md), allowing you to easily resolve the service from other customizations and use its methods.
-
-The Medusa application registers resources, such as a module's service or the [logging tool](https://docs.medusajs.com/docs/learn/debugging-and-testing/logging/index.html.md), in the Medusa container so that you can resolve them from other customizations, as you'll see in later sections. Learn more about it in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/medusa-container/index.html.md).
-
-In this section, you'll create the Sanity Module's service and the methods necessary to connect to Sanity.
-
-Start by creating the file `src/modules/sanity/service.ts` with the following content:
-
-```ts title="src/modules/sanity/service.ts"
-import {
- Logger,
-} from "@medusajs/framework/types"
-import {
- SanityClient,
-} from "@sanity/client"
-
-class SanityModuleService {
- private client: SanityClient
- private studioUrl?: string
- private logger: Logger
-
- // TODO
-}
-
-export default SanityModuleService
-```
-
-You create the `SanityModuleService` class that for now only has three properties:
-
-- `client` property of type `SanityClient` (from the Sanity SDK you installed in the previous step) to send requests to Sanity.
-- `studioUrl` property which will hold the URL to access the Sanity studio.
-- `logger` property, which is an instance of Medusa's [Logger](https://docs.medusajs.com/docs/learn/debugging-and-testing/logging/index.html.md), to log messages.
-
-In the service, you want to initialize the client early-on so that you can use it in the service's methods. This requires options to be passed to the client, like the Sanity API key or project ID.
-
-So, add after the import at the top of the file the following types:
-
-```ts title="src/modules/sanity/service.ts"
-// other imports...
-
-const SyncDocumentTypes = {
- PRODUCT: "product",
-} as const
-
-type SyncDocumentTypes =
- (typeof SyncDocumentTypes)[keyof typeof SyncDocumentTypes];
-
-type ModuleOptions = {
- api_token: string;
- project_id: string;
- api_version: string;
- dataset: "production" | "development";
- type_map?: Record;
- studio_url?: string;
-}
-```
-
-The `ModuleOptions` type defines the type of options that the module expects:
-
-- `api_token`: API token to connect to Sanity.
-- `project_id`: The ID of the Sanity project.
-- `api_version`: The Sanity API version.
-- `dataset`: The dataset to use, which is either `production` or `development`.
-- `type_map`: The types to sync from Medusa to Sanity. For simplicity, this guide only covers syncing products, but you can support other data types like product categories, too.
-- `studio_url`: The URL to the Sanity studio. This is used to show the studio URL later in the Medusa Admin dashboard.
-
-You can now initialize the client, which you'll do in the `constructor` of the `SanityModuleService`:
-
-```ts title="src/modules/sanity/service.ts"
-import {
- // other imports...
- createClient,
-} from "@sanity/client"
-
-// types...
-
-type InjectedDependencies = {
- logger: Logger
-};
-
-class SanityModuleService {
- // properties...
- constructor({
- logger,
- }: InjectedDependencies, options: ModuleOptions) {
- this.client = createClient({
- projectId: options.project_id,
- apiVersion: options.api_version,
- dataset: options.dataset,
- token: options.api_token,
- })
- this.logger = logger
-
- this.logger.info("Connected to Sanity")
-
- this.studioUrl = options.studio_url
-
- // TODO initialize more properties
- }
-}
-```
-
-The service's constructor accepts two parameters:
-
-1. Resources to resolve from the Module's container. A module has a different container than the Medusa application, which you can learn more about it in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/container/index.html.md).
-2. The options passed to the module.
-
-In the constructor, you create a Sanity client using the `createClient` function imported from `@sanity/client`. You pass it the options that the module receives.
-
-You also initialize the `logger` and `studioUrl` properties, and log a message indicating that connection to Sanity was successful.
-
-#### Transform Product Data
-
-When you create or update products in Sanity, you must prepare the product object based on what Sanity expects.
-
-So, you'll add methods to the service that transform a Medusa product to a Sanity document object.
-
-Start by adding the following types and class properties to `src/modules/sanity/service.ts`:
-
-```ts title="src/modules/sanity/service.ts"
-type SyncDocumentInputs = T extends "product"
- ? ProductDTO
- : never
-
-type TransformationMap = Record<
- SyncDocumentTypes,
- (data: SyncDocumentInputs) => any
->;
-
-class SanityModuleService {
- // other properties...
- private typeMap: Record
- private createTransformationMap: TransformationMap
- private updateTransformationMap: TransformationMap
-
- // ...
-}
-```
-
-First, you define types for a transformation map, which is a map that pairs up a document type (such as `product`) to a function that handles transforming its data.
-
-Then, in the service, you define three new properties:
-
-- `typeMap`: Pair of `SyncDocumentTypes` values (for example, `product`) and their type name in Sanity.
-- `createTransformationMap`: Pair of `SyncDocumentTypes` values (for example, `product`) and the method used to transform a Medusa product to a Sanity document data to be created.
-- `updateTransformationMap`: Pair of `SyncDocumentTypes` values (for example, `product`) and the method used to transform a Medusa product to a Sanity update operation.
-
-Next, add the following two methods to transform a product:
-
-```ts title="src/modules/sanity/service.ts"
-// other imports...
-import {
- ProductDTO,
-} from "@medusajs/framework/types"
-
-class SanityModuleService {
- // ...
- private transformProductForCreate = (product: ProductDTO) => {
- return {
- _type: this.typeMap[SyncDocumentTypes.PRODUCT],
- _id: product.id,
- title: product.title,
- specs: [
- {
- _key: product.id,
- _type: "spec",
- title: product.title,
- lang: "en",
- },
- ],
- }
- }
-
- private transformProductForUpdate = (product: ProductDTO) => {
- return {
- set: {
- title: product.title,
- },
- }
- }
-}
-```
-
-The `transformProductForCreate` method accepts a product and returns an object that you'll later pass to Sanity to create the product document. Similarly, the `transformProductForUpdate` method accepts a product and returns an object that you'll later pass to Sanity to update the product document.
-
-The Sanity document's schema type will be defined in a later chapter. If you add other fields to it, make sure to edit these methods.
-
-Finally, initialize the new properties you added in the `SanityModuleService`'s constructor:
-
-```ts title="src/modules/sanity/service.ts"
-class SanityModuleService {
- // ...
- constructor({
- logger,
- }: InjectedDependencies, options: ModuleOptions) {
- // ...
- this.typeMap = Object.assign(
- {},
- {
- [SyncDocumentTypes.PRODUCT]: "product",
- },
- options.type_map || {}
- )
-
- this.createTransformationMap = {
- [SyncDocumentTypes.PRODUCT]: this.transformProductForCreate,
- }
-
- this.updateTransformationMap = {
- [SyncDocumentTypes.PRODUCT]: this.transformProductForUpdate,
- }
- }
- // ...
-}
-```
-
-You initialize the `typeMap` property to map the `product` type in Medusa to the `product` schema type in Sanity. You also initialize the `createTransformationMap` and `updateTransformationMap` to map the methods to transform a product for creation or update.
-
-You can modify these properties to add support for other schema types, such as product categories or collections.
-
-#### Methods to Manage Documents
-
-In this section, you'll add the methods that accept data from Medusa and create or update them as documents in Sanity.
-
-Add the following methods to the `SanityModuleService` class:
-
-```ts title="src/modules/sanity/service.ts" highlights={syncMethodsHighlights}
-// other imports...
-import {
- // ...
- FirstDocumentMutationOptions,
-} from "@sanity/client"
-
-class SanityModuleService {
- // ...
- async upsertSyncDocument(
- type: T,
- data: SyncDocumentInputs
- ) {
- const existing = await this.client.getDocument(data.id)
- if (existing) {
- return await this.updateSyncDocument(type, data)
- }
-
- return await this.createSyncDocument(type, data)
- }
-
- async createSyncDocument(
- type: T,
- data: SyncDocumentInputs,
- options?: FirstDocumentMutationOptions
- ) {
- const doc = this.createTransformationMap[type](data)
- return await this.client.create(doc, options)
- }
-
- async updateSyncDocument(
- type: T,
- data: SyncDocumentInputs
- ) {
- const operations = this.updateTransformationMap[type](data)
- return await this.client.patch(data.id, operations).commit()
- }
-}
-```
-
-You add three methods:
-
-- `upsertSyncDocument`: Creates or updates a document in Sanity for a data type in Medusa.
-- `createSyncDocument`: Creates a document in Sanity for a data type in Medusa. It uses the `createTransformationMap` property to use the transform method of the specified Medusa data type (for example, a product's data).
-- `updateSyncDocument`: Updates a document in Sanity for a data type in Medusa. It uses the `updateTransformationMap` property to use the transform method of the specified Medusa data type (for example, a product's data).
-
-You also need methods to manage the Sanity documents without transformations. So, add the following methods to `SanityModuleService`:
-
-```ts title="src/modules/sanity/service.ts" highlights={methodsHighlights}
-class SanityModuleService {
- // ...
- async retrieve(id: string) {
- return this.client.getDocument(id)
- }
-
- async delete(id: string) {
- return this.client.delete(id)
- }
-
- async update(id: string, data: any) {
- return await this.client.patch(id, {
- set: data,
- }).commit()
- }
-
- async list(
- filter: {
- id: string | string[]
- }
- ) {
- const data = await this.client.getDocuments(
- Array.isArray(filter.id) ? filter.id : [filter.id]
- )
-
- return data.map((doc) => ({
- id: doc?._id,
- ...doc,
- }))
- }
-}
-```
-
-You add other three methods:
-
-- `retrieve` to retrieve a document by its ID.
-- `delete` to delete a document by its ID.
-- `update` to update a document by its ID with new data.
-- `list` to list documents, with ability to filter them by their IDs. Since a Sanity document's ID is a product's ID, you can pass product IDs as a filter to retrieve their documents.
-
-### Export Module Definition
-
-The `SanityModuleService` class now has the methods necessary to connect to and perform actions in Sanity.
-
-Next, you must export the Module definition, which lets Medusa know what the Module's name is and what is its service.
-
-Create the file `src/modules/sanity/index.ts` with the following content:
-
-```ts title="src/modules/sanity/index.ts"
-import { Module } from "@medusajs/framework/utils"
-import SanityModuleService from "./service"
-
-export const SANITY_MODULE = "sanity"
-
-export default Module(SANITY_MODULE, {
- service: SanityModuleService,
-})
-```
-
-In the file, you export the `SANITY_MODULE` which is the Module's name. You'll use it later when you resolve the module from the Medusa container.
-
-You also export the module definition using `Module` from the Modueles SDK, which accepts as a first parameter the module's name, and as a second parameter an object having a `service` property, indicating the module's service.
-
-### Add Module to Configurations
-
-Finally, to register a module in Medusa, you must add it to Medusa's configurations.
-
-Medusa's configurations are set in the `medusa-config.ts` file, which is at the root directory of your Medusa application. The configuration object accepts a `modules` array, whose value is an array of modules to add to the application.
-
-Add the `modules` property to the exported configurations in `medusa-config.ts`:
-
-```ts title="medusa-config.ts"
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "./src/modules/sanity",
- options: {
- api_token: process.env.SANITY_API_TOKEN,
- project_id: process.env.SANITY_PROJECT_ID,
- api_version: new Date().toISOString().split("T")[0],
- dataset: "production",
- studio_url: process.env.SANITY_STUDIO_URL ||
- "http://localhost:3000/studio",
- type_map: {
- product: "product",
- },
- },
- },
- ],
-})
-```
-
-In the `modules` array, you pass a module object having the following properties:
-
-- `resolve`: The path to the module to register in the application. It can also be the name of an NPM package.
-- `options`: An object of options to pass to the module. These are the options you expect and use in the module's service.
-
-Some of the module's options, such as the Sanity API key, are set in environment variables. So, add the following environment variables to `.env`:
-
-```shell
-SANITY_API_TOKEN=
-SANITY_PROJECT_ID=
-SANITY_STUDIO_URL=http://localhost:8000/studio
-```
-
-Where:
-
-- `SANITY_API_TOKEN`: The API key token to connect to Sanity, which you can retrieve from the Sanity project's dashboard:
- - Go to the API tab.
-
-
-
-- Scroll down to Tokens and click on the "Add API Token" button.
-
-
-
-- Enter a name for the API token, choose "Editor" for the permissions, then click Save.
-
-
-
-- `SANITY_PROJECT_ID`: The ID of the project, which you can find at the top section of your Sanity project's dashboard.
-
-
-
-- `SANITY_STUDIO_URL`: The URL to access the studio. You'll set the studio up in a later section, but for now set it to `http://localhost:8000/studio`.
-
-### Test the Module
-
-To test that the module is working, you'll start the Medusa application and see if the "Connected to Sanity" message is logged in the console.
-
-To start the Medusa application, run the following command in the application's directory:
-
-```bash npm2yarn
-npm run dev
-```
-
-If you see the following message among the logs:
-
-```bash
-info: Connected to Sanity
-```
-
-That means your Sanity credentials were correct, and Medusa was able to connect to Sanity.
-
-In the next steps, you'll create a link between the Product and Sanity modules to retrieve data between them easily, and build a flow around the Sanity Module to sync data.
-
-***
-
-## Step 5: Link Product and Sanity Modules
-
-Since a product has a document in Sanity, you want to build an association between the [Product](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md) and Sanity modules so that when you retrieve a product, you also retrieve its associated Sanity document.
-
-However, modules are [isolated](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md) to ensure they're re-usable and don't have side effects when integrated into the Medusa application. So, to build associations between modules, you define [module links](https://docs.medusajs.com/docs/learn/fundamentals/module-links/index.html.md).
-
-A Module Link associates two modules' data models while maintaining module isolation. A data model can be a table in the database or a virtual model from an external systems.
-
-In this section, you'll define a link between the Product and Sanity modules.
-
-Links are defined in a TypeScript or JavaScript file under the `src/links` directory. So, create the file `src/links/product-sanity.ts` with the following content:
-
-```ts title="src/links/product-sanity.ts"
-import { defineLink } from "@medusajs/framework/utils"
-import ProductModule from "@medusajs/medusa/product"
-import { SANITY_MODULE } from "../modules/sanity"
-
-defineLink(
- {
- ...ProductModule.linkable.product.id,
- field: "id",
- },
- {
- linkable: {
- serviceName: SANITY_MODULE,
- alias: "sanity_product",
- primaryKey: "id",
- },
- },
- {
- readOnly: true,
- }
-)
-```
-
-You define a link using `defineLink` from the Modules SDK. It accepts three parameters:
-
-1. The first data model part of the link. In this case, it's the Product Module's `product` data model. A module has a special `linkable` property that contain link configurations for its data models.
-2. The second data model part of the link. Since the Sanity Module doesn't have a Medusa data model, you specify the configurations in a `linkable` object that has the following properties:
- - `serviceName`: The registration name in the Medusa container of the service managing the data model, which in this case is the Sanity Module's name (since the module's service is registered under that name).
- - `alias`: The name to refer to the model part of this link, such as when retrieving the Sanity document of a product. You'll use this in a later section.
- - `primaryKey`: The name of the data model's primary key field.
-3. An object of configurations for the module link. By default, Medusa creates a table in the database to represent the link you define. Since the module link isn't created between two Medusa data models, you enable the `readOnly` configuration, which will tell Medusa not to create a table in the database for this link.
-
-In the next steps, you'll see how this link allows you to retrieve documents when retrieving products.
-
-***
-
-## Step 6: Sync Data to Sanity
-
-After integrating Sanity with a custom module, you now want to sync product data from Medusa to Sanity, automatically and manually. To implement the sync logic, you need a workflow.
-
-A workflow is a series of queries and actions, called steps, that complete a task. You construct a workflow like you construct a function, but it's a special function that allows you to track its executions' progress, define roll-back logic, and configure other advanced features. You'll see how all of this works in the upcoming sections.
-
-Within a workflow's steps, you resolve modules to use their service's functionalities as part of a bigger flow. Then, you can execute the workflow from other customizations, such as in response to an event or in an API route.
-
-Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md)
-
-In this section, you'll create a workflow that syncs products from Medusa to Sanity. Later, you'll execute this workflow when a product is created or updated, or when an admin user triggers the syncing manually.
-
-### Create Step
-
-The syncing workflow will have a single step that syncs products provided as an input to Sanity.
-
-So, to implement that step, create the file `src/workflows/sanity-sync-products/steps/sync.ts` with the following content:
-
-```ts title="src/workflows/sanity-sync-products/steps/sync.ts" highlights={syncStepHighlights}
-import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
-import { ProductDTO } from "@medusajs/framework/types"
-import {
- ContainerRegistrationKeys,
- promiseAll,
-} from "@medusajs/framework/utils"
-import SanityModuleService from "../../../modules/sanity/service"
-import { SANITY_MODULE } from "../../../modules/sanity"
-
-export type SyncStepInput = {
- product_ids?: string[];
-}
-
-export const syncStep = createStep(
- { name: "sync-step", async: true },
- async (input: SyncStepInput, { container }) => {
- const sanityModule: SanityModuleService = container.resolve(SANITY_MODULE)
- const query = container.resolve(ContainerRegistrationKeys.QUERY)
-
- const total = 0
- const upsertMap: {
- before: any
- after: any
- }[] = []
-
- const batchSize = 200
- const hasMore = true
- const offset = 0
- const filters = input.product_ids ? {
- id: input.product_ids,
- } : {}
-
- while (hasMore) {
- const {
- data: products,
- metadata: { count } = {},
- } = await query.graph({
- entity: "product",
- fields: [
- "id",
- "title",
- // @ts-ignore
- "sanity_product.*",
- ],
- filters,
- pagination: {
- skip: offset,
- take: batchSize,
- order: {
- id: "ASC",
- },
- },
- })
-
- // TODO sync products
- }
- }
-)
-```
-
-You define the `syncStep` using the `createStep` function, which accepts two parameters:
-
-- An object of step configurations. The object must have the `name` property, which is this step's unique name. Enabling the `async` property means that the workflow should run asynchronously in the background. This is useful when the workflow is triggered manually through an HTTP request, meaning the response will be returned to the client even if the workflow hasn't finished executing.
-- The step's function definition as a second parameter.
-
-The step function accepts the step's input as a first parameter, and an object of options as a second. The object of options has a `container` property, which is an instance of the Medusa container that you can use to resolve resources.
-
-In the step, you resolve from the Medusa container Sanity Module's service and [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), which is a tool that allows you to retrieve data across modules and links.
-
-You use Query's `graph` method to retrieve products, filtering them by their IDs and applying pagination configurations. The `graph` method accepts a `fields` property in its object parameter, which indicates the product data model's fields and relations to retrieve.
-
-Notice that you pass `sanity_product.*` in the `fields` array. Medusa will retrieve the Sanity document of each product using Sanity Module's `list` method and attach it to the returned product. So, you don't have to retrieve the products and documents separately. Each product object in the returned array will look similar to this:
-
-```json title="Example Product Object"
-{
- "id": "prod_123",
- "title": "Shirt",
- "sanity_product": {
- "id": "prod_123",
- "_type": "product",
- // other Sanity fields...
- }
-}
-```
-
-Next, you want to sync the retrieved products. So, replace the `TODO` in the `while` loop with the following:
-
-```ts title="src/workflows/sanity-sync-products/steps/sync.ts"
-while (hasMore) {
- // ...
- try {
- await promiseAll(
- products.map(async (prod) => {
- const after = await sanityModule.upsertSyncDocument(
- "product",
- prod as ProductDTO
- )
-
- upsertMap.push({
- // @ts-ignore
- before: prod.sanity_product,
- after,
- })
-
- return after
- })
- )
- } catch (e) {
- return StepResponse.permanentFailure(
- `An error occurred while syncing documents: ${e}`,
- upsertMap
- )
- }
-
- offset += batchSize
- hasMore = offset < count
- total += products.length
-}
-```
-
-In the `while` loop, you loop over the array of products to sync them to Sanity. You use the `promiseAll` Medusa utility that loops over an array of promises and ensures that all transactions within these promises are rolled back in case an error occurs.
-
-For each product, you upsert it into Sanity, then push its document before and after the update to the `upsertMap`. You'll learn more about its use later.
-
-You also wrap the `promiseAll` function within a try-catch block. In the catch block, you invoke and return `StepResponse.permanentFailure` which indicates that the step has failed but still invokes the rollback mechanism that you'll implement in a bit. The first parameter of `permanentFailure` is the error message, and the second is the data to use in the rollback mechanism.
-
-Finally, after the `while` loop and at the end of the step, add the following return statement:
-
-```ts title="src/workflows/sanity-sync-products/steps/sync.ts"
-return new StepResponse({ total }, upsertMap)
-```
-
-If no errors occur, the step returns an instance of `StepResponse`, which must be returned by any step. It accepts as a first parameter the data to return to the workflow that executed this step.
-
-#### Add Compensation Function
-
-`StepResponse` accepts a second parameter, which is passed to the compensation function. A compensation function defines the rollback logic of a step, and it's only executed if an error occurs in the workflow. This eliminates data inconsistency if an error occurs and the workflow can't finish execution successfully.
-
-Learn more about compensation functions in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/compensation-function/index.html.md).
-
-The `syncStep` creates or updates products in Sanity. So, the compensation function must delete created documents or revert the update of a document to its previous data. The compensation function is only executed if an error occurs.
-
-To define the compensation function, pass a third-parameter to the `createStep` function:
-
-```ts title="src/workflows/sanity-sync-products/steps/sync.ts"
-export const syncStep = createStep(
- { name: "sync-step", async: true },
- async (input: SyncStepInput, { container }) => {
- // ...
- },
- async (upsertMap, { container }) => {
- if (!upsertMap) {
- return
- }
-
- const sanityModule: SanityModuleService = container.resolve(SANITY_MODULE)
-
- await promiseAll(
- upsertMap.map(({ before, after }) => {
- if (!before) {
- // delete the document
- return sanityModule.delete(after._id)
- }
-
- const { _id: id, ...oldData } = before
-
- return sanityModule.update(
- id,
- oldData
- )
- })
- )
- }
-)
-```
-
-The compensation function accepts the data passed in the step's `StepResponse` second parameter (in this case, `upsertMap`), and an object of options similar to that of the step.
-
-In the compensation function, you resolve the Sanity Module's service, then loop over the `upsertMap` to delete created documents, or revert existing ones.
-
-### Create Workflow
-
-You'll now create the workflow that uses the `syncStep`. This is the workflow that you'll later execute to sync data automatically or manually.
-
-Workflows are created in a file under the `src/workflows` directory. So, create the file `src/workflows/sanity-sync-products/index.ts` with the following content:
-
-```ts title="src/workflows/sanity-sync-products/index.ts"
-import {
- createWorkflow,
- WorkflowResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { syncStep } from "./steps/sync"
-
-export type SanitySyncProductsWorkflowInput = {
- product_ids?: string[];
-};
-
-export const sanitySyncProductsWorkflow = createWorkflow(
- { name: "sanity-sync-products", retentionTime: 10000 },
- function (input: SanitySyncProductsWorkflowInput) {
- const result = syncStep(input)
-
- return new WorkflowResponse(result)
- }
-)
-```
-
-You create a workflow using the `createWorkflow` from the Workflows SDK. It accepts an object of options as a first parameter, where the `name` property is required and indicates the workflow's unique name.
-
-The `retentionTime` property indicates how long should the workflow's progress be saved in the database. This is useful if you later want to track whether the workflow is successfully executing.
-
-`createWorkflow` accepts as a second parameter a constructor function, which is the workflow's implementation. In the function, you execute the `syncStep` to sync the specified products in the input, then return its result. Workflows must return an instance of `WorkflowResponse`.
-
-A workflow's constructor function has some constraints in implementation. Learn more about them in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/constructor-constraints/index.html.md).
-
-You'll execute and test this workflow in the next steps.
-
-***
-
-## Step 7: Handle Product Changes in Medusa
-
-You've defined the workflow to sync the products. Now, you want to execute it when a product is created or updated.
-
-Medusa emits events when certain actions occur, such as when a product is created. Then, you can listen to those events in a subscriber.
-
-A subscriber is an asynchronous function that listens to one or more events. Then, when those events are emitted, the subscriber is executed in the background of your application.
-
-Subscribers are useful when you want to perform an action that isn't an integral part of a flow, but as a reaction to a performed action. In this case, syncing the products to Sanity isn't integral to creating a product, so you do it in a subscriber after the product is created.
-
-Learn more about events and subscribers in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/events-and-subscribers/index.html.md). You can also find the list of emitted events in [this reference](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/events-reference/index.html.md).
-
-So, to run the workflow you defined in the previous event when a product is created or updated, you'll create a subscriber that listens to the `product.created` and `product.updated` events.
-
-Subscribers are created under the `src/subscribers` directory. So, create the file `src/subscribers/sanity-product-sync.ts` with the following content:
-
-```ts title="src/subscribers/sanity-product-sync.ts" highlights={subscriberHighlights}
-import type {
- SubscriberArgs,
- SubscriberConfig,
-} from "@medusajs/medusa"
-import {
- sanitySyncProductsWorkflow,
-} from "../workflows/sanity-sync-products"
-
-export default async function upsertSanityProduct({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- await sanitySyncProductsWorkflow(container).run({
- input: {
- product_ids: [data.id],
- },
- })
-}
-
-export const config: SubscriberConfig = {
- event: ["product.created", "product.updated"],
-}
-```
-
-The subscriber function `upsertSanityProduct` accepts an object as a parameter that has the following properties:
-
-- `event`: An object of the event's details. Its `data` property holds the data payload emitted with the event, which in this case is the ID of the product created or updated.
-- `container`: An instance of the Medusa container to resolve resources.
-
-In the subscriber, you execute the `sanitySyncProductsWorkflow` by invoking it, passing it the container, then invoking its `run` method. You pass the workflow's input in the `input` property of the `run`'s object parameter.
-
-The subscriber file must also export a configuration object. It has an `event` property, which is the names of the events that the subscriber is listening to.
-
-### Test it Out
-
-To test it out, run the Medusa application, then open the Medusa Admin in your browser at `http://localhost:9000/app`. Try creating or updating a product. You'll see the following message in the console:
-
-```bash
-info: Processing product.created which has 1 subscribers
-```
-
-This means that the `product.created` event was emitted and your subscriber was executed.
-
-In the next step, you'll setup Sanity with Next.js, and you can then monitor the updates in Sanity's studio.
-
-***
-
-## Step 8: Setup Sanity with Next.js Starter Storefront
-
-In this step, you'll install Sanity in the Next.js Starter and configure it. You'll then have a Sanity studio in your Next.js storefront, where you'll later view the product documents being synced from Medusa, and update their content that you'll display in the storefront on the product details page.
-
-Sanity has a CLI tool that helps you with the setup. First, change to the Next.js Starter's directory (it's outside the Medusa application's directory and its name is `{project-name}-storefront`, where `{project-name}` is the name of the Medusa application's directory).
-
-Then, run the following command:
-
-```bash badgeLabel="Storefront" badgeColor="blue"
-npx sanity@latest init
-```
-
-You'll then be asked a few questions:
-
-- For the project, select the Sanity project you created earlier in this guide.
-- For dataset, use `production` unless you changed it in the Sanity project.
-- Select yes for adding the Sanity configuration files to the Next.js folder.
-- Select yes for TypeScript.
-- Select yes for Sanity studio, and choose the `/studio` route.
-- Select clean project template.
-- Select yes for adding the project ID and dataset to `.env.local`.
-
-Afterwards, the command will install the necessary dependencies for Sanity.
-
-### Error during installation
-
-If you run into an error during the installation of peer dependencies, try running the following command to install them:
-
-```bash
-yarn add next-sanity@9.8.15 @sanity/client@^6.22.4 @sanity/icons@^3.4.0 @sanity/types@^3.62.0 @sanity/ui@^2.8.10 next@^15.0.0 react@^19.0.0 react-dom@^19.0.0 sanity@^3.62.0 styled-components@^6.1
-```
-
-### Update Middleware
-
-The Next.js Starter storefront has a middleware that ensures all requests start with a country code (for example, `/us`).
-
-Since the Sanity studio runs at `/studio`, the middleware should ignore requests to this path.
-
-Open the file `src/middleware.ts` and find the following `if` condition:
-
-```ts title="src/middleware.ts" badgeLabel="Storefront" badgeColor="blue"
-if (
- urlHasCountryCode &&
- (!isOnboarding || onboardingCookie) &&
- (!cartId || cartIdCookie)
-) {
- return NextResponse.next()
-}
-```
-
-Replace it with the following condition:
-
-```ts title="src/middleware.ts" badgeLabel="Storefront" badgeColor="blue"
-if (
- request.nextUrl.pathname.startsWith("/studio") ||
- urlHasCountryCode &&
- (!isOnboarding || onboardingCookie) &&
- (!cartId || cartIdCookie)
-) {
- return NextResponse.next()
-}
-```
-
-If the path starts with `/studio`, the middleware will stop executing and the page will open.
-
-### Set CORS Settings
-
-Every Sanity project has a configured set of CORS origins allowed, with the default being `http://localhost:3333`.
-
-The Next.js Starter runs on the `8000` port, so you must add it to the allowed CORS origins.
-
-In your Sanity project's dashboard:
-
-1. Click on the API tab.
-
-
-
-2. Scroll down to CORS origins and click the "Add CORS origin" button.
-
-
-
-3. Enter `http://localhost:8000` in the Origin field.
-4. Enable the "Allow credentials" checkbox.
-
-
-
-5. Click the Save button.
-
-### Open Sanity Studio
-
-To open the Sanity studio, start the Next.js Starter's development server:
-
-```bash npm2yarn
-npm run dev
-```
-
-Then, open `http://localhost:8000/studio` in your browser. The Sanity studio will open, but right now it's empty.
-
-***
-
-## Step 9: Add Product Schema Type in Sanity
-
-In this step, you'll define the `product` schema type in Sanity. You' can then view the documents of that schema in the studio and update their content.
-
-To create the schema type, create the file `src/sanity/schemaTypes/documents/product.ts` with the following content:
-
-```ts title="src/sanity/schemaTypes/documents/product.ts" badgeLabel="Storefront" badgeColor="blue"
-import { ComposeIcon } from "@sanity/icons"
-import { DocumentDefinition } from "sanity"
-
-const productSchema: DocumentDefinition = {
- fields: [
- {
- name: "title",
- type: "string",
- },
- {
- group: "content",
- name: "specs",
- of: [
- {
- fields: [
- { name: "lang", title: "Language", type: "string" },
- { name: "title", title: "Title", type: "string" },
- {
- name: "content",
- rows: 3,
- title: "Content",
- type: "text",
- },
- ],
- name: "spec",
- type: "object",
- },
- ],
- type: "array",
- },
- {
- fields: [
- { name: "title", title: "Title", type: "string" },
- {
- name: "products",
- of: [{ to: [{ type: "product" }], type: "reference" }],
- title: "Addons",
- type: "array",
- validation: (Rule) => Rule.max(3),
- },
- ],
- name: "addons",
- type: "object",
- },
- ],
- name: "product",
- preview: {
- select: {
- title: "title",
- },
- },
- title: "Product Page",
- type: "document",
- groups: [{
- default: true,
- // @ts-ignore
- icon: ComposeIcon,
- name: "content",
- title: "Content",
- }],
-}
-
-export default productSchema
-```
-
-This creates a schema that has the following fields:
-
-- `title`: The title of a document, which is in this case the product's type.
-- `specs`: An array of product specs. Each object in the array has the following fields:
- - `lang`: This is useful if you want to have localized content.
- - `title`: The product's title.
- - `content`: Textual content, such as the product's description.
-- `addons`: An object of products related to this product.
-
-When you sync the products from Medusa, you only sync the title. You manage the `specs` and `addons` fields within Sanity.
-
-Next, replace the content of `src/sanity/schemaTypes/index.ts` with the following:
-
-```ts title="src/sanity/schemaTypes/index.ts" badgeLabel="Storefront" badgeColor="blue"
-import { SchemaPluginOptions } from "sanity"
-import productSchema from "./documents/product"
-
-export const schema: SchemaPluginOptions = {
- types: [productSchema],
- templates: (templates) => templates.filter(
- (template) => template.schemaType !== "product"
- ),
-}
-```
-
-You add the product schema to the list of exported schemas, but also disable creating a new product. You can only create the products in Medusa.
-
-### Test it Out
-
-To ensure that your schema is defined correctly and working, start the Next.js storefront's server, and open the Sanity studio again at `http://localhost:8000/studio`.
-
-You'll find "Product Page" under Content. If you click on it, you'll find any product you've synced from Medusa.
-
-If you haven't synced any products yet or you want to see the live update, try now creating or updating a product in Medusa. You'll find it added in the Sanity studio.
-
-If you click on any product, you can edit its existing field under "Specs" or add new ones. In the next section, you'll learn how to show the content in the "Specs" field on the storefront's product details page.
-
-***
-
-## Step 10: Show Sanity Content in Next.js Starter Storefront
-
-Now that you're managing a product's content in Sanity, you want to show that content on the storefront. In this step, you'll customize the Next.js Starter storefront to show a product's content as defined in Sanity.
-
-A product's details are retrieved in the file `src/app/[countryCode]/(main)/products/[handle]/page.tsx`. So, replace the `ProductPage` function with the following:
-
-```tsx title="src/app/[countryCode]/(main)/products/[handle]/page.tsx" badgeLabel="Storefront" badgeColor="blue" highlights={sanityContentHighlights}
-// other imports...
-import { client } from "../../../../../sanity/lib/client"
-
-// ...
-
-export default async function ProductPage(props: Props) {
- const params = await props.params
- const region = await getRegion(params.countryCode)
-
- if (!region) {
- notFound()
- }
-
- const pricedProduct = await listProducts({
- countryCode: params.countryCode,
- queryParams: { handle: params.handle },
- }).then(({ response }) => response.products[0])
-
- if (!pricedProduct) {
- notFound()
- }
-
- // alternatively, you can filter the content by the language
- const sanity = (await client.getDocument(pricedProduct.id))?.specs[0]
-
- return (
-
- )
-}
-```
-
-You import the Sanity client defined in `src/sanity/lib/client.ts` (this was generated by Sanity's CLI). Then, in the page's function, you retrieve the product's document by ID and pass its first step to the `ProductTemplate` component.
-
-This is a simplified approach, but you can also have languages in your storefront and filter the spec based on the current language.
-
-Next, you need to customize the `ProductTemplate` to accept the `sanity` prop. In the file `src/modules/products/templates/index.tsx` add the following to `ProductTemplateProps`:
-
-```tsx title="src/modules/products/templates/index.tsx" badgeLabel="Storefront" badgeColor="blue"
-type ProductTemplateProps = {
- // ...
- sanity?: {
- content: string
- }
-}
-```
-
-Then, add the `sanity` property to the expanded props of the component:
-
-```tsx title="src/modules/products/templates/index.tsx" badgeLabel="Storefront" badgeColor="blue"
-const ProductTemplate: React.FC = ({
- // ...
- sanity,
-}) => {
- // ...
-}
-```
-
-Finally, pass the `sanity` prop to the `ProductInfo` component in the return statement:
-
-```tsx title="src/modules/products/templates/index.tsx" badgeLabel="Storefront" badgeColor="blue"
-
-```
-
-Next, you need to update the `ProductInfo` component to accept and use the `sanity` prop.
-
-In `src/modules/products/templates/product-info/index.tsx`, update the `ProductInfoProps` to accept the `sanity` prop:
-
-```tsx title="src/modules/products/templates/product-info/index.tsx" badgeLabel="Storefront" badgeColor="blue"
-type ProductInfoProps = {
- // ...
- sanity?: {
- content: string
- }
-}
-```
-
-Then, add the `sanity` property to the expanded props of the component:
-
-```tsx title="src/modules/products/templates/index.tsx" badgeLabel="Storefront" badgeColor="blue"
-const ProductInfo = ({
- // ...
- sanity,
-}: ProductInfoProps) => {
- // ...
-}
-```
-
-Next, find the following line in the return statement:
-
-```tsx title="src/modules/products/templates/index.tsx" badgeLabel="Storefront" badgeColor="blue"
-{product.description}
-```
-
-And replace it with the following:
-
-```tsx title="src/modules/products/templates/index.tsx" badgeLabel="Storefront" badgeColor="blue"
-{sanity?.content || product.description}
-```
-
-Instead of showing the product's description on the product details page, this will show the content defined in Sanity if available.
-
-### Test it Out
-
-To test this out, first, run both the Next.js Starter storefront and the Medusa application, and open the Sanity studio. Try editing the content of the first spec of a product.
-
-Then, open the Next.js Starter storefront at `http://localhost:8000` and go to "Store" from the menu, then select the product you edited in Sanity.
-
-In the product's page, you'll find under the product's name the content you put in Sanity.
-
-You can now manage the product's content in Sanity, add more fields, and customize how you show them in the storefront. The Medusa application will also automatically create documents in Sanity for new products you add or update, ensuring your products are always synced across systems.
-
-***
-
-## Step 11: Customize Admin to Manually Sync Data
-
-There are cases where you need to trigger the syncing of products manually, such as when an error occurs or you have products from before creating this integration.
-
-The Medusa Admin dashboard is customizable, allowing you to either inject components, called [widgets](https://docs.medusajs.com/docs/learn/fundamentals/admin/widgets/index.html.md), into existing pages, or adding new pages, called [UI routes](https://docs.medusajs.com/docs/learn/fundamentals/admin/ui-routes/index.html.md). In these customizations, you can send requests to the Medusa application to perform custom operations.
-
-In this step, you'll add a widget to the product's details page. In that page, you'll show whether a product is synced with Sanity, and allow the admin user to trigger syncing it manually.
-
-
-
-Before you do that, however, you need two new API routes in your Medusa application: one to retrieve a document from Sanity, and one to trigger syncing the product data.
-
-An API route is a REST API endpoint that exposes commerce features to the admin dashboard or other frontend clients. Learn more about API routes in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md).
-
-### Get Sanity Document API Route
-
-In this section, you'll create the API route to retrieve a sanity document, and the URL to it in the Sanity studio.
-
-To retrieve the URL to the Sanity studio, add the following method in the Sanity Module's service in `src/modules/sanity/service.ts`:
-
-```ts title="src/modules/sanity/service.ts"
-class SanityModuleService {
- // ...
- async getStudioLink(
- type: string,
- id: string,
- config: { explicit_type?: boolean } = {}
- ) {
- const resolvedType = config.explicit_type ? type : this.typeMap[type]
- if (!this.studioUrl) {
- throw new Error("No studio URL provided")
- }
- return `${this.studioUrl}/structure/${resolvedType};${id}`
- }
-}
-```
-
-The method uses the `studioUrl` property, which you set in the `constructor` using the `studio_url` module option, to get the studio link.
-
-Then, to create the API route, create the file `src/api/admin/sanity/documents/[id]/route.ts` with the following content:
-
-```ts title="src/api/admin/sanity/documents/[id]/route.ts"
-import {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import SanityModuleService from "src/modules/sanity/service"
-import { SANITY_MODULE } from "../../../../../modules/sanity"
-
-export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
- const { id } = req.params
-
- const sanityModule: SanityModuleService = req.scope.resolve(
- SANITY_MODULE
- )
- const sanityDocument = await sanityModule.retrieve(id)
-
- const url = sanityDocument ?
- await sanityModule.getStudioLink(
- sanityDocument._type,
- sanityDocument._id,
- { explicit_type: true }
- )
- : ""
-
- res.json({ sanity_document: sanityDocument, studio_url: url })
-}
-```
-
-This defines a `GET` API route at `/admin/sanity/documents/:id`, where `:id` is a dynamic path parameter indicating the ID of a document to retrieve.
-
-In the `GET` route handler, you resolve the Sanity Module's service and use it to first retrieve the product's document, then the studio link of that document.
-
-You return in the JSON response an object having the `sanity_document` and `studio_url` properties.
-
-You'll test out this route in a later section.
-
-Since the API route is added under the `/admin` prefix, only authenticated admin users can access it. Learn more about protected routes in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/protected-routes/index.html.md).
-
-### Trigger Sanity Sync API Route
-
-In this section, you'll add the API route that manually triggers syncing a product to Sanity.
-
-Since you already have the workflow to sync products, you only need to create an API route that executes it.
-
-Create the file `src/api/admin/sanity/documents/[id]/sync/route.ts` with the following content:
-
-```ts title="src/api/admin/sanity/documents/[id]/sync/route.ts"
-import {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import {
- sanitySyncProductsWorkflow,
-} from "../../../../../../workflows/sanity-sync-products"
-
-export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
- const { transaction } = await sanitySyncProductsWorkflow(req.scope)
- .run({
- input: { product_ids: [req.params.id] },
- })
-
- res.json({ transaction_id: transaction.transactionId })
-}
-```
-
-You add a `POST` API route at `/admin/sanity/documents/:id/sync`, where `:id` is a dynamic path parameter that indicates the ID of a product to sync to Sanity.
-
-In the `POST` API route handler, you execute the `sanitySyncProductsWorkflow`, passing it the ID of the product from the path parameter as an input.
-
-In the next section, you'll customize the admin dashboard and send requests to the API route from there.
-
-### Sanity Product Widget
-
-In this section, you'll add a widget in the product details page. The widget will show the Sanity document of the product and triggers syncing it to Sanity using the API routes you created.
-
-To send requests from admin customizations to the Medusa server, you need to use Medusa's [JS SDK](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/js-sdk/index.html.md). You'll also use [Tanstack Query](https://tanstack.com/query/latest) to benefit from features like data caching and invalidation.
-
-Do not install Tanstack Query as that will cause unexpected errors in your development. If you prefer installing it for better auto-completion in your code editor, make sure to install `v5.64.2` as a development dependency.
-
-To configure the JS SDK, create the file `src/admin/lib/sdk.ts` with the following content:
-
-```ts title="src/admin/lib/sdk.ts"
-import Medusa from "@medusajs/js-sdk"
-
-export const sdk = new Medusa({
- baseUrl: import.meta.env.VITE_BACKEND_URL || "/",
- debug: import.meta.env.DEV,
- auth: {
- type: "session",
- },
-})
-```
-
-You initialize the JS SDK and export it. You can learn more about configuring the JS SDK in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/js-sdk/index.html.md).
-
-Next, you'll create hooks using Tanstack Query to send requests to the API routes you created earlier.
-
-Create the file `src/admin/hooks/sanity.tsx` with the following content:
-
-```ts title="src/admin/hooks/sanity.tsx"
-import {
- useMutation,
- UseMutationOptions,
- useQueryClient,
-} from "@tanstack/react-query"
-import { sdk } from "../lib/sdk"
-
-export const useTriggerSanityProductSync = (
- id: string,
- options?: UseMutationOptions
-) => {
- const queryClient = useQueryClient()
-
- return useMutation({
- mutationFn: () =>
- sdk.client.fetch(`/admin/sanity/documents/${id}/sync`, {
- method: "post",
- }),
- onSuccess: (data: any, variables: any, context: any) => {
- queryClient.invalidateQueries({
- queryKey: [`sanity_document`, `sanity_document_${id}`],
- })
-
- options?.onSuccess?.(data, variables, context)
- },
- ...options,
- })
-}
-```
-
-You define the `useTriggerSanityProductSync` hook which creates a Tanstack Query mutation that, when executed, sends a request to the API route that triggers syncing the product to Sanity.
-
-Add in the same file another hook:
-
-```ts title="src/admin/hooks/sanity.tsx"
-// other imports...
-import {
- // ...
- QueryKey,
- useQuery,
- UseQueryOptions,
-} from "@tanstack/react-query"
-import { FetchError } from "@medusajs/js-sdk"
-
-// ...
-
-export const useSanityDocument = (
- id: string,
- query?: Record,
- options?: Omit<
- UseQueryOptions<
- Record,
- FetchError,
- { sanity_document: Record; studio_url: string },
- QueryKey
- >,
- "queryKey" | "queryFn"
- >
-) => {
- const fetchSanityProductStatus = async (query?: Record) => {
- return await sdk.client.fetch>(
- `/admin/sanity/documents/${id}`,
- {
- query,
- }
- )
- }
-
- const { data, ...rest } = useQuery({
- queryFn: async () => fetchSanityProductStatus(query),
- queryKey: [`sanity_document_${id}`],
- ...options,
- })
-
- return { ...data, ...rest }
-}
-```
-
-You define the hook `useSanityDocument` which retrieves the Sanity document of a product using Tankstack Query.
-
-You can now create the widget injected in a product's details page. Widgets are react components created in a file under the `src/admin/widgets` directory.
-
-So, create the file `src/admin/widgets/sanity-product.tsx` with the following content:
-
-```tsx title="src/admin/widgets/sanity-product.tsx"
-import { defineWidgetConfig } from "@medusajs/admin-sdk"
-import { AdminProduct, DetailWidgetProps } from "@medusajs/types"
-import { ArrowUpRightOnBox } from "@medusajs/icons"
-import { Button, CodeBlock, Container, StatusBadge, toast } from "@medusajs/ui"
-import { useState } from "react"
-import {
- useSanityDocument,
- useTriggerSanityProductSync,
-} from "../hooks/sanity"
-
-const ProductWidget = ({ data }: DetailWidgetProps) => {
- const { mutateAsync, isPending } = useTriggerSanityProductSync(data.id)
- const { sanity_document, studio_url, isLoading } = useSanityDocument(data.id)
- const [showCodeBlock, setShowCodeBlock] = useState(false)
-
- const handleSync = async () => {
- try {
- await mutateAsync(undefined)
- toast.success(`Sync triggered.`)
- } catch (err) {
- toast.error(`Couldn't trigger sync: ${
- (err as Record).message
- }`)
- }
- }
-
- return (
-
-
-
- )
-}
-
-// The widget's configurations
-export const config = defineWidgetConfig({
- zone: "product.details.after",
-})
-
-export default ProductWidget
-```
-
-The file exports a `ProductWidget` component and a `config` object created with `defineWidgetConfig` from the Admin Extension SDK. In the `config` object, you specify the zone to inject the widget into in the `zone` property.
-
-Find all widget injection zones in [this reference](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-widget-injection-zones/index.html.md).
-
-In the widget, you use the `useSanityDocument` to retrieve the product's document from Sanity by sending a request to the API route you created earlier. You show that document's details and a button to trigger syncing the data.
-
-When the "Sync" button is clicked, you use the `useTriggerSanityProductSync` hook which sends a request to the API route you created earlier and executes the workflow that syncs the product to Sanity. The workflow will execute in the background, since you configured its step to be async.
-
-To render a widget that matches the rest of the admin dashboard's design, you use components from the [Medusa UI package](https://docs.medusajs.com/ui/index.html.md), such as the `CodeBlock` or `Container` components.
-
-Learn more about widgets in [this documentation](https://docs.medusajs.com/learn/fundamentals/admin/widgets/index.html.md).
-
-### Test it Out
-
-To test these customizations out, start the Medusa application and open the admin dashboard. Then, choose a product and scroll down to the end of the page.
-
-You'll find a new "Sanity Status" section showing you whether the product is synced to Sanity and its document's details. You can also click the Sync button, which will sync the product to Sanity.
-
-***
-
-## Step 12: Add Track Syncs Page to Medusa Admin
-
-Earlier in this guide when introducing workflows, you learned that you can track the execution of a workflow. As a last step of this guide, you'll add a new page in the admin dashboard that shows the executions of the `sanitySyncProductsWorkflow` and their status. You'll also add the ability to sync all products to Sanity from that page.
-
-
-
-### Retrieve Sync Executions API Route
-
-Medusa has a [workflow engine](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/architectural-modules/workflow-engine/index.html.md) that manages workflow executions, roll-backs, and other functionalities under the hood.
-
-The workflow engine is an [architectural module](https://docs.medusajs.com/docs/learn/fundamentals/modules/architectural-modules/index.html.md), which can be replaced with a [Redis Workflow Engine](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/architectural-modules/workflow-engine/redis/index.html.md), or a custom one of your choice, allowing you to take ownership of your application's tooling.
-
-In your customizations, you can resolve the workflow engine from the container and manage executions of a workflow, such as retrieve them and check their progress.
-
-In this section, you'll create an API route to retrieve the stored executions of the `sanitySyncProductsWorkflow` workflow, so that you can display them later on the dashboard.
-
-When you defined the `sanitySyncProductsWorkflow`, you set its `retentionTime` option so that you can store the workflow execution's details temporarily. If a workflow doesn't have this option set, its execution won't be stored for tracking.
-
-Create the file `src/api/admin/sanity/syncs/route.ts` with the following content:
-
-```ts title="src/api/admin/sanity/syncs/route.ts"
-import { MedusaRequest, MedusaResponse } from "@medusajs/framework"
-import { Modules } from "@medusajs/framework/utils"
-import {
- sanitySyncProductsWorkflow,
-} from "../../../../workflows/sanity-sync-products"
-
-export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
- const workflowEngine = req.scope.resolve(
- Modules.WORKFLOW_ENGINE
- )
-
- const [executions, count] = await workflowEngine
- .listAndCountWorkflowExecutions(
- {
- workflow_id: sanitySyncProductsWorkflow.getName(),
- },
- { order: { created_at: "DESC" } }
- )
-
- res.json({ workflow_executions: executions, count })
-}
-```
-
-You add a `GET` API route at `/admin/sanity/syncs`. In the API route handler, you resolve the Workflow Engine Module's service from the Medusa container. You use the `listAndCountWorkflowExecutions` method to retrieve the executions of the `sanitySyncProductsWorkflow` workflow, filtering by its name.
-
-You return the executions in the JSON response of the route.
-
-### Trigger Sync API Route
-
-In this section, you'll add another API route that triggers syncing all products to Sanity.
-
-In the same file `src/api/admin/sanity/syncs/route.ts`, add the following:
-
-```ts title="api/admin/sanity/syncs/route.ts"
-export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
- const { transaction } = await sanitySyncProductsWorkflow(req.scope).run({
- input: {},
- })
-
- res.json({ transaction_id: transaction.transactionId })
-}
-```
-
-This adds a `POST` API route at `/admin/sanity/syncs`. In the route handler, you execute the `sanitySyncProductsWorkflow` without passing it a `product_ids` input. The step in the workflow will retrieve all products, instead of filtering them by ID, and sync them to Sanity.
-
-You return the transaction ID of the workflow, which you can use to track the execution's progress since the workflow will run in the background. This is not implemented in this guide, but Medusa has a [Get Execution API route](https://docs.medusajs.com/apiadmin#workflows-executions_getworkflowsexecutionsworkflow_idtransaction_id/index.html.md) that you can use to get the details of a workflow's execution.
-
-### Add Sanity UI Route
-
-In this section, you'll add a UI route in the admin dashboard, which is a new page, that shows the list of `sanitySyncProductsWorkflow` executions and allows triggering sync of all products in Medusa.
-
-A UI route is React component exported in a file under the `src/admin/routes` directory. Similar to a widget, a UI route can also send requests to the Medusa application to perform actions using your custom API routes.
-
-Before creating the UI route, you'll create hooks using Tanstack Query that send requests to these UI routes. In the file `src/admin/hooks/sanity.tsx`, add the following two new hooks:
-
-```tsx title="src/admin/hooks/sanity.tsx"
-export const useTriggerSanitySync = (options?: UseMutationOptions) => {
- const queryClient = useQueryClient()
-
- return useMutation({
- mutationFn: () =>
- sdk.client.fetch(`/admin/sanity/syncs`, {
- method: "post",
- }),
- onSuccess: (data: any, variables: any, context: any) => {
- queryClient.invalidateQueries({
- queryKey: [`sanity_sync`],
- })
-
- options?.onSuccess?.(data, variables, context)
- },
- ...options,
- })
-}
-
-export const useSanitySyncs = (
- query?: Record,
- options?: Omit<
- UseQueryOptions<
- Record,
- FetchError,
- { workflow_executions: Record[] },
- QueryKey
- >,
- "queryKey" | "queryFn"
- >
-) => {
- const fetchSanitySyncs = async (query?: Record) => {
- return await sdk.client.fetch>(`/admin/sanity/syncs`, {
- query,
- })
- }
-
- const { data, ...rest } = useQuery({
- queryFn: async () => fetchSanitySyncs(query),
- queryKey: [`sanity_sync`],
- ...options,
- })
-
- return { ...data, ...rest }
-}
-```
-
-The `useTriggerSanitySync` hook creates a mutation that, when executed, sends a request to the trigger sync API route you created earlier to sync all products.
-
-The `useSanitySyncs` hook sends a request to the retrieve sync executions API route that you created earlier to retrieve the workflow's exections.
-
-Finally, to create the UI route, create the file `src/admin/routes/sanity/page.tsx` with the following content:
-
-```tsx title="src/admin/routes/sanity/page.tsx"
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { Sanity } from "@medusajs/icons"
-import {
- Badge,
- Button,
- Container,
- Heading,
- Table,
- Toaster,
- toast,
-} from "@medusajs/ui"
-import { useSanitySyncs, useTriggerSanitySync } from "../../hooks/sanity"
-
-const SanityRoute = () => {
- const { mutateAsync, isPending } = useTriggerSanitySync()
- const { workflow_executions, refetch } = useSanitySyncs()
-
- const handleSync = async () => {
- try {
- await mutateAsync()
- toast.success(`Sync triggered.`)
- refetch()
- } catch (err) {
- toast.error(`Couldn't trigger sync: ${
- (err as Record).message
- }`)
- }
- }
-
- const getBadgeColor = (state: string) => {
- switch (state) {
- case "invoking":
- return "blue"
- case "done":
- return "green"
- case "failed":
- return "red"
- default:
- return "grey"
- }
- }
-
- return (
- <>
-
-
-
- Sanity Syncs
-
-
-
-
-
-
- Sync ID
- Status
- Created At
- Updated At
-
-
-
-
- {(workflow_executions || []).map((execution) => (
-
- (window.location.href = `/app/sanity/${execution.id}`)
- }
- >
- {execution.id}
-
-
- {execution.state}
-
-
- {execution.created_at}
- {execution.updated_at}
-
- ))}
-
-
-
-
- >
- )
-}
-
-export const config = defineRouteConfig({
- label: "Sanity",
- icon: Sanity,
-})
-
-export default SanityRoute
-```
-
-The file's path relative to the `src/admin/routes` directory indicates its path in the admin dashboard. So, this adds a new route at the path `http://localhost:9000/app/sanity`.
-
-The file must export the UI route's component. Also, to add an item in the sidebar for the UI route, you export a configuration object, created with `defineRouteConfig` from the Admin Extension SDK. The function accepts the following properties:
-
-- `label`: The sidebar item's label.
-- `icon`: The icon to the show in the sidebar.
-
-In the UI route, you use the `useSanitySyncs` hook to retrieve the list of sync executions and display them with their status. You also show a "Trigger Sync" button that, when clicked, uses the mutation from the `useTriggerSanitySync` hook to send a request to the Medusa application and trigger the sync.
-
-To display components that match the design of the Medusa Admin, you use components from the [Medusa UI package](https://docs.medusajs.com/ui/index.html.md).
-
-Learn more about UI routes in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/admin/ui-routes/index.html.md).
-
-### Test it Out
-
-To test it out, start the Medusa application and open the admin dashboard. After logging in, you'll find a new "Sanity" item in the sidebar.
-
-If you click on it, you'll see a table of the latest syncs. You also trigger syncing by clicking the "Trigger Sync" button. After you click the button, you should see a new execution added to the table.
-
-***
-
-## Next Steps
-
-You've now integrated Medusa with Sanity and can benefit from powerful commerce and CMS features.
-
-If you're new to Medusa, check out the [main documentation](https://docs.medusajs.com/docs/learn/index.html.md), where you'll get a more in-depth learning of all the concepts you've used in this guide and more.
-
-To learn more about the commerce features that Medusa provides, check out Medusa's [Commerce Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md).
-
-
# Integrate Medusa with Resend
In this guide, you'll learn how to integrate Medusa with Resend.
@@ -44099,6 +42523,1820 @@ If you're new to Medusa, check out the [main documentation](https://docs.medusaj
To learn more about the commerce features that Medusa provides, check out Medusa's [Commerce Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md).
+# Integrate Medusa with Sanity
+
+In this guide, you'll learn how to integrate Medusa with Sanity.
+
+When you install a Medusa application, you get a fully-fledged commerce platform with support for customizations. While Medusa allows you to manage basic content, such as product description and images, you might need rich content-management features, such as localized content. Medusa's framework supports you in integrating a CMS with these features.
+
+Sanity is a CMS that simplifies managing content from third-party sources into a single interface. By integrating it with Medusa, you can manage your storefront and commerce-related content, such as product details, from a single interface. You also benefit from advanced content-management features, such as live-preview editing.
+
+This guide will teach you how to:
+
+- Install and set up Medusa.
+- Install and set up Sanity with Medusa's Next.js Starter storefront.
+- Sync product data from Medusa to Sanity when a product is created or updated.
+- Customize the Medusa Admin dashboard to check the sync status and trigger syncing products to Sanity.
+
+You can follow this guide whether you're new to Medusa or an advanced Medusa developer. This guide also assumes you're familiar with Sanity concepts, which you can learn about in [their documentation](https://www.sanity.io/docs).
+
+[Example Repository](https://github.com/medusajs/examples/tree/main/sanity-integration): Find the full code of the guide in this repository.
+
+***
+
+## Step 1: Install a Medusa Application
+
+### Prerequisites
+
+- [Node.js v20+](https://nodejs.org/en/download)
+- [Git CLI tool](https://git-scm.com/downloads)
+- [PostgreSQL](https://www.postgresql.org/download/)
+
+Start by installing the Medusa application on your machine with the following command:
+
+```bash
+npx create-medusa-app@latest
+```
+
+You'll first be asked for the project's name. Then, when you're asked whether you want to install the Next.js storefront, choose `Y` for yes.
+
+Afterwards, the installation process will start, which will install the Medusa application in a directory with your project's name, and the Next.js storefront in a directory with the `{project-name}-storefront` name.
+
+The Medusa application is composed of a headless Node.js server and an admin dashboard. The storefront is installed or custom-built separately and connects to the Medusa application through its REST endpoints, called [API routes](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md). Learn more about Medusa's architecture in [this documentation](https://docs.medusajs.com/docs/learn/introduction/architecture/index.html.md).
+
+Once the installation finishes successfully, the Medusa Admin dashboard will open with a form to create a new user. Enter the user's credential and submit the form.
+
+Afterwards, you can login with the new user and explore the dashboard. The Next.js storefront is also running at `http://localhost:8000`.
+
+Check out the [troubleshooting guides](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/troubleshooting/create-medusa-app-errors/index.html.md) for help.
+
+***
+
+## Step 2: Install Sanity Client SDK
+
+In this step, you'll install [Sanity's JavaScript client SDK](https://www.sanity.io/docs/js-client) in the Medusa application, which you'll use later in your code when sending requests to Sanity.
+
+In your terminal, move to the Medusa application's directory and run the following command:
+
+```bash npm2yarn
+cd project-name # replace with directory name
+npm install @sanity/client
+```
+
+***
+
+## Step 3: Create a Sanity Project
+
+When the Medusa application connects to Sanity, it must connect to a project in Sanity.
+
+So, before building the integration in Medusa, create a project in Sanity using their website:
+
+1. [Sign in or sign up on the Sanity website.](https://www.sanity.io/login)
+2. On your account's dashboard, click the "Create new project" button.
+
+
+
+3. Enter a project name and click "Create Project"
+
+
+
+You'll go back to the project's setting page in a later step.
+
+***
+
+## Step 4: Create Sanity Module
+
+To integrate third-party services into Medusa, you create a custom module. A module is a re-usable package with functionalities related to a single feature or domain. Medusa integrates the module into your application without implications or side effects on your setup.
+
+In this step, you'll create a Sanity Module that provides the interface to connect to and interact with Sanity. In later steps, you'll use the functionalities provided by this module to sync products to Sanity or retrieve documents from it.
+
+Learn more about modules in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md).
+
+### Create Module Directory
+
+A module is created under the `src/modules` directory of your Medusa application. So, create the directory `src/modules/sanity`.
+
+### Create Service
+
+You define a module's functionalities in a service. A service is a TypeScript or JavaScript class that the module exports. In the service's methods, you can connect to the database, which is useful if your module defines tables in the database, or connect to a third-party service.
+
+Medusa registers the module's service in the [Medusa container](https://docs.medusajs.com/docs/learn/fundamentals/medusa-container/index.html.md), allowing you to easily resolve the service from other customizations and use its methods.
+
+The Medusa application registers resources, such as a module's service or the [logging tool](https://docs.medusajs.com/docs/learn/debugging-and-testing/logging/index.html.md), in the Medusa container so that you can resolve them from other customizations, as you'll see in later sections. Learn more about it in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/medusa-container/index.html.md).
+
+In this section, you'll create the Sanity Module's service and the methods necessary to connect to Sanity.
+
+Start by creating the file `src/modules/sanity/service.ts` with the following content:
+
+```ts title="src/modules/sanity/service.ts"
+import {
+ Logger,
+} from "@medusajs/framework/types"
+import {
+ SanityClient,
+} from "@sanity/client"
+
+class SanityModuleService {
+ private client: SanityClient
+ private studioUrl?: string
+ private logger: Logger
+
+ // TODO
+}
+
+export default SanityModuleService
+```
+
+You create the `SanityModuleService` class that for now only has three properties:
+
+- `client` property of type `SanityClient` (from the Sanity SDK you installed in the previous step) to send requests to Sanity.
+- `studioUrl` property which will hold the URL to access the Sanity studio.
+- `logger` property, which is an instance of Medusa's [Logger](https://docs.medusajs.com/docs/learn/debugging-and-testing/logging/index.html.md), to log messages.
+
+In the service, you want to initialize the client early-on so that you can use it in the service's methods. This requires options to be passed to the client, like the Sanity API key or project ID.
+
+So, add after the import at the top of the file the following types:
+
+```ts title="src/modules/sanity/service.ts"
+// other imports...
+
+const SyncDocumentTypes = {
+ PRODUCT: "product",
+} as const
+
+type SyncDocumentTypes =
+ (typeof SyncDocumentTypes)[keyof typeof SyncDocumentTypes];
+
+type ModuleOptions = {
+ api_token: string;
+ project_id: string;
+ api_version: string;
+ dataset: "production" | "development";
+ type_map?: Record;
+ studio_url?: string;
+}
+```
+
+The `ModuleOptions` type defines the type of options that the module expects:
+
+- `api_token`: API token to connect to Sanity.
+- `project_id`: The ID of the Sanity project.
+- `api_version`: The Sanity API version.
+- `dataset`: The dataset to use, which is either `production` or `development`.
+- `type_map`: The types to sync from Medusa to Sanity. For simplicity, this guide only covers syncing products, but you can support other data types like product categories, too.
+- `studio_url`: The URL to the Sanity studio. This is used to show the studio URL later in the Medusa Admin dashboard.
+
+You can now initialize the client, which you'll do in the `constructor` of the `SanityModuleService`:
+
+```ts title="src/modules/sanity/service.ts"
+import {
+ // other imports...
+ createClient,
+} from "@sanity/client"
+
+// types...
+
+type InjectedDependencies = {
+ logger: Logger
+};
+
+class SanityModuleService {
+ // properties...
+ constructor({
+ logger,
+ }: InjectedDependencies, options: ModuleOptions) {
+ this.client = createClient({
+ projectId: options.project_id,
+ apiVersion: options.api_version,
+ dataset: options.dataset,
+ token: options.api_token,
+ })
+ this.logger = logger
+
+ this.logger.info("Connected to Sanity")
+
+ this.studioUrl = options.studio_url
+
+ // TODO initialize more properties
+ }
+}
+```
+
+The service's constructor accepts two parameters:
+
+1. Resources to resolve from the Module's container. A module has a different container than the Medusa application, which you can learn more about it in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/container/index.html.md).
+2. The options passed to the module.
+
+In the constructor, you create a Sanity client using the `createClient` function imported from `@sanity/client`. You pass it the options that the module receives.
+
+You also initialize the `logger` and `studioUrl` properties, and log a message indicating that connection to Sanity was successful.
+
+#### Transform Product Data
+
+When you create or update products in Sanity, you must prepare the product object based on what Sanity expects.
+
+So, you'll add methods to the service that transform a Medusa product to a Sanity document object.
+
+Start by adding the following types and class properties to `src/modules/sanity/service.ts`:
+
+```ts title="src/modules/sanity/service.ts"
+type SyncDocumentInputs = T extends "product"
+ ? ProductDTO
+ : never
+
+type TransformationMap = Record<
+ SyncDocumentTypes,
+ (data: SyncDocumentInputs) => any
+>;
+
+class SanityModuleService {
+ // other properties...
+ private typeMap: Record
+ private createTransformationMap: TransformationMap
+ private updateTransformationMap: TransformationMap
+
+ // ...
+}
+```
+
+First, you define types for a transformation map, which is a map that pairs up a document type (such as `product`) to a function that handles transforming its data.
+
+Then, in the service, you define three new properties:
+
+- `typeMap`: Pair of `SyncDocumentTypes` values (for example, `product`) and their type name in Sanity.
+- `createTransformationMap`: Pair of `SyncDocumentTypes` values (for example, `product`) and the method used to transform a Medusa product to a Sanity document data to be created.
+- `updateTransformationMap`: Pair of `SyncDocumentTypes` values (for example, `product`) and the method used to transform a Medusa product to a Sanity update operation.
+
+Next, add the following two methods to transform a product:
+
+```ts title="src/modules/sanity/service.ts"
+// other imports...
+import {
+ ProductDTO,
+} from "@medusajs/framework/types"
+
+class SanityModuleService {
+ // ...
+ private transformProductForCreate = (product: ProductDTO) => {
+ return {
+ _type: this.typeMap[SyncDocumentTypes.PRODUCT],
+ _id: product.id,
+ title: product.title,
+ specs: [
+ {
+ _key: product.id,
+ _type: "spec",
+ title: product.title,
+ lang: "en",
+ },
+ ],
+ }
+ }
+
+ private transformProductForUpdate = (product: ProductDTO) => {
+ return {
+ set: {
+ title: product.title,
+ },
+ }
+ }
+}
+```
+
+The `transformProductForCreate` method accepts a product and returns an object that you'll later pass to Sanity to create the product document. Similarly, the `transformProductForUpdate` method accepts a product and returns an object that you'll later pass to Sanity to update the product document.
+
+The Sanity document's schema type will be defined in a later chapter. If you add other fields to it, make sure to edit these methods.
+
+Finally, initialize the new properties you added in the `SanityModuleService`'s constructor:
+
+```ts title="src/modules/sanity/service.ts"
+class SanityModuleService {
+ // ...
+ constructor({
+ logger,
+ }: InjectedDependencies, options: ModuleOptions) {
+ // ...
+ this.typeMap = Object.assign(
+ {},
+ {
+ [SyncDocumentTypes.PRODUCT]: "product",
+ },
+ options.type_map || {}
+ )
+
+ this.createTransformationMap = {
+ [SyncDocumentTypes.PRODUCT]: this.transformProductForCreate,
+ }
+
+ this.updateTransformationMap = {
+ [SyncDocumentTypes.PRODUCT]: this.transformProductForUpdate,
+ }
+ }
+ // ...
+}
+```
+
+You initialize the `typeMap` property to map the `product` type in Medusa to the `product` schema type in Sanity. You also initialize the `createTransformationMap` and `updateTransformationMap` to map the methods to transform a product for creation or update.
+
+You can modify these properties to add support for other schema types, such as product categories or collections.
+
+#### Methods to Manage Documents
+
+In this section, you'll add the methods that accept data from Medusa and create or update them as documents in Sanity.
+
+Add the following methods to the `SanityModuleService` class:
+
+```ts title="src/modules/sanity/service.ts" highlights={syncMethodsHighlights}
+// other imports...
+import {
+ // ...
+ FirstDocumentMutationOptions,
+} from "@sanity/client"
+
+class SanityModuleService {
+ // ...
+ async upsertSyncDocument(
+ type: T,
+ data: SyncDocumentInputs
+ ) {
+ const existing = await this.client.getDocument(data.id)
+ if (existing) {
+ return await this.updateSyncDocument(type, data)
+ }
+
+ return await this.createSyncDocument(type, data)
+ }
+
+ async createSyncDocument(
+ type: T,
+ data: SyncDocumentInputs,
+ options?: FirstDocumentMutationOptions
+ ) {
+ const doc = this.createTransformationMap[type](data)
+ return await this.client.create(doc, options)
+ }
+
+ async updateSyncDocument(
+ type: T,
+ data: SyncDocumentInputs
+ ) {
+ const operations = this.updateTransformationMap[type](data)
+ return await this.client.patch(data.id, operations).commit()
+ }
+}
+```
+
+You add three methods:
+
+- `upsertSyncDocument`: Creates or updates a document in Sanity for a data type in Medusa.
+- `createSyncDocument`: Creates a document in Sanity for a data type in Medusa. It uses the `createTransformationMap` property to use the transform method of the specified Medusa data type (for example, a product's data).
+- `updateSyncDocument`: Updates a document in Sanity for a data type in Medusa. It uses the `updateTransformationMap` property to use the transform method of the specified Medusa data type (for example, a product's data).
+
+You also need methods to manage the Sanity documents without transformations. So, add the following methods to `SanityModuleService`:
+
+```ts title="src/modules/sanity/service.ts" highlights={methodsHighlights}
+class SanityModuleService {
+ // ...
+ async retrieve(id: string) {
+ return this.client.getDocument(id)
+ }
+
+ async delete(id: string) {
+ return this.client.delete(id)
+ }
+
+ async update(id: string, data: any) {
+ return await this.client.patch(id, {
+ set: data,
+ }).commit()
+ }
+
+ async list(
+ filter: {
+ id: string | string[]
+ }
+ ) {
+ const data = await this.client.getDocuments(
+ Array.isArray(filter.id) ? filter.id : [filter.id]
+ )
+
+ return data.map((doc) => ({
+ id: doc?._id,
+ ...doc,
+ }))
+ }
+}
+```
+
+You add other three methods:
+
+- `retrieve` to retrieve a document by its ID.
+- `delete` to delete a document by its ID.
+- `update` to update a document by its ID with new data.
+- `list` to list documents, with ability to filter them by their IDs. Since a Sanity document's ID is a product's ID, you can pass product IDs as a filter to retrieve their documents.
+
+### Export Module Definition
+
+The `SanityModuleService` class now has the methods necessary to connect to and perform actions in Sanity.
+
+Next, you must export the Module definition, which lets Medusa know what the Module's name is and what is its service.
+
+Create the file `src/modules/sanity/index.ts` with the following content:
+
+```ts title="src/modules/sanity/index.ts"
+import { Module } from "@medusajs/framework/utils"
+import SanityModuleService from "./service"
+
+export const SANITY_MODULE = "sanity"
+
+export default Module(SANITY_MODULE, {
+ service: SanityModuleService,
+})
+```
+
+In the file, you export the `SANITY_MODULE` which is the Module's name. You'll use it later when you resolve the module from the Medusa container.
+
+You also export the module definition using `Module` from the Modueles SDK, which accepts as a first parameter the module's name, and as a second parameter an object having a `service` property, indicating the module's service.
+
+### Add Module to Configurations
+
+Finally, to register a module in Medusa, you must add it to Medusa's configurations.
+
+Medusa's configurations are set in the `medusa-config.ts` file, which is at the root directory of your Medusa application. The configuration object accepts a `modules` array, whose value is an array of modules to add to the application.
+
+Add the `modules` property to the exported configurations in `medusa-config.ts`:
+
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ // ...
+ modules: [
+ {
+ resolve: "./src/modules/sanity",
+ options: {
+ api_token: process.env.SANITY_API_TOKEN,
+ project_id: process.env.SANITY_PROJECT_ID,
+ api_version: new Date().toISOString().split("T")[0],
+ dataset: "production",
+ studio_url: process.env.SANITY_STUDIO_URL ||
+ "http://localhost:3000/studio",
+ type_map: {
+ product: "product",
+ },
+ },
+ },
+ ],
+})
+```
+
+In the `modules` array, you pass a module object having the following properties:
+
+- `resolve`: The path to the module to register in the application. It can also be the name of an NPM package.
+- `options`: An object of options to pass to the module. These are the options you expect and use in the module's service.
+
+Some of the module's options, such as the Sanity API key, are set in environment variables. So, add the following environment variables to `.env`:
+
+```shell
+SANITY_API_TOKEN=
+SANITY_PROJECT_ID=
+SANITY_STUDIO_URL=http://localhost:8000/studio
+```
+
+Where:
+
+- `SANITY_API_TOKEN`: The API key token to connect to Sanity, which you can retrieve from the Sanity project's dashboard:
+ - Go to the API tab.
+
+
+
+- Scroll down to Tokens and click on the "Add API Token" button.
+
+
+
+- Enter a name for the API token, choose "Editor" for the permissions, then click Save.
+
+
+
+- `SANITY_PROJECT_ID`: The ID of the project, which you can find at the top section of your Sanity project's dashboard.
+
+
+
+- `SANITY_STUDIO_URL`: The URL to access the studio. You'll set the studio up in a later section, but for now set it to `http://localhost:8000/studio`.
+
+### Test the Module
+
+To test that the module is working, you'll start the Medusa application and see if the "Connected to Sanity" message is logged in the console.
+
+To start the Medusa application, run the following command in the application's directory:
+
+```bash npm2yarn
+npm run dev
+```
+
+If you see the following message among the logs:
+
+```bash
+info: Connected to Sanity
+```
+
+That means your Sanity credentials were correct, and Medusa was able to connect to Sanity.
+
+In the next steps, you'll create a link between the Product and Sanity modules to retrieve data between them easily, and build a flow around the Sanity Module to sync data.
+
+***
+
+## Step 5: Link Product and Sanity Modules
+
+Since a product has a document in Sanity, you want to build an association between the [Product](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md) and Sanity modules so that when you retrieve a product, you also retrieve its associated Sanity document.
+
+However, modules are [isolated](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md) to ensure they're re-usable and don't have side effects when integrated into the Medusa application. So, to build associations between modules, you define [module links](https://docs.medusajs.com/docs/learn/fundamentals/module-links/index.html.md).
+
+A Module Link associates two modules' data models while maintaining module isolation. A data model can be a table in the database or a virtual model from an external systems.
+
+In this section, you'll define a link between the Product and Sanity modules.
+
+Links are defined in a TypeScript or JavaScript file under the `src/links` directory. So, create the file `src/links/product-sanity.ts` with the following content:
+
+```ts title="src/links/product-sanity.ts"
+import { defineLink } from "@medusajs/framework/utils"
+import ProductModule from "@medusajs/medusa/product"
+import { SANITY_MODULE } from "../modules/sanity"
+
+defineLink(
+ {
+ ...ProductModule.linkable.product.id,
+ field: "id",
+ },
+ {
+ linkable: {
+ serviceName: SANITY_MODULE,
+ alias: "sanity_product",
+ primaryKey: "id",
+ },
+ },
+ {
+ readOnly: true,
+ }
+)
+```
+
+You define a link using `defineLink` from the Modules SDK. It accepts three parameters:
+
+1. The first data model part of the link. In this case, it's the Product Module's `product` data model. A module has a special `linkable` property that contain link configurations for its data models.
+2. The second data model part of the link. Since the Sanity Module doesn't have a Medusa data model, you specify the configurations in a `linkable` object that has the following properties:
+ - `serviceName`: The registration name in the Medusa container of the service managing the data model, which in this case is the Sanity Module's name (since the module's service is registered under that name).
+ - `alias`: The name to refer to the model part of this link, such as when retrieving the Sanity document of a product. You'll use this in a later section.
+ - `primaryKey`: The name of the data model's primary key field.
+3. An object of configurations for the module link. By default, Medusa creates a table in the database to represent the link you define. Since the module link isn't created between two Medusa data models, you enable the `readOnly` configuration, which will tell Medusa not to create a table in the database for this link.
+
+In the next steps, you'll see how this link allows you to retrieve documents when retrieving products.
+
+***
+
+## Step 6: Sync Data to Sanity
+
+After integrating Sanity with a custom module, you now want to sync product data from Medusa to Sanity, automatically and manually. To implement the sync logic, you need a workflow.
+
+A workflow is a series of queries and actions, called steps, that complete a task. You construct a workflow like you construct a function, but it's a special function that allows you to track its executions' progress, define roll-back logic, and configure other advanced features. You'll see how all of this works in the upcoming sections.
+
+Within a workflow's steps, you resolve modules to use their service's functionalities as part of a bigger flow. Then, you can execute the workflow from other customizations, such as in response to an event or in an API route.
+
+Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md)
+
+In this section, you'll create a workflow that syncs products from Medusa to Sanity. Later, you'll execute this workflow when a product is created or updated, or when an admin user triggers the syncing manually.
+
+### Create Step
+
+The syncing workflow will have a single step that syncs products provided as an input to Sanity.
+
+So, to implement that step, create the file `src/workflows/sanity-sync-products/steps/sync.ts` with the following content:
+
+```ts title="src/workflows/sanity-sync-products/steps/sync.ts" highlights={syncStepHighlights}
+import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
+import { ProductDTO } from "@medusajs/framework/types"
+import {
+ ContainerRegistrationKeys,
+ promiseAll,
+} from "@medusajs/framework/utils"
+import SanityModuleService from "../../../modules/sanity/service"
+import { SANITY_MODULE } from "../../../modules/sanity"
+
+export type SyncStepInput = {
+ product_ids?: string[];
+}
+
+export const syncStep = createStep(
+ { name: "sync-step", async: true },
+ async (input: SyncStepInput, { container }) => {
+ const sanityModule: SanityModuleService = container.resolve(SANITY_MODULE)
+ const query = container.resolve(ContainerRegistrationKeys.QUERY)
+
+ const total = 0
+ const upsertMap: {
+ before: any
+ after: any
+ }[] = []
+
+ const batchSize = 200
+ const hasMore = true
+ const offset = 0
+ const filters = input.product_ids ? {
+ id: input.product_ids,
+ } : {}
+
+ while (hasMore) {
+ const {
+ data: products,
+ metadata: { count } = {},
+ } = await query.graph({
+ entity: "product",
+ fields: [
+ "id",
+ "title",
+ // @ts-ignore
+ "sanity_product.*",
+ ],
+ filters,
+ pagination: {
+ skip: offset,
+ take: batchSize,
+ order: {
+ id: "ASC",
+ },
+ },
+ })
+
+ // TODO sync products
+ }
+ }
+)
+```
+
+You define the `syncStep` using the `createStep` function, which accepts two parameters:
+
+- An object of step configurations. The object must have the `name` property, which is this step's unique name. Enabling the `async` property means that the workflow should run asynchronously in the background. This is useful when the workflow is triggered manually through an HTTP request, meaning the response will be returned to the client even if the workflow hasn't finished executing.
+- The step's function definition as a second parameter.
+
+The step function accepts the step's input as a first parameter, and an object of options as a second. The object of options has a `container` property, which is an instance of the Medusa container that you can use to resolve resources.
+
+In the step, you resolve from the Medusa container Sanity Module's service and [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), which is a tool that allows you to retrieve data across modules and links.
+
+You use Query's `graph` method to retrieve products, filtering them by their IDs and applying pagination configurations. The `graph` method accepts a `fields` property in its object parameter, which indicates the product data model's fields and relations to retrieve.
+
+Notice that you pass `sanity_product.*` in the `fields` array. Medusa will retrieve the Sanity document of each product using Sanity Module's `list` method and attach it to the returned product. So, you don't have to retrieve the products and documents separately. Each product object in the returned array will look similar to this:
+
+```json title="Example Product Object"
+{
+ "id": "prod_123",
+ "title": "Shirt",
+ "sanity_product": {
+ "id": "prod_123",
+ "_type": "product",
+ // other Sanity fields...
+ }
+}
+```
+
+Next, you want to sync the retrieved products. So, replace the `TODO` in the `while` loop with the following:
+
+```ts title="src/workflows/sanity-sync-products/steps/sync.ts"
+while (hasMore) {
+ // ...
+ try {
+ await promiseAll(
+ products.map(async (prod) => {
+ const after = await sanityModule.upsertSyncDocument(
+ "product",
+ prod as ProductDTO
+ )
+
+ upsertMap.push({
+ // @ts-ignore
+ before: prod.sanity_product,
+ after,
+ })
+
+ return after
+ })
+ )
+ } catch (e) {
+ return StepResponse.permanentFailure(
+ `An error occurred while syncing documents: ${e}`,
+ upsertMap
+ )
+ }
+
+ offset += batchSize
+ hasMore = offset < count
+ total += products.length
+}
+```
+
+In the `while` loop, you loop over the array of products to sync them to Sanity. You use the `promiseAll` Medusa utility that loops over an array of promises and ensures that all transactions within these promises are rolled back in case an error occurs.
+
+For each product, you upsert it into Sanity, then push its document before and after the update to the `upsertMap`. You'll learn more about its use later.
+
+You also wrap the `promiseAll` function within a try-catch block. In the catch block, you invoke and return `StepResponse.permanentFailure` which indicates that the step has failed but still invokes the rollback mechanism that you'll implement in a bit. The first parameter of `permanentFailure` is the error message, and the second is the data to use in the rollback mechanism.
+
+Finally, after the `while` loop and at the end of the step, add the following return statement:
+
+```ts title="src/workflows/sanity-sync-products/steps/sync.ts"
+return new StepResponse({ total }, upsertMap)
+```
+
+If no errors occur, the step returns an instance of `StepResponse`, which must be returned by any step. It accepts as a first parameter the data to return to the workflow that executed this step.
+
+#### Add Compensation Function
+
+`StepResponse` accepts a second parameter, which is passed to the compensation function. A compensation function defines the rollback logic of a step, and it's only executed if an error occurs in the workflow. This eliminates data inconsistency if an error occurs and the workflow can't finish execution successfully.
+
+Learn more about compensation functions in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/compensation-function/index.html.md).
+
+The `syncStep` creates or updates products in Sanity. So, the compensation function must delete created documents or revert the update of a document to its previous data. The compensation function is only executed if an error occurs.
+
+To define the compensation function, pass a third-parameter to the `createStep` function:
+
+```ts title="src/workflows/sanity-sync-products/steps/sync.ts"
+export const syncStep = createStep(
+ { name: "sync-step", async: true },
+ async (input: SyncStepInput, { container }) => {
+ // ...
+ },
+ async (upsertMap, { container }) => {
+ if (!upsertMap) {
+ return
+ }
+
+ const sanityModule: SanityModuleService = container.resolve(SANITY_MODULE)
+
+ await promiseAll(
+ upsertMap.map(({ before, after }) => {
+ if (!before) {
+ // delete the document
+ return sanityModule.delete(after._id)
+ }
+
+ const { _id: id, ...oldData } = before
+
+ return sanityModule.update(
+ id,
+ oldData
+ )
+ })
+ )
+ }
+)
+```
+
+The compensation function accepts the data passed in the step's `StepResponse` second parameter (in this case, `upsertMap`), and an object of options similar to that of the step.
+
+In the compensation function, you resolve the Sanity Module's service, then loop over the `upsertMap` to delete created documents, or revert existing ones.
+
+### Create Workflow
+
+You'll now create the workflow that uses the `syncStep`. This is the workflow that you'll later execute to sync data automatically or manually.
+
+Workflows are created in a file under the `src/workflows` directory. So, create the file `src/workflows/sanity-sync-products/index.ts` with the following content:
+
+```ts title="src/workflows/sanity-sync-products/index.ts"
+import {
+ createWorkflow,
+ WorkflowResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { syncStep } from "./steps/sync"
+
+export type SanitySyncProductsWorkflowInput = {
+ product_ids?: string[];
+};
+
+export const sanitySyncProductsWorkflow = createWorkflow(
+ { name: "sanity-sync-products", retentionTime: 10000 },
+ function (input: SanitySyncProductsWorkflowInput) {
+ const result = syncStep(input)
+
+ return new WorkflowResponse(result)
+ }
+)
+```
+
+You create a workflow using the `createWorkflow` from the Workflows SDK. It accepts an object of options as a first parameter, where the `name` property is required and indicates the workflow's unique name.
+
+The `retentionTime` property indicates how long should the workflow's progress be saved in the database. This is useful if you later want to track whether the workflow is successfully executing.
+
+`createWorkflow` accepts as a second parameter a constructor function, which is the workflow's implementation. In the function, you execute the `syncStep` to sync the specified products in the input, then return its result. Workflows must return an instance of `WorkflowResponse`.
+
+A workflow's constructor function has some constraints in implementation. Learn more about them in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/constructor-constraints/index.html.md).
+
+You'll execute and test this workflow in the next steps.
+
+***
+
+## Step 7: Handle Product Changes in Medusa
+
+You've defined the workflow to sync the products. Now, you want to execute it when a product is created or updated.
+
+Medusa emits events when certain actions occur, such as when a product is created. Then, you can listen to those events in a subscriber.
+
+A subscriber is an asynchronous function that listens to one or more events. Then, when those events are emitted, the subscriber is executed in the background of your application.
+
+Subscribers are useful when you want to perform an action that isn't an integral part of a flow, but as a reaction to a performed action. In this case, syncing the products to Sanity isn't integral to creating a product, so you do it in a subscriber after the product is created.
+
+Learn more about events and subscribers in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/events-and-subscribers/index.html.md). You can also find the list of emitted events in [this reference](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/events-reference/index.html.md).
+
+So, to run the workflow you defined in the previous event when a product is created or updated, you'll create a subscriber that listens to the `product.created` and `product.updated` events.
+
+Subscribers are created under the `src/subscribers` directory. So, create the file `src/subscribers/sanity-product-sync.ts` with the following content:
+
+```ts title="src/subscribers/sanity-product-sync.ts" highlights={subscriberHighlights}
+import type {
+ SubscriberArgs,
+ SubscriberConfig,
+} from "@medusajs/medusa"
+import {
+ sanitySyncProductsWorkflow,
+} from "../workflows/sanity-sync-products"
+
+export default async function upsertSanityProduct({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ await sanitySyncProductsWorkflow(container).run({
+ input: {
+ product_ids: [data.id],
+ },
+ })
+}
+
+export const config: SubscriberConfig = {
+ event: ["product.created", "product.updated"],
+}
+```
+
+The subscriber function `upsertSanityProduct` accepts an object as a parameter that has the following properties:
+
+- `event`: An object of the event's details. Its `data` property holds the data payload emitted with the event, which in this case is the ID of the product created or updated.
+- `container`: An instance of the Medusa container to resolve resources.
+
+In the subscriber, you execute the `sanitySyncProductsWorkflow` by invoking it, passing it the container, then invoking its `run` method. You pass the workflow's input in the `input` property of the `run`'s object parameter.
+
+The subscriber file must also export a configuration object. It has an `event` property, which is the names of the events that the subscriber is listening to.
+
+### Test it Out
+
+To test it out, run the Medusa application, then open the Medusa Admin in your browser at `http://localhost:9000/app`. Try creating or updating a product. You'll see the following message in the console:
+
+```bash
+info: Processing product.created which has 1 subscribers
+```
+
+This means that the `product.created` event was emitted and your subscriber was executed.
+
+In the next step, you'll setup Sanity with Next.js, and you can then monitor the updates in Sanity's studio.
+
+***
+
+## Step 8: Setup Sanity with Next.js Starter Storefront
+
+In this step, you'll install Sanity in the Next.js Starter and configure it. You'll then have a Sanity studio in your Next.js storefront, where you'll later view the product documents being synced from Medusa, and update their content that you'll display in the storefront on the product details page.
+
+Sanity has a CLI tool that helps you with the setup. First, change to the Next.js Starter's directory (it's outside the Medusa application's directory and its name is `{project-name}-storefront`, where `{project-name}` is the name of the Medusa application's directory).
+
+Then, run the following command:
+
+```bash badgeLabel="Storefront" badgeColor="blue"
+npx sanity@latest init
+```
+
+You'll then be asked a few questions:
+
+- For the project, select the Sanity project you created earlier in this guide.
+- For dataset, use `production` unless you changed it in the Sanity project.
+- Select yes for adding the Sanity configuration files to the Next.js folder.
+- Select yes for TypeScript.
+- Select yes for Sanity studio, and choose the `/studio` route.
+- Select clean project template.
+- Select yes for adding the project ID and dataset to `.env.local`.
+
+Afterwards, the command will install the necessary dependencies for Sanity.
+
+### Error during installation
+
+If you run into an error during the installation of peer dependencies, try running the following command to install them:
+
+```bash
+yarn add next-sanity@9.8.15 @sanity/client@^6.22.4 @sanity/icons@^3.4.0 @sanity/types@^3.62.0 @sanity/ui@^2.8.10 next@^15.0.0 react@^19.0.0 react-dom@^19.0.0 sanity@^3.62.0 styled-components@^6.1
+```
+
+### Update Middleware
+
+The Next.js Starter storefront has a middleware that ensures all requests start with a country code (for example, `/us`).
+
+Since the Sanity studio runs at `/studio`, the middleware should ignore requests to this path.
+
+Open the file `src/middleware.ts` and find the following `if` condition:
+
+```ts title="src/middleware.ts" badgeLabel="Storefront" badgeColor="blue"
+if (
+ urlHasCountryCode &&
+ (!isOnboarding || onboardingCookie) &&
+ (!cartId || cartIdCookie)
+) {
+ return NextResponse.next()
+}
+```
+
+Replace it with the following condition:
+
+```ts title="src/middleware.ts" badgeLabel="Storefront" badgeColor="blue"
+if (
+ request.nextUrl.pathname.startsWith("/studio") ||
+ urlHasCountryCode &&
+ (!isOnboarding || onboardingCookie) &&
+ (!cartId || cartIdCookie)
+) {
+ return NextResponse.next()
+}
+```
+
+If the path starts with `/studio`, the middleware will stop executing and the page will open.
+
+### Set CORS Settings
+
+Every Sanity project has a configured set of CORS origins allowed, with the default being `http://localhost:3333`.
+
+The Next.js Starter runs on the `8000` port, so you must add it to the allowed CORS origins.
+
+In your Sanity project's dashboard:
+
+1. Click on the API tab.
+
+
+
+2. Scroll down to CORS origins and click the "Add CORS origin" button.
+
+
+
+3. Enter `http://localhost:8000` in the Origin field.
+4. Enable the "Allow credentials" checkbox.
+
+
+
+5. Click the Save button.
+
+### Open Sanity Studio
+
+To open the Sanity studio, start the Next.js Starter's development server:
+
+```bash npm2yarn
+npm run dev
+```
+
+Then, open `http://localhost:8000/studio` in your browser. The Sanity studio will open, but right now it's empty.
+
+***
+
+## Step 9: Add Product Schema Type in Sanity
+
+In this step, you'll define the `product` schema type in Sanity. You' can then view the documents of that schema in the studio and update their content.
+
+To create the schema type, create the file `src/sanity/schemaTypes/documents/product.ts` with the following content:
+
+```ts title="src/sanity/schemaTypes/documents/product.ts" badgeLabel="Storefront" badgeColor="blue"
+import { ComposeIcon } from "@sanity/icons"
+import { DocumentDefinition } from "sanity"
+
+const productSchema: DocumentDefinition = {
+ fields: [
+ {
+ name: "title",
+ type: "string",
+ },
+ {
+ group: "content",
+ name: "specs",
+ of: [
+ {
+ fields: [
+ { name: "lang", title: "Language", type: "string" },
+ { name: "title", title: "Title", type: "string" },
+ {
+ name: "content",
+ rows: 3,
+ title: "Content",
+ type: "text",
+ },
+ ],
+ name: "spec",
+ type: "object",
+ },
+ ],
+ type: "array",
+ },
+ {
+ fields: [
+ { name: "title", title: "Title", type: "string" },
+ {
+ name: "products",
+ of: [{ to: [{ type: "product" }], type: "reference" }],
+ title: "Addons",
+ type: "array",
+ validation: (Rule) => Rule.max(3),
+ },
+ ],
+ name: "addons",
+ type: "object",
+ },
+ ],
+ name: "product",
+ preview: {
+ select: {
+ title: "title",
+ },
+ },
+ title: "Product Page",
+ type: "document",
+ groups: [{
+ default: true,
+ // @ts-ignore
+ icon: ComposeIcon,
+ name: "content",
+ title: "Content",
+ }],
+}
+
+export default productSchema
+```
+
+This creates a schema that has the following fields:
+
+- `title`: The title of a document, which is in this case the product's type.
+- `specs`: An array of product specs. Each object in the array has the following fields:
+ - `lang`: This is useful if you want to have localized content.
+ - `title`: The product's title.
+ - `content`: Textual content, such as the product's description.
+- `addons`: An object of products related to this product.
+
+When you sync the products from Medusa, you only sync the title. You manage the `specs` and `addons` fields within Sanity.
+
+Next, replace the content of `src/sanity/schemaTypes/index.ts` with the following:
+
+```ts title="src/sanity/schemaTypes/index.ts" badgeLabel="Storefront" badgeColor="blue"
+import { SchemaPluginOptions } from "sanity"
+import productSchema from "./documents/product"
+
+export const schema: SchemaPluginOptions = {
+ types: [productSchema],
+ templates: (templates) => templates.filter(
+ (template) => template.schemaType !== "product"
+ ),
+}
+```
+
+You add the product schema to the list of exported schemas, but also disable creating a new product. You can only create the products in Medusa.
+
+### Test it Out
+
+To ensure that your schema is defined correctly and working, start the Next.js storefront's server, and open the Sanity studio again at `http://localhost:8000/studio`.
+
+You'll find "Product Page" under Content. If you click on it, you'll find any product you've synced from Medusa.
+
+If you haven't synced any products yet or you want to see the live update, try now creating or updating a product in Medusa. You'll find it added in the Sanity studio.
+
+If you click on any product, you can edit its existing field under "Specs" or add new ones. In the next section, you'll learn how to show the content in the "Specs" field on the storefront's product details page.
+
+***
+
+## Step 10: Show Sanity Content in Next.js Starter Storefront
+
+Now that you're managing a product's content in Sanity, you want to show that content on the storefront. In this step, you'll customize the Next.js Starter storefront to show a product's content as defined in Sanity.
+
+A product's details are retrieved in the file `src/app/[countryCode]/(main)/products/[handle]/page.tsx`. So, replace the `ProductPage` function with the following:
+
+```tsx title="src/app/[countryCode]/(main)/products/[handle]/page.tsx" badgeLabel="Storefront" badgeColor="blue" highlights={sanityContentHighlights}
+// other imports...
+import { client } from "../../../../../sanity/lib/client"
+
+// ...
+
+export default async function ProductPage(props: Props) {
+ const params = await props.params
+ const region = await getRegion(params.countryCode)
+
+ if (!region) {
+ notFound()
+ }
+
+ const pricedProduct = await listProducts({
+ countryCode: params.countryCode,
+ queryParams: { handle: params.handle },
+ }).then(({ response }) => response.products[0])
+
+ if (!pricedProduct) {
+ notFound()
+ }
+
+ // alternatively, you can filter the content by the language
+ const sanity = (await client.getDocument(pricedProduct.id))?.specs[0]
+
+ return (
+
+ )
+}
+```
+
+You import the Sanity client defined in `src/sanity/lib/client.ts` (this was generated by Sanity's CLI). Then, in the page's function, you retrieve the product's document by ID and pass its first step to the `ProductTemplate` component.
+
+This is a simplified approach, but you can also have languages in your storefront and filter the spec based on the current language.
+
+Next, you need to customize the `ProductTemplate` to accept the `sanity` prop. In the file `src/modules/products/templates/index.tsx` add the following to `ProductTemplateProps`:
+
+```tsx title="src/modules/products/templates/index.tsx" badgeLabel="Storefront" badgeColor="blue"
+type ProductTemplateProps = {
+ // ...
+ sanity?: {
+ content: string
+ }
+}
+```
+
+Then, add the `sanity` property to the expanded props of the component:
+
+```tsx title="src/modules/products/templates/index.tsx" badgeLabel="Storefront" badgeColor="blue"
+const ProductTemplate: React.FC = ({
+ // ...
+ sanity,
+}) => {
+ // ...
+}
+```
+
+Finally, pass the `sanity` prop to the `ProductInfo` component in the return statement:
+
+```tsx title="src/modules/products/templates/index.tsx" badgeLabel="Storefront" badgeColor="blue"
+
+```
+
+Next, you need to update the `ProductInfo` component to accept and use the `sanity` prop.
+
+In `src/modules/products/templates/product-info/index.tsx`, update the `ProductInfoProps` to accept the `sanity` prop:
+
+```tsx title="src/modules/products/templates/product-info/index.tsx" badgeLabel="Storefront" badgeColor="blue"
+type ProductInfoProps = {
+ // ...
+ sanity?: {
+ content: string
+ }
+}
+```
+
+Then, add the `sanity` property to the expanded props of the component:
+
+```tsx title="src/modules/products/templates/index.tsx" badgeLabel="Storefront" badgeColor="blue"
+const ProductInfo = ({
+ // ...
+ sanity,
+}: ProductInfoProps) => {
+ // ...
+}
+```
+
+Next, find the following line in the return statement:
+
+```tsx title="src/modules/products/templates/index.tsx" badgeLabel="Storefront" badgeColor="blue"
+{product.description}
+```
+
+And replace it with the following:
+
+```tsx title="src/modules/products/templates/index.tsx" badgeLabel="Storefront" badgeColor="blue"
+{sanity?.content || product.description}
+```
+
+Instead of showing the product's description on the product details page, this will show the content defined in Sanity if available.
+
+### Test it Out
+
+To test this out, first, run both the Next.js Starter storefront and the Medusa application, and open the Sanity studio. Try editing the content of the first spec of a product.
+
+Then, open the Next.js Starter storefront at `http://localhost:8000` and go to "Store" from the menu, then select the product you edited in Sanity.
+
+In the product's page, you'll find under the product's name the content you put in Sanity.
+
+You can now manage the product's content in Sanity, add more fields, and customize how you show them in the storefront. The Medusa application will also automatically create documents in Sanity for new products you add or update, ensuring your products are always synced across systems.
+
+***
+
+## Step 11: Customize Admin to Manually Sync Data
+
+There are cases where you need to trigger the syncing of products manually, such as when an error occurs or you have products from before creating this integration.
+
+The Medusa Admin dashboard is customizable, allowing you to either inject components, called [widgets](https://docs.medusajs.com/docs/learn/fundamentals/admin/widgets/index.html.md), into existing pages, or adding new pages, called [UI routes](https://docs.medusajs.com/docs/learn/fundamentals/admin/ui-routes/index.html.md). In these customizations, you can send requests to the Medusa application to perform custom operations.
+
+In this step, you'll add a widget to the product's details page. In that page, you'll show whether a product is synced with Sanity, and allow the admin user to trigger syncing it manually.
+
+
+
+Before you do that, however, you need two new API routes in your Medusa application: one to retrieve a document from Sanity, and one to trigger syncing the product data.
+
+An API route is a REST API endpoint that exposes commerce features to the admin dashboard or other frontend clients. Learn more about API routes in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md).
+
+### Get Sanity Document API Route
+
+In this section, you'll create the API route to retrieve a sanity document, and the URL to it in the Sanity studio.
+
+To retrieve the URL to the Sanity studio, add the following method in the Sanity Module's service in `src/modules/sanity/service.ts`:
+
+```ts title="src/modules/sanity/service.ts"
+class SanityModuleService {
+ // ...
+ async getStudioLink(
+ type: string,
+ id: string,
+ config: { explicit_type?: boolean } = {}
+ ) {
+ const resolvedType = config.explicit_type ? type : this.typeMap[type]
+ if (!this.studioUrl) {
+ throw new Error("No studio URL provided")
+ }
+ return `${this.studioUrl}/structure/${resolvedType};${id}`
+ }
+}
+```
+
+The method uses the `studioUrl` property, which you set in the `constructor` using the `studio_url` module option, to get the studio link.
+
+Then, to create the API route, create the file `src/api/admin/sanity/documents/[id]/route.ts` with the following content:
+
+```ts title="src/api/admin/sanity/documents/[id]/route.ts"
+import {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import SanityModuleService from "src/modules/sanity/service"
+import { SANITY_MODULE } from "../../../../../modules/sanity"
+
+export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
+ const { id } = req.params
+
+ const sanityModule: SanityModuleService = req.scope.resolve(
+ SANITY_MODULE
+ )
+ const sanityDocument = await sanityModule.retrieve(id)
+
+ const url = sanityDocument ?
+ await sanityModule.getStudioLink(
+ sanityDocument._type,
+ sanityDocument._id,
+ { explicit_type: true }
+ )
+ : ""
+
+ res.json({ sanity_document: sanityDocument, studio_url: url })
+}
+```
+
+This defines a `GET` API route at `/admin/sanity/documents/:id`, where `:id` is a dynamic path parameter indicating the ID of a document to retrieve.
+
+In the `GET` route handler, you resolve the Sanity Module's service and use it to first retrieve the product's document, then the studio link of that document.
+
+You return in the JSON response an object having the `sanity_document` and `studio_url` properties.
+
+You'll test out this route in a later section.
+
+Since the API route is added under the `/admin` prefix, only authenticated admin users can access it. Learn more about protected routes in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/protected-routes/index.html.md).
+
+### Trigger Sanity Sync API Route
+
+In this section, you'll add the API route that manually triggers syncing a product to Sanity.
+
+Since you already have the workflow to sync products, you only need to create an API route that executes it.
+
+Create the file `src/api/admin/sanity/documents/[id]/sync/route.ts` with the following content:
+
+```ts title="src/api/admin/sanity/documents/[id]/sync/route.ts"
+import {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import {
+ sanitySyncProductsWorkflow,
+} from "../../../../../../workflows/sanity-sync-products"
+
+export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
+ const { transaction } = await sanitySyncProductsWorkflow(req.scope)
+ .run({
+ input: { product_ids: [req.params.id] },
+ })
+
+ res.json({ transaction_id: transaction.transactionId })
+}
+```
+
+You add a `POST` API route at `/admin/sanity/documents/:id/sync`, where `:id` is a dynamic path parameter that indicates the ID of a product to sync to Sanity.
+
+In the `POST` API route handler, you execute the `sanitySyncProductsWorkflow`, passing it the ID of the product from the path parameter as an input.
+
+In the next section, you'll customize the admin dashboard and send requests to the API route from there.
+
+### Sanity Product Widget
+
+In this section, you'll add a widget in the product details page. The widget will show the Sanity document of the product and triggers syncing it to Sanity using the API routes you created.
+
+To send requests from admin customizations to the Medusa server, you need to use Medusa's [JS SDK](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/js-sdk/index.html.md). You'll also use [Tanstack Query](https://tanstack.com/query/latest) to benefit from features like data caching and invalidation.
+
+Do not install Tanstack Query as that will cause unexpected errors in your development. If you prefer installing it for better auto-completion in your code editor, make sure to install `v5.64.2` as a development dependency.
+
+To configure the JS SDK, create the file `src/admin/lib/sdk.ts` with the following content:
+
+```ts title="src/admin/lib/sdk.ts"
+import Medusa from "@medusajs/js-sdk"
+
+export const sdk = new Medusa({
+ baseUrl: import.meta.env.VITE_BACKEND_URL || "/",
+ debug: import.meta.env.DEV,
+ auth: {
+ type: "session",
+ },
+})
+```
+
+You initialize the JS SDK and export it. You can learn more about configuring the JS SDK in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/js-sdk/index.html.md).
+
+Next, you'll create hooks using Tanstack Query to send requests to the API routes you created earlier.
+
+Create the file `src/admin/hooks/sanity.tsx` with the following content:
+
+```ts title="src/admin/hooks/sanity.tsx"
+import {
+ useMutation,
+ UseMutationOptions,
+ useQueryClient,
+} from "@tanstack/react-query"
+import { sdk } from "../lib/sdk"
+
+export const useTriggerSanityProductSync = (
+ id: string,
+ options?: UseMutationOptions
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: () =>
+ sdk.client.fetch(`/admin/sanity/documents/${id}/sync`, {
+ method: "post",
+ }),
+ onSuccess: (data: any, variables: any, context: any) => {
+ queryClient.invalidateQueries({
+ queryKey: [`sanity_document`, `sanity_document_${id}`],
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+```
+
+You define the `useTriggerSanityProductSync` hook which creates a Tanstack Query mutation that, when executed, sends a request to the API route that triggers syncing the product to Sanity.
+
+Add in the same file another hook:
+
+```ts title="src/admin/hooks/sanity.tsx"
+// other imports...
+import {
+ // ...
+ QueryKey,
+ useQuery,
+ UseQueryOptions,
+} from "@tanstack/react-query"
+import { FetchError } from "@medusajs/js-sdk"
+
+// ...
+
+export const useSanityDocument = (
+ id: string,
+ query?: Record,
+ options?: Omit<
+ UseQueryOptions<
+ Record,
+ FetchError,
+ { sanity_document: Record; studio_url: string },
+ QueryKey
+ >,
+ "queryKey" | "queryFn"
+ >
+) => {
+ const fetchSanityProductStatus = async (query?: Record) => {
+ return await sdk.client.fetch>(
+ `/admin/sanity/documents/${id}`,
+ {
+ query,
+ }
+ )
+ }
+
+ const { data, ...rest } = useQuery({
+ queryFn: async () => fetchSanityProductStatus(query),
+ queryKey: [`sanity_document_${id}`],
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
+```
+
+You define the hook `useSanityDocument` which retrieves the Sanity document of a product using Tankstack Query.
+
+You can now create the widget injected in a product's details page. Widgets are react components created in a file under the `src/admin/widgets` directory.
+
+So, create the file `src/admin/widgets/sanity-product.tsx` with the following content:
+
+```tsx title="src/admin/widgets/sanity-product.tsx"
+import { defineWidgetConfig } from "@medusajs/admin-sdk"
+import { AdminProduct, DetailWidgetProps } from "@medusajs/types"
+import { ArrowUpRightOnBox } from "@medusajs/icons"
+import { Button, CodeBlock, Container, StatusBadge, toast } from "@medusajs/ui"
+import { useState } from "react"
+import {
+ useSanityDocument,
+ useTriggerSanityProductSync,
+} from "../hooks/sanity"
+
+const ProductWidget = ({ data }: DetailWidgetProps) => {
+ const { mutateAsync, isPending } = useTriggerSanityProductSync(data.id)
+ const { sanity_document, studio_url, isLoading } = useSanityDocument(data.id)
+ const [showCodeBlock, setShowCodeBlock] = useState(false)
+
+ const handleSync = async () => {
+ try {
+ await mutateAsync(undefined)
+ toast.success(`Sync triggered.`)
+ } catch (err) {
+ toast.error(`Couldn't trigger sync: ${
+ (err as Record).message
+ }`)
+ }
+ }
+
+ return (
+
+
+
+ )
+}
+
+// The widget's configurations
+export const config = defineWidgetConfig({
+ zone: "product.details.after",
+})
+
+export default ProductWidget
+```
+
+The file exports a `ProductWidget` component and a `config` object created with `defineWidgetConfig` from the Admin Extension SDK. In the `config` object, you specify the zone to inject the widget into in the `zone` property.
+
+Find all widget injection zones in [this reference](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-widget-injection-zones/index.html.md).
+
+In the widget, you use the `useSanityDocument` to retrieve the product's document from Sanity by sending a request to the API route you created earlier. You show that document's details and a button to trigger syncing the data.
+
+When the "Sync" button is clicked, you use the `useTriggerSanityProductSync` hook which sends a request to the API route you created earlier and executes the workflow that syncs the product to Sanity. The workflow will execute in the background, since you configured its step to be async.
+
+To render a widget that matches the rest of the admin dashboard's design, you use components from the [Medusa UI package](https://docs.medusajs.com/ui/index.html.md), such as the `CodeBlock` or `Container` components.
+
+Learn more about widgets in [this documentation](https://docs.medusajs.com/learn/fundamentals/admin/widgets/index.html.md).
+
+### Test it Out
+
+To test these customizations out, start the Medusa application and open the admin dashboard. Then, choose a product and scroll down to the end of the page.
+
+You'll find a new "Sanity Status" section showing you whether the product is synced to Sanity and its document's details. You can also click the Sync button, which will sync the product to Sanity.
+
+***
+
+## Step 12: Add Track Syncs Page to Medusa Admin
+
+Earlier in this guide when introducing workflows, you learned that you can track the execution of a workflow. As a last step of this guide, you'll add a new page in the admin dashboard that shows the executions of the `sanitySyncProductsWorkflow` and their status. You'll also add the ability to sync all products to Sanity from that page.
+
+
+
+### Retrieve Sync Executions API Route
+
+Medusa has a [workflow engine](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/architectural-modules/workflow-engine/index.html.md) that manages workflow executions, roll-backs, and other functionalities under the hood.
+
+The workflow engine is an [architectural module](https://docs.medusajs.com/docs/learn/fundamentals/modules/architectural-modules/index.html.md), which can be replaced with a [Redis Workflow Engine](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/architectural-modules/workflow-engine/redis/index.html.md), or a custom one of your choice, allowing you to take ownership of your application's tooling.
+
+In your customizations, you can resolve the workflow engine from the container and manage executions of a workflow, such as retrieve them and check their progress.
+
+In this section, you'll create an API route to retrieve the stored executions of the `sanitySyncProductsWorkflow` workflow, so that you can display them later on the dashboard.
+
+When you defined the `sanitySyncProductsWorkflow`, you set its `retentionTime` option so that you can store the workflow execution's details temporarily. If a workflow doesn't have this option set, its execution won't be stored for tracking.
+
+Create the file `src/api/admin/sanity/syncs/route.ts` with the following content:
+
+```ts title="src/api/admin/sanity/syncs/route.ts"
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework"
+import { Modules } from "@medusajs/framework/utils"
+import {
+ sanitySyncProductsWorkflow,
+} from "../../../../workflows/sanity-sync-products"
+
+export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
+ const workflowEngine = req.scope.resolve(
+ Modules.WORKFLOW_ENGINE
+ )
+
+ const [executions, count] = await workflowEngine
+ .listAndCountWorkflowExecutions(
+ {
+ workflow_id: sanitySyncProductsWorkflow.getName(),
+ },
+ { order: { created_at: "DESC" } }
+ )
+
+ res.json({ workflow_executions: executions, count })
+}
+```
+
+You add a `GET` API route at `/admin/sanity/syncs`. In the API route handler, you resolve the Workflow Engine Module's service from the Medusa container. You use the `listAndCountWorkflowExecutions` method to retrieve the executions of the `sanitySyncProductsWorkflow` workflow, filtering by its name.
+
+You return the executions in the JSON response of the route.
+
+### Trigger Sync API Route
+
+In this section, you'll add another API route that triggers syncing all products to Sanity.
+
+In the same file `src/api/admin/sanity/syncs/route.ts`, add the following:
+
+```ts title="api/admin/sanity/syncs/route.ts"
+export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
+ const { transaction } = await sanitySyncProductsWorkflow(req.scope).run({
+ input: {},
+ })
+
+ res.json({ transaction_id: transaction.transactionId })
+}
+```
+
+This adds a `POST` API route at `/admin/sanity/syncs`. In the route handler, you execute the `sanitySyncProductsWorkflow` without passing it a `product_ids` input. The step in the workflow will retrieve all products, instead of filtering them by ID, and sync them to Sanity.
+
+You return the transaction ID of the workflow, which you can use to track the execution's progress since the workflow will run in the background. This is not implemented in this guide, but Medusa has a [Get Execution API route](https://docs.medusajs.com/apiadmin#workflows-executions_getworkflowsexecutionsworkflow_idtransaction_id/index.html.md) that you can use to get the details of a workflow's execution.
+
+### Add Sanity UI Route
+
+In this section, you'll add a UI route in the admin dashboard, which is a new page, that shows the list of `sanitySyncProductsWorkflow` executions and allows triggering sync of all products in Medusa.
+
+A UI route is React component exported in a file under the `src/admin/routes` directory. Similar to a widget, a UI route can also send requests to the Medusa application to perform actions using your custom API routes.
+
+Before creating the UI route, you'll create hooks using Tanstack Query that send requests to these UI routes. In the file `src/admin/hooks/sanity.tsx`, add the following two new hooks:
+
+```tsx title="src/admin/hooks/sanity.tsx"
+export const useTriggerSanitySync = (options?: UseMutationOptions) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: () =>
+ sdk.client.fetch(`/admin/sanity/syncs`, {
+ method: "post",
+ }),
+ onSuccess: (data: any, variables: any, context: any) => {
+ queryClient.invalidateQueries({
+ queryKey: [`sanity_sync`],
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useSanitySyncs = (
+ query?: Record,
+ options?: Omit<
+ UseQueryOptions<
+ Record,
+ FetchError,
+ { workflow_executions: Record[] },
+ QueryKey
+ >,
+ "queryKey" | "queryFn"
+ >
+) => {
+ const fetchSanitySyncs = async (query?: Record) => {
+ return await sdk.client.fetch>(`/admin/sanity/syncs`, {
+ query,
+ })
+ }
+
+ const { data, ...rest } = useQuery({
+ queryFn: async () => fetchSanitySyncs(query),
+ queryKey: [`sanity_sync`],
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
+```
+
+The `useTriggerSanitySync` hook creates a mutation that, when executed, sends a request to the trigger sync API route you created earlier to sync all products.
+
+The `useSanitySyncs` hook sends a request to the retrieve sync executions API route that you created earlier to retrieve the workflow's exections.
+
+Finally, to create the UI route, create the file `src/admin/routes/sanity/page.tsx` with the following content:
+
+```tsx title="src/admin/routes/sanity/page.tsx"
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import { Sanity } from "@medusajs/icons"
+import {
+ Badge,
+ Button,
+ Container,
+ Heading,
+ Table,
+ Toaster,
+ toast,
+} from "@medusajs/ui"
+import { useSanitySyncs, useTriggerSanitySync } from "../../hooks/sanity"
+
+const SanityRoute = () => {
+ const { mutateAsync, isPending } = useTriggerSanitySync()
+ const { workflow_executions, refetch } = useSanitySyncs()
+
+ const handleSync = async () => {
+ try {
+ await mutateAsync()
+ toast.success(`Sync triggered.`)
+ refetch()
+ } catch (err) {
+ toast.error(`Couldn't trigger sync: ${
+ (err as Record).message
+ }`)
+ }
+ }
+
+ const getBadgeColor = (state: string) => {
+ switch (state) {
+ case "invoking":
+ return "blue"
+ case "done":
+ return "green"
+ case "failed":
+ return "red"
+ default:
+ return "grey"
+ }
+ }
+
+ return (
+ <>
+
+
+
+ Sanity Syncs
+
+
+
+
+
+
+ Sync ID
+ Status
+ Created At
+ Updated At
+
+
+
+
+ {(workflow_executions || []).map((execution) => (
+
+ (window.location.href = `/app/sanity/${execution.id}`)
+ }
+ >
+ {execution.id}
+
+
+ {execution.state}
+
+
+ {execution.created_at}
+ {execution.updated_at}
+
+ ))}
+
+
+
+
+ >
+ )
+}
+
+export const config = defineRouteConfig({
+ label: "Sanity",
+ icon: Sanity,
+})
+
+export default SanityRoute
+```
+
+The file's path relative to the `src/admin/routes` directory indicates its path in the admin dashboard. So, this adds a new route at the path `http://localhost:9000/app/sanity`.
+
+The file must export the UI route's component. Also, to add an item in the sidebar for the UI route, you export a configuration object, created with `defineRouteConfig` from the Admin Extension SDK. The function accepts the following properties:
+
+- `label`: The sidebar item's label.
+- `icon`: The icon to the show in the sidebar.
+
+In the UI route, you use the `useSanitySyncs` hook to retrieve the list of sync executions and display them with their status. You also show a "Trigger Sync" button that, when clicked, uses the mutation from the `useTriggerSanitySync` hook to send a request to the Medusa application and trigger the sync.
+
+To display components that match the design of the Medusa Admin, you use components from the [Medusa UI package](https://docs.medusajs.com/ui/index.html.md).
+
+Learn more about UI routes in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/admin/ui-routes/index.html.md).
+
+### Test it Out
+
+To test it out, start the Medusa application and open the admin dashboard. After logging in, you'll find a new "Sanity" item in the sidebar.
+
+If you click on it, you'll see a table of the latest syncs. You also trigger syncing by clicking the "Trigger Sync" button. After you click the button, you should see a new execution added to the table.
+
+***
+
+## Next Steps
+
+You've now integrated Medusa with Sanity and can benefit from powerful commerce and CMS features.
+
+If you're new to Medusa, check out the [main documentation](https://docs.medusajs.com/docs/learn/index.html.md), where you'll get a more in-depth learning of all the concepts you've used in this guide and more.
+
+To learn more about the commerce features that Medusa provides, check out Medusa's [Commerce Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md).
+
+
# How to Build a Wishlist Plugin
In this guide, you'll learn how to build a wishlist [plugin](https://docs.medusajs.com/docs/learn/fundamentals/plugins/index.html.md) in Medusa.
@@ -45936,107 +46174,56 @@ To learn more about the commerce features that Medusa provides, check out Medusa
## JS SDK Admin
+- [batchPromotions](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.batchPromotions/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.create/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.delete/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.list/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.retrieve/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.update/index.html.md)
- [batchSalesChannels](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.batchSalesChannels/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.create/index.html.md)
- [delete](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.delete/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.list/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.retrieve/index.html.md)
- [revoke](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.revoke/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.retrieve/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.update/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.list/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.retrieve/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.delete/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.create/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.update/index.html.md)
-- [batchPromotions](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.batchPromotions/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Currency/methods/js_sdk.admin.Currency.retrieve/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Currency/methods/js_sdk.admin.Currency.list/index.html.md)
-- [fetchStream](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.fetchStream/index.html.md)
-- [clearToken](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.clearToken/index.html.md)
-- [fetch](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.fetch/index.html.md)
-- [clearToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.clearToken_/index.html.md)
-- [getApiKeyHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getApiKeyHeader_/index.html.md)
-- [getJwtHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getJwtHeader_/index.html.md)
-- [getToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getToken_/index.html.md)
-- [getTokenStorageInfo\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getTokenStorageInfo_/index.html.md)
-- [initClient](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.initClient/index.html.md)
-- [getPublishableKeyHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getPublishableKeyHeader_/index.html.md)
-- [throwError\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.throwError_/index.html.md)
-- [setToken](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.setToken/index.html.md)
- [getItem](https://docs.medusajs.com/references/js_sdk/admin/CustomStorage/methods/js_sdk.admin.CustomStorage.getItem/index.html.md)
- [removeItem](https://docs.medusajs.com/references/js_sdk/admin/CustomStorage/methods/js_sdk.admin.CustomStorage.removeItem/index.html.md)
- [setItem](https://docs.medusajs.com/references/js_sdk/admin/CustomStorage/methods/js_sdk.admin.CustomStorage.setItem/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Currency/methods/js_sdk.admin.Currency.list/index.html.md)
+- [clearToken](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.clearToken/index.html.md)
+- [fetchStream](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.fetchStream/index.html.md)
+- [fetch](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.fetch/index.html.md)
+- [getApiKeyHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getApiKeyHeader_/index.html.md)
+- [clearToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.clearToken_/index.html.md)
+- [getJwtHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getJwtHeader_/index.html.md)
+- [getPublishableKeyHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getPublishableKeyHeader_/index.html.md)
+- [getTokenStorageInfo\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getTokenStorageInfo_/index.html.md)
+- [getToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getToken_/index.html.md)
- [setToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.setToken_/index.html.md)
+- [initClient](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.initClient/index.html.md)
+- [setToken](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.setToken/index.html.md)
+- [throwError\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.throwError_/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.create/index.html.md)
- [batchCustomerGroups](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.batchCustomerGroups/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.retrieve/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.delete/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.list/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.update/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.delete/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.create/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.create/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.list/index.html.md)
-- [batchCustomers](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.batchCustomers/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.create/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.retrieve/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.delete/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.update/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.list/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.update/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.retrieve/index.html.md)
-- [addInboundItems](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addInboundItems/index.html.md)
-- [addOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addOutboundShipping/index.html.md)
-- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.cancel/index.html.md)
-- [addInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addInboundShipping/index.html.md)
-- [addOutboundItems](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addOutboundItems/index.html.md)
-- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.cancelRequest/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.create/index.html.md)
-- [removeInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.removeInboundItem/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.list/index.html.md)
-- [removeOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.removeOutboundItem/index.html.md)
-- [deleteInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.deleteInboundShipping/index.html.md)
-- [deleteOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.deleteOutboundShipping/index.html.md)
-- [request](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.request/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.retrieve/index.html.md)
-- [updateOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateOutboundItem/index.html.md)
-- [updateOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateOutboundShipping/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentProvider/methods/js_sdk.admin.FulfillmentProvider.list/index.html.md)
-- [updateInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateInboundShipping/index.html.md)
-- [updateInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateInboundItem/index.html.md)
-- [listFulfillmentOptions](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentProvider/methods/js_sdk.admin.FulfillmentProvider.listFulfillmentOptions/index.html.md)
-- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.cancel/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.delete/index.html.md)
-- [createServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.createServiceZone/index.html.md)
-- [deleteServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.deleteServiceZone/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.create/index.html.md)
-- [retrieveServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.retrieveServiceZone/index.html.md)
-- [createShipment](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.createShipment/index.html.md)
-- [updateServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.updateServiceZone/index.html.md)
-- [batchInventoryItemLocationLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.batchInventoryItemLocationLevels/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.create/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.delete/index.html.md)
-- [deleteLevel](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.deleteLevel/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.list/index.html.md)
-- [batchInventoryItemsLocationLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.batchInventoryItemsLocationLevels/index.html.md)
-- [listLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.listLevels/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.retrieve/index.html.md)
-- [batchUpdateLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.batchUpdateLevels/index.html.md)
-- [updateLevel](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.updateLevel/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Notification/methods/js_sdk.admin.Notification.list/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Notification/methods/js_sdk.admin.Notification.retrieve/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.update/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.retrieve/index.html.md)
- [addInboundItems](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addInboundItems/index.html.md)
+- [addItems](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addItems/index.html.md)
- [addInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addInboundShipping/index.html.md)
- [addOutboundItems](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addOutboundItems/index.html.md)
-- [addOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addOutboundShipping/index.html.md)
- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.cancelRequest/index.html.md)
+- [addOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addOutboundShipping/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.create/index.html.md)
-- [deleteInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.deleteInboundShipping/index.html.md)
- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.cancel/index.html.md)
-- [deleteOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.deleteOutboundShipping/index.html.md)
-- [addItems](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addItems/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.list/index.html.md)
-- [removeItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.removeItem/index.html.md)
- [removeInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.removeInboundItem/index.html.md)
+- [deleteInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.deleteInboundShipping/index.html.md)
+- [deleteOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.deleteOutboundShipping/index.html.md)
+- [removeItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.removeItem/index.html.md)
- [removeOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.removeOutboundItem/index.html.md)
- [request](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.request/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.retrieve/index.html.md)
@@ -46044,195 +46231,246 @@ To learn more about the commerce features that Medusa provides, check out Medusa
- [updateInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateInboundShipping/index.html.md)
- [updateItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateItem/index.html.md)
- [updateOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateOutboundItem/index.html.md)
+- [batchCustomers](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.batchCustomers/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.create/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.delete/index.html.md)
- [updateOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateOutboundShipping/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.list/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.retrieve/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.update/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.create/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.update/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.retrieve/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.list/index.html.md)
+- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.cancel/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.create/index.html.md)
+- [createShipment](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.createShipment/index.html.md)
+- [addInboundItems](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addInboundItems/index.html.md)
+- [addOutboundItems](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addOutboundItems/index.html.md)
+- [addOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addOutboundShipping/index.html.md)
+- [addInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addInboundShipping/index.html.md)
+- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.cancel/index.html.md)
+- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.cancelRequest/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.create/index.html.md)
+- [deleteInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.deleteInboundShipping/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.list/index.html.md)
+- [deleteOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.deleteOutboundShipping/index.html.md)
+- [removeInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.removeInboundItem/index.html.md)
+- [removeOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.removeOutboundItem/index.html.md)
+- [request](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.request/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.retrieve/index.html.md)
+- [updateInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateInboundItem/index.html.md)
+- [updateInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateInboundShipping/index.html.md)
+- [updateOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateOutboundItem/index.html.md)
+- [updateOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateOutboundShipping/index.html.md)
+- [createServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.createServiceZone/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.delete/index.html.md)
+- [updateServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.updateServiceZone/index.html.md)
+- [retrieveServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.retrieveServiceZone/index.html.md)
+- [deleteServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.deleteServiceZone/index.html.md)
- [accept](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.accept/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.create/index.html.md)
-- [resend](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.resend/index.html.md)
+- [listFulfillmentOptions](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentProvider/methods/js_sdk.admin.FulfillmentProvider.listFulfillmentOptions/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentProvider/methods/js_sdk.admin.FulfillmentProvider.list/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.list/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.create/index.html.md)
- [delete](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.delete/index.html.md)
+- [resend](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.resend/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.retrieve/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Notification/methods/js_sdk.admin.Notification.list/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Notification/methods/js_sdk.admin.Notification.retrieve/index.html.md)
- [addItems](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.addItems/index.html.md)
-- [confirm](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.confirm/index.html.md)
- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.cancelRequest/index.html.md)
-- [initiateRequest](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.initiateRequest/index.html.md)
+- [confirm](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.confirm/index.html.md)
- [removeAddedItem](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.removeAddedItem/index.html.md)
-- [updateAddedItem](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.updateAddedItem/index.html.md)
- [request](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.request/index.html.md)
+- [initiateRequest](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.initiateRequest/index.html.md)
+- [batchInventoryItemsLocationLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.batchInventoryItemsLocationLevels/index.html.md)
+- [batchInventoryItemLocationLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.batchInventoryItemLocationLevels/index.html.md)
- [updateOriginalItem](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.updateOriginalItem/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.create/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.delete/index.html.md)
-- [batchPrices](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.batchPrices/index.html.md)
-- [linkProducts](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.linkProducts/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.update/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.retrieve/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.list/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.create/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.delete/index.html.md)
-- [markAsPaid](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.markAsPaid/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.create/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.list/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.delete/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.update/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.retrieve/index.html.md)
-- [capture](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.capture/index.html.md)
-- [listPaymentProviders](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.listPaymentProviders/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.list/index.html.md)
-- [refund](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.refund/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.retrieve/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.delete/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.create/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.list/index.html.md)
-- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.updateProducts/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.update/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.retrieve/index.html.md)
-- [batchVariantInventoryItems](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.batchVariantInventoryItems/index.html.md)
-- [batch](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.batch/index.html.md)
-- [confirmImport](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.confirmImport/index.html.md)
-- [batchVariants](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.batchVariants/index.html.md)
-- [createOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.createOption/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.create/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.delete/index.html.md)
-- [deleteVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.deleteVariant/index.html.md)
-- [deleteOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.deleteOption/index.html.md)
-- [createVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.createVariant/index.html.md)
-- [export](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.export/index.html.md)
-- [import](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.import/index.html.md)
-- [listOptions](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.listOptions/index.html.md)
-- [listVariants](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.listVariants/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.list/index.html.md)
-- [retrieveOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieveOption/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.update/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieve/index.html.md)
-- [retrieveVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieveVariant/index.html.md)
-- [updateOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.updateOption/index.html.md)
-- [updateVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.updateVariant/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.create/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.delete/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.list/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.update/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.retrieve/index.html.md)
-- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.updateProducts/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.create/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.list/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.retrieve/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.delete/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.update/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.create/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.retrieve/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.list/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.delete/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.update/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/RefundReason/methods/js_sdk.admin.RefundReason.list/index.html.md)
-- [addRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.addRules/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.create/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.delete/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.list/index.html.md)
-- [listRuleAttributes](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRuleAttributes/index.html.md)
-- [listRuleValues](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRuleValues/index.html.md)
-- [listRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRules/index.html.md)
-- [removeRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.removeRules/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.retrieve/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.update/index.html.md)
-- [updateRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.updateRules/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductVariant/methods/js_sdk.admin.ProductVariant.list/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.create/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.delete/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.list/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.retrieve/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.update/index.html.md)
-- [addReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.addReturnItem/index.html.md)
-- [addReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.addReturnShipping/index.html.md)
-- [cancelReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancelReceive/index.html.md)
-- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancel/index.html.md)
-- [confirmReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.confirmReceive/index.html.md)
-- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancelRequest/index.html.md)
-- [confirmRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.confirmRequest/index.html.md)
-- [deleteReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.deleteReturnShipping/index.html.md)
-- [dismissItems](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.dismissItems/index.html.md)
-- [initiateReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.initiateReceive/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.list/index.html.md)
-- [initiateRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.initiateRequest/index.html.md)
-- [receiveItems](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.receiveItems/index.html.md)
-- [removeDismissItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeDismissItem/index.html.md)
-- [removeReceiveItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeReceiveItem/index.html.md)
-- [removeReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeReturnItem/index.html.md)
-- [updateDismissItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateDismissItem/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.retrieve/index.html.md)
-- [updateReceiveItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReceiveItem/index.html.md)
-- [updateRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateRequest/index.html.md)
-- [updateReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReturnShipping/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.delete/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.create/index.html.md)
-- [updateReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReturnItem/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.update/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.retrieve/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.list/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.create/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.delete/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.list/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.update/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.retrieve/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.create/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.list/index.html.md)
-- [batchProducts](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.batchProducts/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.delete/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.retrieve/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.update/index.html.md)
-- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.updateProducts/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.create/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.delete/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.list/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.retrieve/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.update/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.create/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.list/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.delete/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.update/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.retrieve/index.html.md)
-- [updateRules](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.updateRules/index.html.md)
+- [updateAddedItem](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.updateAddedItem/index.html.md)
+- [batchUpdateLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.batchUpdateLevels/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.create/index.html.md)
+- [deleteLevel](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.deleteLevel/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.list/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.delete/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.retrieve/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.update/index.html.md)
+- [listLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.listLevels/index.html.md)
- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.cancel/index.html.md)
-- [cancelFulfillment](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.cancelFulfillment/index.html.md)
+- [updateLevel](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.updateLevel/index.html.md)
- [cancelTransfer](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.cancelTransfer/index.html.md)
-- [createShipment](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.createShipment/index.html.md)
+- [cancelFulfillment](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.cancelFulfillment/index.html.md)
- [createFulfillment](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.createFulfillment/index.html.md)
+- [createShipment](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.createShipment/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.list/index.html.md)
- [listLineItems](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.listLineItems/index.html.md)
- [listChanges](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.listChanges/index.html.md)
- [markAsDelivered](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.markAsDelivered/index.html.md)
- [requestTransfer](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.requestTransfer/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.retrieve/index.html.md)
-- [retrievePreview](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.retrievePreview/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.update/index.html.md)
+- [batchPrices](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.batchPrices/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.create/index.html.md)
+- [retrievePreview](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.retrievePreview/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.delete/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.retrieve/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.list/index.html.md)
+- [linkProducts](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.linkProducts/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.update/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.create/index.html.md)
+- [capture](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.capture/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.delete/index.html.md)
+- [markAsPaid](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.markAsPaid/index.html.md)
+- [listPaymentProviders](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.listPaymentProviders/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.list/index.html.md)
+- [refund](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.refund/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.retrieve/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.delete/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.create/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.list/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.retrieve/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.delete/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.create/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.update/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.update/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.retrieve/index.html.md)
+- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.updateProducts/index.html.md)
+- [batch](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.batch/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.list/index.html.md)
+- [batchVariantInventoryItems](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.batchVariantInventoryItems/index.html.md)
+- [batchVariants](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.batchVariants/index.html.md)
+- [confirmImport](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.confirmImport/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.create/index.html.md)
+- [createOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.createOption/index.html.md)
+- [createVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.createVariant/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.delete/index.html.md)
+- [deleteOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.deleteOption/index.html.md)
+- [export](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.export/index.html.md)
+- [deleteVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.deleteVariant/index.html.md)
+- [import](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.import/index.html.md)
+- [listVariants](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.listVariants/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieve/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.list/index.html.md)
+- [retrieveOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieveOption/index.html.md)
+- [listOptions](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.listOptions/index.html.md)
+- [retrieveVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieveVariant/index.html.md)
+- [updateOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.updateOption/index.html.md)
+- [updateVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.updateVariant/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.update/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.delete/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.list/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.create/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.retrieve/index.html.md)
+- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.updateProducts/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.update/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductVariant/methods/js_sdk.admin.ProductVariant.list/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.create/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.delete/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.list/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.update/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.retrieve/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.create/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.retrieve/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.list/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.delete/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.update/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.delete/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.retrieve/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.list/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.create/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.update/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.list/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.delete/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.create/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.retrieve/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/RefundReason/methods/js_sdk.admin.RefundReason.list/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.update/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.delete/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.create/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.retrieve/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.list/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.update/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.create/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.delete/index.html.md)
+- [addRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.addRules/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.list/index.html.md)
+- [listRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRules/index.html.md)
+- [listRuleValues](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRuleValues/index.html.md)
+- [listRuleAttributes](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRuleAttributes/index.html.md)
+- [removeRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.removeRules/index.html.md)
+- [updateRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.updateRules/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.update/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.retrieve/index.html.md)
+- [addReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.addReturnItem/index.html.md)
+- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancel/index.html.md)
+- [addReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.addReturnShipping/index.html.md)
+- [cancelReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancelReceive/index.html.md)
+- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancelRequest/index.html.md)
+- [confirmReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.confirmReceive/index.html.md)
+- [confirmRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.confirmRequest/index.html.md)
+- [deleteReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.deleteReturnShipping/index.html.md)
+- [initiateRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.initiateRequest/index.html.md)
+- [initiateReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.initiateReceive/index.html.md)
+- [dismissItems](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.dismissItems/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.list/index.html.md)
+- [removeReceiveItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeReceiveItem/index.html.md)
+- [removeDismissItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeDismissItem/index.html.md)
+- [receiveItems](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.receiveItems/index.html.md)
+- [removeReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeReturnItem/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.retrieve/index.html.md)
+- [updateDismissItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateDismissItem/index.html.md)
+- [updateReceiveItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReceiveItem/index.html.md)
+- [updateRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateRequest/index.html.md)
+- [updateReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReturnShipping/index.html.md)
+- [updateReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReturnItem/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.create/index.html.md)
+- [batchProducts](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.batchProducts/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.delete/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.update/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.list/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.retrieve/index.html.md)
+- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.updateProducts/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.create/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.delete/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.list/index.html.md)
+- [updateRules](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.updateRules/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.retrieve/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.update/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.create/index.html.md)
- [createFulfillmentSet](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.createFulfillmentSet/index.html.md)
- [delete](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.delete/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.list/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.update/index.html.md)
-- [updateFulfillmentProviders](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.updateFulfillmentProviders/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.retrieve/index.html.md)
- [updateSalesChannels](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.updateSalesChannels/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.create/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.delete/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.list/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.retrieve/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.update/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.create/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.delete/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.list/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.retrieve/index.html.md)
+- [updateFulfillmentProviders](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.updateFulfillmentProviders/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.create/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.delete/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.list/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.retrieve/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.update/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/Store/methods/js_sdk.admin.Store.list/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Store/methods/js_sdk.admin.Store.retrieve/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/Store/methods/js_sdk.admin.Store.update/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.create/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.list/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.delete/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.retrieve/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.update/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/Upload/methods/js_sdk.admin.Upload.create/index.html.md)
- [delete](https://docs.medusajs.com/references/js_sdk/admin/Upload/methods/js_sdk.admin.Upload.delete/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Upload/methods/js_sdk.admin.Upload.retrieve/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.list/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.delete/index.html.md)
-- [me](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.me/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.retrieve/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/WorkflowExecution/methods/js_sdk.admin.WorkflowExecution.list/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.update/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/WorkflowExecution/methods/js_sdk.admin.WorkflowExecution.retrieve/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/WorkflowExecution/methods/js_sdk.admin.WorkflowExecution.list/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.create/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.retrieve/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.delete/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.list/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.delete/index.html.md)
+- [me](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.me/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.list/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.retrieve/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.update/index.html.md)
## JS SDK Auth
@@ -46240,23 +46478,23 @@ To learn more about the commerce features that Medusa provides, check out Medusa
- [callback](https://docs.medusajs.com/references/js-sdk/auth/callback/index.html.md)
- [login](https://docs.medusajs.com/references/js-sdk/auth/login/index.html.md)
- [register](https://docs.medusajs.com/references/js-sdk/auth/register/index.html.md)
-- [resetPassword](https://docs.medusajs.com/references/js-sdk/auth/resetPassword/index.html.md)
-- [logout](https://docs.medusajs.com/references/js-sdk/auth/logout/index.html.md)
- [refresh](https://docs.medusajs.com/references/js-sdk/auth/refresh/index.html.md)
+- [logout](https://docs.medusajs.com/references/js-sdk/auth/logout/index.html.md)
+- [resetPassword](https://docs.medusajs.com/references/js-sdk/auth/resetPassword/index.html.md)
- [updateProvider](https://docs.medusajs.com/references/js-sdk/auth/updateProvider/index.html.md)
## JS SDK Store
- [cart](https://docs.medusajs.com/references/js-sdk/store/cart/index.html.md)
-- [category](https://docs.medusajs.com/references/js-sdk/store/category/index.html.md)
-- [fulfillment](https://docs.medusajs.com/references/js-sdk/store/fulfillment/index.html.md)
-- [order](https://docs.medusajs.com/references/js-sdk/store/order/index.html.md)
-- [payment](https://docs.medusajs.com/references/js-sdk/store/payment/index.html.md)
-- [region](https://docs.medusajs.com/references/js-sdk/store/region/index.html.md)
-- [product](https://docs.medusajs.com/references/js-sdk/store/product/index.html.md)
- [collection](https://docs.medusajs.com/references/js-sdk/store/collection/index.html.md)
- [customer](https://docs.medusajs.com/references/js-sdk/store/customer/index.html.md)
+- [fulfillment](https://docs.medusajs.com/references/js-sdk/store/fulfillment/index.html.md)
+- [category](https://docs.medusajs.com/references/js-sdk/store/category/index.html.md)
+- [order](https://docs.medusajs.com/references/js-sdk/store/order/index.html.md)
+- [payment](https://docs.medusajs.com/references/js-sdk/store/payment/index.html.md)
+- [product](https://docs.medusajs.com/references/js-sdk/store/product/index.html.md)
+- [region](https://docs.medusajs.com/references/js-sdk/store/region/index.html.md)
# Configure Medusa Backend
@@ -46924,6 +47162,145 @@ These layout components allow you to set the layout of your [UI routes](https://
These components allow you to use common Medusa Admin components in your custom UI routes and widgets.
+# Single Column Layout - Admin Components
+
+The Medusa Admin has pages with a single column of content.
+
+This doesn't include the sidebar, only the main content.
+
+
+
+To create a layout that you can use in UI routes to support one column of content, create the component `src/admin/layouts/single-column.tsx` with the following content:
+
+```tsx title="src/admin/layouts/single-column.tsx"
+export type SingleColumnLayoutProps = {
+ children: React.ReactNode
+}
+
+export const SingleColumnLayout = ({ children }: SingleColumnLayoutProps) => {
+ return (
+
+ {children}
+
+ )
+}
+```
+
+The `SingleColumnLayout` accepts the content in the `children` props.
+
+***
+
+## Example
+
+Use the `SingleColumnLayout` component in your UI routes that have a single column. For example:
+
+```tsx title="src/admin/routes/custom/page.tsx" highlights={[["9"]]}
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import { ChatBubbleLeftRight } from "@medusajs/icons"
+import { Container } from "../../components/container"
+import { SingleColumnLayout } from "../../layouts/single-column"
+import { Header } from "../../components/header"
+
+const CustomPage = () => {
+ return (
+
+
+
+
+
+ )
+}
+
+export const config = defineRouteConfig({
+ label: "Custom",
+ icon: ChatBubbleLeftRight,
+})
+
+export default CustomPage
+```
+
+This UI route also uses a [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and a [Header]() custom components.
+
+
+# Two Column Layout - Admin Components
+
+The Medusa Admin has pages with two columns of content.
+
+This doesn't include the sidebar, only the main content.
+
+
+
+To create a layout that you can use in UI routes to support two columns of content, create the component `src/admin/layouts/two-column.tsx` with the following content:
+
+```tsx title="src/admin/layouts/two-column.tsx"
+export type TwoColumnLayoutProps = {
+ firstCol: React.ReactNode
+ secondCol: React.ReactNode
+}
+
+export const TwoColumnLayout = ({
+ firstCol,
+ secondCol,
+}: TwoColumnLayoutProps) => {
+ return (
+
+
+ {firstCol}
+
+
+ {secondCol}
+
+
+ )
+}
+```
+
+The `TwoColumnLayout` accepts two props:
+
+- `firstCol` indicating the content of the first column.
+- `secondCol` indicating the content of the second column.
+
+***
+
+## Example
+
+Use the `TwoColumnLayout` component in your UI routes that have a single column. For example:
+
+```tsx title="src/admin/routes/custom/page.tsx" highlights={[["9"]]}
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import { ChatBubbleLeftRight } from "@medusajs/icons"
+import { Container } from "../../components/container"
+import { Header } from "../../components/header"
+import { TwoColumnLayout } from "../../layouts/two-column"
+
+const CustomPage = () => {
+ return (
+
+
+
+ }
+ secondCol={
+
+
+
+ }
+ />
+ )
+}
+
+export const config = defineRouteConfig({
+ label: "Custom",
+ icon: ChatBubbleLeftRight,
+})
+
+export default CustomPage
+```
+
+This UI route also uses [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and [Header]() custom components.
+
+
# Action Menu - Admin Components
The Medusa Admin often provides additional actions in a dropdown shown when users click a three-dot icon.
@@ -47149,65 +47526,6 @@ export default ProductWidget
```
-# Container - Admin Components
-
-The Medusa Admin wraps each section of a page in a container.
-
-
-
-To create a component that uses the same container styling in your widgets or UI routes, create the file `src/admin/components/container.tsx` with the following content:
-
-```tsx
-import {
- Container as UiContainer,
- clx,
-} from "@medusajs/ui"
-
-type ContainerProps = React.ComponentProps
-
-export const Container = (props: ContainerProps) => {
- return (
-
- )
-}
-```
-
-The `Container` component re-uses the component from the [Medusa UI package](https://docs.medusajs.com/ui/components/container/index.html.md) and applies to it classes to match the Medusa Admin's design conventions.
-
-***
-
-## Example
-
-Use that `Container` component in any widget or UI route.
-
-For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content:
-
-```tsx title="src/admin/widgets/product-widget.tsx"
-import { defineWidgetConfig } from "@medusajs/admin-sdk"
-import { Container } from "../components/container"
-import { Header } from "../components/header"
-
-const ProductWidget = () => {
- return (
-
-
-
- )
-}
-
-export const config = defineWidgetConfig({
- zone: "product.details.before",
-})
-
-export default ProductWidget
-```
-
-This widget also uses a [Header](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/header/index.html.md) custom component.
-
-
# Forms - Admin Components
The Medusa Admin has two types of forms:
@@ -48414,64 +48732,383 @@ export default ProductWidget
This widget also uses a [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) custom component.
-# Single Column Layout - Admin Components
+# Container - Admin Components
-The Medusa Admin has pages with a single column of content.
+The Medusa Admin wraps each section of a page in a container.
-This doesn't include the sidebar, only the main content.
+
-
+To create a component that uses the same container styling in your widgets or UI routes, create the file `src/admin/components/container.tsx` with the following content:
-To create a layout that you can use in UI routes to support one column of content, create the component `src/admin/layouts/single-column.tsx` with the following content:
+```tsx
+import {
+ Container as UiContainer,
+ clx,
+} from "@medusajs/ui"
-```tsx title="src/admin/layouts/single-column.tsx"
-export type SingleColumnLayoutProps = {
- children: React.ReactNode
-}
+type ContainerProps = React.ComponentProps
-export const SingleColumnLayout = ({ children }: SingleColumnLayoutProps) => {
+export const Container = (props: ContainerProps) => {
return (
-
- {children}
-
+
)
}
```
-The `SingleColumnLayout` accepts the content in the `children` props.
+The `Container` component re-uses the component from the [Medusa UI package](https://docs.medusajs.com/ui/components/container/index.html.md) and applies to it classes to match the Medusa Admin's design conventions.
***
## Example
-Use the `SingleColumnLayout` component in your UI routes that have a single column. For example:
+Use that `Container` component in any widget or UI route.
-```tsx title="src/admin/routes/custom/page.tsx" highlights={[["9"]]}
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { ChatBubbleLeftRight } from "@medusajs/icons"
-import { Container } from "../../components/container"
-import { SingleColumnLayout } from "../../layouts/single-column"
-import { Header } from "../../components/header"
+For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content:
-const CustomPage = () => {
+```tsx title="src/admin/widgets/product-widget.tsx"
+import { defineWidgetConfig } from "@medusajs/admin-sdk"
+import { Container } from "../components/container"
+import { Header } from "../components/header"
+
+const ProductWidget = () => {
return (
-
-
-
-
-
+
+
+
)
}
-export const config = defineRouteConfig({
- label: "Custom",
- icon: ChatBubbleLeftRight,
+export const config = defineWidgetConfig({
+ zone: "product.details.before",
})
-export default CustomPage
+export default ProductWidget
```
-This UI route also uses a [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and a [Header]() custom components.
+This widget also uses a [Header](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/header/index.html.md) custom component.
+
+
+# Section Row - Admin Components
+
+The Medusa Admin often shows information in rows of label-values, such as when showing a product's details.
+
+
+
+To create a component that shows information in the same structure, create the file `src/admin/components/section-row.tsx` with the following content:
+
+```tsx title="src/admin/components/section-row.tsx"
+import { Text, clx } from "@medusajs/ui"
+
+export type SectionRowProps = {
+ title: string
+ value?: React.ReactNode | string | null
+ actions?: React.ReactNode
+}
+
+export const SectionRow = ({ title, value, actions }: SectionRowProps) => {
+ const isValueString = typeof value === "string" || !value
+
+ return (
+
+ )
+}
+```
+
+The `SectionRow` component shows a title and a value in the same row.
+
+It accepts the following props:
+
+- title: (\`string\`) The title to show on the left side.
+- value: (\`React.ReactNode\` \\| \`string\` \\| \`null\`) The value to show on the right side.
+- actions: (\`React.ReactNode\`) The actions to show at the end of the row.
+
+***
+
+## Example
+
+Use the `SectionRow` component in any widget or UI route.
+
+For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content:
+
+```tsx title="src/admin/widgets/product-widget.tsx"
+import { defineWidgetConfig } from "@medusajs/admin-sdk"
+import { Container } from "../components/container"
+import { Header } from "../components/header"
+import { SectionRow } from "../components/section-row"
+
+const ProductWidget = () => {
+ return (
+
+
+
+
+ )
+}
+
+export const config = defineWidgetConfig({
+ zone: "product.details.before",
+})
+
+export default ProductWidget
+```
+
+This widget also uses the [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and [Header](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/header/index.html.md) custom component.
+
+
+# JSON View - Admin Components
+
+Detail pages in the Medusa Admin show a JSON section to view the current page's details in JSON format.
+
+
+
+To create a component that shows a JSON section in your customizations, create the file `src/admin/components/json-view-section.tsx` with the following content:
+
+```tsx title="src/admin/components/json-view-section.tsx"
+import {
+ ArrowUpRightOnBox,
+ Check,
+ SquareTwoStack,
+ TriangleDownMini,
+ XMarkMini,
+} from "@medusajs/icons"
+import {
+ Badge,
+ Container,
+ Drawer,
+ Heading,
+ IconButton,
+ Kbd,
+} from "@medusajs/ui"
+import Primitive from "@uiw/react-json-view"
+import { CSSProperties, MouseEvent, Suspense, useState } from "react"
+
+type JsonViewSectionProps = {
+ data: object
+ title?: string
+}
+
+export const JsonViewSection = ({ data }: JsonViewSectionProps) => {
+ const numberOfKeys = Object.keys(data).length
+
+ return (
+
+
+ JSON
+
+ {numberOfKeys} keys
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {numberOfKeys}
+
+
+
+
+
+
+ esc
+
+
+
+
+
+
+
+
+
+
+
}
+ >
+
+ } />
+ (
+ null
+ )}
+ />
+ (
+ undefined
+ )}
+ />
+ {
+ return (
+
+ {Object.keys(value as object).length} items
+
+ )
+ }}
+ />
+
+
+
+
+ :
+
+ {
+ return
+ }}
+ />
+
+
+
+
+
+
+
+ )
+}
+
+type CopiedProps = {
+ style?: CSSProperties
+ value: object | undefined
+}
+
+const Copied = ({ style, value }: CopiedProps) => {
+ const [copied, setCopied] = useState(false)
+
+ const handler = (e: MouseEvent) => {
+ e.stopPropagation()
+ setCopied(true)
+
+ if (typeof value === "string") {
+ navigator.clipboard.writeText(value)
+ } else {
+ const json = JSON.stringify(value, null, 2)
+ navigator.clipboard.writeText(json)
+ }
+
+ setTimeout(() => {
+ setCopied(false)
+ }, 2000)
+ }
+
+ const styl = { whiteSpace: "nowrap", width: "20px" }
+
+ if (copied) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+
+
+
+ )
+}
+```
+
+The `JsonViewSection` component shows a section with the "JSON" title and a button to show the data as JSON in a drawer or side window.
+
+The `JsonViewSection` accepts a `data` prop, which is the data to show as a JSON object in the drawer.
+
+***
+
+## Example
+
+Use the `JsonViewSection` component in any widget or UI route.
+
+For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content:
+
+```tsx title="src/admin/widgets/product-widget.tsx"
+import { defineWidgetConfig } from "@medusajs/admin-sdk"
+import { JsonViewSection } from "../components/json-view-section"
+
+const ProductWidget = () => {
+ return
+}
+
+export const config = defineWidgetConfig({
+ zone: "product.details.before",
+})
+
+export default ProductWidget
+```
+
+This shows the JSON section at the top of the product page, passing it the object `{ name: "John" }`.
# Table - Admin Components
@@ -48764,405 +49401,6 @@ If `data` isn't `undefined`, you display the `Table` component passing it the fo
To test it out, log into the Medusa Admin and open `http://localhost:9000/app/custom`. You'll find a table of products with pagination.
-# JSON View - Admin Components
-
-Detail pages in the Medusa Admin show a JSON section to view the current page's details in JSON format.
-
-
-
-To create a component that shows a JSON section in your customizations, create the file `src/admin/components/json-view-section.tsx` with the following content:
-
-```tsx title="src/admin/components/json-view-section.tsx"
-import {
- ArrowUpRightOnBox,
- Check,
- SquareTwoStack,
- TriangleDownMini,
- XMarkMini,
-} from "@medusajs/icons"
-import {
- Badge,
- Container,
- Drawer,
- Heading,
- IconButton,
- Kbd,
-} from "@medusajs/ui"
-import Primitive from "@uiw/react-json-view"
-import { CSSProperties, MouseEvent, Suspense, useState } from "react"
-
-type JsonViewSectionProps = {
- data: object
- title?: string
-}
-
-export const JsonViewSection = ({ data }: JsonViewSectionProps) => {
- const numberOfKeys = Object.keys(data).length
-
- return (
-
-
- JSON
-
- {numberOfKeys} keys
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {numberOfKeys}
-
-
-
-
-
-
- esc
-
-
-
-
-
-
-
-
-
-
-
}
- >
-
- } />
- (
- null
- )}
- />
- (
- undefined
- )}
- />
- {
- return (
-
- {Object.keys(value as object).length} items
-
- )
- }}
- />
-
-
-
-
- :
-
- {
- return
- }}
- />
-
-
-
-
-
-
-
- )
-}
-
-type CopiedProps = {
- style?: CSSProperties
- value: object | undefined
-}
-
-const Copied = ({ style, value }: CopiedProps) => {
- const [copied, setCopied] = useState(false)
-
- const handler = (e: MouseEvent) => {
- e.stopPropagation()
- setCopied(true)
-
- if (typeof value === "string") {
- navigator.clipboard.writeText(value)
- } else {
- const json = JSON.stringify(value, null, 2)
- navigator.clipboard.writeText(json)
- }
-
- setTimeout(() => {
- setCopied(false)
- }, 2000)
- }
-
- const styl = { whiteSpace: "nowrap", width: "20px" }
-
- if (copied) {
- return (
-
-
-
- )
- }
-
- return (
-
-
-
- )
-}
-```
-
-The `JsonViewSection` component shows a section with the "JSON" title and a button to show the data as JSON in a drawer or side window.
-
-The `JsonViewSection` accepts a `data` prop, which is the data to show as a JSON object in the drawer.
-
-***
-
-## Example
-
-Use the `JsonViewSection` component in any widget or UI route.
-
-For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content:
-
-```tsx title="src/admin/widgets/product-widget.tsx"
-import { defineWidgetConfig } from "@medusajs/admin-sdk"
-import { JsonViewSection } from "../components/json-view-section"
-
-const ProductWidget = () => {
- return
-}
-
-export const config = defineWidgetConfig({
- zone: "product.details.before",
-})
-
-export default ProductWidget
-```
-
-This shows the JSON section at the top of the product page, passing it the object `{ name: "John" }`.
-
-
-# Section Row - Admin Components
-
-The Medusa Admin often shows information in rows of label-values, such as when showing a product's details.
-
-
-
-To create a component that shows information in the same structure, create the file `src/admin/components/section-row.tsx` with the following content:
-
-```tsx title="src/admin/components/section-row.tsx"
-import { Text, clx } from "@medusajs/ui"
-
-export type SectionRowProps = {
- title: string
- value?: React.ReactNode | string | null
- actions?: React.ReactNode
-}
-
-export const SectionRow = ({ title, value, actions }: SectionRowProps) => {
- const isValueString = typeof value === "string" || !value
-
- return (
-
- )
-}
-```
-
-The `SectionRow` component shows a title and a value in the same row.
-
-It accepts the following props:
-
-- title: (\`string\`) The title to show on the left side.
-- value: (\`React.ReactNode\` \\| \`string\` \\| \`null\`) The value to show on the right side.
-- actions: (\`React.ReactNode\`) The actions to show at the end of the row.
-
-***
-
-## Example
-
-Use the `SectionRow` component in any widget or UI route.
-
-For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content:
-
-```tsx title="src/admin/widgets/product-widget.tsx"
-import { defineWidgetConfig } from "@medusajs/admin-sdk"
-import { Container } from "../components/container"
-import { Header } from "../components/header"
-import { SectionRow } from "../components/section-row"
-
-const ProductWidget = () => {
- return (
-
-
-
-
- )
-}
-
-export const config = defineWidgetConfig({
- zone: "product.details.before",
-})
-
-export default ProductWidget
-```
-
-This widget also uses the [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and [Header](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/header/index.html.md) custom component.
-
-
-# Two Column Layout - Admin Components
-
-The Medusa Admin has pages with two columns of content.
-
-This doesn't include the sidebar, only the main content.
-
-
-
-To create a layout that you can use in UI routes to support two columns of content, create the component `src/admin/layouts/two-column.tsx` with the following content:
-
-```tsx title="src/admin/layouts/two-column.tsx"
-export type TwoColumnLayoutProps = {
- firstCol: React.ReactNode
- secondCol: React.ReactNode
-}
-
-export const TwoColumnLayout = ({
- firstCol,
- secondCol,
-}: TwoColumnLayoutProps) => {
- return (
-
-
- {firstCol}
-
-
- {secondCol}
-
-
- )
-}
-```
-
-The `TwoColumnLayout` accepts two props:
-
-- `firstCol` indicating the content of the first column.
-- `secondCol` indicating the content of the second column.
-
-***
-
-## Example
-
-Use the `TwoColumnLayout` component in your UI routes that have a single column. For example:
-
-```tsx title="src/admin/routes/custom/page.tsx" highlights={[["9"]]}
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { ChatBubbleLeftRight } from "@medusajs/icons"
-import { Container } from "../../components/container"
-import { Header } from "../../components/header"
-import { TwoColumnLayout } from "../../layouts/two-column"
-
-const CustomPage = () => {
- return (
-
-
-
- }
- secondCol={
-
-
-
- }
- />
- )
-}
-
-export const config = defineRouteConfig({
- label: "Custom",
- icon: ChatBubbleLeftRight,
-})
-
-export default CustomPage
-```
-
-This UI route also uses [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and [Header]() custom components.
-
-
# Service Factory Reference
This section of the documentation provides a reference of the methods generated for services extending the service factory (`MedusaService`), and how to use them.
@@ -49228,6 +49466,469 @@ const posts = await postModuleService.createPosts([
If an array is passed of the method, an array of the created records is also returned.
+# Filter Records - Service Factory Reference
+
+Many of the service factory's generated methods allow passing filters to perform an operation, such as to update or delete records matching the filters.
+
+This guide provides examples of using filters.
+
+The `list` method is used in the example snippets of this reference, but you can use the same filtering mechanism with any method that accepts filters.
+
+***
+
+## Match Exact Value
+
+```ts
+const posts = await postModuleService.listPosts({
+ title: "My Post 2",
+})
+```
+
+If you pass a property with its value, only records whose properties exactly match the value are selected.
+
+In the example above, only posts having the title `My Post 2` are retrieved.
+
+***
+
+## Doesn't Match Exact Value
+
+```ts
+const posts = await postModuleService.listPosts({
+ title: {
+ $ne: "My Post",
+ },
+})
+```
+
+To find records with a property that doesn't match a value, pass an object with a `$ne` property. Its value is the value that a record's property shouldn't match.
+
+In the example above, only posts that don't have the title `My Post` are retrieved.
+
+***
+
+## Match Multiple Values
+
+```ts
+const posts = await postModuleService.listPosts({
+ views: [
+ 50,
+ 100,
+ ],
+})
+```
+
+To find records with a property matching multiple values, pass an array of those values as the property's value in the filter.
+
+In the example above, only posts having either `50` or `100` views are retrieved.
+
+***
+
+## Don't Match Multiple Values
+
+```ts
+const posts = await postModuleService.listPosts({
+ title: {
+ $nin: [
+ "My Post",
+ ],
+ },
+})
+```
+
+To find records with a property that doesn't match one or more values, pass an object with a `$nin` property. Its value is an array of multiple values that a record's property shouldn't match.
+
+In the example above, only posts that don't have the title `My Post` are retrieved.
+
+***
+
+## Match Text Like Value
+
+This filter only applies to text-like properties, including `text`, `id`, and `enum` properties.
+
+```ts
+const posts = await postModuleService.listPosts({
+ title: {
+ $like: "My%",
+ },
+})
+```
+
+To perform a `like` filter on a record's property, set the property's value to an object with a `$like` property. Its value is the string to use when applying the `like` filter.
+
+The example above matches all posts whose title starts with `My`.
+
+***
+
+## Filter by Null or Not Null
+
+To retrieve records with a property that is `null`, set the property's value to `null`.
+
+For example:
+
+```ts
+const posts = await postModuleService.listPosts({
+ published_at: null,
+})
+```
+
+In the example above, only posts that have a `null` publish date are retrieved.
+
+On the other hand, to retrieve records with a property that isn't `null`, set the property's value to an object with a `$ne` property.
+
+For example:
+
+```ts
+const posts = await postModuleService.listPosts({
+ published_at: {
+ $ne: null,
+ },
+})
+```
+
+In the example above, only posts that have a publish date are retrieved.
+
+***
+
+## Apply Range Filters
+
+This filter only applies to the `number` and `dateTime` properties.
+
+```ts
+const posts = await postModuleService.listPosts({
+ published_at: {
+ $lt: new Date(),
+ },
+})
+```
+
+To filter a record's property to be within a range, set the property's value to an object with any of the following properties:
+
+1. `$lt`: The property's value must be less than the supplied value.
+2. `$lte`: The property's value must be less than or equal to the supplied value.
+3. `$gt`: The property's value must be greater than the supplied value.
+4. `$gte`: The property's value must be greater than or equal the supplied value.
+
+In the example above, only posts whose `published_at` property is before the current date and time are retrieved.
+
+### Example: Retrieve Posts Published Today
+
+```ts
+const startToday = new Date()
+startToday.setHours(0, 0, 0, 0)
+
+const endToday = new Date()
+endToday.setHours(23, 59, 59, 59)
+
+const posts = await postModuleService.listPosts({
+ published_at: {
+ $gte: startToday,
+ $lte: endToday,
+ },
+})
+```
+
+The `dateTime` property also stores the time. So, when matching for an exact day, you must set a range filter to be between the beginning and end of the day.
+
+In this example, you retrieve the current date twice: once to set its time to `00:00:00`, and another to set its time `23:59:59`. Then, you retrieve posts whose `published_at` property is between `00:00:00` and `23:59:59` of today.
+
+### Example: Range Filter on Number Property
+
+```ts
+const posts = await postModuleService.listPosts({
+ views: {
+ $gte: 50,
+ $lte: 100,
+ },
+})
+```
+
+In the example above, only posts with `views` between `50` and `100` are retrieved.
+
+***
+
+## Apply Or Condition
+
+```ts
+const posts = await postModuleService.listPosts({
+ $or: [
+ {
+ title: "My Post",
+ },
+ {
+ published_at: {
+ $lt: new Date(),
+ },
+ },
+ ],
+})
+```
+
+To use an `or` condition, pass to the filter object the `$or` property, whose value is an array of filters.
+
+In the example above, posts whose title is `My Post` or their `published_at` date is less than the current date and time are retrieved.
+
+***
+
+## Apply And Condition
+
+```ts
+const posts = await postModuleService.listPosts({
+ $and: [
+ {
+ title: "My Post",
+ },
+ {
+ published_at: {
+ $lt: new Date(),
+ },
+ },
+ ],
+})
+```
+
+To use an `and` condition, pass to the filter object the `$and` property, whose value is an array of filters.
+
+In the example above, only posts whose title is `My Post` and their `published_at` date is less than the current date and time are retrieved.
+
+***
+
+## Complex Filters Example
+
+```ts
+const posts = await postModuleService.listPosts({
+ $or: [
+ {
+ $and: [
+ { views: { $gte: 50 } },
+ {
+ published_at: {
+ $gte: new Date(new Date().getFullYear(), 0, 1),
+ },
+ },
+ ],
+ },
+ {
+ title: {
+ $like: "%Featured%",
+ },
+ },
+ ],
+})
+```
+
+In the example above, posts are retrieved if they meet either of the following conditions:
+
+1. The post has at least `50` views and was published after the beginning of the current year.
+2. The post's title contains the word `Featured`.
+
+By combining `and` and `or` conditions, you can create complex filters to retrieve records that meet specific criteria.
+
+
+# list Method - Service Factory Reference
+
+This method retrieves a list of records.
+
+## Retrieve List of Records
+
+```ts
+const posts = await postModuleService.listPosts()
+```
+
+If no parameters are passed, the method returns an array of the first `15` records.
+
+***
+
+## Filter Records
+
+```ts
+const posts = await postModuleService.listPosts({
+ id: ["123", "321"],
+})
+```
+
+### Parameters
+
+To retrieve records matching a set of filters, pass an object of the filters as a first parameter.
+
+Learn more about accepted filters in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/tips/filtering/index.html.md).
+
+### Returns
+
+The method returns an array of the first `15` records matching the filters.
+
+***
+
+## Retrieve Relations
+
+This applies to relations between data models of the same module. To retrieve linked records of different modules, use [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md).
+
+```ts
+const posts = await postModuleService.listPosts({}, {
+ relations: ["author"],
+})
+```
+
+### Parameters
+
+To retrieve records with their relations, pass as a second parameter an object having a `relations` property. `relations`'s value is an array of relation names.
+
+### Returns
+
+The method returns an array of the first `15` records matching the filters.
+
+***
+
+## Select Properties
+
+```ts
+const posts = await postModuleService.listPosts({}, {
+ select: ["id", "name"],
+})
+```
+
+### Parameters
+
+By default, retrieved records have all their properties. To select specific properties to retrieve, pass in the second object parameter a `select` property.
+
+`select`'s value is an array of property names to retrieve.
+
+### Returns
+
+The method returns an array of the first `15` records matching the filters.
+
+***
+
+## Paginate Relations
+
+```ts
+const posts = await postModuleService.listPosts({}, {
+ take: 20,
+ skip: 10,
+})
+```
+
+### Parameters
+
+To paginate the returned records, the second object parameter accepts the following properties:
+
+- `take`: a number indicating how many records to retrieve. By default, it's `15`.
+- `skip`: a number indicating how many records to skip before the retrieved records. By default, it's `0`.
+
+### Returns
+
+The method returns an array of records. The number of records is less than or equal to `take`'s value.
+
+***
+
+## Sort Records
+
+```ts
+const posts = await postModuleService.listPosts({}, {
+ order: {
+ name: "ASC",
+ },
+})
+```
+
+### Parameters
+
+To sort records by one or more properties, pass to the second object parameter the `order` property. Its value is an object whose keys are the property names, and values can either be:
+
+- `ASC` to sort by this property in the ascending order.
+- `DESC` to sort by this property in the descending order.
+
+### Returns
+
+The method returns an array of the first `15` records matching the filters.
+
+
+# restore Method - Service Factory Reference
+
+This method restores one or more records of the data model that were [soft-deleted](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/methods/soft-delete/index.html.md).
+
+## Restore One Record
+
+```ts
+const restoredPosts = await postModuleService.restorePosts("123")
+```
+
+### Parameters
+
+To restore one record, pass its ID as a parameter of the method.
+
+### Returns
+
+The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of restored records' IDs.
+
+For example, the returned object of the above example is:
+
+```ts
+restoredPosts = {
+ post_id: ["123"],
+}
+```
+
+***
+
+## Restore Multiple Records
+
+```ts
+const restoredPosts = await postModuleService.restorePosts([
+ "123",
+ "321",
+])
+```
+
+### Parameters
+
+To restore multiple records, pass an array of IDs as a parameter of the method.
+
+### Returns
+
+The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of restored records' IDs.
+
+For example, the returned object of the above example is:
+
+```ts
+restoredPosts = {
+ post_id: [
+ "123",
+ "321",
+ ],
+}
+```
+
+***
+
+## Restore Records Matching Filters
+
+```ts
+const restoredPosts = await postModuleService.restorePosts({
+ name: "My Post",
+})
+```
+
+### Parameters
+
+To restore records matching a set of filters, pass an object of fitlers as a parameter of the method.
+
+Learn more about accepted filters in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/tips/filtering/index.html.md).
+
+### Returns
+
+The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of restored records' IDs.
+
+For example, the returned object of the above example is:
+
+```ts
+restoredPosts = {
+ post_id: [
+ "123",
+ ],
+}
+```
+
+
# listAndCount Method - Service Factory Reference
This method retrieves a list of records with the total count.
@@ -49364,124 +50065,6 @@ The method returns an array with two items:
2. The second is the total count of records.
-# list Method - Service Factory Reference
-
-This method retrieves a list of records.
-
-## Retrieve List of Records
-
-```ts
-const posts = await postModuleService.listPosts()
-```
-
-If no parameters are passed, the method returns an array of the first `15` records.
-
-***
-
-## Filter Records
-
-```ts
-const posts = await postModuleService.listPosts({
- id: ["123", "321"],
-})
-```
-
-### Parameters
-
-To retrieve records matching a set of filters, pass an object of the filters as a first parameter.
-
-Learn more about accepted filters in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/tips/filtering/index.html.md).
-
-### Returns
-
-The method returns an array of the first `15` records matching the filters.
-
-***
-
-## Retrieve Relations
-
-This applies to relations between data models of the same module. To retrieve linked records of different modules, use [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md).
-
-```ts
-const posts = await postModuleService.listPosts({}, {
- relations: ["author"],
-})
-```
-
-### Parameters
-
-To retrieve records with their relations, pass as a second parameter an object having a `relations` property. `relations`'s value is an array of relation names.
-
-### Returns
-
-The method returns an array of the first `15` records matching the filters.
-
-***
-
-## Select Properties
-
-```ts
-const posts = await postModuleService.listPosts({}, {
- select: ["id", "name"],
-})
-```
-
-### Parameters
-
-By default, retrieved records have all their properties. To select specific properties to retrieve, pass in the second object parameter a `select` property.
-
-`select`'s value is an array of property names to retrieve.
-
-### Returns
-
-The method returns an array of the first `15` records matching the filters.
-
-***
-
-## Paginate Relations
-
-```ts
-const posts = await postModuleService.listPosts({}, {
- take: 20,
- skip: 10,
-})
-```
-
-### Parameters
-
-To paginate the returned records, the second object parameter accepts the following properties:
-
-- `take`: a number indicating how many records to retrieve. By default, it's `15`.
-- `skip`: a number indicating how many records to skip before the retrieved records. By default, it's `0`.
-
-### Returns
-
-The method returns an array of records. The number of records is less than or equal to `take`'s value.
-
-***
-
-## Sort Records
-
-```ts
-const posts = await postModuleService.listPosts({}, {
- order: {
- name: "ASC",
- },
-})
-```
-
-### Parameters
-
-To sort records by one or more properties, pass to the second object parameter the `order` property. Its value is an object whose keys are the property names, and values can either be:
-
-- `ASC` to sort by this property in the ascending order.
-- `DESC` to sort by this property in the descending order.
-
-### Returns
-
-The method returns an array of the first `15` records matching the filters.
-
-
# delete Method - Service Factory Reference
This method deletes one or more records.
@@ -49522,93 +50105,6 @@ To delete records matching a set of filters, pass an object of filters as a para
Learn more about accepted filters in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/tips/filtering/index.html.md).
-# restore Method - Service Factory Reference
-
-This method restores one or more records of the data model that were [soft-deleted](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/methods/soft-delete/index.html.md).
-
-## Restore One Record
-
-```ts
-const restoredPosts = await postModuleService.restorePosts("123")
-```
-
-### Parameters
-
-To restore one record, pass its ID as a parameter of the method.
-
-### Returns
-
-The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of restored records' IDs.
-
-For example, the returned object of the above example is:
-
-```ts
-restoredPosts = {
- post_id: ["123"],
-}
-```
-
-***
-
-## Restore Multiple Records
-
-```ts
-const restoredPosts = await postModuleService.restorePosts([
- "123",
- "321",
-])
-```
-
-### Parameters
-
-To restore multiple records, pass an array of IDs as a parameter of the method.
-
-### Returns
-
-The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of restored records' IDs.
-
-For example, the returned object of the above example is:
-
-```ts
-restoredPosts = {
- post_id: [
- "123",
- "321",
- ],
-}
-```
-
-***
-
-## Restore Records Matching Filters
-
-```ts
-const restoredPosts = await postModuleService.restorePosts({
- name: "My Post",
-})
-```
-
-### Parameters
-
-To restore records matching a set of filters, pass an object of fitlers as a parameter of the method.
-
-Learn more about accepted filters in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/tips/filtering/index.html.md).
-
-### Returns
-
-The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of restored records' IDs.
-
-For example, the returned object of the above example is:
-
-```ts
-restoredPosts = {
- post_id: [
- "123",
- ],
-}
-```
-
-
# retrieve Method - Service Factory Reference
This method retrieves one record of the data model by its ID.
@@ -49666,6 +50162,93 @@ By default, all of the record's properties are retrieved. To select specific one
The method returns the record as an object.
+# softDelete Method - Service Factory Reference
+
+This method soft deletes one or more records of the data model.
+
+## Soft Delete One Record
+
+```ts
+const deletedPosts = await postModuleService.softDeletePosts(
+ "123"
+)
+```
+
+### Parameters
+
+To soft delete a record, pass its ID as a parameter of the method.
+
+### Returns
+
+The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs.
+
+For example, the returned object of the above example is:
+
+```ts
+deletedPosts = {
+ post_id: ["123"],
+}
+```
+
+***
+
+## Soft Delete Multiple Records
+
+```ts
+const deletedPosts = await postModuleService.softDeletePosts([
+ "123",
+ "321",
+])
+```
+
+### Parameters
+
+To soft delete multiple records, pass an array of IDs as a parameter of the method.
+
+### Returns
+
+The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs.
+
+For example, the returned object of the above example is:
+
+```ts
+deletedPosts = {
+ post_id: [
+ "123",
+ "321",
+ ],
+}
+```
+
+***
+
+## Soft Delete Records Matching Filters
+
+```ts
+const deletedPosts = await postModuleService.softDeletePosts({
+ name: "My Post",
+})
+```
+
+### Parameters
+
+To soft delete records matching a set of filters, pass an object of filters as a parameter.
+
+Learn more about accepted filters in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/tips/filtering/index.html.md).
+
+### Returns
+
+The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs.
+
+For example, the returned object of the above example is:
+
+```ts
+deletedPosts = {
+ post_id: ["123"],
+}
+```
+
+
# update Method - Service Factory Reference
This method updates one or more records of the data model.
@@ -49789,236 +50372,6 @@ Learn more about accepted filters in [this documentation](https://docs.medusajs.
The method returns an array of objects of updated records.
-# Filter Records - Service Factory Reference
-
-Many of the service factory's generated methods allow passing filters to perform an operation, such as to update or delete records matching the filters.
-
-This guide provides examples of using filters.
-
-The `list` method is used in the example snippets of this reference, but you can use the same filtering mechanism with any method that accepts filters.
-
-***
-
-## Match Exact Value
-
-```ts
-const posts = await postModuleService.listPosts({
- name: "My Post 2",
-})
-```
-
-If you pass a property with its value, only records whose properties exactly match the value are selected.
-
-In the example above, only posts having the name `My Post 2` are retrieved.
-
-***
-
-## Match Multiple Values
-
-```ts
-const posts = await postModuleService.listPosts({
- views: [
- 50,
- 100,
- ],
-})
-```
-
-To find records with a property matching multiple values, pass an array of those values as the property's value in the filter.
-
-In the example above, only posts having either `50` or `100` views are retrieved.
-
-***
-
-## Don't Match Values
-
-```ts
-const posts = await postModuleService.listPosts({
- name: {
- $nin: [
- "My Post",
- ],
- },
-})
-```
-
-To find records with a property that doesn't match one or more values, pass an object with a `$nin` property. Its value is an array of multiple values that a record's property shouldn't match.
-
-In the example above, only posts that don't have the name `My Post` are retrieved.
-
-***
-
-## Match Text Like Value
-
-This filter only applies to text-like properties, including `id` and `enum` properties.
-
-```ts
-const posts = await postModuleService.listPosts({
- name: {
- $like: "My%",
- },
-})
-```
-
-To perform a `like` filter on a record's property, set the property's value to an object with a `$like` property. Its value is the string to use when applying the `like` filter.
-
-The example above matches all posts whose name starts with `My`.
-
-***
-
-## Apply Range Filters
-
-This filter only applies to the `number` and `dateTime` properties.
-
-```ts
-const posts = await postModuleService.listPosts({
- published_at: {
- $lt: new Date(),
- },
-})
-```
-
-To filter a record's property to be within a range, set the property's value to an object with any of the following properties:
-
-1. `$lt`: The property's value must be less than the supplied value.
-2. `$lte`: The property's value must be less than or equal to the supplied value.
-3. `$gt`: The property's value must be greater than the supplied value.
-4. `$gte`: The property's value must be greater than or equal the supplied value.
-
-In the example above, only posts whose `published_at` property is before the current date and time are retrieved.
-
-### Example: Retrieve Posts Published Today
-
-```ts
-const startToday = new Date()
-startToday.setHours(0, 0, 0, 0)
-
-const endToday = new Date()
-endToday.setHours(23, 59, 59, 59)
-
-const posts = await postModuleService.listPosts({
- published_at: {
- $gte: startToday,
- $lte: endToday,
- },
-})
-```
-
-The `dateTime` property also stores the time. So, when matching for an exact day, you must set a range filter to be between the beginning and end of the day.
-
-In this example, you retrieve the current date twice: once to set its time to `00:00:00`, and another to set its time `23:59:59`. Then, you retrieve posts whose `published_at` property is between `00:00:00` and `23:59:59` of today.
-
-***
-
-## Apply Or Condition
-
-```ts
-const posts = await postModuleService.listPosts({
- $or: [
- {
- name: "My Post",
- },
- {
- published_at: {
- $lt: new Date(),
- },
- },
- ],
-})
-```
-
-To use an `or` condition, pass to the filter object the `$or` property, whose value is an array of filters.
-
-In the example above, posts whose name is `My Post` or their `published_at` date is less than the current date and time are retrieved.
-
-
-# softDelete Method - Service Factory Reference
-
-This method soft deletes one or more records of the data model.
-
-## Soft Delete One Record
-
-```ts
-const deletedPosts = await postModuleService.softDeletePosts(
- "123"
-)
-```
-
-### Parameters
-
-To soft delete a record, pass its ID as a parameter of the method.
-
-### Returns
-
-The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs.
-
-For example, the returned object of the above example is:
-
-```ts
-deletedPosts = {
- post_id: ["123"],
-}
-```
-
-***
-
-## Soft Delete Multiple Records
-
-```ts
-const deletedPosts = await postModuleService.softDeletePosts([
- "123",
- "321",
-])
-```
-
-### Parameters
-
-To soft delete multiple records, pass an array of IDs as a parameter of the method.
-
-### Returns
-
-The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs.
-
-For example, the returned object of the above example is:
-
-```ts
-deletedPosts = {
- post_id: [
- "123",
- "321",
- ],
-}
-```
-
-***
-
-## Soft Delete Records Matching Filters
-
-```ts
-const deletedPosts = await postModuleService.softDeletePosts({
- name: "My Post",
-})
-```
-
-### Parameters
-
-To soft delete records matching a set of filters, pass an object of filters as a parameter.
-
-Learn more about accepted filters in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/tips/filtering/index.html.md).
-
-### Returns
-
-The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs.
-
-For example, the returned object of the above example is:
-
-```ts
-deletedPosts = {
- post_id: ["123"],
-}
-```
-
-
Just Getting Started?
@@ -50579,49 +50932,6 @@ npm install @medusajs/ui
```
-# clx
-
-Utility function for working with classNames.
-
-## Usage
-
-***
-
-The `clx` function is a utility function for working with classNames. It is built using [clsx](https://www.npmjs.com/package/clsx) and [tw-merge](https://www.npmjs.com/package/tw-merge) and is intended to be used with [Tailwind CSS](https://tailwindcss.com/).
-
-```tsx
-import { clx } from "@medusajs/ui"
-
-type BoxProps = {
- className?: string
- children: React.ReactNode
- mt: "sm" | "md" | "lg"
-}
-
-const Box = ({ className, children, mt }: BoxProps) => {
- return (
-
- {children}
-
- )
-}
-
-```
-
-In the above example the utility is used to apply a base style, a margin top that is dependent on the `mt` prop and a custom className.
-The Box component accepts a `className` prop that is merged with the other classNames, and the underlying usage of `tw-merge` ensures that all Tailwind CSS classes are merged without style conflicts.
-
-
# Alert
A component for displaying important messages.
@@ -57015,3 +57325,46 @@ If you're using the `Tooltip` component in a project other than the Medusa Admin
- delayDuration: (number) The duration from when the pointer enters the trigger until the tooltip gets opened. Default: 100
- skipDelayDuration: (number) How much time a user has to enter another trigger without incurring a delay again. Default: 300
- disableHoverableContent: (boolean) When \`true\`, trying to hover the content will result in the tooltip closing as the pointer leaves the trigger.
+
+
+# clx
+
+Utility function for working with classNames.
+
+## Usage
+
+***
+
+The `clx` function is a utility function for working with classNames. It is built using [clsx](https://www.npmjs.com/package/clsx) and [tw-merge](https://www.npmjs.com/package/tw-merge) and is intended to be used with [Tailwind CSS](https://tailwindcss.com/).
+
+```tsx
+import { clx } from "@medusajs/ui"
+
+type BoxProps = {
+ className?: string
+ children: React.ReactNode
+ mt: "sm" | "md" | "lg"
+}
+
+const Box = ({ className, children, mt }: BoxProps) => {
+ return (
+
+ {children}
+
+ )
+}
+
+```
+
+In the above example the utility is used to apply a base style, a margin top that is dependent on the `mt` prop and a custom className.
+The Box component accepts a `className` prop that is merged with the other classNames, and the underlying usage of `tw-merge` ensures that all Tailwind CSS classes are merged without style conflicts.
diff --git a/www/apps/resources/app/deployment/storefront/vercel/page.mdx b/www/apps/resources/app/deployment/storefront/vercel/page.mdx
index f59f4a06ed..f4b167eb8a 100644
--- a/www/apps/resources/app/deployment/storefront/vercel/page.mdx
+++ b/www/apps/resources/app/deployment/storefront/vercel/page.mdx
@@ -2,7 +2,8 @@
sidebar_label: "Vercel"
---
-import { Prerequisites } from "docs-ui"
+import RewritesError from "../../../troubleshooting/_sections/storefront/rewrites.mdx"
+import { Prerequisites, DetailsList } from "docs-ui"
export const metadata = {
title: `Deploy Medusa Next.js to Vercel`,
@@ -143,3 +144,12 @@ Once the redeployment is done, test out the storefront by going to its URL. Try
## Troubleshooting
If you’re running into issues in your storefront, find the logs in your Vercel project’s dashboard under the Logs tab.
+
+
+ }
+ ]}
+/>
\ No newline at end of file
diff --git a/www/apps/resources/app/how-to-tutorials/tutorials/product-reviews/page.mdx b/www/apps/resources/app/how-to-tutorials/tutorials/product-reviews/page.mdx
index 00f54d5a33..feeed13c5e 100644
--- a/www/apps/resources/app/how-to-tutorials/tutorials/product-reviews/page.mdx
+++ b/www/apps/resources/app/how-to-tutorials/tutorials/product-reviews/page.mdx
@@ -337,7 +337,7 @@ export default defineLink(
},
ProductModule.linkable.product,
{
- readOnly: true
+ readOnly: true,
}
)
```
@@ -1610,7 +1610,7 @@ export default defineMiddlewares({
"first_name",
"last_name",
"content",
- "created_at"
+ "created_at",
],
}),
],
diff --git a/www/apps/resources/app/service-factory-reference/tips/filtering/page.mdx b/www/apps/resources/app/service-factory-reference/tips/filtering/page.mdx
index 9f4c518b2c..846834e6d5 100644
--- a/www/apps/resources/app/service-factory-reference/tips/filtering/page.mdx
+++ b/www/apps/resources/app/service-factory-reference/tips/filtering/page.mdx
@@ -24,13 +24,29 @@ The `list` method is used in the example snippets of this reference, but you can
```ts
const posts = await postModuleService.listPosts({
- name: "My Post 2",
+ title: "My Post 2",
})
```
If you pass a property with its value, only records whose properties exactly match the value are selected.
-In the example above, only posts having the name `My Post 2` are retrieved.
+In the example above, only posts having the title `My Post 2` are retrieved.
+
+---
+
+## Doesn't Match Exact Value
+
+```ts
+const posts = await postModuleService.listPosts({
+ title: {
+ $ne: "My Post",
+ },
+})
+```
+
+To find records with a property that doesn't match a value, pass an object with a `$ne` property. Its value is the value that a record's property shouldn't match.
+
+In the example above, only posts that don't have the title `My Post` are retrieved.
---
@@ -51,11 +67,11 @@ In the example above, only posts having either `50` or `100` views are retrieved
---
-## Don't Match Values
+## Don't Match Multiple Values
```ts
const posts = await postModuleService.listPosts({
- name: {
+ title: {
$nin: [
"My Post",
],
@@ -65,7 +81,7 @@ const posts = await postModuleService.listPosts({
To find records with a property that doesn't match one or more values, pass an object with a `$nin` property. Its value is an array of multiple values that a record's property shouldn't match.
-In the example above, only posts that don't have the name `My Post` are retrieved.
+In the example above, only posts that don't have the title `My Post` are retrieved.
---
@@ -73,13 +89,13 @@ In the example above, only posts that don't have the name `My Post` are retrieve
-This filter only applies to text-like properties, including `id` and `enum` properties.
+This filter only applies to text-like properties, including `text`, `id`, and `enum` properties.
```ts
const posts = await postModuleService.listPosts({
- name: {
+ title: {
$like: "My%",
},
})
@@ -87,7 +103,37 @@ const posts = await postModuleService.listPosts({
To perform a `like` filter on a record's property, set the property's value to an object with a `$like` property. Its value is the string to use when applying the `like` filter.
-The example above matches all posts whose name starts with `My`.
+The example above matches all posts whose title starts with `My`.
+
+---
+
+## Filter by Null or Not Null
+
+To retrieve records with a property that is `null`, set the property's value to `null`.
+
+For example:
+
+```ts
+const posts = await postModuleService.listPosts({
+ published_at: null,
+})
+```
+
+In the example above, only posts that have a `null` publish date are retrieved.
+
+On the other hand, to retrieve records with a property that isn't `null`, set the property's value to an object with a `$ne` property.
+
+For example:
+
+```ts
+const posts = await postModuleService.listPosts({
+ published_at: {
+ $ne: null,
+ },
+})
+```
+
+In the example above, only posts that have a publish date are retrieved.
---
@@ -137,6 +183,19 @@ The `dateTime` property also stores the time. So, when matching for an exact day
In this example, you retrieve the current date twice: once to set its time to `00:00:00`, and another to set its time `23:59:59`. Then, you retrieve posts whose `published_at` property is between `00:00:00` and `23:59:59` of today.
+### Example: Range Filter on Number Property
+
+```ts
+const posts = await postModuleService.listPosts({
+ views: {
+ $gte: 50,
+ $lte: 100,
+ },
+})
+```
+
+In the example above, only posts with `views` between `50` and `100` are retrieved.
+
---
## Apply Or Condition
@@ -145,7 +204,7 @@ In this example, you retrieve the current date twice: once to set its time to `0
const posts = await postModuleService.listPosts({
$or: [
{
- name: "My Post",
+ title: "My Post",
},
{
published_at: {
@@ -158,4 +217,60 @@ const posts = await postModuleService.listPosts({
To use an `or` condition, pass to the filter object the `$or` property, whose value is an array of filters.
-In the example above, posts whose name is `My Post` or their `published_at` date is less than the current date and time are retrieved.
+In the example above, posts whose title is `My Post` or their `published_at` date is less than the current date and time are retrieved.
+
+---
+
+## Apply And Condition
+
+```ts
+const posts = await postModuleService.listPosts({
+ $and: [
+ {
+ title: "My Post",
+ },
+ {
+ published_at: {
+ $lt: new Date(),
+ },
+ },
+ ],
+})
+```
+
+To use an `and` condition, pass to the filter object the `$and` property, whose value is an array of filters.
+
+In the example above, only posts whose title is `My Post` and their `published_at` date is less than the current date and time are retrieved.
+
+---
+
+## Complex Filters Example
+
+```ts
+const posts = await postModuleService.listPosts({
+ $or: [
+ {
+ $and: [
+ { views: { $gte: 50 } },
+ {
+ published_at: {
+ $gte: new Date(new Date().getFullYear(), 0, 1),
+ },
+ },
+ ],
+ },
+ {
+ title: {
+ $like: "%Featured%",
+ },
+ },
+ ],
+})
+```
+
+In the example above, posts are retrieved if they meet either of the following conditions:
+
+1. The post has at least `50` views and was published after the beginning of the current year.
+2. The post's title contains the word `Featured`.
+
+By combining `and` and `or` conditions, you can create complex filters to retrieve records that meet specific criteria.
\ No newline at end of file
diff --git a/www/apps/resources/app/troubleshooting/_sections/admin/blocked-request.mdx b/www/apps/resources/app/troubleshooting/_sections/admin/blocked-request.mdx
new file mode 100644
index 0000000000..75b6f4f4a9
--- /dev/null
+++ b/www/apps/resources/app/troubleshooting/_sections/admin/blocked-request.mdx
@@ -0,0 +1,51 @@
+If you see the following error in the Medusa Admin's console:
+
+```bash
+Blocked request. This host (X) is not allowed. To allow this host, add X to server.allowedHosts in vite.config.js.
+```
+
+Where `X` is the host that's being blocked. For example, `example.com`.
+
+## Why this Error Occurred
+
+This error occurs if you deploy Medusa but are using development mode (`NODE_ENV=development`). This can happen accidentally or unintentionally, but Medusa defaults `NODE_ENV` to `production`.
+
+## How to Fix it
+
+### Option 1: Use Production Mode
+
+To resolve this error, ensure that you're running Medusa in production mode. You can set the `NODE_ENV` environment variable to `production` when starting Medusa:
+
+```bash
+NODE_ENV=production
+```
+
+### Option 2: Allow the Host
+
+If you intentionally want to use development mode in your deployed Medusa instance, you can allow the host that's being blocked using the `admin.vite` configuration in `medusa-config.ts`.
+
+For example:
+
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ // ...
+ admin: {
+ vite: () => {
+ return {
+ server: {
+ allowedHosts: [".example.com"],
+ },
+ }
+ },
+ },
+})
+```
+
+In the above example, you allow the host `example.com` to access the Medusa Admin. Make sure that when you replace `example.com` with your actual host, you include the leading `.` before the domain name.
+
+---
+
+## Additional Resources
+
+- [Environment Variables](!docs!/learn/fundamentals/environment-variables)
+- [admin.vite Configuration](!docs!/learn/configurations/medusa-config#vite)
diff --git a/www/apps/resources/app/troubleshooting/_sections/api-routes/additional-data.mdx b/www/apps/resources/app/troubleshooting/_sections/api-routes/additional-data.mdx
new file mode 100644
index 0000000000..e617f07a39
--- /dev/null
+++ b/www/apps/resources/app/troubleshooting/_sections/api-routes/additional-data.mdx
@@ -0,0 +1,22 @@
+If you send a request to an API route and receive the following error:
+
+```json
+{
+ "type": "invalid_data",
+ "message": "Invalid request: Unrecognized fields: 'additional_data'"
+}
+```
+
+## Why this Error Occured
+
+This error occurs when you send the `additional_data` request body parameter to a route that doesn't support it.
+
+The [Additional Data property](https://docs.medusajs.com/learn/fundamentals/api-routes/additional-data) is useful to pass custom data to an API route, but it's not supported by all API routes.
+
+---
+
+## How to Fix it
+
+For the list of API routes that support passing `additional_data`, refer to the [Additional Data documentation](https://docs.medusajs.com/learn/fundamentals/api-routes/additional-data).
+
+If the route you need isn't on the list, you have to create a custom API route with your desired functionality.
diff --git a/www/apps/resources/app/troubleshooting/_sections/data-models/default-fields.mdx b/www/apps/resources/app/troubleshooting/_sections/data-models/default-fields.mdx
new file mode 100644
index 0000000000..f239e06b0e
--- /dev/null
+++ b/www/apps/resources/app/troubleshooting/_sections/data-models/default-fields.mdx
@@ -0,0 +1,23 @@
+If you get the following error after creating a new data model in your Medusa application:
+
+```bash
+error: Error: Cannot define field(s) "created_at,updated_at,deleted_at" as they are implicitly defined on every model
+```
+
+The error may include the three fields, or only some of them, based on which fields you're trying to define in the data model.
+
+## Why this Error Occurred
+
+This error occurs because you're trying to define the `created_at`, `updated_at`, or `deleted_at` properties on a new data model. These properties are implicitly defined on every data model in Medusa and can't be redefined.
+
+---
+
+## How to Fix it
+
+To resolve this error, remove the `created_at`, `updated_at`, and `deleted_at` properties from your data model definition. You'll still be able to use these properties in your application, but you can't redefine them in your data models.
+
+---
+
+## Additional Resources
+
+- [Data Model's Default Properties](!docs!/learn/fundamentals/data-models/properties#data-models-default-properties)
diff --git a/www/apps/resources/app/troubleshooting/_sections/query/not-existing-property.mdx b/www/apps/resources/app/troubleshooting/_sections/query/not-existing-property.mdx
new file mode 100644
index 0000000000..39f95df724
--- /dev/null
+++ b/www/apps/resources/app/troubleshooting/_sections/query/not-existing-property.mdx
@@ -0,0 +1,55 @@
+If you get the following error in your Medusa application's terminal:
+
+```bash
+Trying to query by not existing property LinkModel.X.
+```
+
+Where `X` is the name of a data model. For example, `LinkModel.cart`.
+
+## Why this Error Occurred
+
+This error occurs when you're using Query and you're trying to filter by a linked module, which isn't allowed.
+
+For example, assuming you have a `Post` data model and you linked it to the `Cart` data model, this isn't allowed:
+
+```ts
+const { data } = await query.graph({
+ entity: "post",
+ fields: ["*"],
+ filter: {
+ cart: {
+ id: "cart_123",
+ },
+ },
+})
+```
+
+You can't filter your custom `post` data model by the ID of their linked cart.
+
+---
+
+## How to Fix it
+
+You need to query the link's table directly and apply the filters on its ID columns. For example:
+
+```ts
+import PostCartLink from "../links/post-cart"
+
+// ...
+
+const { data } = await query.graph({
+ entity: PostCartLink.entryPoint,
+ fields: ["post.*", "cart.*"],
+ filters: {
+ cart_id: "cart_123",
+ },
+})
+```
+
+In the above example, you query the `PostCartLink` data model directly and apply the filters to the `cart_id` field, which holds the ID of the cart linked to the post. You'll then only retrieve the posts linked to the cart with the ID `cart_123`.
+
+---
+
+## Additional Resources
+
+- [Query](!docs!/learn/fundamentals/module-links/query#apply-filters-and-pagination-on-linked-records)
diff --git a/www/apps/resources/app/troubleshooting/_sections/query/service-list.mdx b/www/apps/resources/app/troubleshooting/_sections/query/service-list.mdx
new file mode 100644
index 0000000000..c7d0c0b609
--- /dev/null
+++ b/www/apps/resources/app/troubleshooting/_sections/query/service-list.mdx
@@ -0,0 +1,86 @@
+If you get the following error when using Query to retrieve records of your custom data model:
+
+```bash
+service.list is not a function
+```
+
+## Why this Error Occurred
+
+To retrieve records of your data model, Query uses the `listX` method of your module's service.
+
+For example, if you have a Blog Module and you are trying to retrieve posts, Query will try to call the `listPost` method of the Blog Module's service. If it doesn't find the method, it will throw the above error.
+
+---
+
+## How to Fix it
+
+### Option 1: Extend Service Factory
+
+To resolve this error, make sure that your module's service has a `listX` method that returns the records of your data model.
+
+This method is generated for you if your service extends `MedusaService`:
+
+```ts title="src/modules/blog/service.ts"
+import { MedusaService } from "@medusajs/framework/utils"
+import Post from "./models/post"
+
+class BlogModuleService extends MedusaService({
+ Post,
+}){
+}
+
+export default BlogModuleService
+```
+
+In the above example, the `listPost` method is generated for you because the `BlogModuleService` extends `MedusaService`.
+
+### Option 2: Implement the Method
+
+If your module is returning data from a third-party service, or you have some custom mechanism to define and manage your data models, then you need to implement the `list` or just `list` method in your service.
+
+For example:
+
+```ts title="src/modules/blog/service.ts"
+type BlogModuleOptions = {
+ apiKey: string
+}
+
+export default class BlogModuleService {
+ private client
+
+ constructor({}, options: BlogModuleOptions) {
+ this.client = new Client(options)
+ }
+
+ async list(
+ filter: {
+ id: string | string[]
+ }
+ ) {
+ return this.client.getPosts(filter)
+ /**
+ * Example of returned data:
+ *
+ * [
+ * {
+ * "id": "post_123",
+ * "product_id": "prod_321"
+ * },
+ * {
+ * "id": "post_456",
+ * "product_id": "prod_654"
+ * }
+ * ]
+ */
+ }
+}
+```
+
+---
+
+## Additional Resources
+
+- [Query](!docs!/learn/fundamentals/module-links/query)
+- [Service Factory documentation](!docs!/learn/fundamentals/modules/service-factory)
+- [Service Factory reference](../../../service-factory-reference/page.mdx)
+- [Example: Read-Only Module Link for Virtual Data Models](!docs!/learn/fundamentals/module-links/read-only#example-read-only-module-link-for-virtual-data-models)
diff --git a/www/apps/resources/app/troubleshooting/_sections/storefront/pak-sc.mdx b/www/apps/resources/app/troubleshooting/_sections/storefront/pak-sc.mdx
new file mode 100644
index 0000000000..d457b465fa
--- /dev/null
+++ b/www/apps/resources/app/troubleshooting/_sections/storefront/pak-sc.mdx
@@ -0,0 +1,18 @@
+If you send a request to the `/store/products` or `/store/products/:id` API routes and receive the following error in the response:
+
+```bash
+{
+ "type": "invalid_data",
+ "message": "Publishable key needs to have a sales channel configured."
+}
+```
+
+## Why this Error Occurred
+
+This error means you passed a valid `x-publishable-api-key` header, but the key does not have a sales channel configured. The sales channel is required to make requests to the `/store/products` and `/store/products/:id` routes, as it's used to retrieve products available in that sales channel.
+
+---
+
+## How to Fix it
+
+To resolve this error, you need to add at least one sales channel to your publishable API key. Refer to the [Manage Publishable API Key User Guide](!user-guide!/settings/developer/publishable-api-keys#manage-publishable-api-keys-sales-channels) to learn how to do that.
diff --git a/www/apps/resources/app/troubleshooting/_sections/storefront/pak.mdx b/www/apps/resources/app/troubleshooting/_sections/storefront/pak.mdx
new file mode 100644
index 0000000000..bcdfc388a0
--- /dev/null
+++ b/www/apps/resources/app/troubleshooting/_sections/storefront/pak.mdx
@@ -0,0 +1,33 @@
+If you receive the following error response when you send a request to a `/store` route:
+
+```json
+{
+ "type": "not_allowed",
+ "message": "Publishable API key required in the request header: x-publishable-api-key. You can manage your keys in settings in the dashboard."
+}
+```
+
+## Why this Error Occurred
+
+This error occurs because the request is missing the `x-publishable-api-key` header. The `x-publishable-api-key` header is required for all requests to the `/store` route.
+
+---
+
+## How to Fix it
+
+To resolve this error, add the `x-publishable-api-key` header to your request. You can find your publishable API key in the [Medusa Admin Dashboard](!user-guide!/settings/developer/publishable-api-keys).
+
+For example:
+
+```bash
+curl -X GET https://localhost:9000/store/products \
+ -H "x-publishable-api-key: your-publishable-api-key"
+```
+
+Where `your-publishable-api-key` is your publishable API key.
+
+---
+
+## Additional Resources
+
+- [API reference](!api!/store#publishable-api-key)
diff --git a/www/apps/resources/app/troubleshooting/_sections/storefront/rewrites.mdx b/www/apps/resources/app/troubleshooting/_sections/storefront/rewrites.mdx
new file mode 100644
index 0000000000..f7b537e5b5
--- /dev/null
+++ b/www/apps/resources/app/troubleshooting/_sections/storefront/rewrites.mdx
@@ -0,0 +1,32 @@
+The Next.js Starter Storefront uses parallel routes to show account pages. However, there's a [reported error](https://github.com/vercel/next.js/issues/71626) when hosting Next.js with parallel routes on Google's Cloud Run, where the error stops working.
+
+## Why this Error Occurred
+
+This error occurs because Google Cloud Run decodes chunk URLs, but Next.js expects them to be encoded.
+
+---
+
+## How to Fix it
+
+To resolve the error, add the following rewrites in your Next.js Starter Storefront's `next.config.ts` file:
+
+```ts title="next.config.ts"
+const nextConfig = {
+ // ... other config
+ rewrites: async () => {
+ return {
+ beforeFiles: [
+ {
+ source: "/_next/static/chunks/app/:folder*/@login/:path*",
+ destination: "/_next/static/chunks/app/:folder*/%40login/:path*",
+ },
+ // Repeat this pattern if other similar errors occur
+ ],
+ afterFiles: [],
+ fallback: [],
+ }
+ },
+}
+```
+
+You can add the rewrite to other parallel routes you have in your application. For example, if you have a `/@dashboard` route, you can add a rewrite for it as well.
\ No newline at end of file
diff --git a/www/apps/resources/app/troubleshooting/_sections/workflows/step-x-defined.mdx b/www/apps/resources/app/troubleshooting/_sections/workflows/step-x-defined.mdx
new file mode 100644
index 0000000000..f756b964fc
--- /dev/null
+++ b/www/apps/resources/app/troubleshooting/_sections/workflows/step-x-defined.mdx
@@ -0,0 +1,45 @@
+If you get the following error when you start the Medusa application:
+
+```bash
+Error: Step X is already defined in workflow.
+```
+
+Where `X` is any step, such as `create-remote-link`.
+
+## Why this Error Occurred
+
+This error indicates that you're re-using a step in a workflow, meaning that more than one step in the workflow have the same name.
+
+---
+
+## How to Fix it
+
+To resolve this error, use the `config` method of a step to specify a custom name within the workflow's scope. For example:
+
+```ts
+const helloWorkflow = createWorkflow(
+ "hello",
+ () => {
+ const { data: products } = useQueryGraphStep({
+ entity: "product",
+ fields: ["id"],
+ })
+
+ // ✓ No error occurs, the step has a different ID.
+ const { data: customers } = useQueryGraphStep({
+ entity: "customer",
+ fields: ["id"],
+ }).config({ name: "fetch-customers" })
+ }
+)
+```
+
+In the above example, you use the `config` method on the second `useQueryGraphStep` usage to change its name to `fetch-customers`.
+
+Make sure to change the name of every usage after the first one in a workflow.
+
+---
+
+## Additional Resources
+
+- [Multiple Step Usage in Workflow documentation](https://docs.medusajs.com/learn/fundamentals/workflows/multiple-step-usage)
diff --git a/www/apps/resources/app/troubleshooting/api-routes/page.mdx b/www/apps/resources/app/troubleshooting/api-routes/page.mdx
new file mode 100644
index 0000000000..78e602d6ba
--- /dev/null
+++ b/www/apps/resources/app/troubleshooting/api-routes/page.mdx
@@ -0,0 +1,9 @@
+import AdditionalDataError from "../_sections/api-routes/additional-data.mdx"
+
+export const metadata = {
+ title: `Unrecognized fields \`additiona_data\` Error`,
+}
+
+# {metadata.title}
+
+
\ No newline at end of file
diff --git a/www/apps/resources/app/troubleshooting/data-models/default-fields/page.mdx b/www/apps/resources/app/troubleshooting/data-models/default-fields/page.mdx
new file mode 100644
index 0000000000..b578a82c16
--- /dev/null
+++ b/www/apps/resources/app/troubleshooting/data-models/default-fields/page.mdx
@@ -0,0 +1,9 @@
+import DataModelDefaults from "../../_sections/data-models/default-fields.mdx"
+
+export const metadata = {
+ title: `Can't define fields Error`,
+}
+
+# {metadata.title}
+
+
\ No newline at end of file
diff --git a/www/apps/resources/app/troubleshooting/medusa-admin/blocked-request/page.mdx b/www/apps/resources/app/troubleshooting/medusa-admin/blocked-request/page.mdx
new file mode 100644
index 0000000000..3ee1323cf1
--- /dev/null
+++ b/www/apps/resources/app/troubleshooting/medusa-admin/blocked-request/page.mdx
@@ -0,0 +1,9 @@
+import AdminBlockedRequest from "../../_sections/admin/blocked-request.mdx"
+
+export const metadata = {
+ title: `Blocked Request Error in Medusa Admin`,
+}
+
+# {metadata.title}
+
+
\ No newline at end of file
diff --git a/www/apps/resources/app/troubleshooting/nextjs-starter-rewrites/page.mdx b/www/apps/resources/app/troubleshooting/nextjs-starter-rewrites/page.mdx
new file mode 100644
index 0000000000..5858d1efc0
--- /dev/null
+++ b/www/apps/resources/app/troubleshooting/nextjs-starter-rewrites/page.mdx
@@ -0,0 +1,9 @@
+import RewritesTroubleshooting from "../_sections/storefront/rewrites.mdx"
+
+export const metadata = {
+ title: `Next.js Starter Storefront Login Page Error on Google Cloud Run`,
+}
+
+# {metadata.title}
+
+
\ No newline at end of file
diff --git a/www/apps/resources/app/troubleshooting/query/filter-linked/page.mdx b/www/apps/resources/app/troubleshooting/query/filter-linked/page.mdx
new file mode 100644
index 0000000000..6fac75c879
--- /dev/null
+++ b/www/apps/resources/app/troubleshooting/query/filter-linked/page.mdx
@@ -0,0 +1,9 @@
+import NotExistingPropertyError from "../../_sections/query/not-existing-property.mdx"
+
+export const metadata = {
+ title: `Trying to query by not existing property Error`,
+}
+
+# {metadata.title}
+
+
\ No newline at end of file
diff --git a/www/apps/resources/app/troubleshooting/query/service-list/page.mdx b/www/apps/resources/app/troubleshooting/query/service-list/page.mdx
new file mode 100644
index 0000000000..aacea011f9
--- /dev/null
+++ b/www/apps/resources/app/troubleshooting/query/service-list/page.mdx
@@ -0,0 +1,9 @@
+import ServiceListError from "../../_sections/query/service-list.mdx"
+
+export const metadata = {
+ title: `service.list is not a function Error`,
+}
+
+# {metadata.title}
+
+
\ No newline at end of file
diff --git a/www/apps/resources/app/troubleshooting/storefront-missing-pak/page.mdx b/www/apps/resources/app/troubleshooting/storefront-missing-pak/page.mdx
new file mode 100644
index 0000000000..09e7d06443
--- /dev/null
+++ b/www/apps/resources/app/troubleshooting/storefront-missing-pak/page.mdx
@@ -0,0 +1,9 @@
+import PakTroubleshooting from "../_sections/storefront/pak.mdx"
+
+export const metadata = {
+ title: `Publishable API key required Error`,
+}
+
+# {metadata.title}
+
+
\ No newline at end of file
diff --git a/www/apps/resources/app/troubleshooting/storefront-pak-sc/page.mdx b/www/apps/resources/app/troubleshooting/storefront-pak-sc/page.mdx
new file mode 100644
index 0000000000..91120db3db
--- /dev/null
+++ b/www/apps/resources/app/troubleshooting/storefront-pak-sc/page.mdx
@@ -0,0 +1,9 @@
+import PakScTroubleshooting from "../_sections/storefront/pak-sc.mdx"
+
+export const metadata = {
+ title: `Publishable API needs to have a sales channel Error`,
+}
+
+# {metadata.title}
+
+
\ No newline at end of file
diff --git a/www/apps/resources/app/troubleshooting/workflow-errors/page.mdx b/www/apps/resources/app/troubleshooting/workflow-errors/page.mdx
deleted file mode 100644
index 0de9db5ec8..0000000000
--- a/www/apps/resources/app/troubleshooting/workflow-errors/page.mdx
+++ /dev/null
@@ -1,24 +0,0 @@
-export const metadata = {
- title: `Workflow Errors`,
-}
-
-# {metadata.title}
-
-## When-Then Error: Handler for action X Not Found
-
-The following error may occur in production if you use a `when-then` block in your workflow:
-
-```plain
-custom-workflow:when-then-01JE8Z0M1FXSE2NCK1G04S0RR2:invoke - Handler for action \"when-then-01JE8Z0M1FXSE2NCK1G04S0RR2\" not found...
-```
-
-This occurs if the `when-then` block doesn't return a step's result and doesn't have a name specified. You can resolve it by passing a name as a first parameter of `when`:
-
-```ts
-const result = when(
- "custom-when-condition"
- // ... rest of the parameters
-)
-```
-
-Learn more about passing a name for `when-then` in [this documentation](!docs!/learn/fundamentals/workflows/conditions#specify-name-for-when-then)
\ No newline at end of file
diff --git a/www/apps/resources/app/troubleshooting/workflow-errors/step-x-defined/page.mdx b/www/apps/resources/app/troubleshooting/workflow-errors/step-x-defined/page.mdx
new file mode 100644
index 0000000000..434a1d11cf
--- /dev/null
+++ b/www/apps/resources/app/troubleshooting/workflow-errors/step-x-defined/page.mdx
@@ -0,0 +1,9 @@
+import StepXDefined from "../../_sections/workflows/step-x-defined.mdx"
+
+export const metadata = {
+ title: `Step X is already defined in workflow Error`,
+}
+
+# {metadata.title}
+
+
\ No newline at end of file
diff --git a/www/apps/resources/app/troubleshooting/workflow-errors/when-then/page.mdx b/www/apps/resources/app/troubleshooting/workflow-errors/when-then/page.mdx
new file mode 100644
index 0000000000..c1827833b8
--- /dev/null
+++ b/www/apps/resources/app/troubleshooting/workflow-errors/when-then/page.mdx
@@ -0,0 +1,49 @@
+export const metadata = {
+ title: `Handler for action X Not Found Workflow Error`,
+}
+
+# {metadata.title}
+
+The following error may occur in production if you use a `when-then` block in your workflow:
+
+```plain
+custom-workflow:when-then-01JE8Z0M1FXSE2NCK1G04S0RR2:invoke - Handler for action \"when-then-01JE8Z0M1FXSE2NCK1G04S0RR2\" not found...
+```
+
+## Why this Error Occured
+
+This error occurs if the `when-then` block doesn't return a step's result and doesn't have a name specified.
+
+For example:
+
+```ts
+when(input, (input) => !input.is_active)
+ .then(() => {
+ console.log("not returning anything")
+ })
+```
+
+The above `when-then` block doesn't return a step's result, which causes the error.
+
+---
+
+## How to Fix it
+
+You can resolve this error by passing a name as a first parameter of `when`:
+
+```ts
+const result = when(
+ "custom-when-condition",
+ input,
+ (input) => !input.is_active
+ )
+ .then(() => {
+ console.log("not returning anything")
+ })
+```
+
+---
+
+## Additional Resources
+
+- [When-Then documentation](!docs!/learn/fundamentals/workflows/conditions#specify-name-for-when-then)
diff --git a/www/apps/resources/generated/edit-dates.mjs b/www/apps/resources/generated/edit-dates.mjs
index 92b68175e3..a185acf438 100644
--- a/www/apps/resources/generated/edit-dates.mjs
+++ b/www/apps/resources/generated/edit-dates.mjs
@@ -102,7 +102,7 @@ export const generatedEditDates = {
"app/create-medusa-app/page.mdx": "2025-01-16T10:00:25.975Z",
"app/deployment/admin/vercel/page.mdx": "2024-10-16T08:10:29.377Z",
"app/deployment/medusa-application/railway/page.mdx": "2025-03-11T08:56:02.100Z",
- "app/deployment/storefront/vercel/page.mdx": "2025-01-06T12:19:31.142Z",
+ "app/deployment/storefront/vercel/page.mdx": "2025-03-21T07:19:24.818Z",
"app/deployment/page.mdx": "2024-11-25T14:31:45.277Z",
"app/integrations/page.mdx": "2025-02-26T11:37:17.771Z",
"app/medusa-cli/page.mdx": "2024-08-28T11:25:32.382Z",
@@ -132,7 +132,7 @@ export const generatedEditDates = {
"app/service-factory-reference/methods/retrieve/page.mdx": "2024-07-31T17:01:33+03:00",
"app/service-factory-reference/methods/soft-delete/page.mdx": "2024-07-31T17:01:33+03:00",
"app/service-factory-reference/methods/update/page.mdx": "2024-07-31T17:01:33+03:00",
- "app/service-factory-reference/tips/filtering/page.mdx": "2024-07-31T17:01:33+03:00",
+ "app/service-factory-reference/tips/filtering/page.mdx": "2025-03-21T08:32:39.125Z",
"app/service-factory-reference/page.mdx": "2024-07-26T14:40:56+00:00",
"app/storefront-development/cart/context/page.mdx": "2025-01-06T16:00:34.296Z",
"app/storefront-development/cart/create/page.mdx": "2025-02-26T11:44:58.922Z",
@@ -5578,7 +5578,6 @@ export const generatedEditDates = {
"references/types/DmlTypes/types/types.DmlTypes.KnownDataTypes/page.mdx": "2024-12-17T16:57:19.922Z",
"references/types/DmlTypes/types/types.DmlTypes.RelationshipTypes/page.mdx": "2024-12-10T14:54:55.435Z",
"app/recipes/commerce-automation/restock-notification/page.mdx": "2025-03-17T07:36:21.511Z",
- "app/troubleshooting/workflow-errors/page.mdx": "2024-12-11T08:44:36.598Z",
"app/integrations/guides/shipstation/page.mdx": "2025-02-26T11:21:46.879Z",
"app/nextjs-starter/guides/customize-stripe/page.mdx": "2024-12-25T14:48:55.877Z",
"references/core_flows/Cart/Workflows_Cart/functions/core_flows.Cart.Workflows_Cart.listShippingOptionsForCartWithPricingWorkflow/page.mdx": "2025-03-04T13:33:40.584Z",
@@ -6061,5 +6060,15 @@ export const generatedEditDates = {
"app/nextjs-starter/guides/revalidate-cache/page.mdx": "2025-03-18T08:47:59.628Z",
"app/storefront-development/cart/totals/page.mdx": "2025-03-18T09:20:59.533Z",
"app/storefront-development/checkout/order-confirmation/page.mdx": "2025-03-18T09:44:14.561Z",
- "app/how-to-tutorials/tutorials/product-reviews/page.mdx": "2025-03-19T13:00:56.901Z"
+ "app/how-to-tutorials/tutorials/product-reviews/page.mdx": "2025-03-19T13:00:56.901Z",
+ "app/troubleshooting/api-routes/page.mdx": "2025-03-21T07:17:56.248Z",
+ "app/troubleshooting/data-models/default-fields/page.mdx": "2025-03-21T06:59:06.775Z",
+ "app/troubleshooting/medusa-admin/blocked-request/page.mdx": "2025-03-21T06:53:34.854Z",
+ "app/troubleshooting/nextjs-starter-rewrites/page.mdx": "2025-03-21T07:09:08.901Z",
+ "app/troubleshooting/query/filter-linked/page.mdx": "2025-03-21T07:07:51.046Z",
+ "app/troubleshooting/query/service-list/page.mdx": "2025-03-21T07:08:32.825Z",
+ "app/troubleshooting/storefront-missing-pak/page.mdx": "2025-03-21T07:08:52.294Z",
+ "app/troubleshooting/storefront-pak-sc/page.mdx": "2025-03-21T07:08:57.546Z",
+ "app/troubleshooting/workflow-errors/step-x-defined/page.mdx": "2025-03-21T07:09:02.741Z",
+ "app/troubleshooting/workflow-errors/when-then/page.mdx": "2025-03-21T08:35:45.145Z"
}
\ No newline at end of file
diff --git a/www/apps/resources/generated/files-map.mjs b/www/apps/resources/generated/files-map.mjs
index 10a7d1d8c4..6ce4efae9c 100644
--- a/www/apps/resources/generated/files-map.mjs
+++ b/www/apps/resources/generated/files-map.mjs
@@ -1247,6 +1247,10 @@ export const filesMap = [
"filePath": "/www/apps/resources/app/tools/page.mdx",
"pathname": "/tools"
},
+ {
+ "filePath": "/www/apps/resources/app/troubleshooting/api-routes/page.mdx",
+ "pathname": "/troubleshooting/api-routes"
+ },
{
"filePath": "/www/apps/resources/app/troubleshooting/cors-errors/page.mdx",
"pathname": "/troubleshooting/cors-errors"
@@ -1255,6 +1259,10 @@ export const filesMap = [
"filePath": "/www/apps/resources/app/troubleshooting/create-medusa-app-errors/page.mdx",
"pathname": "/troubleshooting/create-medusa-app-errors"
},
+ {
+ "filePath": "/www/apps/resources/app/troubleshooting/data-models/default-fields/page.mdx",
+ "pathname": "/troubleshooting/data-models/default-fields"
+ },
{
"filePath": "/www/apps/resources/app/troubleshooting/database-errors/page.mdx",
"pathname": "/troubleshooting/database-errors"
@@ -1279,25 +1287,53 @@ export const filesMap = [
"filePath": "/www/apps/resources/app/troubleshooting/general-errors/page.mdx",
"pathname": "/troubleshooting/general-errors"
},
+ {
+ "filePath": "/www/apps/resources/app/troubleshooting/medusa-admin/blocked-request/page.mdx",
+ "pathname": "/troubleshooting/medusa-admin/blocked-request"
+ },
{
"filePath": "/www/apps/resources/app/troubleshooting/medusa-admin/no-widget-route/page.mdx",
"pathname": "/troubleshooting/medusa-admin/no-widget-route"
},
+ {
+ "filePath": "/www/apps/resources/app/troubleshooting/nextjs-starter-rewrites/page.mdx",
+ "pathname": "/troubleshooting/nextjs-starter-rewrites"
+ },
{
"filePath": "/www/apps/resources/app/troubleshooting/page.mdx",
"pathname": "/troubleshooting"
},
+ {
+ "filePath": "/www/apps/resources/app/troubleshooting/query/filter-linked/page.mdx",
+ "pathname": "/troubleshooting/query/filter-linked"
+ },
+ {
+ "filePath": "/www/apps/resources/app/troubleshooting/query/service-list/page.mdx",
+ "pathname": "/troubleshooting/query/service-list"
+ },
{
"filePath": "/www/apps/resources/app/troubleshooting/s3/page.mdx",
"pathname": "/troubleshooting/s3"
},
+ {
+ "filePath": "/www/apps/resources/app/troubleshooting/storefront-missing-pak/page.mdx",
+ "pathname": "/troubleshooting/storefront-missing-pak"
+ },
+ {
+ "filePath": "/www/apps/resources/app/troubleshooting/storefront-pak-sc/page.mdx",
+ "pathname": "/troubleshooting/storefront-pak-sc"
+ },
{
"filePath": "/www/apps/resources/app/troubleshooting/test-errors/page.mdx",
"pathname": "/troubleshooting/test-errors"
},
{
- "filePath": "/www/apps/resources/app/troubleshooting/workflow-errors/page.mdx",
- "pathname": "/troubleshooting/workflow-errors"
+ "filePath": "/www/apps/resources/app/troubleshooting/workflow-errors/step-x-defined/page.mdx",
+ "pathname": "/troubleshooting/workflow-errors/step-x-defined"
+ },
+ {
+ "filePath": "/www/apps/resources/app/troubleshooting/workflow-errors/when-then/page.mdx",
+ "pathname": "/troubleshooting/workflow-errors/when-then"
},
{
"filePath": "/www/apps/resources/references/api_key/IApiKeyModuleService/methods/api_key.IApiKeyModuleService.authenticate/page.mdx",
diff --git a/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs b/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs
index adfee854bd..150dff7421 100644
--- a/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs
+++ b/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs
@@ -1071,6 +1071,14 @@ const generatedgeneratedCommerceModulesSidebarSidebar = {
"sort_sidebar": "alphabetize",
"description": "Learn how to use the Cart Module in your customizations on the Medusa application server.",
"children": [
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "ref",
+ "title": "Abandoned Cart Notification",
+ "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/abandoned-cart",
+ "children": []
+ },
{
"loaded": true,
"isPathHref": true,
diff --git a/www/apps/resources/generated/generated-troubleshooting-sidebar.mjs b/www/apps/resources/generated/generated-troubleshooting-sidebar.mjs
index 5767c0bec1..bd479edaa5 100644
--- a/www/apps/resources/generated/generated-troubleshooting-sidebar.mjs
+++ b/www/apps/resources/generated/generated-troubleshooting-sidebar.mjs
@@ -50,7 +50,24 @@ const generatedgeneratedTroubleshootingSidebarSidebar = {
"loaded": true,
"isPathHref": true,
"type": "category",
- "title": "Medusa Application",
+ "title": "Upgrade",
+ "initialOpen": true,
+ "children": [
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "link",
+ "path": "/troubleshooting/errors-after-upgrading",
+ "title": "Errors After Upgrading",
+ "children": []
+ }
+ ]
+ },
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "category",
+ "title": "Framework",
"initialOpen": true,
"children": [
{
@@ -80,10 +97,42 @@ const generatedgeneratedTroubleshootingSidebarSidebar = {
{
"loaded": true,
"isPathHref": true,
- "type": "link",
- "path": "/troubleshooting/workflow-errors",
- "title": "Workflow Errors",
- "children": []
+ "type": "sub-category",
+ "title": "Query",
+ "children": [
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "link",
+ "path": "/troubleshooting/query/filter-linked",
+ "title": "Not Exising Property",
+ "children": []
+ }
+ ]
+ },
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "sub-category",
+ "title": "Workflows",
+ "children": [
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "link",
+ "path": "/troubleshooting/workflow-errors/when-then",
+ "title": "Handler Not Found",
+ "children": []
+ },
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "link",
+ "path": "/troubleshooting/workflow-errors/step-x-defined",
+ "title": "Step Already Defined",
+ "children": []
+ }
+ ]
},
{
"loaded": true,
@@ -95,40 +144,6 @@ const generatedgeneratedTroubleshootingSidebarSidebar = {
}
]
},
- {
- "loaded": true,
- "isPathHref": true,
- "type": "category",
- "title": "Admin Development",
- "initialOpen": true,
- "children": [
- {
- "loaded": true,
- "isPathHref": true,
- "type": "link",
- "path": "/troubleshooting/medusa-admin/no-widget-route",
- "title": "Widget or Route not Showing",
- "children": []
- }
- ]
- },
- {
- "loaded": true,
- "isPathHref": true,
- "type": "category",
- "title": "Upgrade",
- "initialOpen": true,
- "children": [
- {
- "loaded": true,
- "isPathHref": true,
- "type": "link",
- "path": "/troubleshooting/errors-after-upgrading",
- "title": "Errors After Upgrading",
- "children": []
- }
- ]
- },
{
"loaded": true,
"isPathHref": true,
@@ -162,6 +177,80 @@ const generatedgeneratedTroubleshootingSidebarSidebar = {
"children": []
}
]
+ },
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "category",
+ "title": "Admin Development",
+ "initialOpen": true,
+ "children": [
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "link",
+ "path": "/troubleshooting/medusa-admin/no-widget-route",
+ "title": "Widget or Route not Showing",
+ "children": []
+ },
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "link",
+ "path": "/troubleshooting/medusa-admin/blocked-request",
+ "title": "Blocked Request",
+ "children": []
+ }
+ ]
+ },
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "category",
+ "title": "Storefront",
+ "initialOpen": true,
+ "children": [
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "sub-category",
+ "title": "Next.js Starter",
+ "children": [
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "link",
+ "path": "/troubleshooting/nextjs-starter-rewrites",
+ "title": "Cloud Run Error",
+ "children": []
+ }
+ ]
+ },
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "sub-category",
+ "title": "Publishable API Key Errors",
+ "children": [
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "link",
+ "path": "/troubleshooting/storefront-missing-pak",
+ "title": "Missing Publishable API Key",
+ "children": []
+ },
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "link",
+ "path": "/troubleshooting/storefront-pak-sc",
+ "title": "Sales Channels Error",
+ "children": []
+ }
+ ]
+ }
+ ]
}
]
}
diff --git a/www/apps/resources/next.config.mjs b/www/apps/resources/next.config.mjs
index f71c80e44c..d55eb26d09 100644
--- a/www/apps/resources/next.config.mjs
+++ b/www/apps/resources/next.config.mjs
@@ -158,6 +158,11 @@ const nextConfig = {
destination: `${process.env.NEXT_PUBLIC_BASE_URL}/learn/configurations/medusa-config`,
permanent: true,
},
+ {
+ source: "/troubleshooting/workflow-errors",
+ destination: "/troubleshooting/workflow-errors/when-then",
+ permanent: true,
+ },
]
},
outputFileTracingExcludes: {
diff --git a/www/apps/resources/sidebars/troubleshooting.mjs b/www/apps/resources/sidebars/troubleshooting.mjs
index 345f7b0598..2811a81e2b 100644
--- a/www/apps/resources/sidebars/troubleshooting.mjs
+++ b/www/apps/resources/sidebars/troubleshooting.mjs
@@ -32,7 +32,19 @@ export const troubleshootingSidebar = [
},
{
type: "category",
- title: "Medusa Application",
+ title: "Upgrade",
+ initialOpen: true,
+ children: [
+ {
+ type: "link",
+ path: "/troubleshooting/errors-after-upgrading",
+ title: "Errors After Upgrading",
+ },
+ ],
+ },
+ {
+ type: "category",
+ title: "Framework",
initialOpen: true,
children: [
{
@@ -51,9 +63,31 @@ export const troubleshootingSidebar = [
title: "Importing from /dist",
},
{
- type: "link",
- path: "/troubleshooting/workflow-errors",
- title: "Workflow Errors",
+ type: "sub-category",
+ title: "Query",
+ children: [
+ {
+ type: "link",
+ path: "/troubleshooting/query/filter-linked",
+ title: "Not Exising Property",
+ },
+ ],
+ },
+ {
+ type: "sub-category",
+ title: "Workflows",
+ children: [
+ {
+ type: "link",
+ path: "/troubleshooting/workflow-errors/when-then",
+ title: "Handler Not Found",
+ },
+ {
+ type: "link",
+ path: "/troubleshooting/workflow-errors/step-x-defined",
+ title: "Step Already Defined",
+ },
+ ],
},
{
type: "link",
@@ -62,30 +96,6 @@ export const troubleshootingSidebar = [
},
],
},
- {
- type: "category",
- title: "Admin Development",
- initialOpen: true,
- children: [
- {
- type: "link",
- path: "/troubleshooting/medusa-admin/no-widget-route",
- title: "Widget or Route not Showing",
- },
- ],
- },
- {
- type: "category",
- title: "Upgrade",
- initialOpen: true,
- children: [
- {
- type: "link",
- path: "/troubleshooting/errors-after-upgrading",
- title: "Errors After Upgrading",
- },
- ],
- },
{
type: "category",
title: "Frontend",
@@ -110,4 +120,55 @@ export const troubleshootingSidebar = [
},
],
},
+ {
+ type: "category",
+ title: "Admin Development",
+ initialOpen: true,
+ children: [
+ {
+ type: "link",
+ path: "/troubleshooting/medusa-admin/no-widget-route",
+ title: "Widget or Route not Showing",
+ },
+ {
+ type: "link",
+ path: "/troubleshooting/medusa-admin/blocked-request",
+ title: "Blocked Request",
+ },
+ ],
+ },
+ {
+ type: "category",
+ title: "Storefront",
+ initialOpen: true,
+ children: [
+ {
+ type: "sub-category",
+ title: "Next.js Starter",
+ children: [
+ {
+ type: "link",
+ path: "/troubleshooting/nextjs-starter-rewrites",
+ title: "Cloud Run Error",
+ },
+ ],
+ },
+ {
+ type: "sub-category",
+ title: "Publishable API Key Errors",
+ children: [
+ {
+ type: "link",
+ path: "/troubleshooting/storefront-missing-pak",
+ title: "Missing Publishable API Key",
+ },
+ {
+ type: "link",
+ path: "/troubleshooting/storefront-pak-sc",
+ title: "Sales Channels Error",
+ },
+ ],
+ },
+ ],
+ },
]