docs: improve commerce modules [1/n] (#9498)

Improve and add docs for API Key, Auth, and Cart Modules

[1/n]
This commit is contained in:
Shahed Nasser
2024-10-11 18:19:13 +03:00
committed by GitHub
parent dd162d69be
commit 69b9e73be7
29 changed files with 1025 additions and 323 deletions
@@ -4,7 +4,8 @@ export const metadata = {
# {metadata.title}
In this document, youll learn how about the different types of API keys, and their expiration and verification.
In this document, youll learn about the different types of API keys, their expiration and verification.
## API Key Types
There are two types of API keys:
@@ -13,13 +13,12 @@ In this guide, youll find common examples of how you can use the API Key Modu
<CodeTabs groupId="app-type">
<CodeTab label="Medusa API Router" value="medusa">
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { IApiKeyModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
export async function POST(request: MedusaRequest, res: MedusaResponse) {
const apiKeyModuleService: IApiKeyModuleService = request.scope.resolve(
const apiKeyModuleService = request.scope.resolve(
Modules.API_KEY
)
@@ -68,13 +67,12 @@ export async function POST(request: Request) {
<CodeTabs groupId="app-type">
<CodeTab label="Medusa API Router" value="medusa">
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { IApiKeyModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
export async function GET(request: MedusaRequest, res: MedusaResponse) {
const apiKeyModuleService: IApiKeyModuleService = request.scope.resolve(
const apiKeyModuleService = request.scope.resolve(
Modules.API_KEY
)
@@ -113,14 +111,13 @@ export async function GET(request: Request) {
```ts collapsibleLines="1-9" expandButtonLabel="Show Imports"
import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { IApiKeyModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
export async function POST(
request: AuthenticatedMedusaRequest,
res: MedusaResponse
) {
const apiKeyModuleService: IApiKeyModuleService = request.scope.resolve(
const apiKeyModuleService = request.scope.resolve(
Modules.API_KEY
)
@@ -172,13 +169,12 @@ export async function POST(request: Request, { params }: ContextType) {
<CodeTabs groupId="app-type">
<CodeTab label="Medusa API Router" value="medusa">
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { IApiKeyModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
export async function POST(request: MedusaRequest, res: MedusaResponse) {
const apiKeyModuleService: IApiKeyModuleService = request.scope.resolve(
const apiKeyModuleService = request.scope.resolve(
Modules.API_KEY
)
@@ -229,19 +225,18 @@ export async function POST(request: Request, { params }: ContextType) {
<CodeTabs groupId="app-type">
<CodeTab label="Medusa API Router" value="medusa">
```ts collapsibleLines="1-8" expandButtonLabel="Show Imports"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
import { IApiKeyModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
```ts collapsibleLines="1-8" expandButtonLabel="Show Imports"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
export async function POST(
request: AuthenticatedMedusaRequest,
res: MedusaResponse
) {
const apiKeyModuleService: IApiKeyModuleService = request.scope.resolve(
const apiKeyModuleService = request.scope.resolve(
Modules.API_KEY
)
@@ -1,14 +1,14 @@
export const metadata = {
title: `Relations between API Key Module and Other Modules`,
title: `Links between API Key Module and Other Modules`,
}
# {metadata.title}
This document showcases the link modules defined between the API Key Module and other commerce modules.
This document showcases the module links defined between the API Key Module and other commerce modules.
## Sales Channel Module
You can create a publishable API key and associate it with a sales channel. Medusa defines a link module that builds a relation between the `ApiKey` and the `SalesChannel` data models.
You can create a publishable API key and associate it with a sales channel. Medusa defines a link between the `ApiKey` and the `SalesChannel` data models.
![A diagram showcasing an example of how data models from the API Key and Sales Channel modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1709812064/Medusa%20Resources/sales-channel-api-key_zmqi2l.jpg)
@@ -6,27 +6,42 @@ export const metadata = {
# {metadata.title}
The API Key Module is the `@medusajs/medusa/api-key` NPM package that provides API-key-related features in your Medusa and Node.js applications.
The API Key Module provides API-key-related features in your Medusa and Node.js applications.
## How to Use API Key Module's Service
You can use the API Key Module's main service by resolving from the Medusa container the resource `Modules.API_KEY` imported from `@medusajs/framework/utils`.
Use the API Key Module's main service by resolving from the Medusa container the resource `Modules.API_KEY` imported from `@medusajs/framework/utils`.
For example:
<CodeTabs groupId="resource-type">
<CodeTab label="Workflow Step" value="workflow-step">
```ts title="src/workflows/hello-world/step1.ts"
import { createStep } from "@medusajs/framework/workflows-sdk"
import { Modules } from "@medusajs/framework/utils"
const step1 = createStep("step-1", async (_, { container }) => {
const apiKeyModuleService = container.resolve(
Modules.API_KEY
)
const apiKeys = await apiKeyModuleService.listApiKeys()
})
```
</CodeTab>
<CodeTab label="API Route" value="api-route">
```ts title="src/api/store/custom/route.ts"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { IApiKeyModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
```ts title="src/api/store/custom/route.ts"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
export async function GET(
request: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const apiKeyModuleService: IApiKeyModuleService = request.scope.resolve(
const apiKeyModuleService = request.scope.resolve(
Modules.API_KEY
)
@@ -39,35 +54,17 @@ export async function GET(
</CodeTab>
<CodeTab label="Subscriber" value="subscribers">
```ts title="src/subscribers/custom-handler.ts"
import { SubscriberArgs } from "@medusajs/framework"
import { IApiKeyModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
```ts title="src/subscribers/custom-handler.ts"
import { SubscriberArgs } from "@medusajs/framework"
import { Modules } from "@medusajs/framework/utils"
export default async function subscriberHandler({ container }: SubscriberArgs) {
const apiKeyModuleService: IApiKeyModuleService = container.resolve(
const apiKeyModuleService = container.resolve(
Modules.API_KEY
)
const apiKeys = await apiKeyModuleService.listApiKeys()
}
```
</CodeTab>
<CodeTab label="Workflow Step" value="workflow-step">
```ts title="src/workflows/hello-world/step1.ts"
import { createStep } from "@medusajs/framework/workflows-sdk"
import { IApiKeyModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
const step1 = createStep("step-1", async (_, { container }) => {
const apiKeyModuleService: IApiKeyModuleService = container.resolve(
Modules.API_KEY
)
const apiKeys = await apiKeyModuleService.listApiKeys()
})
```
</CodeTab>
@@ -79,7 +76,7 @@ const step1 = createStep("step-1", async (_, { container }) => {
### API Key Types and Management
Store and manage API keys in your store. You can create both publishable and secret API keys for different use cases, such as:
Manage API keys in your store. You can create both publishable and secret API keys for different use cases, such as:
- Publishable API Key associated with resources like sales channels.
- Authentication token for admin users to access Admin API Routes.
@@ -8,19 +8,39 @@ In this document, youll learn about concepts related to identity and actors i
## What is an Auth Identity?
The [AuthIdentity data model](/references/auth/models/AuthIdentity) represents a registered user.
The [AuthIdentity data model](/references/auth/models/AuthIdentity) represents a user registered by an [authentication provider](../auth-providers/page.mdx).
When a user is registered, a record of `AuthIdentity` is created. This record is used to validate the users authentication in future requests.
When a user is registered using an authentication provider, it creates a record of `AuthIdentity`.
Then, when the user logs-in in the future with the same authentication provider, the associated auth identity is used to validate their credentials.
---
## Actor Types
An actor type is a type of user that can be authenticated. This user is a record of a data model defined by a module.
An actor type is a type of user that can be authenticated.
For example, the `customer` actor type belongs to the Customer Modules `Customer` data model. Similarly, the `user` actor type belongs to the User Modules `User` data model.
The Auth Module doesn't store or manage any user-like models, such as for customers or users.
### Protect Routes by Actor Type
Instead, the user types are created and managed by other modules. For example, a customer is managed by the Customer Module.
Then, when an auth identity is created for the actor type, the ID of the user is stored in the `app_metadata` property of the auth identity.
For example, an auth identity of a customer has the following `app_metadata` property:
```json
{
"app_metadata": {
"customer_id": "cus_123"
}
}
```
The ID of the user is stored in the key `{actor_type}_id` of the `app_metadata` property.
---
## Protect Routes by Actor Type
When you protect routes with the `authenticate` middleware, you specify in its first parameter the actor type that must be authenticated to access the specified API routes.
@@ -54,7 +74,7 @@ By specifying `user` as the first parameter of `authenticate`, only authenticate
## Custom Actor Types
You can define custom actor types that point to the data model of your module.
You can define custom actor types that allows a custom user, managed by your custom module, to authenticate into Medusa.
For example, if you have a custom module with a `Manager` data model, you can authenticate managers with the `manager` actor type.
@@ -88,6 +88,8 @@ const modules = {
</Table.Body>
</Table>
---
## Related Guides
- [How to register a customer using email and password](../../../../storefront-development/customers/register/page.mdx)
@@ -8,7 +8,7 @@ export const metadata = {
In this document, youll learn about the GitHub Auth Module Provider and how to install and use it in the Auth Module.
The Github Auth Module Provider handles authenticating users with their GitHub account.
The Github Auth Module Provider authenticates users with their GitHub account.
<Note title="Tip">
@@ -8,11 +8,11 @@ export const metadata = {
In this document, youll learn about the Google Auth Module Provider and how to install and use it in the Auth Module.
The Google Auth Module Provider handles authenticating users with their Google account.
The Google Auth Module Provider authenticates users with their Google account.
<Note title="Tip">
Learn about the authentication flow in [this guide](../../authentication-route/page.mdx).
Learn about the authentication flow for third-party providers in [this guide](../../authentication-route/page.mdx#2-third-party-service-authenticate-flow).
</Note>
@@ -143,4 +143,4 @@ GOOGLE_CALLBACK_URL=<YOUR_GOOGLE_CALLBACK_URL>
## Examples
- [How to implement Google social login in the storefront.](../../../../storefront-development/customers/third-party-login/page.mdx).
- [How to implement Google social login in the storefront](../../../../storefront-development/customers/third-party-login/page.mdx).
@@ -37,7 +37,7 @@ For example, the EmailPass Auth Module Provider authenticates a user using their
## Configure Allowed Auth Providers of Actor Types
By default, users of all actor types can authenticate with all installed auth module providerss.
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](/references/medusa-config#http-authMethodsPerActor-1-3) in Medusa's configurations:
@@ -8,7 +8,13 @@ In this document, learn how to create an actor type and authenticate its associa
## 0. Create Module with Data Model
Before creating an actor type, you must define a data model the actor type belongs to. The data model is defined in a custom module.
Before creating an actor type, you must have a module with a data model representing the actor type.
<Note title="Tip">
Learn how to create a module in [this guide](!docs!/basics/modules).
</Note>
The rest of this guide uses this `Manager` data model as an example:
@@ -31,7 +37,7 @@ export default Manager
Start by creating a workflow that does two things:
- Create a record of the `Manager` data model.
- Creates a record of the `Manager` data model.
- Sets the `app_metadata` property of the associated `AuthIdentity` record based on the new actor type.
For example, create the file `src/workflows/create-manager.ts`. with the following content:
@@ -19,21 +19,21 @@ This example uses the [jsonwebtoken NPM package](https://www.npmjs.com/package/j
<CodeTabs groupId="app-type">
<CodeTab label="Medusa API Router" value="medusa">
```ts collapsibleLines="1-10" expandButtonLabel="Show Imports"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import {
IAuthModuleService,
AuthenticationInput,
} from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
import { MedusaError } from "@medusajs/framework/utils"
import jwt from "jsonwebtoken"
```ts collapsibleLines="1-10" expandButtonLabel="Show Imports"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import {
IAuthModuleService,
AuthenticationInput,
} from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
import { MedusaError } from "@medusajs/framework/utils"
import jwt from "jsonwebtoken"
export async function POST(
req: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const authModuleService: IAuthModuleService = req.scope.resolve(
const authModuleService = req.scope.resolve(
Modules.AUTH
)
@@ -122,21 +122,21 @@ This example uses the [jsonwebtoken NPM package](https://www.npmjs.com/package/j
<CodeTabs groupId="app-type">
<CodeTab label="Medusa API Router" value="medusa">
```ts collapsibleLines="1-10" expandButtonLabel="Show Imports"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import {
IAuthModuleService,
AuthenticationInput,
} from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
import { MedusaError } from "@medusajs/framework/utils"
import jwt from "jsonwebtoken"
```ts collapsibleLines="1-10" expandButtonLabel="Show Imports"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import {
IAuthModuleService,
AuthenticationInput,
} from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
import { MedusaError } from "@medusajs/framework/utils"
import jwt from "jsonwebtoken"
export async function POST(
req: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const authModuleService: IAuthModuleService = req.scope.resolve(
const authModuleService = req.scope.resolve(
Modules.AUTH
)
@@ -224,16 +224,15 @@ export async function POST(request: Request) {
<CodeTabs groupId="app-type">
<CodeTab label="Medusa API Router" value="medusa">
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { IAuthModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
export async function POST(
req: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const authModuleService: IAuthModuleService = req.scope.resolve(
const authModuleService = req.scope.resolve(
Modules.AUTH
)
@@ -280,16 +279,15 @@ export async function POST(request: Request) {
<CodeTabs groupId="app-type">
<CodeTab label="Medusa API Router" value="medusa">
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { IAuthModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const authModuleService: IAuthModuleService = req.scope.resolve(
const authModuleService = req.scope.resolve(
Modules.AUTH
)
@@ -327,15 +325,14 @@ export async function GET(request: Request) {
<CodeTab label="Medusa API Router" value="medusa">
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { IAuthModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
export async function POST(
req: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const authModuleService: IAuthModuleService = req.scope.resolve(
const authModuleService = req.scope.resolve(
Modules.AUTH
)
@@ -393,15 +390,14 @@ export async function POST(request: Request, { params }: ContextType) {
<CodeTab label="Medusa API Router" value="medusa">
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { IAuthModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
export async function DELETE(
req: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const authModuleService: IAuthModuleService = req.scope.resolve(
const authModuleService = req.scope.resolve(
Modules.AUTH
)
@@ -18,6 +18,12 @@ The `providers` option is an array of auth module providers.
When the Medusa application starts, these providers are registered and can be used to handle authentication.
<Note title="Tip">
By default, the `emailpass` provider is registered to authenticate customers and admin users.
</Note>
For example:
```js title="medusa-config.js"
@@ -46,7 +52,7 @@ module.exports = defineConfig({
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.
- `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.
@@ -54,9 +60,11 @@ The `providers` option is an array of objects that accept the following properti
## Auth CORS
The Medusa application's authentication API routes are defined under the `/auth` prefix that requires setting the `authCors` property of the `http` configuration. So, before using these routes, make sure to set that configuration.
The Medusa application's authentication API routes are defined under the `/auth` prefix that requires setting the `authCors` property of the `http` configuration.
Refer to [Medusa's configuration guide](/references/medusa-config#authCors) for more details.
By default, the Medusa application you created will have an `AUTH_CORS` environment variable, which is used as the value of `authCors`.
Refer to [Medusa's configuration guide](/references/medusa-config#authCors) to learn more about the `authCors` configuration.
---
@@ -64,4 +72,4 @@ Refer to [Medusa's configuration guide](/references/medusa-config#authCors) for
The Medusa application's configuration accept an `authMethodsPerActor` configuration which restricts the allowed auth providers used with an actor type.
Learn more about the `authMethodsPerActor` configuration in [this guide](../auth-providers/page.mdx#configure-allowed-auth-providers-of-actor-types).
Learn more about the `authMethodsPerActor` configuration in [this guide](../auth-providers/page.mdx#configure-allowed-auth-providers-of-actor-types).
@@ -6,27 +6,41 @@ export const metadata = {
# {metadata.title}
The Auth Module is the `@medusajs/medusa/auth` NPM package that provides authentication-related features in your Medusa and Node.js applications.
The Auth Module provides authentication-related features in your Medusa and Node.js applications.
## How to Use Auth Module's Service
You can use the Auth Module's main service by resolving from the Medusa container the resource `Modules.AUTH` imported from `@medusajs/framework/utils`.
Use the Auth Module's main service by resolving from the Medusa container the resource `Modules.AUTH` imported from `@medusajs/framework/utils`.
For example:
<CodeTabs groupId="resource-type">
<CodeTab label="Workflow Step" value="workflow-step">
```ts title="src/workflows/hello-world/step1.ts"
import { createStep } from "@medusajs/framework/workflows-sdk"
import { Modules } from "@medusajs/framework/utils"
const step1 = createStep("step-1", async (_, { container }) => {
const authModuleService = container.resolve(
Modules.AUTH
)
const authIdentitys = await authModuleService.listAuthIdentities()
})
```
</CodeTab>
<CodeTab label="API Route" value="api-route">
```ts title="src/api/store/custom/route.ts"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { IAuthModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const authModuleService: IAuthModuleService = req.scope.resolve(
const authModuleService = req.scope.resolve(
Modules.AUTH
)
@@ -41,32 +55,15 @@ export async function GET(
```ts title="src/subscribers/custom-handler.ts"
import { SubscriberArgs } from "@medusajs/framework"
import { IAuthModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
export default async function subscriberHandler({ container }: SubscriberArgs) {
const authModuleService: IAuthModuleService = container.resolve(
const authModuleService = container.resolve(
Modules.AUTH
)
const authIdentitys = await authModuleService.listAuthIdentities()
}
```
</CodeTab>
<CodeTab label="Workflow Step" value="workflow-step">
```ts title="src/workflows/hello-world/step1.ts"
import { createStep } from "@medusajs/framework/workflows-sdk"
import { IAuthModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
const step1 = createStep("step-1", async (_, { container }) => {
const authModuleService: IAuthModuleService = container.resolve(
Modules.AUTH
)
const authIdentitys = await authModuleService.listAuthIdentities()
})
```
</CodeTab>
@@ -78,7 +75,7 @@ const step1 = createStep("step-1", async (_, { container }) => {
### Basic User Authentication
With the Auth Module, authenticate users using their email and password credentials.
Authenticate users using their email and password credentials.
```ts
const { success, authIdentity, error } = await authModuleService.authenticate(
@@ -6,7 +6,9 @@ export const metadata = {
# {metadata.title}
In this guide, you'll learn how to handle the `auth.password_reset` event, which is emitted when a request is sent to the [Generate Reset Password Token API route](../authentication-route/page.mdx#generate-reset-password-token-route), to send reset instructions to the user.
In this guide, you'll learn how to handle the `auth.password_reset` event, which is emitted when a request is sent to the [Generate Reset Password Token API route](../authentication-route/page.mdx#generate-reset-password-token-route).
You'll create a subscriber that listens to the event. When the event is emitted, the subscriber sends an email notification to the user.
<Prerequisites
items={[
@@ -29,14 +31,14 @@ export const highlights=[
["10", "token", "The password reset token."],
["11", "actor_type", "The user's actor type."],
["19", "urlPrefix", "Set the page's URL based on the user's actor type."],
["21", "createNotifications", "Send a notification to the user."],
["23", `"email"`, "The channel to send the notification through."],
["24", "template", "The template defined in the third-party provider."],
["25", "data", "The data to pass to the template in the third-party provider."],
["27", "url", "The frontend URL to redirect the user to reset their password."]
["23", "createNotifications", "Send a notification to the user."],
["25", `"email"`, "The channel to send the notification through."],
["26", "template", "The template defined in the third-party provider."],
["27", "data", "The data to pass to the template in the third-party provider."],
["29", "url", "The frontend URL to redirect the user to reset their password."]
]
```ts title="src/subscribers/handle-reset.ts" collapsibleLines="1-6" expandMoreLabel="Show Imports"
```ts title="src/subscribers/handle-reset.ts" highlights={highlights} collapsibleLines="1-6" expandMoreLabel="Show Imports"
import {
SubscriberArgs,
type SubscriberConfig,
@@ -55,7 +57,9 @@ export default async function resetPasswordTokenHandler({
Modules.NOTIFICATION
)
const urlPrefix = actor_type === "customer" ? "https://storefront.com" : "https://admin.com"
const urlPrefix = actor_type === "customer" ?
"https://storefront.com" :
"https://admin.com"
await notificationModuleService.createNotifications({
to: email,
@@ -16,13 +16,13 @@ A cart has a shipping and billing address. Both of these addresses are represent
## Line Items
A line item, represented by the `LineItem` data model, is a product variant added to the cart. A cart has multiple line items.
A line item, represented by the [LineItem](/references/cart/models/LineItem) 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 variants properties, such as the `product_title` and `product_description`. It also stores data related to the items quantity and price.
<Note>
A product variant can be from the [Product Module](../../product/page.mdx) but can also be a custom item used only in this cart.
In the Medusa application, a product variant is implemented in the [Product Module](../../product/page.mdx).
</Note>
@@ -32,12 +32,12 @@ A product variant can be from the [Product Module](../../product/page.mdx) but c
A shipping method, represented by the [ShippingMethod data model](/references/cart/models/ShippingMethod), is used to fulfill the items in the cart after the order is placed. A cart can have more than one shipping method.
If the shipping method is created from a shipping option, available through the [Fulfillment Module](../../fulfillment/page.mdx), its ID is stored in the `shipping_option_id`.
A shipping method can also be a custom method associated with this cart only.
In the Medusa application, the shipping method is created from a shipping option, available through the [Fulfillment Module](../../fulfillment/page.mdx). Its ID is stored in the `shipping_option_id` property of the method.
### data Property
After an order is placed, you may 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, you can add this data in the `ShippingMethod`'s `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.
@@ -13,16 +13,15 @@ In this guide, youll find common examples of how you can use the Cart Module
<CodeTabs groupId="app-type">
<CodeTab label="Medusa API Router" value="medusa">
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { ICartModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
export async function POST(
req: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const cartModuleService: ICartModuleService = req.scope.resolve(
const cartModuleService = req.scope.resolve(
Modules.CART
)
@@ -87,16 +86,15 @@ export async function POST(request: Request) {
<CodeTabs groupId="app-type">
<CodeTab label="Medusa API Router" value="medusa">
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { ICartModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const cartModuleService: ICartModuleService = req.scope.resolve(
const cartModuleService = req.scope.resolve(
Modules.CART
)
@@ -135,16 +133,15 @@ export async function GET(request: Request) {
<CodeTabs groupId="app-type">
<CodeTab label="Medusa API Router" value="medusa">
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { ICartModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
export async function POST(
req: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const cartModuleService: ICartModuleService = req.scope.resolve(
const cartModuleService = req.scope.resolve(
Modules.CART
)
@@ -195,16 +192,15 @@ export async function POST(request: Request) {
<CodeTabs groupId="app-type">
<CodeTab label="Medusa API Router" value="medusa">
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { ICartModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
export async function POST(
req: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const cartModuleService: ICartModuleService = req.scope.resolve(
const cartModuleService = req.scope.resolve(
Modules.CART
)
@@ -253,16 +249,15 @@ export async function POST(request: Request) {
<CodeTabs groupId="app-type">
<CodeTab label="Medusa API Router" value="medusa">
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { ICartModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
export async function POST(
req: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const cartModuleService: ICartModuleService = req.scope.resolve(
const cartModuleService = req.scope.resolve(
Modules.CART
)
@@ -311,16 +306,15 @@ export async function POST(request: Request) {
<CodeTabs groupId="app-type">
<CodeTab label="Medusa API Router" value="medusa">
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { ICartModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
export async function POST(
req: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const cartModuleService: ICartModuleService = req.scope.resolve(
const cartModuleService = req.scope.resolve(
Modules.CART
)
@@ -371,16 +365,15 @@ export async function POST(request: Request) {
<CodeTabs groupId="app-type">
<CodeTab label="Medusa API Router" value="medusa">
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { ICartModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
export async function DELETE(
req: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const cartModuleService: ICartModuleService = req.scope.resolve(
const cartModuleService = req.scope.resolve(
Modules.CART
)
@@ -415,16 +408,15 @@ export async function DELETE(request: Request) {
<CodeTabs groupId="app-type">
<CodeTab label="Medusa API Router" value="medusa">
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { ICartModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
export async function DELETE(
req: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const cartModuleService: ICartModuleService = req.scope.resolve(
const cartModuleService = req.scope.resolve(
Modules.CART
)
@@ -437,16 +429,15 @@ export async function DELETE(
</CodeTab>
<CodeTab label="Next.js App Router" value="nextjs">
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { ICartModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
```ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
export async function DELETE(
req: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const cartModuleService: ICartModuleService = req.scope.resolve(
const cartModuleService = req.scope.resolve(
Modules.CART
)
@@ -0,0 +1,666 @@
import { Prerequisites } from "docs-ui"
export const metadata = {
title: `Extend Cart Data Model`,
}
# {metadata.title}
In this documentation, you'll learn how to extend a data model of the Cart Module to add a custom property.
You'll create a `Custom` data model in a module. This data model will have a `custom_name` property, which is the property you want to add to the [Cart data model](/references/cart/models/Cart) defined in the Cart Module.
You'll then learn how to:
- Link the `Custom` data model to the `Cart` data model.
- Set the `custom_name` property when a cart is created or updated using Medusa's API routes.
- Retrieve the `custom_name` property with the cart's details, in custom or existing API routes.
## Step 1: Define Custom Data Model
Consider you have a Hello Module defined in the `/src/modules/hello` directory.
<Note title="Tip">
If you don't have a module, follow [this guide](!docs!/basics/modules) to create one.
</Note>
To add the `custom_name` property to the `Cart` data model, you'll create in the Hello Module a data model that has the `custom_name` property.
Create the file `src/modules/hello/models/custom.ts` with the following content:
```ts title="src/modules/hello/models/custom.ts"
import { model } from "@medusajs/framework/utils"
export const Custom = model.define("custom", {
id: model.id().primaryKey(),
custom_name: model.text(),
})
```
This creates a `Custom` data model that has the `id` and `custom_name` properties.
<Note title="Tip">
Learn more about data models in [this guide](!docs!/data-models).
</Note>
---
## Step 2: Define Link to Cart Data Model
Next, you'll define a module link between the `Custom` and `Cart` data model. A module link allows you to form a relation between two data models of separate modules while maintaining module isolation.
<Note title="Tip">
Learn more about module links in [this guide](!docs!/module-links).
</Note>
Create the file `src/links/cart-custom.ts` with the following content:
```ts title="src/links/cart-custom.ts"
import { defineLink } from "@medusajs/framework/utils"
import HelloModule from "../modules/hello"
import CartModule from "@medusajs/medusa/cart"
export default defineLink(
CartModule.linkable.cart,
HelloModule.linkable.custom
)
```
This defines a link between the `Cart` and `Custom` data models. Using this link, you'll later query data across the modules, and link records of each data model.
---
## Step 3: Generate and Run Migrations
<Prerequisites
items={[
{
text: "Module must be registered in medusa-config.js",
link: "!docs!/basics/modules#4-add-module-to-configurations"
}
]}
/>
To reflect the `Custom` data model in the database, generate a migration that defines the table to be created for it.
Run the following command in your Medusa project's root:
```bash
npx medusa db:generate helloModuleService
```
Where `helloModuleService` is your module's name.
Then, run the `db:migrate` command to run the migrations and create a table in the database for the link between the `Cart` and `Custom` data models:
```bash
npx medusa db:migrate
```
A table for the link is now created in the database. You can now retrieve and manage the link between records of the data models.
---
## Step 4: Consume cartCreated Workflow Hook
When a cart is created, you also want to create a `Custom` record and set the `custom_name` property, then create a link between the `Cart` and `Custom` records.
To do that, you'll consume the [cartCreated](/references/medusa-workflows/createCartWorkflow#cartcreated) hook of the [createCartWorkflow](/references/medusa-workflows/createCartWorkflow). This workflow is executed in the [Create Cart API Route](!api!/store#carts_postcarts).
<Note title="Tip">
Learn more about workflow hooks in [this guide](!docs!/advanced-development/workflows/workflow-hooks).
</Note>
The API route accepts in its request body an `additional_data` parameter. You can pass in it custom data, which is passed to the workflow hook handler.
### Add custom_name to Additional Data Validation
To pass the `custom_name` in the `additional_data` parameter, you must add a validation rule that tells the Medusa application about this custom property.
Create the file `src/api/middlewares.ts` with the following content:
```ts title="src/api/middlewares.ts"
import { defineMiddlewares } from "@medusajs/medusa"
import { z } from "zod"
export default defineMiddlewares({
routes: [
{
method: "POST",
matcher: "/store/carts",
additionalDataValidator: {
custom_name: z.string().optional(),
},
},
],
})
```
The `additional_data` parameter validation is customized using the `defineMiddlewares` utility function. In the routes middleware configuration object, the `additionalDataValidator` property accepts [Zod](https://zod.dev/) validaiton rules.
In the snippet above, you add a validation rule indicating that `custom_name` is a string that can be passed in the `additional_data` object.
<Note title="Tip">
Learn more about additional data validation in [this guide](!docs!/advanced-development/api-routes/additional-data).
</Note>
### Create Workflow to Create Custom Record
You'll now create a workflow that will be used in the hook handler.
This workflow will create a `Custom` record, then link it to the cart.
Start by creating the step that creates the `Custom` record. Create the file `src/workflows/create-custom-from-cart/steps/create-custom.ts` with the following content:
```ts title="src/workflows/create-custom-from-cart/steps/create-custom.ts"
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
import HelloModuleService from "../../../modules/hello/service"
import { HELLO_MODULE } from "../../../modules/hello"
type CreateCustomStepInput = {
custom_name?: string
}
export const createCustomStep = createStep(
"create-custom",
async (data: CreateCustomStepInput, { container }) => {
if (!data.custom_name) {
return
}
const helloModuleService: HelloModuleService = container.resolve(
HELLO_MODULE
)
const custom = await helloModuleService.createCustoms(data)
return new StepResponse(custom, custom)
},
async (custom, { container }) => {
const helloModuleService: HelloModuleService = container.resolve(
HELLO_MODULE
)
await helloModuleService.deleteCustoms(custom.id)
}
)
```
In the step, you resolve the Hello Module's main service and create a `Custom` record.
In the compensation function that undoes the step's actions in case of an error, you delete the created record.
<Note title="Tip">
Learn more about compensation functions in [this guide](!docs!/advanced-development/workflows/compensation-function).
</Note>
Then, create the workflow at `src/workflows/create-custom-from-cart/index.ts` with the following content:
```ts title="src/workflows/create-custom-from-cart/index.ts" collapsibleLines="1-7" expandButtonLabel="Show Imports"
import { createWorkflow, transform, when, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
import { CartDTO } from "@medusajs/framework/types"
import { createCustomStep } from "./steps/create-custom"
import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
import { Modules } from "@medusajs/framework/utils"
import { HELLO_MODULE } from "../../modules/hello"
export type CreateCustomFromCartWorkflowInput = {
cart: CartDTO
additional_data?: {
custom_name?: string
}
}
export const createCustomFromCartWorkflow = createWorkflow(
"create-custom-from-cart",
(input: CreateCustomFromCartWorkflowInput) => {
const customName = transform(
{
input,
},
(data) => data.input.additional_data?.custom_name || ""
)
const custom = createCustomStep({
custom_name: customName,
})
when(({ custom }), ({ custom }) => custom !== undefined)
.then(() => {
createRemoteLinkStep([{
[Modules.CART]: {
cart_id: input.cart.id,
},
[HELLO_MODULE]: {
custom_id: custom.id,
},
}])
})
return new WorkflowResponse({
custom,
})
}
)
```
The workflow accepts as an input the created cart and the `additional_data` parameter passed in the request. This is the same input that the `cartCreated` hook accepts.
In the workflow, you:
1. Use the `transform` utility to get the value of `custom_name` based on whether it's set in `additional_data`. Learn more about why you can't use conditional operators in a workflow without using `transform` in [this guide](!docs!/advanced-development/workflows/conditions#why-if-conditions-arent-allowed-in-workflows).
2. Create the `Custom` record using the `createCustomStep`.
3. Use the `when-then` utility to link the cart to the `Custom` record if it was created. Learn more about why you can't use if-then conditions in a workflow without using `when-then` in [this guide](!docs!/advanced-development/workflows/conditions#why-if-conditions-arent-allowed-in-workflows).
You'll next call the workflow in the hook handler.
### Consume Workflow Hook
You can now consume the `cartCreated` hook, which is executed in the `createCartWorkflow` after the cart is created.
To consume the hook, create the file `src/workflow/hooks/cart-created.ts` with the following content:
```ts title="src/workflow/hooks/cart-created.ts" collapsibleLines="1-6" expandButtonLabel="Show Imports"
import { createCartWorkflow } from "@medusajs/medusa/core-flows"
import {
createCustomFromCartWorkflow,
CreateCustomFromCartWorkflowInput,
} from "../create-custom-from-cart"
createCartWorkflow.hooks.cartCreated(
async (hookData, { container }) => {
await createCustomFromCartWorkflow(container)
.run({
input: hookData as CreateCustomFromCartWorkflowInput,
})
}
)
```
The hook handler executes the `createCustomFromCartWorkflow`, passing it its input.
### Test it Out
To test it out, send a `POST` request to `/store/carts` to create a cart, passing `custom_name` in `additional_data`:
```bash
curl -X POST 'localhost:9000/store/carts' \
-H 'x-publishable-api-key: {publishable_api_key}' \
-H 'Content-Type: application/json' \
--data '{
"region_id": "reg_01J9NNNWVV0T71PT44EAMTJCMP",
"additional_data": {
"custom_name": "test"
}
}'
```
Make sure to replace `{publishable_api_key}` with your publishable API key, which you can retrieve from the Medusa Admin. Also, replace the value of `region_id` with an ID of a region in your application.
The request will return the cart's details. You'll learn how to retrive the `custom_name` property with the cart's details in the next section.
---
## Step 5: Retrieve custom_name with Cart Details
When you extend an existing data model through links, you also want to retrieve the custom properties with the data model.
### Retrieve in API Routes
To retrieve the `custom_name` property when you're retrieving the cart through API routes, such as the [Get Cart API Route](!api!/store#carts_getcartsid), pass in the `fields` query parameter `+custom.*`, which retrieves the linked `Custom` record's details.
<Note title="Tip">
The `+` prefix in `+custom.*` indicates that the relation should be retrieved with the default cart fields. Learn more about selecting fields and relations in the [API reference](!api!/store#select-fields-and-relations).
</Note>
For example:
```bash
curl -X POST 'localhost:9000/store/carts/{cart_id}?fields=+custom.*' \
-H 'x-publishable-api-key: {publishable_api_key}'
```
Make sure to replace `{cart_id}` with the cart's ID, and `{publishable_api_key}` with your publishable API key, which you can retrieve from the Medusa Admin.
Among the returned `cart` object, you'll find a `custom` property which holds the details of the linked `Custom` record:
```json
{
"cart": {
// ...
"custom": {
"id": "01J9NP7ANXDZ0EAYF0956ZE1ZA",
"custom_name": "test",
"created_at": "2024-10-08T09:09:06.877Z",
"updated_at": "2024-10-08T09:09:06.877Z",
"deleted_at": null
}
}
}
```
### Retrieve using Query
You can also retrieve the `Custom` record linked to a cart in your code using [Query](!docs!/advanced-development/module-links/query).
For example:
```ts
const { data: [cart] } = await query.graph({
entity: "cart",
fields: ["*", "custom.*"],
filters: {
id: cart_id,
},
})
```
Learn more about how to use Query in [this guide](!docs!/advanced-development/module-links/query).
---
## Step 6: Consume cartUpdated Workflow Hook
Similar to the `cartCreated` hook, you'll consume the [cartUpdated](/references/medusa-workflows/updateCartWorkflow#cartupdated) hook of the [updateCartWorkflow](/references/medusa-workflows/updateCartWorkflow) to update `custom_name` when the cart is updated.
The `updateCartWorkflow` is executed by the [Update Cart API route](!api!/store#carts_postcartsid), which accepts the `additional_data` parameter to pass custom data to the hook.
### Add custom_name to Additional Data Validation
To allow passing `custom_name` in the `additional_data` parameter of the update cart route, add in `src/api/middlewares.ts` a new route middleware configuration object:
```ts title="src/api/middlewares.ts"
import { defineMiddlewares } from "@medusajs/medusa"
import { z } from "zod"
export default defineMiddlewares({
routes: [
// ...
{
method: "POST",
matcher: "/store/carts/:id",
additionalDataValidator: {
custom_name: z.string().nullish(),
},
},
],
})
```
The validation schema is the similar to that of the Create Cart API route, except you can pass a `null` value for `custom_name` to remove or unset the `custom_name`'s value.
### Create Workflow to Update Custom Record
Next, you'll create a workflow that creates, updates, or deletes `Custom` records based on the provided `additional_data` parameter:
1. If `additional_data.custom_name` is set and it's `null`, the `Custom` record linked to the cart is deleted.
2. If `additional_data.custom_name` is set and the cart doesn't have a linked `Custom` record, a new record is created and linked to the cart.
3. If `additional_data.custom_name` is set and the cart has a linked `Custom` record, the `custom_name` property of the `Custom` record is updated.
Start by creating the step that updates a `Custom` record. Create the file `src/workflows/update-custom-from-cart/steps/update-custom.ts` with the following content:
```ts title="src/workflows/update-custom-from-cart/steps/update-custom.ts"
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
import { HELLO_MODULE } from "../../../modules/hello"
import HelloModuleService from "../../../modules/hello/service"
type UpdateCustomStepInput = {
id: string
custom_name: string
}
export const updateCustomStep = createStep(
"update-custom",
async ({ id, custom_name }: UpdateCustomStepInput, { container }) => {
const helloModuleService: HelloModuleService = container.resolve(
HELLO_MODULE
)
const prevData = await helloModuleService.retrieveCustom(id)
const custom = await helloModuleService.updateCustoms({
id,
custom_name,
})
return new StepResponse(custom, prevData)
},
async (prevData, { container }) => {
const helloModuleService: HelloModuleService = container.resolve(
HELLO_MODULE
)
await helloModuleService.updateCustoms(prevData)
}
)
```
In this step, you update a `Custom` record. In the compensation function, you revert the update.
Next, you'll create the step that deletes a `Custom` record. Create the file `src/workflows/update-custom-from-cart/steps/delete-custom.ts` with the following content:
```ts title="src/workflows/update-custom-from-cart/steps/delete-custom.ts" collapsibleLines="1-6" expandButtonLabel="Show Imports"
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
import { Custom } from "../../../modules/hello/models/custom"
import { InferTypeOf } from "@medusajs/framework/types"
import HelloModuleService from "../../../modules/hello/service"
import { HELLO_MODULE } from "../../../modules/hello"
type DeleteCustomStepInput = {
custom: InferTypeOf<typeof Custom>
}
export const deleteCustomStep = createStep(
"delete-custom",
async ({ custom }: DeleteCustomStepInput, { container }) => {
const helloModuleService: HelloModuleService = container.resolve(
HELLO_MODULE
)
await helloModuleService.deleteCustoms(custom.id)
return new StepResponse(custom, custom)
},
async (custom, { container }) => {
const helloModuleService: HelloModuleService = container.resolve(
HELLO_MODULE
)
await helloModuleService.createCustoms(custom)
}
)
```
In this step, you delete a `Custom` record. In the compensation function, you create it again.
Finally, you'll create the workflow. Create the file `src/workflows/update-custom-from-cart/index.ts` with the following content:
```ts title="src/workflows/update-custom-from-cart/index.ts" collapsibleLines="1-9" expandButtonLabel="Show Imports"
import { CartDTO } from "@medusajs/framework/types"
import { createWorkflow, when, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
import { createRemoteLinkStep, dismissRemoteLinkStep, useRemoteQueryStep } from "@medusajs/medusa/core-flows"
import { createCustomStep } from "../create-custom-from-cart/steps/create-custom"
import { Modules } from "@medusajs/framework/utils"
import { HELLO_MODULE } from "../../modules/hello"
import { deleteCustomStep } from "./steps/delete-custom"
import { updateCustomStep } from "./steps/update-custom"
export type UpdateCustomFromCartStepInput = {
cart: CartDTO
additional_data?: {
custom_name?: string | null
}
}
export const updateCustomFromCartWorkflow = createWorkflow(
"update-custom-from-cart",
(input: UpdateCustomFromCartStepInput) => {
const cartData = useRemoteQueryStep({
entry_point: "cart",
fields: ["custom.*"],
variables: {
filters: {
id: input.cart.id,
},
},
list: false,
})
// TODO create, update, or delete Custom record
}
)
```
The workflow accepts the same input as the `cartUpdated` workflow hook handler would.
In the workflow, you retrieve the cart's linked `Custom` record using Query.
Next, replace the `TODO` with the following:
```ts title="src/workflows/update-custom-from-cart/index.ts"
const created = when({
input,
cartData,
}, (data) =>
!data.cartData.custom &&
data.input.additional_data?.custom_name?.length > 0
)
.then(() => {
const custom = createCustomStep({
custom_name: input.additional_data.custom_name,
})
createRemoteLinkStep([{
[Modules.CART]: {
cart_id: input.cart.id,
},
[HELLO_MODULE]: {
custom_id: custom.id,
},
}])
return custom
})
// TODO update, or delete Custom record
```
Using the `when-then` utility, you check if the cart doesn't have a linked `Custom` record and the `custom_name` property is set. If so, you create a `Custom` record and link it to the cart.
To create the `Custom` record, you use the `createCustomStep` you created in an earlier section.
Next, replace the new `TODO` with the following:
```ts title="src/workflows/update-custom-from-cart/index.ts"
const deleted = when({
input,
cartData,
}, (data) =>
data.cartData.custom && (
data.input.additional_data?.custom_name === null ||
data.input.additional_data?.custom_name.length === 0
)
)
.then(() => {
deleteCustomStep({
custom: cartData.custom,
})
dismissRemoteLinkStep({
[HELLO_MODULE]: {
custom_id: cartData.custom.id,
},
})
return cartData.custom.id
})
// TODO delete Custom record
```
Using the `when-then` utility, you check if the cart has a linked `Custom` record and `custom_name` is `null` or an empty string. If so, you delete the linked `Custom` record and dismiss its links.
Finally, replace the new `TODO` with the following:
```ts title="src/workflows/update-custom-from-cart/index.ts"
const updated = when({
input,
cartData,
}, (data) => data.cartData.custom && data.input.additional_data?.custom_name?.length > 0)
.then(() => {
const custom = updateCustomStep({
id: cartData.custom.id,
custom_name: input.additional_data.custom_name,
})
return custom
})
return new WorkflowResponse({
created,
updated,
deleted,
})
```
Using the `when-then` utility, you check if the cart has a linked `Custom` record and `custom_name` is passed in the `additional_data`. If so, you update the linked `Custom` recod.
You return in the workflow response the created, updated, and deleted `Custom` record.
### Consume cartUpdated Workflow Hook
You can now consume the `cartUpdated` and execute the workflow you created.
Create the file `src/workflows/hooks/cart-updated.ts` with the following content:
```ts title="src/workflows/hooks/cart-updated.ts"
import { updateCartWorkflow } from "@medusajs/medusa/core-flows"
import {
UpdateCustomFromCartStepInput,
updateCustomFromCartWorkflow,
} from "../update-custom-from-cart"
updateCartWorkflow.hooks.cartUpdated(
async (hookData, { container }) => {
await updateCustomFromCartWorkflow(container)
.run({
input: hookData as UpdateCustomFromCartStepInput,
})
}
)
```
In the workflow hook handler, you execute the workflow, passing it the hook's input.
### Test it Out
To test it out, send a `POST` request to `/store/carts/:id` to update a cart, passing `custom_name` in `additional_data`:
```bash
curl -X POST 'localhost:9000/store/carts/{cart_id}?fields=+custom.*' \
-H 'x-publishable-api-key: {publishable_api_key}' \
-H 'Content-Type: application/json' \
--data '{
"additional_data": {
"custom_name": "test 2"
}
}'
```
Make sure to replace `{cart_id}` with the cart's ID, and `{publishable_api_key}` with your publishable API key, which you can retrieve from the Medusa Admin.
The request will return the cart's details with the updated `custom` linked record.
@@ -0,0 +1,35 @@
export const metadata = {
title: `Links between Cart Module and Other Modules`,
}
# {metadata.title}
This document showcases the module links defined between the Cart Module and other commerce modules.
## Payment Module
The Payment Module 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.
![A diagram showcasing an example of how data models from the Cart and Payment modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1711537849/Medusa%20Resources/cart-payment_ixziqm.jpg)
---
## Promotion Module
The Promotion Module provides discount features.
Medusa defines a link between the `Cart` and `Promotion` data models. This indicates the promotions applied on a cart.
![A diagram showcasing an example of how data models from the Cart and Promotion modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1711538015/Medusa%20Resources/cart-promotion_kuh9vm.jpg)
---
## Order Module
The Order Module 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.
![A diagram showcasing an example of how data models from the Cart and Order modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1728375735/Medusa%20Resources/cart-order_ijwmfs.jpg)
@@ -6,7 +6,7 @@ export const metadata = {
# {metadata.title}
The Cart Module is the `@medusajs/medusa/cart` NPM package that provides cart-related features in your Medusa and Node.js applications.
The Cart Module provides cart-related features in your Medusa and Node.js applications.
## How to Use Cart Module's Service
@@ -15,18 +15,33 @@ You can use the Cart Module's main service by resolving from the Medusa containe
For example:
<CodeTabs groupId="resource-type">
<CodeTab label="Workflow Step" value="workflow-step">
```ts title="src/workflows/hello-world/step1.ts"
import { createStep } from "@medusajs/framework/workflows-sdk"
import { Modules } from "@medusajs/framework/utils"
const step1 = createStep("step-1", async (_, { container }) => {
const cartModuleService = container.resolve(
Modules.CART
)
const carts = await cartModuleService.listCarts()
})
```
</CodeTab>
<CodeTab label="API Route" value="api-route">
```ts title="src/api/store/custom/route.ts"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { ICartModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
```ts title="src/api/store/custom/route.ts"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const cartModuleService: ICartModuleService = req.scope.resolve(
const cartModuleService = req.scope.resolve(
Modules.CART
)
@@ -39,35 +54,17 @@ export async function GET(
</CodeTab>
<CodeTab label="Subscriber" value="subscribers">
```ts title="src/subscribers/custom-handler.ts"
import { SubscriberArgs } from "@medusajs/framework"
import { ICartModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
```ts title="src/subscribers/custom-handler.ts"
import { SubscriberArgs } from "@medusajs/framework"
import { Modules } from "@medusajs/framework/utils"
export default async function subscriberHandler({ container }: SubscriberArgs) {
const cartModuleService: ICartModuleService = container.resolve(
const cartModuleService = container.resolve(
Modules.CART
)
const carts = await cartModuleService.listCarts()
}
```
</CodeTab>
<CodeTab label="Workflow Step" value="workflow-step">
```ts title="src/workflows/hello-world/step1.ts"
import { createStep } from "@medusajs/framework/workflows-sdk"
import { ICartModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
const step1 = createStep("step-1", async (_, { container }) => {
const cartModuleService: ICartModuleService = container.resolve(
Modules.CART
)
const carts = await cartModuleService.listCarts()
})
```
</CodeTab>
@@ -121,7 +118,7 @@ const shippingAdjustments =
A cart is scoped to a sales channel, region, and a customer.
When used with their respective modules and other commerce modules, you benefit from features like:
The Medusa application links the Cart Module to each of their respective modules, providing with features like:
- Checking product availability in a sales channel.
- Retrieving pricing per region.
@@ -14,7 +14,7 @@ In this document, youll learn how a promotion is applied to a carts line i
An adjustment line indicates a change to an item or a shipping methods amount. Its used to apply promotions or discounts on a cart.
The `LineItemAdjustment` data model represents changes on a line item, and the `ShippingMethodAdjustment` data model represents changes on a shipping method.
The [LineItemAdjustment](/references/cart/models/LineItemAdjustment) data model represents changes on a line item, and the [ShippingMethodAdjustment](/references/cart/models/ShippingMethodAdjustment) data model represents changes on a shipping method.
![A diagram showcasing the relations between other data models and adjustment line models](https://res.cloudinary.com/dza7lstvk/image/upload/v1711534248/Medusa%20Resources/cart-adjustments_k4sttb.jpg)
@@ -24,7 +24,7 @@ The `amount` property of the adjustment line indicates the amount to be discount
## Discountable Option
The `LineItem` data model has an `is_discountable` property that indicates whether promotions can be applied to the line item. Its enabled by default.
The [LineItem](/references/cart/models/LineItem) data model has an `is_discountable` property that indicates whether promotions can be applied to the line item. Its enabled by default.
When disabled, a promotion cant be applied to a line item. In the context of the Promotion Module, the promotion isnt applied to the line item even if it matches its rules.
@@ -33,7 +33,7 @@ When disabled, a promotion cant be applied to a line item. In the context of
## Promotion Actions
When using the Cart and Promotion modules together, use the [computeActions method of the Promotion Modules main service](/references/promotion/computeActions). It retrieves the actions of line items and shipping methods.
When using the Cart and Promotion modules together, such as in the Medusa application, use the [computeActions method of the Promotion Modules main service](/references/promotion/computeActions). It retrieves the actions of line items and shipping methods.
<Note>
@@ -41,6 +41,8 @@ Learn more about actions in the [Promotion Modules documentation](../../promo
</Note>
For example:
```ts collapsibleLines="1-8" expandButtonLabel="Show Imports"
import {
ComputeActionAdjustmentLine,
@@ -101,7 +103,7 @@ The `computeActions` method accepts the existing adjustments of line items and s
Then, use the returned `addItemAdjustment` and `addShippingMethodAdjustment` actions to set the carts line item and the shipping methods adjustments.
```ts collapsibleLines="1-9" expandButtonLabel="Show Imports"
```ts collapsibleLines="1-8" expandButtonLabel="Show Imports"
import {
AddItemAdjustmentAction,
AddShippingMethodAdjustment,
@@ -1,53 +0,0 @@
export const metadata = {
title: `Relations between Cart Module and Other Modules`,
}
# {metadata.title}
This document showcases the link modules defined between the Cart Module and other commerce modules.
## Customer Module
A cart is scoped to the customer using it. Medusa defines a link module that builds a relationship between the `Cart` and `Customer` data models.
![A diagram showcasing an example of how data models from the Cart and Customer modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1711537276/Medusa%20Resources/cart-customer_pnjvuw.jpg)
---
## Payment Module
The Payment Module allows you to associate payments with a cart. Medusa defines a link module that builds a relationship between the `Cart` and `PaymentCollection` data models.
![A diagram showcasing an example of how data models from the Cart and Payment modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1711537849/Medusa%20Resources/cart-payment_ixziqm.jpg)
---
## Product Module
A cart's line item is associated with a product and its variant. Medusa defines a link module that builds a relationship between the `Cart`, `Product`, and `ProductVariant` data models.
![A diagram showcasing an example of how data models from the Cart and Product modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716546229/Medusa%20Resources/cart-product_x82x9j.jpg)
---
## Promotion Module
A promotion can be applied on line items and shipping methods of a cart. Medusa defines a link module that builds a relationship between the `Cart`, `LineItemAdjustment`, and `Promotion` data models.
![A diagram showcasing an example of how data models from the Cart and Promotion modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1711538015/Medusa%20Resources/cart-promotion_kuh9vm.jpg)
---
## Region Module
A cart is scoped to a region. Medusa defines a link module that builds a relationship between the `Cart` and `Region` data models.
![A diagram showcasing an example of how data models from the Cart and Region modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716543714/Medusa%20Resources/customer-region_rtmymb.jpg)
---
## Sales Channel Module
A cart is scoped to a sales channel. Medusa defines a link module that builds a relationship between the `Cart` and `SalesChannel` data models.
![A diagram showcasing an example of how data models from the Cart and Sales Channel modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1711538159/Medusa%20Resources/cart-sales-channel_n2oi0v.jpg)
@@ -16,7 +16,7 @@ A tax line indicates the tax rate of a line item or a shipping method. The [Line
## Tax Inclusivity
By default, the tax amount is calculated by taking the tax rate from the line item or shipping methods amount, and then added to the item/methods subtotal.
By default, the tax amount is calculated by taking the tax rate from the line item or shipping methods amount, and then adding them to the item/methods subtotal.
However, line items and shipping methods have an `is_tax_inclusive` property that, when enabled, indicates that the item or methods price already includes taxes.
@@ -30,9 +30,11 @@ The following diagram is a simplified showcase of how a subtotal is calculated f
![A diagram showing an example of calculating the subtotal of a line item using its taxes](https://res.cloudinary.com/dza7lstvk/image/upload/v1711535295/Medusa%20Resources/cart-tax-inclusive_shpr3t.jpg)
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`.
---
## Retrieving Tax Lines
## Retrieve Tax Lines
When using the Cart and Tax modules together, you can use the `getTaxLines` method of the Tax Modules main service. It retrieves the tax lines for a carts line items and shipping methods.
@@ -6,7 +6,13 @@ export const metadata = {
# {metadata.title}
In this section of the documentation, you'll find guides and references related to Medusa's Commerce Modules.
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 also defines data models, representing tables in the database. Medusa's framework and tools allow you to extend these data models to add custom fields.
## Commerce Modules List
<ChildDocs onlyTopLevel={true} />
@@ -10,7 +10,7 @@ For example, this is not allowed:
```ts
import {
AdminGetApiKeysParams
AdminGetApiKeysParams,
} from "@medusajs/medusa/dist/api/admin/api-keys" // <-- Not allowed
```
@@ -31,6 +31,6 @@ So, this is allowed:
```ts
import {
AdminGetApiKeysParams
AdminGetApiKeysParams,
} from "@medusajs/medusa/api/admin/api-keys" // <-- Allowed
```
+23 -20
View File
@@ -1,18 +1,17 @@
export const generatedEditDates = {
"app/commerce-modules/auth/auth-providers/emailpass/page.mdx": "2024-09-30T08:43:53.153Z",
"app/commerce-modules/auth/auth-providers/page.mdx": "2024-09-05T12:15:19.491Z",
"app/commerce-modules/auth/auth-providers/emailpass/page.mdx": "2024-10-08T07:35:59.167Z",
"app/commerce-modules/auth/auth-providers/page.mdx": "2024-10-08T07:27:21.859Z",
"app/commerce-modules/auth/authentication-route/page.mdx": "2024-09-05T12:06:38.155Z",
"app/commerce-modules/auth/examples/page.mdx": "2024-09-30T08:43:53.154Z",
"app/commerce-modules/auth/module-options/page.mdx": "2024-09-30T08:43:53.156Z",
"app/commerce-modules/auth/page.mdx": "2024-09-30T08:43:53.156Z",
"app/commerce-modules/auth/examples/page.mdx": "2024-10-07T15:36:17.212Z",
"app/commerce-modules/auth/module-options/page.mdx": "2024-10-07T15:33:05.989Z",
"app/commerce-modules/auth/page.mdx": "2024-10-08T07:38:27.522Z",
"app/commerce-modules/cart/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
"app/commerce-modules/cart/_events/page.mdx": "2024-07-03T19:27:13+03:00",
"app/commerce-modules/cart/concepts/page.mdx": "2024-06-26T07:55:59+00:00",
"app/commerce-modules/cart/examples/page.mdx": "2024-09-30T08:43:53.157Z",
"app/commerce-modules/cart/promotions/page.mdx": "2024-09-30T08:43:53.158Z",
"app/commerce-modules/cart/relations-to-other-modules/page.mdx": "2024-05-29T11:08:06+00:00",
"app/commerce-modules/cart/tax-lines/page.mdx": "2024-06-26T07:55:59+00:00",
"app/commerce-modules/cart/page.mdx": "2024-09-30T08:43:53.157Z",
"app/commerce-modules/cart/concepts/page.mdx": "2024-10-08T07:49:03.737Z",
"app/commerce-modules/cart/examples/page.mdx": "2024-10-08T07:42:13.336Z",
"app/commerce-modules/cart/promotions/page.mdx": "2024-10-08T07:54:31.120Z",
"app/commerce-modules/cart/tax-lines/page.mdx": "2024-10-08T07:57:19.168Z",
"app/commerce-modules/cart/page.mdx": "2024-10-08T07:41:22.711Z",
"app/commerce-modules/currency/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
"app/commerce-modules/currency/_events/page.mdx": "2024-07-03T19:27:13+03:00",
"app/commerce-modules/currency/examples/page.mdx": "2024-10-08T15:06:26.492Z",
@@ -122,7 +121,7 @@ export const generatedEditDates = {
"app/commerce-modules/user/module-options/page.mdx": "2024-09-30T08:43:53.171Z",
"app/commerce-modules/user/user-creation-flows/page.mdx": "2024-06-26T07:55:59+00:00",
"app/commerce-modules/user/page.mdx": "2024-09-30T08:43:53.172Z",
"app/commerce-modules/page.mdx": "2024-05-03T17:36:38+03:00",
"app/commerce-modules/page.mdx": "2024-10-07T13:55:08.014Z",
"app/contribution-guidelines/_admin-translations/page.mdx": "2024-05-13T18:55:11+03:00",
"app/contribution-guidelines/docs/page.mdx": "2024-05-13T18:55:11+03:00",
"app/create-medusa-app/page.mdx": "2024-08-05T11:10:55+03:00",
@@ -220,15 +219,14 @@ export const generatedEditDates = {
"app/commerce-modules/auth/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
"app/commerce-modules/auth/auth-flows/page.mdx": "2024-09-05T08:50:11.671Z",
"app/commerce-modules/auth/_events/page.mdx": "2024-07-03T19:27:13+03:00",
"app/commerce-modules/auth/auth-identity-and-actor-types/page.mdx": "2024-09-05T08:11:28.936Z",
"app/commerce-modules/api-key/page.mdx": "2024-09-30T08:43:53.152Z",
"app/commerce-modules/auth/create-actor-type/page.mdx": "2024-09-30T08:43:53.154Z",
"app/commerce-modules/auth/auth-identity-and-actor-types/page.mdx": "2024-10-08T07:08:43.428Z",
"app/commerce-modules/api-key/page.mdx": "2024-10-07T13:57:33.042Z",
"app/commerce-modules/auth/create-actor-type/page.mdx": "2024-10-08T07:31:11.256Z",
"app/architectural-modules/page.mdx": "2024-05-28T13:25:03+03:00",
"app/commerce-modules/api-key/relations-to-other-modules/page.mdx": "2024-05-29T11:08:06+00:00",
"app/architectural-modules/workflow-engine/redis/page.mdx": "2024-09-30T08:43:53.152Z",
"app/commerce-modules/api-key/examples/page.mdx": "2024-09-30T08:43:53.152Z",
"app/commerce-modules/api-key/examples/page.mdx": "2024-10-07T13:58:49.457Z",
"app/architectural-modules/notification/sendgrid/page.mdx": "2024-09-30T08:43:53.151Z",
"app/commerce-modules/api-key/concepts/page.mdx": "2024-06-26T07:55:59+00:00",
"app/commerce-modules/api-key/concepts/page.mdx": "2024-10-07T13:59:37.529Z",
"app/architectural-modules/workflow-engine/page.mdx": "2024-05-28T13:25:03+03:00",
"app/_events-reference/page.mdx": "2024-07-03T19:27:13+03:00",
"app/architectural-modules/cache/page.mdx": "2024-05-28T13:25:03+03:00",
@@ -906,8 +904,8 @@ export const generatedEditDates = {
"references/types/HttpTypes/interfaces/types.HttpTypes.AdminClaimPreviewResponse/page.mdx": "2024-10-03T00:11:52.828Z",
"references/types/HttpTypes/interfaces/types.HttpTypes.AdminOrderEditPreviewResponse/page.mdx": "2024-10-03T00:11:53.030Z",
"references/types/interfaces/types.BaseClaim/page.mdx": "2024-10-03T00:11:52.699Z",
"app/commerce-modules/auth/auth-providers/github/page.mdx": "2024-09-30T08:43:53.153Z",
"app/commerce-modules/auth/auth-providers/google/page.mdx": "2024-09-30T08:43:53.153Z",
"app/commerce-modules/auth/auth-providers/github/page.mdx": "2024-10-08T07:37:27.882Z",
"app/commerce-modules/auth/auth-providers/google/page.mdx": "2024-10-08T07:37:06.517Z",
"app/storefront-development/customers/third-party-login/page.mdx": "2024-09-11T09:58:51.801Z",
"references/types/HttpTypes/types/types.HttpTypes.AdminWorkflowRunResponse/page.mdx": "2024-09-17T00:10:58.803Z",
"references/types/HttpTypes/types/types.HttpTypes.BatchResponse/page.mdx": "2024-09-05T00:11:17.182Z",
@@ -2220,6 +2218,11 @@ export const generatedEditDates = {
"references/user/interfaces/user.IModuleService/page.mdx": "2024-10-03T00:12:20.657Z",
"references/user/interfaces/user.MessageAggregatorFormat/page.mdx": "2024-10-03T00:12:20.662Z",
"app/troubleshooting/dist-imports/page.mdx": "2024-10-03T09:19:37.639Z",
"app/commerce-modules/auth/reset-password/page.mdx": "2024-10-08T07:34:08.488Z",
"app/storefront-development/customers/reset-password/page.mdx": "2024-09-25T10:21:46.647Z",
"app/commerce-modules/api-key/links-to-other-modules/page.mdx": "2024-10-08T08:05:36.596Z",
"app/commerce-modules/cart/extend/page.mdx": "2024-10-08T11:22:22.523Z",
"app/commerce-modules/cart/links-to-other-modules/page.mdx": "2024-10-08T08:22:35.190Z",
"app/commerce-modules/auth/reset-password/page.mdx": "2024-09-25T09:36:26.592Z",
"app/storefront-development/customers/reset-password/page.mdx": "2024-09-25T10:21:46.647Z",
"app/commerce-modules/customer/extend/page.mdx": "2024-10-08T14:18:55.407Z",
+12 -8
View File
@@ -88,12 +88,12 @@ export const filesMap = [
"pathname": "/commerce-modules/api-key/examples"
},
{
"filePath": "/www/apps/resources/app/commerce-modules/api-key/page.mdx",
"pathname": "/commerce-modules/api-key"
"filePath": "/www/apps/resources/app/commerce-modules/api-key/links-to-other-modules/page.mdx",
"pathname": "/commerce-modules/api-key/links-to-other-modules"
},
{
"filePath": "/www/apps/resources/app/commerce-modules/api-key/relations-to-other-modules/page.mdx",
"pathname": "/commerce-modules/api-key/relations-to-other-modules"
"filePath": "/www/apps/resources/app/commerce-modules/api-key/page.mdx",
"pathname": "/commerce-modules/api-key"
},
{
"filePath": "/www/apps/resources/app/commerce-modules/auth/auth-flows/page.mdx",
@@ -151,6 +151,14 @@ export const filesMap = [
"filePath": "/www/apps/resources/app/commerce-modules/cart/examples/page.mdx",
"pathname": "/commerce-modules/cart/examples"
},
{
"filePath": "/www/apps/resources/app/commerce-modules/cart/extend/page.mdx",
"pathname": "/commerce-modules/cart/extend"
},
{
"filePath": "/www/apps/resources/app/commerce-modules/cart/links-to-other-modules/page.mdx",
"pathname": "/commerce-modules/cart/links-to-other-modules"
},
{
"filePath": "/www/apps/resources/app/commerce-modules/cart/page.mdx",
"pathname": "/commerce-modules/cart"
@@ -159,10 +167,6 @@ export const filesMap = [
"filePath": "/www/apps/resources/app/commerce-modules/cart/promotions/page.mdx",
"pathname": "/commerce-modules/cart/promotions"
},
{
"filePath": "/www/apps/resources/app/commerce-modules/cart/relations-to-other-modules/page.mdx",
"pathname": "/commerce-modules/cart/relations-to-other-modules"
},
{
"filePath": "/www/apps/resources/app/commerce-modules/cart/tax-lines/page.mdx",
"pathname": "/commerce-modules/cart/tax-lines"
+12 -4
View File
@@ -56,8 +56,8 @@ export const generatedSidebar = [
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/commerce-modules/api-key/relations-to-other-modules",
"title": "Relation to Modules",
"path": "/commerce-modules/api-key/links-to-other-modules",
"title": "Link to Modules",
"children": []
}
]
@@ -538,6 +538,14 @@ export const generatedSidebar = [
"title": "Examples",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/commerce-modules/cart/extend",
"title": "Extend Module",
"children": []
},
{
"loaded": true,
"isPathHref": true,
@@ -572,8 +580,8 @@ export const generatedSidebar = [
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/commerce-modules/cart/relations-to-other-modules",
"title": "Relations to Other Modules",
"path": "/commerce-modules/cart/links-to-other-modules",
"title": "Links to Other Modules",
"children": []
}
]
+10
View File
@@ -44,6 +44,16 @@ const nextConfig = {
basePath: process.env.NEXT_PUBLIC_BASE_PATH || "/v2/resources",
async redirects() {
return [
{
source: "/commerce-modules/api-key/relations-to-other-modules",
destination: "/commerce-modules/api-key/links-to-other-modules",
permanent: true,
},
{
source: "/commerce-modules/cart/relations-to-other-modules",
destination: "/commerce-modules/cart/links-to-other-modules",
permanent: true,
},
{
source: "/commerce-modules/fulfillment/relations-to-other-modules",
destination: "/commerce-modules/fulfillment/links-to-other-modules",
+9 -4
View File
@@ -39,8 +39,8 @@ export const sidebar = sidebarAttachHrefCommonOptions([
},
{
type: "link",
path: "/commerce-modules/api-key/relations-to-other-modules",
title: "Relation to Modules",
path: "/commerce-modules/api-key/links-to-other-modules",
title: "Link to Modules",
},
],
},
@@ -223,6 +223,11 @@ export const sidebar = sidebarAttachHrefCommonOptions([
path: "/commerce-modules/cart/examples",
title: "Examples",
},
{
type: "link",
path: "/commerce-modules/cart/extend",
title: "Extend Module",
},
{
type: "sub-category",
title: "Concepts",
@@ -244,8 +249,8 @@ export const sidebar = sidebarAttachHrefCommonOptions([
},
{
type: "link",
path: "/commerce-modules/cart/relations-to-other-modules",
title: "Relations to Other Modules",
path: "/commerce-modules/cart/links-to-other-modules",
title: "Links to Other Modules",
},
],
},