Skip to content

Commit 94376f6

Browse files
authored
docs: document Query Context (medusajs#11289)
* docs: document Query Context * add vale exception
1 parent e0851b2 commit 94376f6

File tree

5 files changed

+229
-2
lines changed

5 files changed

+229
-2
lines changed
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
export const metadata = {
2+
title: `${pageNumber} Query Context`,
3+
}
4+
5+
# {metadata.title}
6+
7+
In this chapter, you'll learn how to pass contexts when retrieving data with [Query](../query/page.mdx).
8+
9+
## What is Query Context?
10+
11+
Query context is a way to pass additional information when retrieving data with Query. This data can be useful when applying custom transformations to the retrieved data based on the current context.
12+
13+
For example, consider you have a Blog Module with posts and authors. You can accept the user's language as a context and return the posts in the user's language. Another example is how Medusa uses Query Context to [retrieve product variants' prices based on the customer's currency](!resources!/commerce-modules/product/guides/price).
14+
15+
---
16+
17+
## How to Use Query Context
18+
19+
The `query.graph` method accepts an optional `context` parameter that can be used to pass additional context either to the data model you're retrieving (for example, `post`), or its related and linked models (for example, `author`).
20+
21+
You initialize a context using `QueryContext` from the Modules SDK. It accepts an object of contexts as an argument.
22+
23+
For example, to retrieve posts using Query while passing the user's language as a context:
24+
25+
export const highlights1 = [
26+
["4", "context", "Pass additional context to the query."],
27+
["4", "QueryContext", "Create a query context."]
28+
]
29+
30+
```ts
31+
const { data } = await query.graph({
32+
entity: "post",
33+
fields: ["*"],
34+
context: QueryContext({
35+
lang: "es",
36+
})
37+
})
38+
```
39+
40+
In this example, you pass in the context a `lang` property whose value is `es`.
41+
42+
Then, to handle the context while retrieving records of the data model, in the associated module's service you override the generated `list` method of the data model.
43+
44+
For example, continuing the example above, you can override the `listPosts` method of the Blog Module's service to handle the context:
45+
46+
export const highlights2 = [
47+
["11", "listPosts", "Override the generated listPosts method."],
48+
["16", "context", "The context is passed as part of `filters`."],
49+
["19", "super", "Use the parent's `listPosts` method to retrieve the posts."],
50+
["21", "", "If the language is set in the context, you transform the titles of the posts."]
51+
]
52+
53+
```ts highlights={highlights2}
54+
import { MedusaContext, MedusaService } from "@medusajs/framework/utils"
55+
import { Context, FindConfig } from "@medusajs/framework/types"
56+
import Post from "./models/post"
57+
import Author from "./models/author"
58+
59+
class BlogModuleService extends MedusaService({
60+
Post,
61+
Author
62+
}){
63+
// @ts-ignore
64+
async listPosts(
65+
filters?: any,
66+
config?: FindConfig<any> | undefined,
67+
@MedusaContext() sharedContext?: Context | undefined
68+
) {
69+
const context = filters.context ?? {}
70+
delete filters.context
71+
72+
let posts = await super.listPosts(filters, config, sharedContext)
73+
74+
if (context.lang === "es") {
75+
posts = posts.map((post) => {
76+
return {
77+
...post,
78+
title: post.title + " en español",
79+
}
80+
})
81+
}
82+
83+
return posts
84+
}
85+
}
86+
87+
export default BlogModuleService
88+
```
89+
90+
In the above example, you override the generated `listPosts` method. This method receives as a first parameter the filters passed to the query, but it also includes a `context` property that holds the context passed to the query.
91+
92+
You extract the context from `filters`, then retrieve the posts using the parent's `listPosts` method. After that, if the language is set in the context, you transform the titles of the posts.
93+
94+
All posts returned will now have their titles appended with "en español".
95+
96+
<Note title="Tip">
97+
98+
Learn more about the generated `list` method in [this reference](!resources!/service-factory-reference/methods/list).
99+
100+
</Note>
101+
102+
---
103+
104+
## Passing Query Context to Related Data Models
105+
106+
If you're retrieving a data model and you want to pass context to its associated model in the same module, you can pass them as part of `QueryContext`'s parameter, then handle them in the same `list` method.
107+
108+
<Note>
109+
110+
For linked data models, check out the [next section](#passing-query-context-to-linked-data-models).
111+
112+
</Note>
113+
114+
For example, to pass a context for the post's authors:
115+
116+
export const highlights3 = [
117+
["6", "author", "Pass a context for the author."]
118+
]
119+
120+
```ts highlights={highlights3}
121+
const { data } = await query.graph({
122+
entity: "post",
123+
fields: ["*"],
124+
context: QueryContext({
125+
lang: "es",
126+
author: QueryContext({
127+
lang: "es",
128+
})
129+
})
130+
})
131+
```
132+
133+
Then, in the `listPosts` method, you can handle the context for the post's authors:
134+
135+
export const highlights4 = [
136+
["22", "context.author?.lang", "Access the author's context."]
137+
]
138+
139+
```ts highlights={highlights4}
140+
import { MedusaContext, MedusaService } from "@medusajs/framework/utils"
141+
import { Context, FindConfig } from "@medusajs/framework/types"
142+
import Post from "./models/post"
143+
import Author from "./models/author"
144+
145+
class BlogModuleService extends MedusaService({
146+
Post,
147+
Author
148+
}){
149+
// @ts-ignore
150+
async listPosts(
151+
filters?: any,
152+
config?: FindConfig<any> | undefined,
153+
@MedusaContext() sharedContext?: Context | undefined
154+
) {
155+
const context = filters.context ?? {}
156+
delete filters.context
157+
158+
let posts = await super.listPosts(filters, config, sharedContext)
159+
160+
const isPostLangEs = context.lang === "es"
161+
const isAuthorLangEs = context.author?.lang === "es"
162+
163+
if (isPostLangEs || isAuthorLangEs) {
164+
posts = posts.map((post) => {
165+
return {
166+
...post,
167+
title: isPostLangEs ? post.title + " en español" : post.title,
168+
author: {
169+
...post.author,
170+
name: isAuthorLangEs ? post.author.name + " en español" : post.author.name,
171+
}
172+
}
173+
})
174+
}
175+
176+
return posts
177+
}
178+
}
179+
180+
export default BlogModuleService
181+
```
182+
183+
The context in `filters` will also have the context for `author`, which you can use to make transformations to the post's authors.
184+
185+
---
186+
187+
## Passing Query Context to Linked Data Models
188+
189+
If you're retrieving a data model and you want to pass context to a linked model in a different module, pass to the `context` property an object instead, where its keys are the linked model's name and the values are the context for that linked model.
190+
191+
For example, consider the Product Module's `Product` data model is linked to the Blog Module's `Post` data model. You can pass context to the `Post` data model while retrieving products like so:
192+
193+
export const highlights5 = [
194+
["5", "post", "Pass a context for posts."]
195+
]
196+
197+
```ts highlights={highlights5}
198+
const { data } = await query.graph({
199+
entity: "product",
200+
fields: ["*", "post.*"],
201+
context: {
202+
post: QueryContext({
203+
lang: "es",
204+
})
205+
}
206+
})
207+
```
208+
209+
In this example, you retrieve products and their associated posts. You also pass a context for `post`, indicating the customer's language.
210+
211+
To handle the context, you override the generated `listPosts` method of the Blog Module as explained [previously](#how-to-use-query-context).

www/apps/book/generated/edit-dates.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,5 +114,6 @@ export const generatedEditDates = {
114114
"app/learn/fundamentals/plugins/create/page.mdx": "2025-01-31T13:17:48.052Z",
115115
"app/learn/fundamentals/plugins/page.mdx": "2025-01-22T10:14:10.433Z",
116116
"app/learn/customization/reuse-customizations/page.mdx": "2025-01-22T10:01:57.665Z",
117-
"app/learn/update/page.mdx": "2025-01-27T08:45:19.030Z"
117+
"app/learn/update/page.mdx": "2025-01-27T08:45:19.030Z",
118+
"app/learn/fundamentals/module-links/query-context/page.mdx": "2025-02-03T17:04:24.479Z"
118119
}

www/apps/book/generated/sidebar.mjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,15 @@ export const generatedSidebar = [
368368
"title": "Custom Columns",
369369
"children": [],
370370
"chapterTitle": "3.3.4. Custom Columns"
371+
},
372+
{
373+
"loaded": true,
374+
"isPathHref": true,
375+
"type": "link",
376+
"path": "/learn/fundamentals/module-links/query-context",
377+
"title": "Query Context",
378+
"children": [],
379+
"chapterTitle": "3.3.5. Query Context"
371380
}
372381
],
373382
"chapterTitle": "3.3. Module Links"

www/apps/book/sidebar.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,11 @@ export const sidebar = sidebarAttachHrefCommonOptions([
214214
path: "/learn/fundamentals/module-links/custom-columns",
215215
title: "Custom Columns",
216216
},
217+
{
218+
type: "link",
219+
path: "/learn/fundamentals/module-links/query-context",
220+
title: "Query Context",
221+
},
217222
],
218223
},
219224
{

www/vale/styles/docs/ModuleNames.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ exceptions:
1717
- the first module
1818
- the second module
1919
- the module provider
20-
- the specified module
20+
- the specified module
21+
- the associated module

0 commit comments

Comments
 (0)