docs: various improvements and fixes (#14257)

This commit is contained in:
Shahed Nasser authored and GitHub committed 2025-12-09 16:16:45 +02:00
1 parent 75eac9ebec
commit 0084460765
25 files changed
+141626 -131305

No files matched your search

@@ -1,3 +1,5 @@
import { CodeTab, CodeTabs } from "docs-ui"
export const metadata = {
title: `${pageNumber} Add Columns to a Link Table`,
}
@@ -97,10 +99,13 @@ For example:
<Note>
Learn more about Link, how to resolve it, and its methods in [this chapter](../link/page.mdx).
Refer to the [Link](../link/page.mdx) chapter to learn how to import and use the `link` instance.
</Note>
<CodeTabs group="link">
<CodeTab label="Link" value="link">
```ts
await link.create({
[Modules.PRODUCT]: {
@@ -117,6 +122,50 @@ await link.create({
})
```
</CodeTab>
<CodeTab label="createRemoteLinksStep" value="createRemoteLinksStep">
```ts
import { Modules } from "@medusajs/framework/utils"
import { BLOG_MODULE } from "../modules/blog"
import { createRemoteLinksStep } from "@medusajs/medusa/core-flows"
import {
createWorkflow,
transform,
} from "@medusajs/framework/workflows-sdk"
export const myWorkflow = createWorkflow(
"my-workflow",
() => {
// ...
const linkData = transform({
// TODO pass input data
}, () => {
return [
{
[Modules.PRODUCT]: {
product_id: "123",
},
[BLOG_MODULE]: {
post_id: "321",
},
data: {
metadata: {
test: true,
},
},
},
]
})
createRemoteLinksStep(linkData)
// ...
}
)
```
</CodeTab>
</CodeTabs>
---
## Retrieve Custom Column with Link
@@ -189,6 +238,9 @@ So, to update the value of a custom column in a created link, use the `create` m
For example:
<CodeTabs group="link">
<CodeTab label="Link" value="link">
```ts
await link.create({
[Modules.PRODUCT]: {
@@ -204,3 +256,47 @@ await link.create({
},
})
```
</CodeTab>
<CodeTab label="updateRemoteLinksStep" value="updateRemoteLinksStep">
```ts
import { Modules } from "@medusajs/framework/utils"
import { BLOG_MODULE } from "../modules/blog"
import { updateRemoteLinksStep } from "@medusajs/medusa/core-flows"
import {
createWorkflow,
transform,
} from "@medusajs/framework/workflows-sdk"
export const myWorkflow = createWorkflow(
"my-workflow",
() => {
// ...
const linkData = transform({
// TODO pass input data
}, () => {
return [
{
[Modules.PRODUCT]: {
product_id: "123",
},
[BLOG_MODULE]: {
post_id: "321",
},
data: {
metadata: {
test: false,
},
},
},
]
})
updateRemoteLinksStep(linkData)
// ...
}
)
```
</CodeTab>
</CodeTabs>
@@ -1,3 +1,5 @@
import { CodeTab, CodeTabs } from "docs-ui"
export const metadata = {
title: `${pageNumber} Link`,
}
@@ -41,6 +43,17 @@ export async function POST(
You can use its methods to manage links, such as create or delete links.
### Link Usage in Workflows
To perform link operations in a [workflow](../../workflows/page.mdx), Medusa provides the following steps out-of-the-box:
- [createRemoteLinkStep](!resources!/references/helper-steps/createRemoteLinkStep): Creates a link between records of two data models.
- [dismissRemoteLinkStep](!resources!/references/helper-steps/dismissRemoteLinkStep): Removes a link between records of two data models.
- [removeRemoteLinkStep](!resources!/references/helper-steps/removeRemoteLinkStep): Deletes all linked records whose links are defined with cascade deletion.
- [updateRemoteLinksStep](!resources!/references/helper-steps/updateRemoteLinksStep): Updates links between records of two data models.
The rest of this chapter provides examples using the Link class and the workflow steps.
---
## Create Link
@@ -49,6 +62,9 @@ To create a link between records of two data models, use the `create` method of
For example:
<CodeTabs group="link">
<CodeTab label="Link" value="link">
```ts
import { Modules } from "@medusajs/framework/utils"
@@ -58,12 +74,50 @@ await link.create({
[Modules.PRODUCT]: {
product_id: "prod_123",
},
"helloModuleService": {
my_custom_id: "mc_123",
blog: {
post_id: "post_123",
},
})
```
</CodeTab>
<CodeTab label="createRemoteLinkStep" value="createRemoteLinkStep">
```ts
import { Modules } from "@medusajs/framework/utils"
import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
import {
createWorkflow,
transform,
} from "@medusajs/framework/workflows-sdk"
export const myWorkflow = createWorkflow(
"my-workflow",
() => {
// ...
const linkData = transform({
// TODO pass input data
}, () => {
return [
{
[Modules.PRODUCT]: {
product_id: "prod_123",
},
blog: {
post_id: "post_123",
},
},
]
})
createRemoteLinkStep(linkData)
// ...
}
)
```
</CodeTab>
</CodeTabs>
The `create` method accepts as a parameter an object. The object’s keys are the names of the linked modules.
<Note title="Important">
@@ -74,7 +128,7 @@ The keys (names of linked modules) must be in the same [direction](../directions
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.
So, in the example above, you link a record of the `Post` data model in a `blog` module to a `Product` record in the Product Module.
### Enforced Integrity Constraints on Link Creation
@@ -88,23 +142,23 @@ await link.create({
[Modules.PRODUCT]: {
product_id: "prod_123",
},
"helloModuleService": {
my_custom_id: "mc_123",
blog: {
post_id: "post_123",
},
})
// throws an error because `prod_123` already has a link to `mc_123`
// throws an error because `prod_123` already has a link to `post_123`
await link.create({
[Modules.PRODUCT]: {
product_id: "prod_123",
},
"helloModuleService": {
my_custom_id: "mc_456",
blog: {
post_id: "mc_456",
},
})
```
- If the link is one-to-many and the "one" side already has a link to another record of the same data model. For example, if a product can have many `MyCustom` records, but a `MyCustom` record can only have one product:
- If the link is one-to-many and the "one" side already has a link to another record of the same data model. For example, if a product can have many `Post` records, but a `Post` record can only have one product:
```ts
// no error
@@ -112,8 +166,8 @@ await link.create({
[Modules.PRODUCT]: {
product_id: "prod_123",
},
"helloModuleService": {
my_custom_id: "mc_123",
blog: {
post_id: "post_123",
},
})
@@ -122,18 +176,18 @@ await link.create({
[Modules.PRODUCT]: {
product_id: "prod_123",
},
"helloModuleService": {
my_custom_id: "mc_456",
blog: {
post_id: "mc_456",
},
})
// throws an error because `mc_123` already has a link to `prod_123`
// throws an error because `post_123` already has a link to `prod_123`
await link.create({
[Modules.PRODUCT]: {
product_id: "prod_456",
},
"helloModuleService": {
my_custom_id: "mc_123",
blog: {
post_id: "post_123",
},
})
```
@@ -148,6 +202,9 @@ To remove a link between records of two data models, use the `dismiss` method of
For example:
<CodeTabs group="link">
<CodeTab label="Link" value="link">
```ts
import { Modules } from "@medusajs/framework/utils"
@@ -157,12 +214,50 @@ await link.dismiss({
[Modules.PRODUCT]: {
product_id: "prod_123",
},
"helloModuleService": {
my_custom_id: "mc_123",
blog: {
post_id: "post_123",
},
})
```
</CodeTab>
<CodeTab label="dismissRemoteLinkStep" value="dismissRemoteLinkStep">
```ts
import { Modules } from "@medusajs/framework/utils"
import { dismissRemoteLinkStep } from "@medusajs/medusa/core-flows"
import {
createWorkflow,
transform,
} from "@medusajs/framework/workflows-sdk"
export const myWorkflow = createWorkflow(
"my-workflow",
() => {
// ...
const linkData = transform({
// TODO pass input data
}, () => {
return [
{
[Modules.PRODUCT]: {
product_id: "prod_123",
},
blog: {
post_id: "post_123",
},
},
]
})
dismissRemoteLinkStep(linkData)
// ...
}
)
```
</CodeTab>
</CodeTabs>
The `dismiss` method accepts the same parameter type as the [create method](#create-link).
<Note title="Important">
@@ -179,6 +274,9 @@ If you delete a record using a workflow or its module's service, use the `delete
For example:
<CodeTabs group="link">
<CodeTab label="Link" value="link">
```ts
import { Modules } from "@medusajs/framework/utils"
@@ -193,6 +291,41 @@ await link.delete({
})
```
</CodeTab>
<CodeTab label="removeRemoteLinkStep" value="removeRemoteLinkStep">
```ts
import { Modules } from "@medusajs/framework/utils"
import { removeRemoteLinkStep } from "@medusajs/medusa/core-flows"
import {
createWorkflow,
transform,
} from "@medusajs/framework/workflows-sdk"
export const myWorkflow = createWorkflow(
"my-workflow",
() => {
// ...
const linkData = transform({
// TODO pass input data
}, () => {
return [
{
[Modules.PRODUCT]: {
product_id: "prod_123",
},
},
]
})
removeRemoteLinkStep(linkData)
// ...
}
)
```
</CodeTab>
</CodeTabs>
This deletes all records linked to the deleted product.
---
@@ -216,3 +349,83 @@ await link.restore({
},
})
```
---
## Update Links
Links may have [custom columns](../custom-columns/page.mdx) to store additional information, such as a `metadata` column to store JSON data about the link.
You can update the custom columns of existing links by calling the `create` method again with the same linked records and the new values for the custom columns.
For example:
<CodeTabs group="link">
<CodeTab label="Link" value="link">
```ts
import { Modules } from "@medusajs/framework/utils"
// ...
await link.create({
[Modules.PRODUCT]: {
product_id: "prod_123",
},
blog: {
post_id: "post_123",
},
data: {
metadata: {
featured: true,
},
},
})
```
</CodeTab>
<CodeTab label="updateRemoteLinksStep" value="updateRemoteLinksStep">
```ts
import { Modules } from "@medusajs/framework/utils"
import { updateRemoteLinksStep } from "@medusajs/medusa/core-flows"
import {
createWorkflow,
transform,
} from "@medusajs/framework/workflows-sdk"
export const myWorkflow = createWorkflow(
"my-workflow",
() => {
// ...
const linkData = transform({
// TODO pass input data
}, () => {
return [
{
[Modules.PRODUCT]: {
product_id: "prod_123",
},
blog: {
post_id: "post_123",
},
data: {
metadata: {
featured: true,
},
},
},
]
})
updateRemoteLinksStep(linkData)
// ...
}
)
```
</CodeTab>
</CodeTabs>
The object parameter you pass to the `create` method (or the `updateRemoteLinksStep` step) accepts an optional `data` property. The value of this property is an object whose keys are the names of the custom columns to update, and values are the new values for those columns.
Learn more in the [Custom Columns](../custom-columns/page.mdx) chapter.
@@ -134,9 +134,15 @@ Refer to the [Locking Module reference](!resources!/references/locking-service)
## Locks in Nested Workflows
[Nested workflows](../execute-another-workflow/page.mdx) can't acquire locks. So, if you're using a workflow that acquires locks within another workflow, the parent workflow must handle the locking mechanism.
When a [Nested workflows](../execute-another-workflow/page.mdx) acquires a lock with `acquireLockStep`, the lock is not acquired. So, if you're using a workflow that acquires locks with `acquireLockStep` within another workflow, the parent workflow must handle the locking mechanism.
For example, consider a custom workflow that executes Medusa's [completeCartWorkflow](!resources!/references/medusa-workflows/completeCartWorkflow). The `completeCartWorkflow` acquires locks on the cart to prevent race conditions.
<Note>
If the nested workflow acquires a lock in a step using the Locking Module's service, the lock will be acquired as expected.
</Note>
For example, consider a custom workflow that executes Medusa's [completeCartWorkflow](!resources!/references/medusa-workflows/completeCartWorkflow). The `completeCartWorkflow` acquires locks on the cart with `acquireLockStep` to prevent race conditions.
Therefore, the custom workflow must acquire and release the locks around the `completeCartWorkflow` execution. For example:
+3 -3
View File
@@ -62,7 +62,7 @@ export const generatedEditDates = {
"app/learn/fundamentals/api-routes/errors/page.mdx": "2025-06-19T16:09:08.563Z",
"app/learn/fundamentals/admin/constraints/page.mdx": "2025-07-21T08:20:43.223Z",
"app/learn/debugging-and-testing/testing-tools/modules-tests/page.mdx": "2025-07-23T15:32:18.008Z",
"app/learn/fundamentals/module-links/custom-columns/page.mdx": "2025-09-29T16:09:36.116Z",
"app/learn/fundamentals/module-links/custom-columns/page.mdx": "2025-12-09T13:27:05.446Z",
"app/learn/fundamentals/module-links/directions/page.mdx": "2025-03-17T12:52:06.161Z",
"app/learn/fundamentals/module-links/page.mdx": "2025-11-28T13:45:06.742Z",
"app/learn/fundamentals/module-links/query/page.mdx": "2025-10-27T09:30:26.957Z",
@@ -98,7 +98,7 @@ export const generatedEditDates = {
"app/learn/fundamentals/workflows/multiple-step-usage/page.mdx": "2025-08-01T14:59:59.501Z",
"app/learn/installation/page.mdx": "2025-12-04T14:30:00.510Z",
"app/learn/fundamentals/data-models/check-constraints/page.mdx": "2025-07-25T13:50:21.065Z",
"app/learn/fundamentals/module-links/link/page.mdx": "2025-11-28T13:42:03.037Z",
"app/learn/fundamentals/module-links/link/page.mdx": "2025-12-09T13:27:05.446Z",
"app/learn/fundamentals/workflows/store-executions/page.mdx": "2025-04-17T08:29:10.166Z",
"app/learn/fundamentals/plugins/create/page.mdx": "2025-04-17T08:29:09.910Z",
"app/learn/fundamentals/plugins/page.mdx": "2025-01-22T10:14:10.433Z",
@@ -132,7 +132,7 @@ export const generatedEditDates = {
"app/learn/fundamentals/scheduled-jobs/interval/page.mdx": "2025-09-02T08:36:12.714Z",
"app/learn/debugging-and-testing/feature-flags/create/page.mdx": "2025-09-02T08:36:12.714Z",
"app/learn/debugging-and-testing/feature-flags/page.mdx": "2025-09-02T08:36:12.714Z",
"app/learn/fundamentals/workflows/locks/page.mdx": "2025-11-26T11:40:04.279Z",
"app/learn/fundamentals/workflows/locks/page.mdx": "2025-12-09T11:20:25.011Z",
"app/learn/codemods/page.mdx": "2025-09-29T15:40:03.620Z",
"app/learn/codemods/replace-imports/page.mdx": "2025-10-09T11:37:44.754Z",
"app/learn/fundamentals/admin/translations/page.mdx": "2025-10-30T11:55:32.221Z",
+304 -27
View File
@@ -15533,7 +15533,9 @@ The object you pass to Link's `create` method accepts a `data` property. Its val
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).
Refer to the [Link](https://docs.medusajs.com/learn/fundamentals/module-links/link/index.html.md) chapter to learn how to import and use the `link` instance.
### Link
```ts
await link.create({
@@ -15551,6 +15553,46 @@ await link.create({
})
```
### createRemoteLinksStep
```ts
import { Modules } from "@medusajs/framework/utils"
import { BLOG_MODULE } from "../modules/blog"
import { createRemoteLinksStep } from "@medusajs/medusa/core-flows"
import {
createWorkflow,
transform,
} from "@medusajs/framework/workflows-sdk"
export const myWorkflow = createWorkflow(
"my-workflow",
() => {
// ...
const linkData = transform({
// TODO pass input data
}, () => {
return [
{
[Modules.PRODUCT]: {
product_id: "123",
},
[BLOG_MODULE]: {
post_id: "321",
},
data: {
metadata: {
test: true,
},
},
},
]
})
createRemoteLinksStep(linkData)
// ...
}
)
```
***
## Retrieve Custom Column with Link
@@ -15611,6 +15653,8 @@ So, to update the value of a custom column in a created link, use the `create` m
For example:
### Link
```ts
await link.create({
[Modules.PRODUCT]: {
@@ -15627,6 +15671,46 @@ await link.create({
})
```
### updateRemoteLinksStep
```ts
import { Modules } from "@medusajs/framework/utils"
import { BLOG_MODULE } from "../modules/blog"
import { updateRemoteLinksStep } from "@medusajs/medusa/core-flows"
import {
createWorkflow,
transform,
} from "@medusajs/framework/workflows-sdk"
export const myWorkflow = createWorkflow(
"my-workflow",
() => {
// ...
const linkData = transform({
// TODO pass input data
}, () => {
return [
{
[Modules.PRODUCT]: {
product_id: "123",
},
[BLOG_MODULE]: {
post_id: "321",
},
data: {
metadata: {
test: false,
},
},
},
]
})
updateRemoteLinksStep(linkData)
// ...
}
)
```
# Module Link Direction
@@ -16488,6 +16572,17 @@ export async function POST(
You can use its methods to manage links, such as create or delete links.
### Link Usage in Workflows
To perform link operations in a [workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), Medusa provides the following steps out-of-the-box:
- [createRemoteLinkStep](https://docs.medusajs.com/resources/references/helper-steps/createRemoteLinkStep/index.html.md): Creates a link between records of two data models.
- [dismissRemoteLinkStep](https://docs.medusajs.com/resources/references/helper-steps/dismissRemoteLinkStep/index.html.md): Removes a link between records of two data models.
- [removeRemoteLinkStep](https://docs.medusajs.com/resources/references/helper-steps/removeRemoteLinkStep/index.html.md): Deletes all linked records whose links are defined with cascade deletion.
- [updateRemoteLinksStep](https://docs.medusajs.com/resources/references/helper-steps/updateRemoteLinksStep/index.html.md): Updates links between records of two data models.
The rest of this chapter provides examples using the Link class and the workflow steps.
***
## Create Link
@@ -16496,6 +16591,8 @@ To create a link between records of two data models, use the `create` method of
For example:
### Link
```ts
import { Modules } from "@medusajs/framework/utils"
@@ -16505,19 +16602,53 @@ await link.create({
[Modules.PRODUCT]: {
product_id: "prod_123",
},
"helloModuleService": {
my_custom_id: "mc_123",
blog: {
post_id: "post_123",
},
})
```
### createRemoteLinkStep
```ts
import { Modules } from "@medusajs/framework/utils"
import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
import {
createWorkflow,
transform,
} from "@medusajs/framework/workflows-sdk"
export const myWorkflow = createWorkflow(
"my-workflow",
() => {
// ...
const linkData = transform({
// TODO pass input data
}, () => {
return [
{
[Modules.PRODUCT]: {
product_id: "prod_123",
},
blog: {
post_id: "post_123",
},
},
]
})
createRemoteLinkStep(linkData)
// ...
}
)
```
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.
So, in the example above, you link a record of the `Post` data model in a `blog` module to a `Product` record in the Product Module.
### Enforced Integrity Constraints on Link Creation
@@ -16531,23 +16662,23 @@ await link.create({
[Modules.PRODUCT]: {
product_id: "prod_123",
},
"helloModuleService": {
my_custom_id: "mc_123",
blog: {
post_id: "post_123",
},
})
// throws an error because `prod_123` already has a link to `mc_123`
// throws an error because `prod_123` already has a link to `post_123`
await link.create({
[Modules.PRODUCT]: {
product_id: "prod_123",
},
"helloModuleService": {
my_custom_id: "mc_456",
blog: {
post_id: "mc_456",
},
})
```
- If the link is one-to-many and the "one" side already has a link to another record of the same data model. For example, if a product can have many `MyCustom` records, but a `MyCustom` record can only have one product:
- If the link is one-to-many and the "one" side already has a link to another record of the same data model. For example, if a product can have many `Post` records, but a `Post` record can only have one product:
```ts
// no error
@@ -16555,8 +16686,8 @@ await link.create({
[Modules.PRODUCT]: {
product_id: "prod_123",
},
"helloModuleService": {
my_custom_id: "mc_123",
blog: {
post_id: "post_123",
},
})
@@ -16565,18 +16696,18 @@ await link.create({
[Modules.PRODUCT]: {
product_id: "prod_123",
},
"helloModuleService": {
my_custom_id: "mc_456",
blog: {
post_id: "mc_456",
},
})
// throws an error because `mc_123` already has a link to `prod_123`
// throws an error because `post_123` already has a link to `prod_123`
await link.create({
[Modules.PRODUCT]: {
product_id: "prod_456",
},
"helloModuleService": {
my_custom_id: "mc_123",
blog: {
post_id: "post_123",
},
})
```
@@ -16591,6 +16722,8 @@ To remove a link between records of two data models, use the `dismiss` method of
For example:
### Link
```ts
import { Modules } from "@medusajs/framework/utils"
@@ -16600,12 +16733,46 @@ await link.dismiss({
[Modules.PRODUCT]: {
product_id: "prod_123",
},
"helloModuleService": {
my_custom_id: "mc_123",
blog: {
post_id: "post_123",
},
})
```
### dismissRemoteLinkStep
```ts
import { Modules } from "@medusajs/framework/utils"
import { dismissRemoteLinkStep } from "@medusajs/medusa/core-flows"
import {
createWorkflow,
transform,
} from "@medusajs/framework/workflows-sdk"
export const myWorkflow = createWorkflow(
"my-workflow",
() => {
// ...
const linkData = transform({
// TODO pass input data
}, () => {
return [
{
[Modules.PRODUCT]: {
product_id: "prod_123",
},
blog: {
post_id: "post_123",
},
},
]
})
dismissRemoteLinkStep(linkData)
// ...
}
)
```
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.
@@ -16618,6 +16785,8 @@ If you delete a record using a workflow or its module's service, use the `delete
For example:
### Link
```ts
import { Modules } from "@medusajs/framework/utils"
@@ -16632,6 +16801,37 @@ await link.delete({
})
```
### removeRemoteLinkStep
```ts
import { Modules } from "@medusajs/framework/utils"
import { removeRemoteLinkStep } from "@medusajs/medusa/core-flows"
import {
createWorkflow,
transform,
} from "@medusajs/framework/workflows-sdk"
export const myWorkflow = createWorkflow(
"my-workflow",
() => {
// ...
const linkData = transform({
// TODO pass input data
}, () => {
return [
{
[Modules.PRODUCT]: {
product_id: "prod_123",
},
},
]
})
removeRemoteLinkStep(linkData)
// ...
}
)
```
This deletes all records linked to the deleted product.
***
@@ -16656,6 +16856,81 @@ await link.restore({
})
```
***
## Update Links
Links may have [custom columns](https://docs.medusajs.com/learn/fundamentals/module-links/custom-columns/index.html.md) to store additional information, such as a `metadata` column to store JSON data about the link.
You can update the custom columns of existing links by calling the `create` method again with the same linked records and the new values for the custom columns.
For example:
### Link
```ts
import { Modules } from "@medusajs/framework/utils"
// ...
await link.create({
[Modules.PRODUCT]: {
product_id: "prod_123",
},
blog: {
post_id: "post_123",
},
data: {
metadata: {
featured: true,
},
},
})
```
### updateRemoteLinksStep
```ts
import { Modules } from "@medusajs/framework/utils"
import { updateRemoteLinksStep } from "@medusajs/medusa/core-flows"
import {
createWorkflow,
transform,
} from "@medusajs/framework/workflows-sdk"
export const myWorkflow = createWorkflow(
"my-workflow",
() => {
// ...
const linkData = transform({
// TODO pass input data
}, () => {
return [
{
[Modules.PRODUCT]: {
product_id: "prod_123",
},
blog: {
post_id: "post_123",
},
data: {
metadata: {
featured: true,
},
},
},
]
})
updateRemoteLinksStep(linkData)
// ...
}
)
```
The object parameter you pass to the `create` method (or the `updateRemoteLinksStep` step) accepts an optional `data` property. The value of this property is an object whose keys are the names of the custom columns to update, and values are the new values for those columns.
Learn more in the [Custom Columns](https://docs.medusajs.com/learn/fundamentals/module-links/custom-columns/index.html.md) chapter.
# Define Module Link
@@ -23420,9 +23695,11 @@ Refer to the [Locking Module reference](https://docs.medusajs.com/resources/refe
## Locks in Nested Workflows
[Nested workflows](https://docs.medusajs.com/learn/fundamentals/workflows/execute-another-workflow/index.html.md) can't acquire locks. So, if you're using a workflow that acquires locks within another workflow, the parent workflow must handle the locking mechanism.
When a [Nested workflows](https://docs.medusajs.com/learn/fundamentals/workflows/execute-another-workflow/index.html.md) acquires a lock with `acquireLockStep`, the lock is not acquired. So, if you're using a workflow that acquires locks with `acquireLockStep` within another workflow, the parent workflow must handle the locking mechanism.
For example, consider a custom workflow that executes Medusa's [completeCartWorkflow](https://docs.medusajs.com/resources/references/medusa-workflows/completeCartWorkflow/index.html.md). The `completeCartWorkflow` acquires locks on the cart to prevent race conditions.
If the nested workflow acquires a lock in a step using the Locking Module's service, the lock will be acquired as expected.
For example, consider a custom workflow that executes Medusa's [completeCartWorkflow](https://docs.medusajs.com/resources/references/medusa-workflows/completeCartWorkflow/index.html.md). The `completeCartWorkflow` acquires locks on the cart with `acquireLockStep` to prevent race conditions.
Therefore, the custom workflow must acquire and release the locks around the `completeCartWorkflow` execution. For example:
@@ -35434,7 +35711,7 @@ const totalRefunded = refundTransactions?.reduce((acc, t) => acc + Math.abs(t!.a
In this section of the documentation, you'll find guides and references related to Medusa's Commerce Modules.
A Commerce Module provides features for a commerce domain within its service. The Medusa application exposes these features in its API routes to clients.
A Commerce Module provides features for a commerce domain within its service. When you install the Medusa application, all Commerce Modules are available out-of-the-box. The Medusa application exposes their features in its core API routes to clients.
A Commerce Module also defines data models, representing tables in the database. The Medusa Framework and tools allow you to extend these data models to add custom fields.
@@ -35465,7 +35742,7 @@ A Commerce Module also defines data models, representing tables in the database.
The Commerce Modules can be used in many use cases, including:
- Medusa Application: The Medusa application uses the Commerce Modules to expose commerce features through the REST APIs.
- Medusa Application: The Medusa application comes with all Commerce Modules pre-installed and configured. Their commerce features are exposed through REST APIs.
- Serverless Application: Use the Commerce Modules in a serverless application, such as a Next.js application, without having to manage a fully-fledged ecommerce system. You can use it by installing it in your Node.js project as an NPM package.
- Node.js Application: Use the Commerce Modules in any Node.js application by installing it with NPM.
@@ -41850,7 +42127,7 @@ Learn more about why modules are isolated in [this documentation](https://docs.m
## Store Features
- [Store Management](https://docs.medusajs.com/references/store/models/Store/index.html.md): Create and manage stores in your application.
- [Multi-Tenancy Support](https://docs.medusajs.com/references/store/models/Store/index.html.md): Create multiple stores, each having its own configurations.
- [Multi-Tenancy Support](https://docs.medusajs.com/references/store/models/Store/index.html.md): While Medusa doesn't natively support multi-tenancy, the Store Module allows you to create and manage multiple stores within a single Medusa instance. You can then build customizations to link products, customers, orders, and other resources to specific stores.
***
@@ -46201,8 +46478,8 @@ module.exports = defineConfig({
// keep jobs for 1 hour or up to 1000 jobs
age: 3600,
count: 1000,
}
}
},
},
},
},
],
@@ -95255,7 +95532,7 @@ const StripeSavedPaymentMethodsContainer = ({
await initiatePaymentSession(cart, {
provider_id: method.provider_id,
data: {
payment_method_id: method.id,
payment_method: method.id,
},
}).catch((error) => {
setError(error.message)
+15 -3
View File
@@ -12,8 +12,6 @@ Medusa offers a managed S3 storage service for your project environments on Clou
So, when you create a new project, Medusa creates a production S3 bucket for the production environment. If you create a staging environment, Medusa creates a separate S3 bucket for that environment as well.
By default, the S3 bucket is private, but the files you upload to the bucket are publicly accessible. This is necessary for serving product images and other assets in your Medusa application.
![Diagram showcasing S3 isolation between environments](https://res.cloudinary.com/dza7lstvk/image/upload/v1750230909/Cloud/s3-cloud_smrtfc.jpg)
---
@@ -44,4 +42,18 @@ Then, configure the S3 File Module Provider in your `medusa-config.ts` file to c
To set the connection options of your external S3 instance, refer to the [Environments](../environments/environment-variables/page.mdx#add-environment-variables) guide to learn how to add environment variables. You can then use these variables in your `medusa-config.ts` file to connect to your S3 instance.
</Note>
</Note>
---
## Upload Files to S3 on Cloud
When you upload files through your Medusa application hosted on Cloud, such as product images or other assets, the files are stored in the S3 bucket associated with that application's environment.
You can also upload files using Medusa's [Upload Files API route](!api!/admin#uploads_postuploads), or in a [custom API route that handles file upload](!docs!/learn/fundamentals/api-routes/parse-body#configure-file-uploads).
---
## S3 Bucket Privacy on Cloud
By default, the S3 bucket of your Cloud projects is private, but the files you upload to the bucket are publicly accessible. This is necessary for serving product images and other assets in your Medusa application.
@@ -8,7 +8,7 @@ export const metadata = {
In this section of the documentation, you'll find guides and references related to Medusa's Commerce Modules.
A Commerce Module provides features for a commerce domain within its service. The Medusa application exposes these features in its API routes to clients.
A Commerce Module provides features for a commerce domain within its service. When you install the Medusa application, all Commerce Modules are available out-of-the-box. The Medusa application exposes their features in its core API routes to clients.
A Commerce Module also defines data models, representing tables in the database. The Medusa Framework and tools allow you to extend these data models to add custom fields.
@@ -97,6 +97,6 @@ A Commerce Module also defines data models, representing tables in the database.
The Commerce Modules can be used in many use cases, including:
- Medusa Application: The Medusa application uses the Commerce Modules to expose commerce features through the REST APIs.
- Medusa Application: The Medusa application comes with all Commerce Modules pre-installed and configured. Their commerce features are exposed through REST APIs.
- Serverless Application: Use the Commerce Modules in a serverless application, such as a Next.js application, without having to manage a fully-fledged ecommerce system. You can use it by installing it in your Node.js project as an NPM package.
- Node.js Application: Use the Commerce Modules in any Node.js application by installing it with NPM.
@@ -29,7 +29,7 @@ Learn more about why modules are isolated in [this documentation](!docs!/learn/f
## Store Features
- [Store Management](/references/store/models/Store): Create and manage stores in your application.
- [Multi-Tenancy Support](/references/store/models/Store): Create multiple stores, each having its own configurations.
- [Multi-Tenancy Support](/references/store/models/Store): While Medusa doesn't natively support multi-tenancy, the Store Module allows you to create and manage multiple stores within a single Medusa instance. You can then build customizations to link products, customers, orders, and other resources to specific stores.
---
@@ -628,7 +628,7 @@ const StripeSavedPaymentMethodsContainer = ({
await initiatePaymentSession(cart, {
provider_id: method.provider_id,
data: {
payment_method_id: method.id,
payment_method: method.id,
},
}).catch((error) => {
setError(error.message)
@@ -58,8 +58,8 @@ module.exports = defineConfig({
// keep jobs for 1 hour or up to 1000 jobs
age: 3600,
count: 1000,
}
}
},
},
},
},
],
@@ -29,7 +29,7 @@ These guides are for developers who want to build a storefront for their Medusa
All guides under this documentation section use the [JS SDK](../js-sdk/page.mdx) to interact with the Medusa application's Store API Routes. The JS SDK is a NPM package that facilitates interacting with the backend's REST APIs. You can use it in any JavaScript framework, such as Next.js, React, Vue, or Angular.
To install and set up the JS SDK, refer to the [JS SDK documentation](../js-sdk/page.mdx).
Refer to the [Connect to the Medusa Application](./tips/page.mdx) guide to learn how to set up the JS SDK in your storefront application.
### Not Using JavaScript?
@@ -30,7 +30,7 @@ Learn how to install and configure the JS SDK in the [JS SDK documentation](../.
<CodeTab label="React" value="react">
export const highlights = [
["17"], ["18"], ["19"], ["20"], ["21"]
["18"], ["19"], ["20"], ["21"], ["22"]
]
```tsx highlights={highlights}
@@ -38,6 +38,7 @@ export const highlights = [
import { useEffect, useState } from "react"
import { HttpTypes } from "@medusajs/types"
import { sdk } from "@/lib/sdk"
export default function Products() {
const [loading, setLoading] = useState(true)
@@ -70,6 +70,17 @@ Make sure to replace `apk_123` with the ID of the publishable API key, and `sc_1
In your storefront, pass the `x-publishable-api-key` in the header of all your requests to the Medusa application.
For example:
```bash
curl '{backend_url}/store/products' \
-H 'x-publishable-api-key: {your_publishable_api_key}'
```
Where `{your_publishable_api_key}` is the token of your publishable API key.
### Using the JS SDK
When using the JS SDK, set the publishable API key as an environment variable and pass it to the JS SDK's configurations.
For example:
@@ -94,6 +105,6 @@ export const sdk = new Medusa({
})
```
Where `NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY` is an environment variable that holds your publishable API key's token.
Where `NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY` is an environment variable that holds your publishable API key's token. Based on your storefront framework, make sure the environment variable is accessible in the browser. For example, in Next.js, prefix the variable name with `NEXT_PUBLIC_`.
The JS SDK will now take care of passing the publishable API key in the header of all requests to the Medusa application.
@@ -4,12 +4,12 @@ tags:
---
export const metadata = {
title: `Storefront Development Tips`,
title: `Connect Your Storefront to Medusa`,
}
# {metadata.title}
In this document, you’ll find tips useful when building a storefront.
In this guide, you’ll learn how to send requests from your storefront application to the Medusa application.
## Connect to the Medusa Application
@@ -18,34 +18,68 @@ To send requests from the storefront to the Medusa application’s Store API Rou
- **For JavaScript frameworks**: use Medusa’s [JS SDK](../../js-sdk/page.mdx) in any JavaScript framework. This NPM package facilitates interacting with the backend’s REST APIs. All Storefront Development guides use the JS SDK.
- **For other frontend technologies**: interact directly with the Medusa application by sending requests to its [Store REST APIs](https://docs.medusajs.com/api/store).
---
### Set Up the JS SDK
## Install Types Package
The `@medusajs/types` package provide API routes' request and response types.
If you're not using the JS SDK but are building your application with TypeScript, install `@medusajs/types` to use the correct request and response types:
To set up the JS SDK in your storefront application, install the `@medusajs/js-sdk` package with the types package:
```bash npm2yarn
npm install @medusajs/types@latest
npm install @medusajs/js-sdk@latest @medusajs/types@latest
```
Then, create a file that initializes the JS SDK client. For example, create the file `src/lib/sdk.ts` with the following content:
```ts title="src/lib/sdk.ts"
import Medusa from "@medusajs/js-sdk"
let MEDUSA_BACKEND_URL = "http://localhost:9000"
if (process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL) {
MEDUSA_BACKEND_URL = process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL
}
export const sdk = new Medusa({
baseUrl: MEDUSA_BACKEND_URL,
debug: process.env.NODE_ENV === "development",
publishableKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY,
})
```
Where:
- `NEXT_PUBLIC_MEDUSA_BACKEND_URL` is the URL of your Medusa application. Based on your storefront framework, make sure the environment variable is accessible in the browser. For example, in Next.js, prefix the variable name with `NEXT_PUBLIC_`.
- `NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY` is the publishable API key of your storefront. Learn more in the [Publishable API Keys](../publishable-api-keys/page.mdx) guide.
You can then import the `sdk` instance from `src/lib/sdk.ts` and use it to send requests to the Medusa application. For example:
```ts
import { sdk } from "@/lib/sdk"
sdk.store.product.list()
.then(({ products }) => {
console.log(products)
})
```
Refer to the [JS SDK reference](../../js-sdk/page.mdx) to learn more about using the SDK to interact with the Medusa application.
---
## Configure CORS
## Configure Store CORS in Medusa
The Medusa application’s API routes are guarded by a CORS middleware. Make sure to set the `storeCors` property of the `http` configuration in `medusa-config.ts` to the storefront’s URL.
For example:
```ts title="medusa-config.ts"
```ts title="medusa-config.ts" badgeLabel="Medusa Application" badgeColor="green"
module.exports = defineConfig({
projectConfig: {
http: {
storeCors: "http://localhost:3000",
storeCors: "http://localhost:3000,http://localhost:8000",
// ...
},
},
// ...
})
```
Refer to the [Medusa configuration](!docs!/learn/configurations/medusa-config#httpstorecors) guide to learn more about configuring CORS in the Medusa application.
+8 -8
View File
@@ -85,7 +85,7 @@ export const generatedEditDates = {
"app/commerce-modules/stock-location/page.mdx": "2025-04-17T08:48:26.441Z",
"app/commerce-modules/store/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
"app/commerce-modules/store/_events/page.mdx": "2024-07-03T19:27:13+03:00",
"app/commerce-modules/store/page.mdx": "2025-04-17T08:48:34.141Z",
"app/commerce-modules/store/page.mdx": "2025-12-09T10:56:06.938Z",
"app/commerce-modules/tax/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
"app/commerce-modules/tax/_events/page.mdx": "2024-07-03T19:27:13+03:00",
"app/commerce-modules/tax/module-options/page.mdx": "2025-05-20T07:51:40.711Z",
@@ -98,7 +98,7 @@ export const generatedEditDates = {
"app/commerce-modules/user/module-options/page.mdx": "2025-10-31T10:08:14.831Z",
"app/commerce-modules/user/user-creation-flows/page.mdx": "2025-02-26T11:35:54.685Z",
"app/commerce-modules/user/page.mdx": "2025-04-17T08:48:17.980Z",
"app/commerce-modules/page.mdx": "2025-04-17T08:48:34.855Z",
"app/commerce-modules/page.mdx": "2025-12-09T10:58:53.037Z",
"app/create-medusa-app/page.mdx": "2025-07-14T08:51:06.654Z",
"app/deployment/admin/vercel/page.mdx": "2024-10-16T08:10:29.377Z",
"app/deployment/storefront/vercel/page.mdx": "2025-05-20T07:51:40.712Z",
@@ -163,7 +163,7 @@ export const generatedEditDates = {
"app/storefront-development/products/collections/products/page.mdx": "2025-03-27T14:46:51.466Z",
"app/storefront-development/products/collections/retrieve/page.mdx": "2025-03-27T14:46:51.458Z",
"app/storefront-development/products/collections/page.mdx": "2024-06-11T19:55:56+02:00",
"app/storefront-development/products/list/page.mdx": "2025-03-27T14:46:51.431Z",
"app/storefront-development/products/list/page.mdx": "2025-12-09T13:32:29.583Z",
"app/storefront-development/products/price/examples/sale-price/page.mdx": "2025-03-27T14:47:14.308Z",
"app/storefront-development/products/price/examples/show-price/page.mdx": "2025-03-27T14:47:14.292Z",
"app/storefront-development/products/price/examples/tax-price/page.mdx": "2025-03-27T14:47:14.292Z",
@@ -175,8 +175,8 @@ export const generatedEditDates = {
"app/storefront-development/regions/list/page.mdx": "2025-03-27T14:46:52.029Z",
"app/storefront-development/regions/store-retrieve-region/page.mdx": "2025-03-27T13:29:54.041Z",
"app/storefront-development/regions/page.mdx": "2024-06-09T15:19:09+02:00",
"app/storefront-development/tips/page.mdx": "2025-03-26T10:31:43.816Z",
"app/storefront-development/page.mdx": "2025-03-27T13:28:22.077Z",
"app/storefront-development/tips/page.mdx": "2025-12-09T13:33:33.997Z",
"app/storefront-development/page.mdx": "2025-12-09T13:30:25.379Z",
"app/troubleshooting/cors-errors/page.mdx": "2024-05-03T17:36:38+03:00",
"app/troubleshooting/create-medusa-app-errors/page.mdx": "2025-12-04T14:12:21.480Z",
"app/troubleshooting/database-errors/page.mdx": "2025-12-04T14:14:46.745Z",
@@ -203,7 +203,7 @@ export const generatedEditDates = {
"app/commerce-modules/api-key/_events/page.mdx": "2024-07-03T19:27:13+03:00",
"app/commerce-modules/api-key/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
"app/infrastructure-modules/cache/redis/page.mdx": "2025-06-24T08:50:10.115Z",
"app/infrastructure-modules/event/redis/page.mdx": "2025-12-04T13:38:38.038Z",
"app/infrastructure-modules/event/redis/page.mdx": "2025-12-09T13:29:13.949Z",
"app/infrastructure-modules/event/local/page.mdx": "2025-03-27T14:53:13.309Z",
"app/infrastructure-modules/workflow-engine/in-memory/page.mdx": "2024-11-19T16:37:47.262Z",
"app/infrastructure-modules/cache/in-memory/page.mdx": "2024-11-19T16:37:47.261Z",
@@ -937,7 +937,7 @@ export const generatedEditDates = {
"references/order/IOrderModuleService/methods/order.IOrderModuleService.updateOrderChangeActions/page.mdx": "2025-10-21T08:10:49.750Z",
"references/types/ModulesSdkTypes/types/types.ModulesSdkTypes.RemoteQueryFunction/page.mdx": "2025-10-21T08:10:47.957Z",
"references/types/types.CommonTypes/page.mdx": "2025-10-31T09:41:29.372Z",
"app/storefront-development/publishable-api-keys/page.mdx": "2025-03-26T10:31:43.815Z",
"app/storefront-development/publishable-api-keys/page.mdx": "2025-12-09T13:26:27.641Z",
"references/api_key/types/api_key.ExpandScalar/page.mdx": "2024-09-17T00:10:59.563Z",
"references/api_key/types/api_key.FilterQuery/page.mdx": "2024-11-27T16:33:40.706Z",
"references/api_key/types/api_key.FilterValue/page.mdx": "2024-09-17T00:10:59.571Z",
@@ -6227,7 +6227,7 @@ export const generatedEditDates = {
"references/events/events.Region/page.mdx": "2025-05-20T07:51:40.897Z",
"references/events/events.Sales_Channel/page.mdx": "2025-05-20T07:51:40.897Z",
"references/events/events.User/page.mdx": "2025-05-20T07:51:40.897Z",
"app/how-to-tutorials/tutorials/saved-payment-methods/page.mdx": "2025-09-11T13:03:49.152Z",
"app/how-to-tutorials/tutorials/saved-payment-methods/page.mdx": "2025-12-09T10:56:41.512Z",
"references/core_flows/Cart/Workflows_Cart/functions/core_flows.Cart.Workflows_Cart.refundPaymentAndRecreatePaymentSessionWorkflow/page.mdx": "2025-12-03T08:41:35.675Z",
"references/core_flows/Cart/Workflows_Cart/variables/core_flows.Cart.Workflows_Cart.refundPaymentAndRecreatePaymentSessionWorkflowId/page.mdx": "2025-05-20T07:51:40.757Z",
"references/core_flows/Draft_Order/Workflows_Draft_Order/variables/core_flows.Draft_Order.Workflows_Draft_Order.convertDraftOrderWorkflowId/page.mdx": "2025-05-20T07:51:40.768Z",
@@ -24,7 +24,7 @@ const generatedgeneratedStorefrontDevelopmentSidebarSidebar = {
"isPathHref": true,
"type": "link",
"path": "/storefront-development/tips",
"title": "Tips",
"title": "Connect to Medusa",
"children": []
},
{
+1 -1
View File
@@ -15,7 +15,7 @@ export const storefrontDevelopmentSidebar = [
{
type: "link",
path: "/storefront-development/tips",
title: "Tips",
title: "Connect to Medusa",
},
{
type: "link",