docs: fixes to read-only links + add examples for filtering by relations (#13218)
This commit is contained in:
@@ -373,6 +373,38 @@ While it's not possible to filter by a linked data model's property, you can fil
|
|||||||
|
|
||||||
In the example above, only posts that have an author with the name `John` are retrieved.
|
In the example above, only posts that have an author with the name `John` are retrieved.
|
||||||
|
|
||||||
|
#### Filter by Relation Property Not Matching Value
|
||||||
|
|
||||||
|
```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"], ["9"], ["10"], ["11"], ["12"], ["13"], ["14"], ["15"], ["16"], ["17"], ["18"]]}
|
||||||
|
const { data: posts } = await query.graph({
|
||||||
|
entity: "post",
|
||||||
|
fields: ["id", "title"],
|
||||||
|
filters: {
|
||||||
|
author: {
|
||||||
|
$or: [
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
$eq: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
$ne: "John",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
When you need to filter by a relationship property whose value doesn't match a specific condition, you must use an `$or` operator that applies the following conditions:
|
||||||
|
|
||||||
|
1. The relationship's property is not set. This is necessary to exclude posts that don't have an author.
|
||||||
|
2. The relationship's property is not equal to the specific value.
|
||||||
|
|
||||||
|
So, in the example above, the query retrieves posts that either don't have an author or have an author whose name is not "John".
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Apply Pagination
|
## Apply Pagination
|
||||||
|
|||||||
@@ -376,42 +376,82 @@ In this case, the relation would always be one-to-many, even if only one post is
|
|||||||
|
|
||||||
## Example: Read-Only Module Link for Virtual Data Models
|
## Example: Read-Only Module Link for Virtual Data Models
|
||||||
|
|
||||||
Read-only module links are most useful when working with data models that aren't stored in your Medusa database. For example, data that is stored in a third-party system. In those cases, you can define a read-only module link between a data model in Medusa and the data model in the external system, facilitating the retrieval of the linked data.
|
Read-only module links are most useful when working with data models that aren't stored in your Medusa database. For example, data that is stored in a third-party system.
|
||||||
|
|
||||||
|
In those cases, you can define a read-only module link between a data model in Medusa and the data model in the external system, facilitating the retrieval of the linked data.
|
||||||
|
|
||||||
To define the read-only module link to a virtual data model, you must:
|
To define the read-only module link to a virtual data model, you must:
|
||||||
|
|
||||||
1. Create a `list` method in the custom module's service. This method retrieves the linked records filtered by the ID(s) of the first data model.
|
1. Define the read-only module link from the Medusa data model to the virtual data model.
|
||||||
|
2. Create a `list` method in the custom module's service. This method retrieves the linked records filtered by the ID(s) of the Medusa data model.
|
||||||
- You can also create a `listAndCount` method to retrieve the related records with pagination.
|
- You can also create a `listAndCount` method to retrieve the related records with pagination.
|
||||||
2. Define the read-only module link from the first data model to the virtual data model.
|
3. Use Query to retrieve the Medusa data model and its linked records from the virtual data model.
|
||||||
3. Use Query to retrieve the first data model and its linked records from the virtual data model.
|
|
||||||
|
|
||||||
For example, consider you have a third-party Content-Management System (CMS) that you're integrating with Medusa, and you want to retrieve the posts in the CMS associated with a product in Medusa.
|
For example, consider you have a CMS Module that integrates a third-party Content-Management System (CMS) with Medusa, and you want to retrieve the posts in the CMS associated with a product in Medusa. The next steps showcase how to implement this.
|
||||||
|
|
||||||
To do that, first, create a CMS Module having the following service:
|
### a. Define Read-Only Module Link
|
||||||
|
|
||||||
<Note>
|
Start by defining a read-only module link from the `Product` data model in Medusa to the external `post` data model in the CMS:
|
||||||
|
|
||||||
Refer to the [Modules chapter](../../modules/page.mdx) to learn how to create a module and its service.
|
export const linkHighlights = [
|
||||||
|
["8", "field", "The field to filter by."],
|
||||||
|
["13", "alias", "The alias to use when querying the linked records."],
|
||||||
|
["14", "primaryKey", "The primary key of the linked records. It's also used as the filter name."]
|
||||||
|
]
|
||||||
|
|
||||||
</Note>
|
```ts title="src/links/product-cms.ts" highlights={linkHighlights}
|
||||||
|
import { defineLink } from "@medusajs/framework/utils"
|
||||||
|
import ProductModule from "@medusajs/medusa/product"
|
||||||
|
import { CMS_MODULE } from "../modules/cms"
|
||||||
|
|
||||||
```ts title="src/modules/cms/service.ts"
|
export default defineLink(
|
||||||
import { FindConfig } from "@medusajs/framework/types"
|
{
|
||||||
|
linkable: ProductModule.linkable.product,
|
||||||
type CmsModuleOptions = {
|
field: "id",
|
||||||
apiKey: string
|
},
|
||||||
|
{
|
||||||
|
linkable: {
|
||||||
|
serviceName: CMS_MODULE,
|
||||||
|
alias: "cms_post",
|
||||||
|
primaryKey: "product_id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
readOnly: true,
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
To define the read-only module link, you must pass to `defineLink`:
|
||||||
|
|
||||||
|
1. An object with the linkable configuration of the data model in Medusa, and the fields that will be passed as a filter to the CMS service.
|
||||||
|
- For example, if you want to filter by product title instead, you can pass `title` instead of `id`.
|
||||||
|
2. An object with the linkable configuration of the virtual data model in the CMS. This object must have the following properties:
|
||||||
|
- `serviceName`: The name of the service, which is the CMS Module's name. Medusa uses this name to resolve the module's service from the [Medusa container](../../medusa-container/page.mdx).
|
||||||
|
- `alias`: The alias to use when querying the linked records. You'll see how that works in a bit.
|
||||||
|
- `primaryKey`: The field in the CMS data model that holds the ID of a product.
|
||||||
|
3. The third parameter: an object with the `readOnly` property set to `true`.
|
||||||
|
|
||||||
|
### b. Add List Methods to CMS Module Service
|
||||||
|
|
||||||
|
Next, add the following methods to the CMS Module's service:
|
||||||
|
|
||||||
|
export const serviceHighlights = [
|
||||||
|
["4", "client", "An SDK to interact with the CMS API."],
|
||||||
|
["9", "product_id", "The field to filter by. It must match the `primary_key` in the link configuration."],
|
||||||
|
["32", "product_id", "The field to filter by. It must match the `primary_key` in the link configuration."]
|
||||||
|
]
|
||||||
|
|
||||||
|
```ts title="src/modules/cms/service.ts" highlights={serviceHighlights}
|
||||||
|
import { FindConfig } from "@medusajs/framework/types"
|
||||||
|
|
||||||
export default class CmsModuleService {
|
export default class CmsModuleService {
|
||||||
private client
|
private client
|
||||||
|
// ...
|
||||||
constructor({}, options: CmsModuleOptions) {
|
|
||||||
this.client = new Client(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
async list(
|
async list(
|
||||||
filter: {
|
filter: {
|
||||||
id: string | string[]
|
product_id: string | string[]
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
return this.client.getPosts(filter)
|
return this.client.getPosts(filter)
|
||||||
@@ -434,7 +474,7 @@ export default class CmsModuleService {
|
|||||||
// To retrieve with pagination
|
// To retrieve with pagination
|
||||||
async listAndCount(
|
async listAndCount(
|
||||||
filter: {
|
filter: {
|
||||||
id: string | string[]
|
product_id: string | string[]
|
||||||
},
|
},
|
||||||
config?: FindConfig<any> | undefined
|
config?: FindConfig<any> | undefined
|
||||||
) {
|
) {
|
||||||
@@ -463,56 +503,32 @@ export default class CmsModuleService {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The above service initializes a client, assuming your CMS has an SDK that allows you to retrieve posts.
|
To retrieve the linked records, you must implement a `list` method in the CMS Module's service.
|
||||||
|
|
||||||
The service must have a `list` method to be part of the read-only module link. This method accepts the ID(s) of the products to retrieve their associated posts. The posts must include the product's ID in a field, such as `product_id`.
|
The `list` method accepts an object of filters holding the ID(s) of the products to retrieve their associated posts. The name of the filter property must match the `primary_key` defined in the link configuration, which is `product_id` in this case.
|
||||||
|
|
||||||
|
The returned posts must include the product's ID in a field with the same name as the `primary_key` option in the link configurations, which is `product_id` in this case.
|
||||||
|
|
||||||
You can also create a `listAndCount` method to retrieve the posts with pagination. This method is called if you pass [pagination parameters to Query](../query/page.mdx#apply-pagination).
|
You can also create a `listAndCount` method to retrieve the posts with pagination. This method is called if you pass [pagination parameters to Query](../query/page.mdx#apply-pagination).
|
||||||
|
|
||||||
Next, define a read-only module link from the Product Module to the CMS Module:
|
### c. Query Linked Records
|
||||||
|
|
||||||
```ts title="src/links/product-cms.ts"
|
|
||||||
import { defineLink } from "@medusajs/framework/utils"
|
|
||||||
import ProductModule from "@medusajs/medusa/product"
|
|
||||||
import { CMS_MODULE } from "../modules/cms"
|
|
||||||
|
|
||||||
export default defineLink(
|
|
||||||
{
|
|
||||||
linkable: ProductModule.linkable.product,
|
|
||||||
field: "id",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
linkable: {
|
|
||||||
serviceName: CMS_MODULE,
|
|
||||||
alias: "cms_post",
|
|
||||||
primaryKey: "product_id",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
readOnly: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
To define the read-only module link, you must pass to `defineLink`:
|
|
||||||
|
|
||||||
1. The first parameter: an object with the linkable configuration of the data model in Medusa, and the fields that will be passed as a filter to the CMS service. For example, if you want to filter by product title instead, you can pass `title` instead of `id`.
|
|
||||||
2. The second parameter: an object with the linkable configuration of the virtual data model in the CMS. This object must have the following properties:
|
|
||||||
- `serviceName`: The name of the service, which is the CMS Module's name. Medusa uses this name to resolve the module's service from the [Medusa container](../../medusa-container/page.mdx).
|
|
||||||
- `alias`: The alias to use when querying the linked records. You'll see how that works in a bit.
|
|
||||||
- `primaryKey`: The field in the CMS data model that holds the ID of a product.
|
|
||||||
3. The third parameter: an object with the `readOnly` property set to `true`.
|
|
||||||
|
|
||||||
Now, you can use Query to retrieve a product and its linked post from the CMS:
|
Now, you can use Query to retrieve a product and its linked post from the CMS:
|
||||||
|
|
||||||
```ts
|
export const queryHighlights = [
|
||||||
|
["3", `"cms_post"`, "The `alias` of the virtual data model in the link configuration."]
|
||||||
|
]
|
||||||
|
|
||||||
|
```ts highlights={queryHighlights}
|
||||||
const { data } = await query.graph({
|
const { data } = await query.graph({
|
||||||
entity: "product",
|
entity: "product",
|
||||||
fields: ["id", "cms_post.*"],
|
fields: ["id", "cms_post.*"],
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
In the above example, each product that has a CMS post with the `product_id` field set to the product's ID will be retrieved:
|
In the above example, you pass `cms_post.*` in the fields, which is the `alias` of the virtual data model in the [link configuration](#a-define-read-only-module-link).
|
||||||
|
|
||||||
|
Each product will have a `cms_post` field that holds the posts whose `product_id` matches the product's ID. For example:
|
||||||
|
|
||||||
```json title="Example Data"
|
```json title="Example Data"
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export const generatedEditDates = {
|
|||||||
"app/learn/fundamentals/module-links/custom-columns/page.mdx": "2025-03-11T13:29:54.752Z",
|
"app/learn/fundamentals/module-links/custom-columns/page.mdx": "2025-03-11T13:29:54.752Z",
|
||||||
"app/learn/fundamentals/module-links/directions/page.mdx": "2025-03-17T12:52:06.161Z",
|
"app/learn/fundamentals/module-links/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/page.mdx": "2025-04-17T08:50:17.036Z",
|
||||||
"app/learn/fundamentals/module-links/query/page.mdx": "2025-06-26T16:01:59.548Z",
|
"app/learn/fundamentals/module-links/query/page.mdx": "2025-08-15T12:06:30.572Z",
|
||||||
"app/learn/fundamentals/modules/db-operations/page.mdx": "2025-04-25T14:26:25.000Z",
|
"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/multiple-services/page.mdx": "2025-03-18T15:11:44.632Z",
|
||||||
"app/learn/fundamentals/modules/page.mdx": "2025-07-18T15:31:32.371Z",
|
"app/learn/fundamentals/modules/page.mdx": "2025-07-18T15:31:32.371Z",
|
||||||
@@ -114,7 +114,7 @@ export const generatedEditDates = {
|
|||||||
"app/learn/configurations/medusa-config/page.mdx": "2025-07-14T09:28:54.302Z",
|
"app/learn/configurations/medusa-config/page.mdx": "2025-07-14T09:28:54.302Z",
|
||||||
"app/learn/configurations/ts-aliases/page.mdx": "2025-07-23T15:32:18.008Z",
|
"app/learn/configurations/ts-aliases/page.mdx": "2025-07-23T15:32:18.008Z",
|
||||||
"app/learn/production/worker-mode/page.mdx": "2025-07-18T15:19:45.352Z",
|
"app/learn/production/worker-mode/page.mdx": "2025-07-18T15:19:45.352Z",
|
||||||
"app/learn/fundamentals/module-links/read-only/page.mdx": "2025-07-25T07:58:54.327Z",
|
"app/learn/fundamentals/module-links/read-only/page.mdx": "2025-08-15T11:52:13.403Z",
|
||||||
"app/learn/fundamentals/data-models/properties/page.mdx": "2025-07-31T08:22:20.431Z",
|
"app/learn/fundamentals/data-models/properties/page.mdx": "2025-07-31T08:22:20.431Z",
|
||||||
"app/learn/fundamentals/framework/page.mdx": "2025-06-26T14:26:22.120Z",
|
"app/learn/fundamentals/framework/page.mdx": "2025-06-26T14:26:22.120Z",
|
||||||
"app/learn/fundamentals/api-routes/retrieve-custom-links/page.mdx": "2025-07-14T10:24:32.582Z",
|
"app/learn/fundamentals/api-routes/retrieve-custom-links/page.mdx": "2025-07-14T10:24:32.582Z",
|
||||||
|
|||||||
@@ -14149,6 +14149,38 @@ While it's not possible to filter by a linked data model's property, you can fil
|
|||||||
|
|
||||||
In the example above, only posts that have an author with the name `John` are retrieved.
|
In the example above, only posts that have an author with the name `John` are retrieved.
|
||||||
|
|
||||||
|
#### Filter by Relation Property Not Matching Value
|
||||||
|
|
||||||
|
```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"], ["9"], ["10"], ["11"], ["12"], ["13"], ["14"], ["15"], ["16"], ["17"], ["18"]]}
|
||||||
|
const { data: posts } = await query.graph({
|
||||||
|
entity: "post",
|
||||||
|
fields: ["id", "title"],
|
||||||
|
filters: {
|
||||||
|
author: {
|
||||||
|
$or: [
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
$eq: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
$ne: "John",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
When you need to filter by a relationship property whose value doesn't match a specific condition, you must use an `$or` operator that applies the following conditions:
|
||||||
|
|
||||||
|
1. The relationship's property is not set. This is necessary to exclude posts that don't have an author.
|
||||||
|
2. The relationship's property is not equal to the specific value.
|
||||||
|
|
||||||
|
So, in the example above, the query retrieves posts that either don't have an author or have an author whose name is not "John".
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
## Apply Pagination
|
## Apply Pagination
|
||||||
@@ -14760,38 +14792,70 @@ In this case, the relation would always be one-to-many, even if only one post is
|
|||||||
|
|
||||||
## Example: Read-Only Module Link for Virtual Data Models
|
## Example: Read-Only Module Link for Virtual Data Models
|
||||||
|
|
||||||
Read-only module links are most useful when working with data models that aren't stored in your Medusa database. For example, data that is stored in a third-party system. In those cases, you can define a read-only module link between a data model in Medusa and the data model in the external system, facilitating the retrieval of the linked data.
|
Read-only module links are most useful when working with data models that aren't stored in your Medusa database. For example, data that is stored in a third-party system.
|
||||||
|
|
||||||
|
In those cases, you can define a read-only module link between a data model in Medusa and the data model in the external system, facilitating the retrieval of the linked data.
|
||||||
|
|
||||||
To define the read-only module link to a virtual data model, you must:
|
To define the read-only module link to a virtual data model, you must:
|
||||||
|
|
||||||
1. Create a `list` method in the custom module's service. This method retrieves the linked records filtered by the ID(s) of the first data model.
|
1. Define the read-only module link from the Medusa data model to the virtual data model.
|
||||||
|
2. Create a `list` method in the custom module's service. This method retrieves the linked records filtered by the ID(s) of the Medusa data model.
|
||||||
- You can also create a `listAndCount` method to retrieve the related records with pagination.
|
- You can also create a `listAndCount` method to retrieve the related records with pagination.
|
||||||
2. Define the read-only module link from the first data model to the virtual data model.
|
3. Use Query to retrieve the Medusa data model and its linked records from the virtual data model.
|
||||||
3. Use Query to retrieve the first data model and its linked records from the virtual data model.
|
|
||||||
|
|
||||||
For example, consider you have a third-party Content-Management System (CMS) that you're integrating with Medusa, and you want to retrieve the posts in the CMS associated with a product in Medusa.
|
For example, consider you have a CMS Module that integrates a third-party Content-Management System (CMS) with Medusa, and you want to retrieve the posts in the CMS associated with a product in Medusa. The next steps showcase how to implement this.
|
||||||
|
|
||||||
To do that, first, create a CMS Module having the following service:
|
### a. Define Read-Only Module Link
|
||||||
|
|
||||||
Refer to the [Modules chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md) to learn how to create a module and its service.
|
Start by defining a read-only module link from the `Product` data model in Medusa to the external `post` data model in the CMS:
|
||||||
|
|
||||||
```ts title="src/modules/cms/service.ts"
|
```ts title="src/links/product-cms.ts" highlights={linkHighlights}
|
||||||
import { FindConfig } from "@medusajs/framework/types"
|
import { defineLink } from "@medusajs/framework/utils"
|
||||||
|
import ProductModule from "@medusajs/medusa/product"
|
||||||
|
import { CMS_MODULE } from "../modules/cms"
|
||||||
|
|
||||||
type CmsModuleOptions = {
|
export default defineLink(
|
||||||
apiKey: string
|
{
|
||||||
|
linkable: ProductModule.linkable.product,
|
||||||
|
field: "id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
linkable: {
|
||||||
|
serviceName: CMS_MODULE,
|
||||||
|
alias: "cms_post",
|
||||||
|
primaryKey: "product_id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
readOnly: true,
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
To define the read-only module link, you must pass to `defineLink`:
|
||||||
|
|
||||||
|
1. An object with the linkable configuration of the data model in Medusa, and the fields that will be passed as a filter to the CMS service.
|
||||||
|
- For example, if you want to filter by product title instead, you can pass `title` instead of `id`.
|
||||||
|
2. An object with the linkable configuration of the virtual data model in the CMS. This object must have the following properties:
|
||||||
|
- `serviceName`: The name of the service, which is the CMS Module's name. Medusa uses this name to resolve the module's service from the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md).
|
||||||
|
- `alias`: The alias to use when querying the linked records. You'll see how that works in a bit.
|
||||||
|
- `primaryKey`: The field in the CMS data model that holds the ID of a product.
|
||||||
|
3. The third parameter: an object with the `readOnly` property set to `true`.
|
||||||
|
|
||||||
|
### b. Add List Methods to CMS Module Service
|
||||||
|
|
||||||
|
Next, add the following methods to the CMS Module's service:
|
||||||
|
|
||||||
|
```ts title="src/modules/cms/service.ts" highlights={serviceHighlights}
|
||||||
|
import { FindConfig } from "@medusajs/framework/types"
|
||||||
|
|
||||||
export default class CmsModuleService {
|
export default class CmsModuleService {
|
||||||
private client
|
private client
|
||||||
|
// ...
|
||||||
constructor({}, options: CmsModuleOptions) {
|
|
||||||
this.client = new Client(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
async list(
|
async list(
|
||||||
filter: {
|
filter: {
|
||||||
id: string | string[]
|
product_id: string | string[]
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
return this.client.getPosts(filter)
|
return this.client.getPosts(filter)
|
||||||
@@ -14814,7 +14878,7 @@ export default class CmsModuleService {
|
|||||||
// To retrieve with pagination
|
// To retrieve with pagination
|
||||||
async listAndCount(
|
async listAndCount(
|
||||||
filter: {
|
filter: {
|
||||||
id: string | string[]
|
product_id: string | string[]
|
||||||
},
|
},
|
||||||
config?: FindConfig<any> | undefined
|
config?: FindConfig<any> | undefined
|
||||||
) {
|
) {
|
||||||
@@ -14843,56 +14907,28 @@ export default class CmsModuleService {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The above service initializes a client, assuming your CMS has an SDK that allows you to retrieve posts.
|
To retrieve the linked records, you must implement a `list` method in the CMS Module's service.
|
||||||
|
|
||||||
The service must have a `list` method to be part of the read-only module link. This method accepts the ID(s) of the products to retrieve their associated posts. The posts must include the product's ID in a field, such as `product_id`.
|
The `list` method accepts an object of filters holding the ID(s) of the products to retrieve their associated posts. The name of the filter property must match the `primary_key` defined in the link configuration, which is `product_id` in this case.
|
||||||
|
|
||||||
|
The returned posts must include the product's ID in a field with the same name as the `primary_key` option in the link configurations, which is `product_id` in this case.
|
||||||
|
|
||||||
You can also create a `listAndCount` method to retrieve the posts with pagination. This method is called if you pass [pagination parameters to Query](https://docs.medusajs.com/learn/fundamentals/module-links/query#apply-pagination/index.html.md).
|
You can also create a `listAndCount` method to retrieve the posts with pagination. This method is called if you pass [pagination parameters to Query](https://docs.medusajs.com/learn/fundamentals/module-links/query#apply-pagination/index.html.md).
|
||||||
|
|
||||||
Next, define a read-only module link from the Product Module to the CMS Module:
|
### c. Query Linked Records
|
||||||
|
|
||||||
```ts title="src/links/product-cms.ts"
|
|
||||||
import { defineLink } from "@medusajs/framework/utils"
|
|
||||||
import ProductModule from "@medusajs/medusa/product"
|
|
||||||
import { CMS_MODULE } from "../modules/cms"
|
|
||||||
|
|
||||||
export default defineLink(
|
|
||||||
{
|
|
||||||
linkable: ProductModule.linkable.product,
|
|
||||||
field: "id",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
linkable: {
|
|
||||||
serviceName: CMS_MODULE,
|
|
||||||
alias: "cms_post",
|
|
||||||
primaryKey: "product_id",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
readOnly: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
To define the read-only module link, you must pass to `defineLink`:
|
|
||||||
|
|
||||||
1. The first parameter: an object with the linkable configuration of the data model in Medusa, and the fields that will be passed as a filter to the CMS service. For example, if you want to filter by product title instead, you can pass `title` instead of `id`.
|
|
||||||
2. The second parameter: an object with the linkable configuration of the virtual data model in the CMS. This object must have the following properties:
|
|
||||||
- `serviceName`: The name of the service, which is the CMS Module's name. Medusa uses this name to resolve the module's service from the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md).
|
|
||||||
- `alias`: The alias to use when querying the linked records. You'll see how that works in a bit.
|
|
||||||
- `primaryKey`: The field in the CMS data model that holds the ID of a product.
|
|
||||||
3. The third parameter: an object with the `readOnly` property set to `true`.
|
|
||||||
|
|
||||||
Now, you can use Query to retrieve a product and its linked post from the CMS:
|
Now, you can use Query to retrieve a product and its linked post from the CMS:
|
||||||
|
|
||||||
```ts
|
```ts highlights={queryHighlights}
|
||||||
const { data } = await query.graph({
|
const { data } = await query.graph({
|
||||||
entity: "product",
|
entity: "product",
|
||||||
fields: ["id", "cms_post.*"],
|
fields: ["id", "cms_post.*"],
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
In the above example, each product that has a CMS post with the `product_id` field set to the product's ID will be retrieved:
|
In the above example, you pass `cms_post.*` in the fields, which is the `alias` of the virtual data model in the [link configuration](#a-define-read-only-module-link).
|
||||||
|
|
||||||
|
Each product will have a `cms_post` field that holds the posts whose `product_id` matches the product's ID. For example:
|
||||||
|
|
||||||
```json title="Example Data"
|
```json title="Example Data"
|
||||||
[
|
[
|
||||||
@@ -80170,7 +80206,7 @@ Next, use that token to register the customer:
|
|||||||
curl -X POST 'http://localhost:9000/store/customers' \
|
curl -X POST 'http://localhost:9000/store/customers' \
|
||||||
--header 'Content-Type: application/json' \
|
--header 'Content-Type: application/json' \
|
||||||
-H 'x-publishable-api-key: {api_key}' \
|
-H 'x-publishable-api-key: {api_key}' \
|
||||||
--header 'Authorization Bearer {token}' \
|
--header 'Authorization: Bearer {token}' \
|
||||||
--data-raw '{
|
--data-raw '{
|
||||||
"email": "customer@gmail.com"
|
"email": "customer@gmail.com"
|
||||||
}'
|
}'
|
||||||
@@ -84865,6 +84901,56 @@ const posts = await postModuleService.listPosts({
|
|||||||
|
|
||||||
In the example above, only posts that have a publish date are retrieved.
|
In the example above, only posts that have a publish date are retrieved.
|
||||||
|
|
||||||
|
\--
|
||||||
|
|
||||||
|
## Filter by Relation's Property
|
||||||
|
|
||||||
|
Consider that your module also has an `Author` data model, and the `Post` data model has a relation to the `Author` data model.
|
||||||
|
|
||||||
|
To filter posts by a property of the `Author` data model, you can use nested objects.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const posts = await postModuleService.listPosts({
|
||||||
|
author: {
|
||||||
|
name: "John",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
In the example above, you retrieve posts whose `author` relation has a `name` property equal to `John`.
|
||||||
|
|
||||||
|
### Filter by Relation's Property Not Equal
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const posts = await postModuleService.listPosts({
|
||||||
|
author: {
|
||||||
|
name: {
|
||||||
|
$or: [
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
$eq: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
$ne: "John",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
When you need to filter by a relationship property whose value doesn't match a specific condition, you must use an `$or` operator that applies the following conditions:
|
||||||
|
|
||||||
|
1. The relationship's property is not set. This is necessary to exclude posts that don't have an author.
|
||||||
|
2. The relationship's property is not equal to the specific value.
|
||||||
|
|
||||||
|
So, in the example above, you retrieve posts either not having an author or having an author whose name is not "John".
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
## Apply Range Filters
|
## Apply Range Filters
|
||||||
@@ -85028,7 +85114,6 @@ The following operators are supported by the service factory filtering mechanism
|
|||||||
|Logical Operators|
|
|Logical Operators|
|
||||||
|\`$and\`|Joins two or more conditions with a logical AND.|
|
|\`$and\`|Joins two or more conditions with a logical AND.|
|
||||||
|\`$or\`|Joins two or more conditions with a logical OR.|
|
|\`$or\`|Joins two or more conditions with a logical OR.|
|
||||||
|\`$not\`|Inverts the logic of a condition. For example, |
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -826,7 +826,7 @@ Next, use that token to register the customer:
|
|||||||
curl -X POST 'http://localhost:9000/store/customers' \
|
curl -X POST 'http://localhost:9000/store/customers' \
|
||||||
--header 'Content-Type: application/json' \
|
--header 'Content-Type: application/json' \
|
||||||
-H 'x-publishable-api-key: {api_key}' \
|
-H 'x-publishable-api-key: {api_key}' \
|
||||||
--header 'Authorization Bearer {token}' \
|
--header 'Authorization: Bearer {token}' \
|
||||||
--data-raw '{
|
--data-raw '{
|
||||||
"email": "customer@gmail.com"
|
"email": "customer@gmail.com"
|
||||||
}'
|
}'
|
||||||
|
|||||||
@@ -137,6 +137,56 @@ const posts = await postModuleService.listPosts({
|
|||||||
|
|
||||||
In the example above, only posts that have a publish date are retrieved.
|
In the example above, only posts that have a publish date are retrieved.
|
||||||
|
|
||||||
|
--
|
||||||
|
|
||||||
|
## Filter by Relation's Property
|
||||||
|
|
||||||
|
Consider that your module also has an `Author` data model, and the `Post` data model has a relation to the `Author` data model.
|
||||||
|
|
||||||
|
To filter posts by a property of the `Author` data model, you can use nested objects.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const posts = await postModuleService.listPosts({
|
||||||
|
author: {
|
||||||
|
name: "John",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
In the example above, you retrieve posts whose `author` relation has a `name` property equal to `John`.
|
||||||
|
|
||||||
|
### Filter by Relation's Property Not Equal
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const posts = await postModuleService.listPosts({
|
||||||
|
author: {
|
||||||
|
name: {
|
||||||
|
$or: [
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
$eq: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
$ne: "John",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
When you need to filter by a relationship property whose value doesn't match a specific condition, you must use an `$or` operator that applies the following conditions:
|
||||||
|
|
||||||
|
1. The relationship's property is not set. This is necessary to exclude posts that don't have an author.
|
||||||
|
2. The relationship's property is not equal to the specific value.
|
||||||
|
|
||||||
|
So, in the example above, you retrieve posts either not having an author or having an author whose name is not "John".
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Apply Range Filters
|
## Apply Range Filters
|
||||||
@@ -442,14 +492,6 @@ The following operators are supported by the service factory filtering mechanism
|
|||||||
Joins two or more conditions with a logical OR.
|
Joins two or more conditions with a logical OR.
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
<Table.Row>
|
|
||||||
<Table.Cell>
|
|
||||||
`$not`
|
|
||||||
</Table.Cell>
|
|
||||||
<Table.Cell>
|
|
||||||
Inverts the logic of a condition. For example, `$not: { $eq: value }` matches any value that is not equal to the specified value.
|
|
||||||
</Table.Cell>
|
|
||||||
</Table.Row>
|
|
||||||
</Table.Body>
|
</Table.Body>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
|
|||||||
@@ -300,10 +300,10 @@ In this example, you receive the `token` and `email` from the page's query param
|
|||||||
|
|
||||||
Then, when the form that has the password field is submitted, you send a request to the [Reset Password API route](!api!/store#auth_postactor_typeauth_providerupdate), passing it the token, email, and new password.
|
Then, when the form that has the password field is submitted, you send a request to the [Reset Password API route](!api!/store#auth_postactor_typeauth_providerupdate), passing it the token, email, and new password.
|
||||||
|
|
||||||
Notice that the JS SDK passes the token in the `Authorization Bearer` header. So, if you're implementing this flow without using the JS SDK, make sure to pass the token accordingly.
|
Notice that the JS SDK passes the token in the `Authorization: Bearer` header. So, if you're implementing this flow without using the JS SDK, make sure to pass the token accordingly.
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
|
|
||||||
Before [Medusa v2.6](https://github.com/medusajs/medusa/releases/tag/v2.6), you passed the token as a query parameter. Now, you must pass it in the `Authorization Bearer` header.
|
Before [Medusa v2.6](https://github.com/medusajs/medusa/releases/tag/v2.6), you passed the token as a query parameter. Now, you must pass it in the `Authorization: Bearer` header.
|
||||||
|
|
||||||
</Note>
|
</Note>
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ export const generatedEditDates = {
|
|||||||
"app/service-factory-reference/methods/retrieve/page.mdx": "2024-07-31T17:01:33+03:00",
|
"app/service-factory-reference/methods/retrieve/page.mdx": "2024-07-31T17:01:33+03:00",
|
||||||
"app/service-factory-reference/methods/soft-delete/page.mdx": "2024-07-31T17:01:33+03:00",
|
"app/service-factory-reference/methods/soft-delete/page.mdx": "2024-07-31T17:01:33+03:00",
|
||||||
"app/service-factory-reference/methods/update/page.mdx": "2025-07-31T08:24:03.685Z",
|
"app/service-factory-reference/methods/update/page.mdx": "2025-07-31T08:24:03.685Z",
|
||||||
"app/service-factory-reference/tips/filtering/page.mdx": "2025-04-23T14:38:29.068Z",
|
"app/service-factory-reference/tips/filtering/page.mdx": "2025-08-15T12:06:28.709Z",
|
||||||
"app/service-factory-reference/page.mdx": "2025-07-31T13:29:12.136Z",
|
"app/service-factory-reference/page.mdx": "2025-07-31T13:29:12.136Z",
|
||||||
"app/storefront-development/cart/context/page.mdx": "2025-03-27T14:47:14.258Z",
|
"app/storefront-development/cart/context/page.mdx": "2025-03-27T14:47:14.258Z",
|
||||||
"app/storefront-development/cart/create/page.mdx": "2025-03-27T14:46:51.473Z",
|
"app/storefront-development/cart/create/page.mdx": "2025-03-27T14:46:51.473Z",
|
||||||
@@ -2099,7 +2099,7 @@ export const generatedEditDates = {
|
|||||||
"app/admin-components/layouts/two-column/page.mdx": "2025-08-01T15:18:00.109Z",
|
"app/admin-components/layouts/two-column/page.mdx": "2025-08-01T15:18:00.109Z",
|
||||||
"app/admin-components/components/forms/page.mdx": "2025-08-01T15:18:59.686Z",
|
"app/admin-components/components/forms/page.mdx": "2025-08-01T15:18:59.686Z",
|
||||||
"app/commerce-modules/auth/reset-password/page.mdx": "2025-08-01T12:07:32.023Z",
|
"app/commerce-modules/auth/reset-password/page.mdx": "2025-08-01T12:07:32.023Z",
|
||||||
"app/storefront-development/customers/reset-password/page.mdx": "2025-03-27T14:46:51.424Z",
|
"app/storefront-development/customers/reset-password/page.mdx": "2025-08-15T10:52:54.220Z",
|
||||||
"app/commerce-modules/api-key/links-to-other-modules/page.mdx": "2025-04-17T15:39:39.374Z",
|
"app/commerce-modules/api-key/links-to-other-modules/page.mdx": "2025-04-17T15:39:39.374Z",
|
||||||
"app/commerce-modules/cart/extend/page.mdx": "2024-12-25T12:48:59.149Z",
|
"app/commerce-modules/cart/extend/page.mdx": "2024-12-25T12:48:59.149Z",
|
||||||
"app/commerce-modules/cart/links-to-other-modules/page.mdx": "2025-04-17T15:40:03.112Z",
|
"app/commerce-modules/cart/links-to-other-modules/page.mdx": "2025-04-17T15:40:03.112Z",
|
||||||
@@ -5832,7 +5832,7 @@ export const generatedEditDates = {
|
|||||||
"references/core_flows/types/core_flows.ThrowUnlessPaymentCollectionNotePaidInput/page.mdx": "2025-06-25T10:11:33.516Z",
|
"references/core_flows/types/core_flows.ThrowUnlessPaymentCollectionNotePaidInput/page.mdx": "2025-06-25T10:11:33.516Z",
|
||||||
"references/core_flows/types/core_flows.ValidatePaymentsRefundStepInput/page.mdx": "2025-06-25T10:11:34.185Z",
|
"references/core_flows/types/core_flows.ValidatePaymentsRefundStepInput/page.mdx": "2025-06-25T10:11:34.185Z",
|
||||||
"references/core_flows/types/core_flows.ValidateRefundStepInput/page.mdx": "2025-06-25T10:11:34.177Z",
|
"references/core_flows/types/core_flows.ValidateRefundStepInput/page.mdx": "2025-06-25T10:11:34.177Z",
|
||||||
"app/plugins/guides/wishlist/page.mdx": "2025-07-16T09:54:20.345Z",
|
"app/plugins/guides/wishlist/page.mdx": "2025-08-15T10:52:37.465Z",
|
||||||
"app/plugins/page.mdx": "2025-02-26T11:39:25.709Z",
|
"app/plugins/page.mdx": "2025-02-26T11:39:25.709Z",
|
||||||
"app/admin-components/components/data-table/page.mdx": "2025-03-03T14:55:58.556Z",
|
"app/admin-components/components/data-table/page.mdx": "2025-03-03T14:55:58.556Z",
|
||||||
"references/order_models/variables/order_models.Order/page.mdx": "2025-08-14T12:59:55.945Z",
|
"references/order_models/variables/order_models.Order/page.mdx": "2025-08-14T12:59:55.945Z",
|
||||||
|
|||||||
Reference in New Issue
Block a user