docs: general improvements and additions (#12296)
This commit is contained in:
@@ -25,7 +25,12 @@ npx medusa build
|
||||
|
||||
## Build Output
|
||||
|
||||
The `build` command outputs the production build in the `.medusa/server` directory, and the admin dashboard build in the `.medusa/server/public/admin`.
|
||||
The `build` command creates a `.medusa` directory in the root of your project that contains your build assets. Don't commit this directory to your repository.
|
||||
|
||||
The `.medusa` directory contains the following directories:
|
||||
|
||||
- `.medusa/server`: Contains the production build of your Medusa application.
|
||||
- `.medusa/server/public/admin`: Contains the production build of the admin dashboard.
|
||||
|
||||
### Separate Admin Build
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ export const highlights = [
|
||||
]
|
||||
|
||||
```ts title="src/api/middlewares.ts" highlights={highlights}
|
||||
import { defineMiddlewares } from "@medusajs/medusa";
|
||||
import { defineMiddlewares } from "@medusajs/medusa"
|
||||
|
||||
export default defineMiddlewares({
|
||||
routes: [
|
||||
@@ -69,13 +69,13 @@ export default defineMiddlewares({
|
||||
method: "GET",
|
||||
middlewares: [
|
||||
(req, res, next) => {
|
||||
req.allowed?.push("b2b_company");
|
||||
next();
|
||||
req.allowed?.push("b2b_company")
|
||||
next()
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
})
|
||||
```
|
||||
|
||||
In this example, you apply a middleware to the [Get Customer Admin API Route](!api!/admin#customers_getcustomersid).
|
||||
|
||||
@@ -6,6 +6,12 @@ export const metadata = {
|
||||
|
||||
In this chapter, you'll learn how to manage relationships between data models when creating, updating, or retrieving records using the module's main service.
|
||||
|
||||
<Note>
|
||||
|
||||
This chapter applies to data model relationships within the same module. To manage linked data models across modules, check out [Links](../../module-links/page.mdx) and [Query](../../module-links/query/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## Manage One-to-One Relationship
|
||||
|
||||
### BelongsTo Side of One-to-One
|
||||
|
||||
@@ -222,7 +222,7 @@ export const POST = async (
|
||||
) => {
|
||||
const { result } = await createPostWorkflow(req.scope)
|
||||
.run({
|
||||
input: result.validatedBody
|
||||
input: result.validatedBody,
|
||||
})
|
||||
|
||||
return res.json(result)
|
||||
@@ -987,7 +987,7 @@ export default async function orderConfirmationJob(
|
||||
await sendOrderConfirmationWorkflow(container).run({
|
||||
input: {
|
||||
id: "order_123",
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
export const config = {
|
||||
|
||||
@@ -436,7 +436,7 @@ export default class CmsModuleService {
|
||||
filter: {
|
||||
id: string | string[]
|
||||
},
|
||||
config?: FindConfig<any> | undefined,
|
||||
config?: FindConfig<any> | undefined
|
||||
) {
|
||||
return this.client.getPosts(filter, {
|
||||
limit: config?.take,
|
||||
|
||||
@@ -304,7 +304,7 @@ class BlogModuleService extends MedusaService({
|
||||
protected postRepository_: DAL.RepositoryService<Post>
|
||||
|
||||
constructor({
|
||||
postRepository
|
||||
postRepository,
|
||||
}: InjectedDependencies) {
|
||||
super(...arguments)
|
||||
this.postRepository_ = postRepository
|
||||
@@ -334,7 +334,7 @@ class BlogModuleService {
|
||||
protected postRepository_: DAL.RepositoryService<Post>
|
||||
|
||||
constructor({
|
||||
postRepository
|
||||
postRepository,
|
||||
}: InjectedDependencies) {
|
||||
super(...arguments)
|
||||
this.postRepository_ = postRepository
|
||||
|
||||
@@ -144,11 +144,11 @@ export const skipOnPermanentFailureEnabledHighlights = [
|
||||
|
||||
```ts title="Workflow Equivalent" highlights={skipOnPermanentFailureEnabledHighlights}
|
||||
import {
|
||||
createWorkflow
|
||||
createWorkflow,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import {
|
||||
actionThatThrowsError,
|
||||
moreActions
|
||||
moreActions,
|
||||
} from "./steps"
|
||||
|
||||
export const myWorkflow = createWorkflow(
|
||||
@@ -204,12 +204,12 @@ export const skipOnPermanentFailureStepHighlights = [
|
||||
|
||||
```ts title="Workflow Equivalent" highlights={skipOnPermanentFailureStepHighlights}
|
||||
import {
|
||||
createWorkflow
|
||||
createWorkflow,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import {
|
||||
actionThatThrowsError,
|
||||
moreActions,
|
||||
continueExecutionFromStep
|
||||
continueExecutionFromStep,
|
||||
} from "./steps"
|
||||
|
||||
export const myWorkflow = createWorkflow(
|
||||
@@ -278,11 +278,11 @@ export const continueOnPermanentFailureHighlights = [
|
||||
|
||||
```ts title="Workflow Equivalent" highlights={continueOnPermanentFailureHighlights}
|
||||
import {
|
||||
createWorkflow
|
||||
createWorkflow,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import {
|
||||
actionThatThrowsError,
|
||||
moreActions
|
||||
moreActions,
|
||||
} from "./steps"
|
||||
|
||||
export const myWorkflow = createWorkflow(
|
||||
|
||||
@@ -128,6 +128,9 @@ This directory is the central place for your custom development. It includes the
|
||||
|
||||
This file holds your [Medusa configurations](../configurations/medusa-config/page.mdx), such as your PostgreSQL database configurations.
|
||||
|
||||
### .medusa
|
||||
|
||||
The `.medusa` directory holds types and other files that are generated by Medusa when you run the `build` command. Don't modify any files or commit them to your repository.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ export const generatedEditDates = {
|
||||
"app/learn/fundamentals/workflows/long-running-workflow/page.mdx": "2025-03-28T07:02:34.467Z",
|
||||
"app/learn/fundamentals/workflows/constructor-constraints/page.mdx": "2025-04-24T13:18:24.184Z",
|
||||
"app/learn/fundamentals/data-models/write-migration/page.mdx": "2025-03-24T06:41:48.915Z",
|
||||
"app/learn/fundamentals/data-models/manage-relationships/page.mdx": "2025-03-18T15:09:18.688Z",
|
||||
"app/learn/fundamentals/data-models/manage-relationships/page.mdx": "2025-04-25T14:16:41.124Z",
|
||||
"app/learn/fundamentals/modules/remote-query/page.mdx": "2024-07-21T21:20:24+02:00",
|
||||
"app/learn/fundamentals/modules/options/page.mdx": "2025-03-18T15:12:34.510Z",
|
||||
"app/learn/fundamentals/data-models/relationships/page.mdx": "2025-03-18T07:52:07.421Z",
|
||||
@@ -67,7 +67,7 @@ export const generatedEditDates = {
|
||||
"app/learn/fundamentals/module-links/directions/page.mdx": "2025-03-17T12:52:06.161Z",
|
||||
"app/learn/fundamentals/module-links/page.mdx": "2025-04-17T08:50:17.036Z",
|
||||
"app/learn/fundamentals/module-links/query/page.mdx": "2025-04-18T11:13:02.240Z",
|
||||
"app/learn/fundamentals/modules/db-operations/page.mdx": "2025-04-23T14:24:37.910Z",
|
||||
"app/learn/fundamentals/modules/db-operations/page.mdx": "2025-04-25T14:26:25.000Z",
|
||||
"app/learn/fundamentals/modules/multiple-services/page.mdx": "2025-03-18T15:11:44.632Z",
|
||||
"app/learn/fundamentals/modules/page.mdx": "2025-03-18T07:51:09.049Z",
|
||||
"app/learn/debugging-and-testing/instrumentation/page.mdx": "2025-02-24T08:12:53.132Z",
|
||||
@@ -94,10 +94,10 @@ export const generatedEditDates = {
|
||||
"app/learn/fundamentals/data-models/infer-type/page.mdx": "2025-03-18T07:41:01.936Z",
|
||||
"app/learn/fundamentals/custom-cli-scripts/seed-data/page.mdx": "2024-12-09T14:38:06.385Z",
|
||||
"app/learn/fundamentals/environment-variables/page.mdx": "2025-03-11T08:55:03.343Z",
|
||||
"app/learn/build/page.mdx": "2024-12-09T11:05:17.383Z",
|
||||
"app/learn/build/page.mdx": "2025-04-25T12:34:33.914Z",
|
||||
"app/learn/deployment/general/page.mdx": "2025-04-17T08:29:09.878Z",
|
||||
"app/learn/fundamentals/workflows/multiple-step-usage/page.mdx": "2024-11-25T16:19:32.169Z",
|
||||
"app/learn/installation/page.mdx": "2025-04-18T10:42:42.598Z",
|
||||
"app/learn/installation/page.mdx": "2025-04-25T12:33:42.096Z",
|
||||
"app/learn/fundamentals/data-models/check-constraints/page.mdx": "2024-12-06T14:34:50.384Z",
|
||||
"app/learn/fundamentals/module-links/link/page.mdx": "2025-04-07T08:03:14.513Z",
|
||||
"app/learn/fundamentals/workflows/store-executions/page.mdx": "2025-04-17T08:29:10.166Z",
|
||||
@@ -115,9 +115,9 @@ export const generatedEditDates = {
|
||||
"app/learn/configurations/medusa-config/page.mdx": "2025-04-17T08:29:09.907Z",
|
||||
"app/learn/configurations/ts-aliases/page.mdx": "2025-02-11T16:57:46.683Z",
|
||||
"app/learn/production/worker-mode/page.mdx": "2025-03-11T15:21:50.906Z",
|
||||
"app/learn/fundamentals/module-links/read-only/page.mdx": "2025-04-18T11:09:13.328Z",
|
||||
"app/learn/fundamentals/module-links/read-only/page.mdx": "2025-04-25T14:26:25.000Z",
|
||||
"app/learn/fundamentals/data-models/properties/page.mdx": "2025-03-18T07:57:17.826Z",
|
||||
"app/learn/fundamentals/framework/page.mdx": "2025-04-17T16:07:19.090Z",
|
||||
"app/learn/fundamentals/api-routes/retrieve-custom-links/page.mdx": "2025-04-18T07:38:56.729Z",
|
||||
"app/learn/fundamentals/workflows/errors/page.mdx": "2025-04-24T14:47:13.368Z"
|
||||
"app/learn/fundamentals/framework/page.mdx": "2025-04-25T14:26:25.000Z",
|
||||
"app/learn/fundamentals/api-routes/retrieve-custom-links/page.mdx": "2025-04-25T14:26:25.000Z",
|
||||
"app/learn/fundamentals/workflows/errors/page.mdx": "2025-04-25T14:26:25.000Z"
|
||||
}
|
||||
+16727
-16507
File diff suppressed because it is too large
Load Diff
@@ -206,6 +206,114 @@ Refer to the [user guide](!user-guide!/settings/regions#edit-region-details) to
|
||||
|
||||
---
|
||||
|
||||
## Stripe Payment Provider IDs
|
||||
|
||||
When you register the Stripe Module Provider, it registers different providers, such as basic Stripe payment, Bancontact, and more.
|
||||
|
||||
Each provider is registered and referenced by a unique ID made up of the format `pp_{identifier}_{id}`, where:
|
||||
|
||||
- `{identifier}` is the ID of the payment provider as defined in the Stripe Module Provider.
|
||||
- `{id}` is the ID of the Stripe Module Provider as set in the `medusa-config.ts` file. For example, `stripe`.
|
||||
|
||||
Assuming you set the ID of the Stripe Module Provider to `stripe` in `medusa-config.ts`, the Medusa application will register the following payment providers:
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Provider Name</Table.HeaderCell>
|
||||
<Table.HeaderCell>Provider ID</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
Basic Stripe Payment
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`pp_stripe_stripe`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
Bancontact Payments
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`pp_stripe-bancontact_stripe`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
BLIK Payments
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`pp_stripe-blik_stripe`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
giropay Payments
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`pp_stripe-giropay_stripe`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
iDEAL Payments
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`pp_stripe-ideal_stripe`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
Przelewy24 Payments
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`pp_stripe-przelewy24_stripe`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
PromptPay Payments
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`pp_stripe-promptpay_stripe`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
---
|
||||
|
||||
## Setup Stripe Webhooks
|
||||
|
||||
For production applications, you must set up webhooks in Stripe that inform Medusa of changes and updates to payments. Refer to [Stripe's documentation](https://docs.stripe.com/webhooks#add-a-webhook-endpoint) on how to setup webhooks.
|
||||
@@ -215,7 +323,7 @@ For production applications, you must set up webhooks in Stripe that inform Medu
|
||||
Medusa has a `{server_url}/hooks/payment/{provider_id}` API route that you can use to register webhooks in Stripe, where:
|
||||
|
||||
- `{server_url}` is the URL to your deployed Medusa application in server mode.
|
||||
- `{provider_id}` is the ID of the provider, such as `stripe_stripe` for basic payments.
|
||||
- `{provider_id}` is the ID of the provider as explained in the [Stripe Payment Provider IDs](#stripe-payment-provider-ids) section, without the `pp_` prefix.
|
||||
|
||||
The Stripe Module Provider supports the following payment types, and the webhook endpoint URL is different for each:
|
||||
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
---
|
||||
sidebar_label: "Get Variant Inventory"
|
||||
tags:
|
||||
- product
|
||||
- inventory
|
||||
- sales channel
|
||||
- server
|
||||
- how to
|
||||
---
|
||||
|
||||
import { TypeList } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Get Product Variant Inventory Quantity`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you'll learn how to retrieve the available inventory quantity of a product variant in your Medusa application customizations. That includes API routes, workflows, subscribers, scheduled jobs, and any resource that can access the [Medusa container](!docs!/learn/fundamentals/medusa-container).
|
||||
|
||||
<Note title="Looking for storefront guide?">
|
||||
|
||||
Refer to the [Retrieve Product Variant Inventory](../../../../storefront-development/products/inventory/page.mdx) storefront guide.
|
||||
|
||||
</Note>
|
||||
|
||||
## Understanding Product Variant Inventory Availability
|
||||
|
||||
Product variants have a `manage_inventory` boolean field that indicates whether the Medusa application manages the inventory of the product variant.
|
||||
|
||||
When `manage_inventory` is disabled, the Medusa application always considers the product variant to be in stock. So, you can't retrieve the inventory quantity for those products.
|
||||
|
||||
When `manage_inventory` is enabled, the Medusa application tracks the inventory of the product variant using the [Inventory Module](../../../inventory/page.mdx). For example, when a customer purchases a product variant, the Medusa application decrements the stocked quantity of the product variant.
|
||||
|
||||
This guide explains how to retrieve the inventory quantity of a product variant when `manage_inventory` is enabled.
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Product Variant Inventory
|
||||
|
||||
To retrieve the inventory quantity of a product variant, use the `getVariantAvailability` utility function imported from `@medusajs/framework/utils`. It returns the available quantity of the product variant.
|
||||
|
||||
For example:
|
||||
|
||||
export const variantAvailabilityHighlights = [
|
||||
["6", "query", "Resolve Query from the Medusa container."],
|
||||
["8", "query", "Pass Query as a parameter."],
|
||||
["9", "variant_ids", "The IDs of the variants to retrieve their inventory availability."],
|
||||
["10", "sales_channel_id", "The ID of the sales channel to retrieve the variant availability in."],
|
||||
]
|
||||
|
||||
```ts highlights={variantAvailabilityHighlights}
|
||||
import { getVariantAvailability } from "@medusajs/framework/utils"
|
||||
|
||||
// ...
|
||||
|
||||
// use req.scope instead of container in API routes
|
||||
const query = container.resolve("query")
|
||||
|
||||
const availability = await getVariantAvailability(query, {
|
||||
variant_ids: ["variant_123"],
|
||||
sales_channel_id: "sc_123",
|
||||
})
|
||||
```
|
||||
|
||||
A product variant's inventory quantity is set per [stock location](../../../stock-location/page.mdx). This stock location is linked to a [sales channel](../../../sales-channel/page.mdx).
|
||||
|
||||
So, to retrieve the inventory quantity of a product variant using `getVariantAvailability`, you need to also provide the ID of the sales channel to retrieve the inventory quantity in.
|
||||
|
||||
<Note>
|
||||
|
||||
Refer to the [Retrieve Sales Channel to Use](#retrieve-sales-channel-to-use) section to learn how to retrieve the sales channel ID to use in the `getVariantAvailability` function.
|
||||
|
||||
</Note>
|
||||
|
||||
### Parameters
|
||||
|
||||
The `getVariantAvailability` function accepts the following parameters:
|
||||
|
||||
<TypeList
|
||||
types={[
|
||||
{
|
||||
type: "Query",
|
||||
name: "query",
|
||||
description: "Instance of Query to retrieve the necessary data.",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: "`object`",
|
||||
name: "options",
|
||||
description: "The options to retrieve the variant availability.",
|
||||
required: true,
|
||||
children: [
|
||||
{
|
||||
type: "`string[]`",
|
||||
name: "variant_ids",
|
||||
description: "The IDs of the product variants to retrieve their inventory availability.",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: "`string`",
|
||||
name: "sales_channel_id",
|
||||
description: "The ID of the sales channel to retrieve the variant availability in.",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
openedLevel={1}
|
||||
sectionTitle="Parameters"
|
||||
/>
|
||||
|
||||
### Returns
|
||||
|
||||
The `getVariantAvailability` function resolves to an object whose keys are the IDs of each product variant passed in the `variant_ids` parameter.
|
||||
|
||||
The value of each key is an object with the following properties:
|
||||
|
||||
<TypeList
|
||||
types={[
|
||||
{
|
||||
type: "`number`",
|
||||
name: "availability",
|
||||
description: "The available quantity of the product variant in the stock location linked to the sales channel. If `manage_inventory` is disabled, this value is `0`.",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: "`string`",
|
||||
name: "sales_channel_id",
|
||||
description: "The ID of the sales channel that the availability is scoped to.",
|
||||
required: true,
|
||||
}
|
||||
]}
|
||||
sectionTitle="Returns"
|
||||
/>
|
||||
|
||||
For example, the object may look like this:
|
||||
|
||||
```json title="Example result"
|
||||
{
|
||||
"variant_123": {
|
||||
"availability": 10,
|
||||
"sales_channel_id": "sc_123"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Sales Channel to Use
|
||||
|
||||
To retrieve the sales channel ID to use in the `getVariantAvailability` function, you can either:
|
||||
|
||||
- Use the sales channel of the request's scope.
|
||||
- Use the sales channel that the variant's product is available in.
|
||||
|
||||
### Method 1: Use Sales Channel Scope in Store Routes
|
||||
|
||||
Requests sent to API routes starting with `/store` must include a [publishable API key in the request header](../../../sales-channel/publishable-api-keys/page.mdx). This scopes the request to one or more sales channels associated with the publishable API key.
|
||||
|
||||
So, if you're retrieving the variant inventory availability in an API route starting with `/store`, you can access the sales channel using the `publishable_key_context.sales_channel_ids` property of the request object:
|
||||
|
||||
export const salesChannelScopeHighlights = [
|
||||
["9", "sales_channel_ids", "Retrieve the sales channel IDs from the request's publishable key context."],
|
||||
["13", "sales_channel_ids[0]", "Pass the first sales channel ID to retrieve availability in."]
|
||||
]
|
||||
|
||||
```ts highlights={salesChannelScopeHighlights}
|
||||
import { MedusaStoreRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { getVariantAvailability } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaStoreRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const query = req.scope.resolve("query")
|
||||
const sales_channel_ids = req.publishable_key_context.sales_channel_ids
|
||||
|
||||
const availability = await getVariantAvailability(query, {
|
||||
variant_ids: ["variant_123"],
|
||||
sales_channel_id: sales_channel_ids[0],
|
||||
})
|
||||
|
||||
res.json({
|
||||
availability,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
In this example, you retrieve the scope's sales channel IDs using `req.publishable_key_context.sales_channel_ids`, whose value is an array of IDs.
|
||||
|
||||
Then, you pass the first sales channel ID to the `getVariantAvailability` function to retrieve the inventory availability of the product variant in that sales channel.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Notice that the request object's type is `MedusaStoreRequest` instead of `MedusaRequest` to ensure the availability of the `publishable_key_context` property.
|
||||
|
||||
</Note>
|
||||
|
||||
### Method 2: Use Product's Sales Channel
|
||||
|
||||
A product is linked to the sales channels it's available in. So, you can retrieve the details of the variant's product, including its sales channels.
|
||||
|
||||
For example:
|
||||
|
||||
export const productSalesChannelHighlights = [
|
||||
["10", `"product.sales_channels.*"`, "Retrieve the sales channels of the variant's product."],
|
||||
["18", "sales_channel_id", "Pass the first sales channel ID to retrieve availability in."]
|
||||
]
|
||||
|
||||
```ts highlights={productSalesChannelHighlights}
|
||||
import { getVariantAvailability } from "@medusajs/framework/utils"
|
||||
|
||||
// ...
|
||||
|
||||
// use req.scope instead of container in API routes
|
||||
const query = container.resolve("query")
|
||||
|
||||
const { data: variants } = await query.graph({
|
||||
entity: "variant",
|
||||
fields: ["id", "product.sales_channels.*"],
|
||||
filters: {
|
||||
id: "variant_123",
|
||||
},
|
||||
})
|
||||
|
||||
const availability = await getVariantAvailability(query, {
|
||||
variant_ids: ["variant_123"],
|
||||
sales_channel_id: variants[0].product!.sales_channels![0]!.id,
|
||||
})
|
||||
```
|
||||
|
||||
In this example, you retrieve the sales channels of the variant's product using [Query](!docs!/learn/fundamentals/module-links/query).
|
||||
|
||||
You pass the ID of the variant as a filter, and you specify `product.sales_channels.*` as the fields to retrieve. This retrieves the sales channels linked to the variant's product.
|
||||
|
||||
Then, you pass the first sales channel ID to the `getVariantAvailability` function to retrieve the inventory availability of the product variant in that sales channel.
|
||||
@@ -86,6 +86,7 @@ You can also allow customers to subscribe to restock notifications of a product
|
||||
The following guides provide more details on inventory management in the Medusa application:
|
||||
|
||||
- [Inventory Kits in the Inventory Module](../../inventory/inventory-kit/page.mdx): Learn how you can implement bundled or multi-part products through the Inventory Module.
|
||||
- [Retrieve Product Variant Inventory Quantity](../guides/variant-inventory/page.mdx): Learn how to retrieve the available inventory quantity of a product variant.
|
||||
- [Configure Selling Products](../selling-products/page.mdx): Learn how to use inventory management to support different use cases when selling products.
|
||||
- [Inventory in Flows](../../inventory/inventory-in-flows/page.mdx): Learn how Medusa utilizes inventory management in different flows.
|
||||
- [Storefront guide: how to retrieve a product variant's inventory details](https://docs.medusajs.com/resources/storefront-development/products/inventory).
|
||||
|
||||
@@ -26,3 +26,38 @@ The Medusa application infers the associated sales channels and ensures that onl
|
||||
## How to Create a Publishable API Key?
|
||||
|
||||
To create a publishable API key, either use the [Medusa Admin](!user-guide!/settings/developer/publishable-api-keys) or the [Admin API Routes](!api!/admin#publishable-api-keys).
|
||||
|
||||
---
|
||||
|
||||
## Access Sales Channels in Custom Store API Routes
|
||||
|
||||
If you create an API route under the `/store` prefix, you can access the sales channels associated with the request's publishable API key using the `publishable_key_context` property of the request object.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import { MedusaStoreRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { getVariantAvailability } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaStoreRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const query = req.scope.resolve("query")
|
||||
const sales_channel_ids = req.publishable_key_context.sales_channel_ids
|
||||
|
||||
res.json({
|
||||
sales_channel_id: sales_channel_ids[0],
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
In this example, you retrieve the scope's sales channel IDs using `req.publishable_key_context.sales_channel_ids`, whose value is an array of IDs.
|
||||
|
||||
You can then use these IDs based on your business logic. For example, you can retrieve the sales channels' details using [Query](!docs!/learn/fundamentals/module-links/query).
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Notice that the request object's type is `MedusaStoreRequest` instead of `MedusaRequest` to ensure the availability of the `publishable_key_context` property.
|
||||
|
||||
</Note>
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
If you get the following error when retrieving data either from an API route, using Query, or using a module's service:
|
||||
|
||||
```bash
|
||||
ValidationError: Entity X does not have property Y
|
||||
```
|
||||
|
||||
## Why this Error Occurred
|
||||
|
||||
There are different reasons why this error may occur. This troubleshooting guide will help you identify the cause of the error.
|
||||
|
||||
### Incorrect Property Name
|
||||
|
||||
The most common reason for this error is that the property name you're using is incorrect. Make sure the property name is spelled correctly and matches the property name in the entity.
|
||||
|
||||
For example, a common mistake is retrieving `variant.option` instead of `variant.options`.
|
||||
|
||||
If you're retrieving a property from a Commerce Module, check out the data model references in the [Commerce Modules](../../../commerce-modules/page.mdx) documentation to ensure you're using the correct property name.
|
||||
|
||||
### Referencing Linked Model with Module's Service
|
||||
|
||||
Another common mistake is passing a linked data model's name to a module's service, which isn't allowed.
|
||||
|
||||
For example, retrieving the customer of an order using the Order Module's service is not allowed:
|
||||
|
||||
```ts
|
||||
// Don't do this
|
||||
const { data } = await orderService.retrieveOrder("order_123", {
|
||||
relations: ["customer"],
|
||||
})
|
||||
```
|
||||
|
||||
This isn't allowed because the Order Module's service retrieves data only within the module's scope.
|
||||
|
||||
To retrieve a linked data model like `customer`, use Query instead:
|
||||
|
||||
```ts
|
||||
// use req.scope instead of container in API routes
|
||||
const query = container.resolve("query")
|
||||
|
||||
const { data } = await query.graph({
|
||||
entity: "order",
|
||||
fields: ["customer.*"],
|
||||
filters: {
|
||||
id: "order_123",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Referencing Non-Existent Relation
|
||||
|
||||
To expand a relation of a data model, the data model must have a [relationship property](!docs!/learn/fundamentals/data-models/relationships) in its definition.
|
||||
|
||||
For example, consider you have the following data model:
|
||||
|
||||
```ts
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const Post = model.define("post", {
|
||||
id: model.id().primaryKey(),
|
||||
title: model.text(),
|
||||
author_id: model.text(),
|
||||
})
|
||||
|
||||
export default Post
|
||||
```
|
||||
|
||||
Even if you have an `Author` data model, it's not possible to retrieve the post's author using the `author`:
|
||||
|
||||
```ts
|
||||
// Don't do this
|
||||
const post = await postService.retrievePost("post_123", {
|
||||
relations: ["author"],
|
||||
})
|
||||
```
|
||||
|
||||
This will throw the `ValidationError` because the `Post` data model doesn't have a relationship property with the `Author` data model.
|
||||
|
||||
Instead, add the author as a relationship property to the `Post` data model:
|
||||
|
||||
```ts
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
import Author from "./author"
|
||||
|
||||
const Post = model.define("post", {
|
||||
id: model.id().primaryKey(),
|
||||
title: model.text(),
|
||||
author: model.belongsTo(() => Author, {
|
||||
mappedBy: "posts",
|
||||
}),
|
||||
})
|
||||
|
||||
export default Post
|
||||
```
|
||||
|
||||
Now you can retrieve the post's author using the `author`:
|
||||
|
||||
```ts
|
||||
// You can do this now
|
||||
const post = await postService.retrievePost("post_123", {
|
||||
relations: ["author"],
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Data Model Relationships](!docs!/learn/fundamentals/data-models/relationships)
|
||||
- [Module Links](!docs!/learn/fundamentals/module-links)
|
||||
- [Query](!docs!/learn/fundamentals/module-links/query)
|
||||
@@ -0,0 +1,9 @@
|
||||
import ValidationError from "../_sections/other/validation-error.mdx"
|
||||
|
||||
export const metadata = {
|
||||
title: `ValidationError: Entity X does not have property Y`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
<ValidationError />
|
||||
@@ -48,7 +48,7 @@ export const generatedEditDates = {
|
||||
"app/commerce-modules/payment/payment/page.mdx": "2024-10-09T10:59:08.463Z",
|
||||
"app/commerce-modules/payment/payment-collection/page.mdx": "2024-10-09T10:56:49.510Z",
|
||||
"app/commerce-modules/payment/payment-flow/page.mdx": "2025-01-16T10:43:25.958Z",
|
||||
"app/commerce-modules/payment/payment-provider/stripe/page.mdx": "2025-02-26T11:31:21.090Z",
|
||||
"app/commerce-modules/payment/payment-provider/stripe/page.mdx": "2025-04-25T12:43:03.674Z",
|
||||
"app/commerce-modules/payment/payment-provider/page.mdx": "2025-02-26T11:25:50.645Z",
|
||||
"app/commerce-modules/payment/payment-session/page.mdx": "2024-10-09T10:58:00.960Z",
|
||||
"app/commerce-modules/payment/webhook-events/page.mdx": "2024-11-19T11:45:02.167Z",
|
||||
@@ -77,7 +77,7 @@ export const generatedEditDates = {
|
||||
"app/commerce-modules/region/page.mdx": "2025-04-17T08:48:22.808Z",
|
||||
"app/commerce-modules/sales-channel/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
|
||||
"app/commerce-modules/sales-channel/_events/page.mdx": "2024-07-03T19:27:13+03:00",
|
||||
"app/commerce-modules/sales-channel/publishable-api-keys/page.mdx": "2025-02-26T11:33:40.415Z",
|
||||
"app/commerce-modules/sales-channel/publishable-api-keys/page.mdx": "2025-04-25T14:22:42.329Z",
|
||||
"app/commerce-modules/sales-channel/page.mdx": "2025-04-17T08:48:22.065Z",
|
||||
"app/commerce-modules/stock-location/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
|
||||
"app/commerce-modules/stock-location/_events/page.mdx": "2024-07-03T19:27:13+03:00",
|
||||
@@ -5891,7 +5891,7 @@ export const generatedEditDates = {
|
||||
"references/core_flows/Order/Steps_Order/variables/core_flows.Order.Steps_Order.updateOrderChangesStepId/page.mdx": "2025-01-27T11:43:49.278Z",
|
||||
"app/commerce-modules/payment/account-holder/page.mdx": "2025-04-07T07:31:20.235Z",
|
||||
"app/troubleshooting/test-errors/page.mdx": "2025-01-31T13:08:42.639Z",
|
||||
"app/commerce-modules/product/variant-inventory/page.mdx": "2025-02-26T11:21:20.075Z",
|
||||
"app/commerce-modules/product/variant-inventory/page.mdx": "2025-04-25T13:25:02.408Z",
|
||||
"app/examples/guides/custom-item-price/page.mdx": "2025-04-17T08:50:17.040Z",
|
||||
"references/core_flows/Cart/Steps_Cart/functions/core_flows.Cart.Steps_Cart.validateShippingStep/page.mdx": "2025-04-11T09:04:35.729Z",
|
||||
"references/core_flows/Cart/Steps_Cart/variables/core_flows.Cart.Steps_Cart.validateShippingStepId/page.mdx": "2025-02-11T11:36:39.228Z",
|
||||
@@ -6216,5 +6216,7 @@ export const generatedEditDates = {
|
||||
"references/core_flows/interfaces/core_flows.RemoveDraftOrderPromotionsWorkflowInput/page.mdx": "2025-04-24T08:23:57.282Z",
|
||||
"references/core_flows/interfaces/core_flows.RemoveDraftOrderShippingMethodWorkflowInput/page.mdx": "2025-04-24T08:23:57.283Z",
|
||||
"references/core_flows/interfaces/core_flows.UpdateDraftOrderStepInput/page.mdx": "2025-04-24T08:23:57.295Z",
|
||||
"references/core_flows/interfaces/core_flows.ValidateDraftOrderStepInput/page.mdx": "2025-04-24T08:23:57.277Z"
|
||||
"references/core_flows/interfaces/core_flows.ValidateDraftOrderStepInput/page.mdx": "2025-04-24T08:23:57.277Z",
|
||||
"app/commerce-modules/product/guides/variant-inventory/page.mdx": "2025-04-25T14:22:42.329Z",
|
||||
"app/troubleshooting/validation-error/page.mdx": "2025-04-25T14:14:57.568Z"
|
||||
}
|
||||
@@ -467,6 +467,10 @@ export const filesMap = [
|
||||
"filePath": "/www/apps/resources/app/commerce-modules/product/guides/price-with-taxes/page.mdx",
|
||||
"pathname": "/commerce-modules/product/guides/price-with-taxes"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/commerce-modules/product/guides/variant-inventory/page.mdx",
|
||||
"pathname": "/commerce-modules/product/guides/variant-inventory"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/commerce-modules/product/js-sdk/page.mdx",
|
||||
"pathname": "/commerce-modules/product/js-sdk"
|
||||
@@ -1395,6 +1399,10 @@ export const filesMap = [
|
||||
"filePath": "/www/apps/resources/app/troubleshooting/test-errors/page.mdx",
|
||||
"pathname": "/troubleshooting/test-errors"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/troubleshooting/validation-error/page.mdx",
|
||||
"pathname": "/troubleshooting/validation-error"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/troubleshooting/workflow-errors/step-x-defined/page.mdx",
|
||||
"pathname": "/troubleshooting/workflow-errors/step-x-defined"
|
||||
|
||||
@@ -4955,6 +4955,26 @@ const generatedgeneratedCommerceModulesSidebarSidebar = {
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"type": "category",
|
||||
"title": "Server Guides",
|
||||
"autogenerate_tags": "server+inventory",
|
||||
"autogenerate_as_ref": true,
|
||||
"sort_sidebar": "alphabetize",
|
||||
"description": "Learn how to use the Inventory Module in your customizations on the Medusa application server.",
|
||||
"children": [
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"type": "ref",
|
||||
"title": "Get Variant Inventory",
|
||||
"path": "https://docs.medusajs.com/resources/commerce-modules/product/guides/variant-inventory",
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
@@ -11297,6 +11317,14 @@ const generatedgeneratedCommerceModulesSidebarSidebar = {
|
||||
"title": "Extend Module",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"type": "link",
|
||||
"path": "/commerce-modules/product/guides/variant-inventory",
|
||||
"title": "Get Variant Inventory",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
@@ -14421,6 +14449,26 @@ const generatedgeneratedCommerceModulesSidebarSidebar = {
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"type": "category",
|
||||
"title": "Server Guides",
|
||||
"autogenerate_tags": "server+salesChannel",
|
||||
"autogenerate_as_ref": true,
|
||||
"sort_sidebar": "alphabetize",
|
||||
"description": "Learn how to use the Sales Channel Module in your customizations on the Medusa application server.",
|
||||
"children": [
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"type": "ref",
|
||||
"title": "Get Variant Inventory",
|
||||
"path": "https://docs.medusajs.com/resources/commerce-modules/product/guides/variant-inventory",
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
|
||||
@@ -110,6 +110,14 @@ const generatedgeneratedHowToTutorialsSidebarSidebar = {
|
||||
"path": "https://docs.medusajs.com/resources/references/tax/provider",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"type": "ref",
|
||||
"title": "Get Variant Inventory",
|
||||
"path": "https://docs.medusajs.com/resources/commerce-modules/product/guides/variant-inventory",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
|
||||
@@ -181,6 +181,14 @@ const generatedgeneratedTroubleshootingSidebarSidebar = {
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"type": "link",
|
||||
"title": "ValidationError",
|
||||
"link": "/troubleshooting/validation-error",
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -60,6 +60,11 @@ export const productSidebar = [
|
||||
path: "/commerce-modules/product/guides/price-with-taxes",
|
||||
title: "Get Variant Price with Taxes",
|
||||
},
|
||||
{
|
||||
type: "link",
|
||||
path: "/commerce-modules/product/guides/variant-inventory",
|
||||
title: "Get Variant Inventory",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -120,6 +120,11 @@ export const troubleshootingSidebar = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "link",
|
||||
title: "ValidationError",
|
||||
link: "/troubleshooting/validation-error",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
export const auth = [
|
||||
{
|
||||
"title": "Reset Password",
|
||||
"path": "https://docs.medusajs.com/user-guide/reset-password"
|
||||
},
|
||||
{
|
||||
"title": "Create Actor Type",
|
||||
"path": "https://docs.medusajs.com/resources/commerce-modules/auth/create-actor-type"
|
||||
},
|
||||
{
|
||||
"title": "Reset Password",
|
||||
"path": "https://docs.medusajs.com/user-guide/reset-password"
|
||||
},
|
||||
{
|
||||
"title": "Log-out Customer in Storefront",
|
||||
"path": "https://docs.medusajs.com/resources/storefront-development/customers/log-out"
|
||||
|
||||
@@ -15,6 +15,10 @@ export const howTo = [
|
||||
"title": "Get Variant Price with Taxes",
|
||||
"path": "https://docs.medusajs.com/resources/commerce-modules/product/guides/price-with-taxes"
|
||||
},
|
||||
{
|
||||
"title": "Get Variant Inventory",
|
||||
"path": "https://docs.medusajs.com/resources/commerce-modules/product/guides/variant-inventory"
|
||||
},
|
||||
{
|
||||
"title": "Create Cache Module",
|
||||
"path": "https://docs.medusajs.com/resources/infrastructure-modules/cache/create"
|
||||
|
||||
@@ -15,6 +15,10 @@ export const inventory = [
|
||||
"title": "Inventory Kits",
|
||||
"path": "https://docs.medusajs.com/resources/commerce-modules/inventory/inventory-kit"
|
||||
},
|
||||
{
|
||||
"title": "Get Variant Inventory",
|
||||
"path": "https://docs.medusajs.com/resources/commerce-modules/product/guides/variant-inventory"
|
||||
},
|
||||
{
|
||||
"title": "Product Variant Inventory",
|
||||
"path": "https://docs.medusajs.com/resources/commerce-modules/product/variant-inventory"
|
||||
|
||||
@@ -67,6 +67,10 @@ export const product = [
|
||||
"title": "Get Variant Price with Taxes",
|
||||
"path": "https://docs.medusajs.com/resources/commerce-modules/product/guides/price-with-taxes"
|
||||
},
|
||||
{
|
||||
"title": "Get Variant Inventory",
|
||||
"path": "https://docs.medusajs.com/resources/commerce-modules/product/guides/variant-inventory"
|
||||
},
|
||||
{
|
||||
"title": "Implement Product Reviews",
|
||||
"path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/product-reviews"
|
||||
|
||||
@@ -3,6 +3,10 @@ export const salesChannel = [
|
||||
"title": "Manage Sales Channels",
|
||||
"path": "https://docs.medusajs.com/user-guide/settings/sales-channels"
|
||||
},
|
||||
{
|
||||
"title": "Get Variant Inventory",
|
||||
"path": "https://docs.medusajs.com/resources/commerce-modules/product/guides/variant-inventory"
|
||||
},
|
||||
{
|
||||
"title": "Product Variant Inventory",
|
||||
"path": "https://docs.medusajs.com/resources/commerce-modules/product/variant-inventory"
|
||||
|
||||
@@ -27,6 +27,10 @@ export const server = [
|
||||
"title": "Get Variant Price with Taxes",
|
||||
"path": "https://docs.medusajs.com/resources/commerce-modules/product/guides/price-with-taxes"
|
||||
},
|
||||
{
|
||||
"title": "Get Variant Inventory",
|
||||
"path": "https://docs.medusajs.com/resources/commerce-modules/product/guides/variant-inventory"
|
||||
},
|
||||
{
|
||||
"title": "Extend Promotion",
|
||||
"path": "https://docs.medusajs.com/resources/commerce-modules/promotion/extend"
|
||||
|
||||
+1
-1
@@ -112,7 +112,7 @@ module.exports = defineConfig({
|
||||
`,
|
||||
`## 5. Test it Out
|
||||
|
||||
To test out your Authentication Module Provider, use any of the [Authentication Routes](https://docs.medusajs.com/v2/resources/commerce-modules/auth/authentication-route), using your provider's ID as a path parameter.
|
||||
To test out your Authentication Module Provider, use any of the [Authentication Routes](https://docs.medusajs.com/resources/commerce-modules/auth/authentication-route), using your provider's ID as a path parameter.
|
||||
|
||||
For example, to get a registration token for an admin user, send a \`POST\` request to \`/auth/user/my-auth/register\` replacing \`my-auth\` with your Authentication Module Provider's ID:
|
||||
|
||||
|
||||
+21
-3
@@ -1,11 +1,29 @@
|
||||
import Handlebars from "handlebars"
|
||||
import { SignatureReflection } from "typedoc"
|
||||
import {
|
||||
Reflection,
|
||||
ReflectionKind,
|
||||
SignatureReflection,
|
||||
SourceReference,
|
||||
} from "typedoc"
|
||||
|
||||
export default function () {
|
||||
Handlebars.registerHelper(
|
||||
"sourceCodeLink",
|
||||
function (this: SignatureReflection): string {
|
||||
const source = this.parent.sources?.[0]
|
||||
function (this: Reflection): string {
|
||||
let source: SourceReference | undefined
|
||||
|
||||
switch (this.kind) {
|
||||
case ReflectionKind.GetSignature:
|
||||
if (this.parent instanceof SignatureReflection) {
|
||||
source = this.parent.sources?.[0]
|
||||
}
|
||||
break
|
||||
default:
|
||||
if ("sources" in this) {
|
||||
source = (this.sources as SourceReference[])?.[0]
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (!source?.url) {
|
||||
return ""
|
||||
|
||||
+2
@@ -4,4 +4,6 @@
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{{sourceCodeLink}}}
|
||||
|
||||
{{{dmlProperties}}}
|
||||
Reference in New Issue
Block a user