Hello, I'm trying to fetch a record from my frontend (nextjs) and I want to exclude a field from the data that PayloadCMS will return. In this example I will exclude the ID fields of the record. This is my code:
if (courseIdState && courseTierState) {
;(async function () {
const queryForManualSimulations = {
and: [
{
'coursesWithAccess.course': {
equals: courseIdState,
},
},
{
'coursesWithAccess.tier': {
equals: courseTierState,
},
},
],
}
const stringifyQueryForManualSimulations = qs.stringify(
{
where: queryForManualSimulations,
select: '-id' ,
},
{ addQueryPrefix: true },
)
const reqManualSimulations = await axios.get(
`/backend/api/manual-simulations${stringifyQueryForManualSimulations}`,
)
})()
}
It returns me the record I'm looking for successfully, but it also includes its ID, which I want to exclude from the result. Is there any way to exclude a specific field when fetching a record from PayloadCMS? Thanks!
PS: in the URL, I have a NextJS rewrite that assigns to
/backend
the URL of PayloadCMS
Hello @Lloyd! You can set access control on certain fields so that they excluded from the returned record
import { CollectionConfig } from 'payload/types';
const Posts: CollectionConfig = {
slug: 'posts',
fields: [
{
name: 'title',
type: 'text',
access: {
create: ({ req: { user } }) => { ... },
read: ({ req: { user } }) => { ... },
update: ({ req: { user } }) => { ... },
},
};
],
};
https://payloadcms.com/docs/access-control/fields
For instance, if you want to prevent the field from being included in the request, return a boolean from the function on 'read'
read: ({ req: { user } }) => { return false }
Another way would be to create a custom endpoint with the local API, but I think access control should be what you want here
In addition, if you want more fine grain control, a common pattern is to set roles on the 'user' collection, so that you can refine the access controls on fields to specific groups.
Hi, thank you for the response. I need to exclude the ID field only for the request on one of my pages in NextJS, but I need the field to be included in the request I make from another page. As I said in the post above, I used the
id
field only as an example, but in the real website I want to exclude an array that will contain a lot of info, so I can save some bandwidth on the page that doesn't need that additional info, but still be able to fetch this info on another page that needs it.
Can I selectively exclude fields from the request, or do you suggest to write a custom endpoint for it?
@Lloyd Ah I see now. Are you familiar with Express? Payload uses express in the background to manage endpoints
In your
server.ts
file, you can add a route to your app
(Which is an express app)
And then you can do something like
(inside the route)
const result = await payload.find({
collection: "posts", // required
depth: 2,
page: 1,
limit: 10,
where: {}, // pass a `where` query here
sort: "-title",
locale: "en",
fallbackLocale: false,
user: dummyUser,
overrideAccess: false,
showHiddenFields: true,
queryHiddenFields: false,
});
one sec, improving examplke
Does the
sort
remove the field that I need to exclude?
Oops
(Btw yes I am familiar with express, I've already written some custom endpoints for payload, but I thought there is other ways to exclude fields from a query without writing custom endpoints )
You know what, there's an easier way
On your collection, you can define custom routes that extend the collection
const Orders: CollectionConfig = {
slug: 'orders',
fields: [ /* ... */ ],
endpoints: [
{
path: '/:id/tracking',
method: 'get',
handler: async (req, res, next) => {
const tracking = await getTrackingInfo(req.params.id);
if (tracking) {
res.status(200).send({ tracking });
} else {
res.status(404).send({ error: 'not found' });
}
}
}
],
}
So that's one way, not sure how to manage the fields in this instance
Let me show a custom endpoint with express as an example
Wait a second, I think I got the custom field part figured out. Do you know by any chance how to exclude a field when doing a query with the Local API?
This way I can save some bandwidth on the backend side, making the request faster
yes
well possibly, id still get the collection, but then filter what i send
Oh I see, so there is no way to tell it to fetch only specific fields, it will get all of them
well
one sec
In the docs I have this example
// Result will be a paginated set of Posts.
// See /docs/queries/pagination for more.
const result = await payload.find({
collection: "posts", // required
depth: 2,
page: 1,
limit: 10,
where: {}, // pass a `where` query here
sort: "-title",
locale: "en",
fallbackLocale: false,
user: dummyUser,
overrideAccess: false,
showHiddenFields: true,
queryHiddenFields: false,
});
I saw no ways to fetch only specific fields
import express, { Request, Response } from "express";
const app = express();
// GET request route
app.get("/getPosts", async (req: Request, res: Response) => {
try {
const posts = await payload.find({
collection: "posts",
}).map((post) => {
const {id, foo} = post
return {
id,
foo
}
})
res.json(posts)
} catch (err) {
// handle error
res.status(404).end()
}
});
note: wrote this in discord so hopefully i didn't make a mistake
so we use the local api on the server to get the collection, then we filter out the fields we dont want
It tells me that property
map
does not exist on type
Promise
, but I get how it works. I will make a custom route that will filter out the info before sending them to frontend. Thanks a lot ☺️
Ahhhh
That's odd
The promise should be resolved once you await it and return the right type
Maybe i condensed it too much
Let me know if you get it working 😄
I ended up doing this
endpoints: [
{
// This returns the simulations that are supposed to be used to list them, so it contains only the title and the dates, it does not contain the actual content of the simulation like questions ecc
path: '/custom/manual-simulations-list/:courseId/:courseTier',
method: 'get',
handler: async (req, res, next) => {
const courseIdParam = req.params.courseId
const courseTierParam = req.params.courseTier
const queryForManualSimulationsList = {
and: [
{
'coursesWithAccess.course': {
equals: courseIdParam,
},
},
{
'coursesWithAccess.tier': {
equals: courseTierParam,
},
},
],
}
const reqManualSimulationsList = await payload.find({
collection: ManualSimulations.slug,
where: queryForManualSimulationsList,
depth: 0,
})
if (reqManualSimulationsList.docs.length > 0) {
// The query did find at least one record
const filteredManualSimulationsList =
reqManualSimulationsList.docs.map((e) => {
delete e.simulationBlocks
return e
})
res.status(200).send(filteredManualSimulationsList)
return
}
res.status(204).send('Request successful, but no content found.')
},
},
],
It seems to be working.
Btw if you're curious what I'm doing, I'm making a website for lessons people can take online, and then there will be some tests they can do. This endpoint basically sends to the frontend a list with the tests available (I only needed the titles and some basic info, sending all the questions contained in each test would have made a very big request with data that were not needed for the page that displays the tests, that's why I wanted to exclude it)
Ahhh I see
Very cool!
And I'm glad you got it working!!!
Thanks ☺️
@Lloyd Is there any benefit to using .json() opposed to .send() when sending found documents?
I'm not sure, I'm building the custom endpoints the same way I've seen on the Payload documentation. For example if you go there
https://payloadcms.com/docs/rest-api/overviewyou will see them use
send
This is a code snippet from the page linked:
import { CollectionConfig } from 'payload/types';
// a collection of 'orders' with an additional route for tracking details, reachable at /api/orders/:id/tracking
const Orders: CollectionConfig = {
slug: 'orders',
fields: [ /* ... */ ],
endpoints: [
{
path: '/:id/tracking',
method: 'get',
handler: async (req, res, next) => {
const tracking = await getTrackingInfo(req.params.id);
if (tracking) {
res.status(200).send({ tracking });
} else {
res.status(404).send({ error: 'not found' });
}
}
}
],
}
They use
send
so I'm doing the same
I just asked chatgpt and it told me that there are some benefits of using json(), do you mind if I send you the response in private? I don't know if it's allright to send so much stuff in a closed community-help question
Oh this is totally fine
It will be helpful to people in the future
But if you feel more comfortable sending a dm, please do so
Here's what chatgpt told me
Yes, there are some benefits to using the .json() method over the .send() method when sending found documents in an HTTP response.
The .json() method is specifically designed for sending JSON data in the response body, whereas the .send() method is a more generic method that can send various types of data, including strings, buffers, objects, and arrays. When using .json(), the data is automatically serialized to JSON format and the appropriate Content-Type header is set to application/json.
Using .json() has a few advantages over using .send():
More explicit: By using .json(), you are explicitly stating that the response contains JSON data, which makes the code more readable and easier to understand for other developers.
Better performance: Since .json() automatically sets the Content-Type header to application/json, it can be more efficient than using .send(), which requires you to set the header manually. This can be especially important in large-scale applications where performance is critical.
Consistency: By using .json(), you are ensuring that the response is always returned in JSON format, which can help ensure consistency in your API responses.
In summary, using .json() when sending found documents is a good practice since it is more explicit, can improve performance, and promotes consistency in API responses.
I've built a tool/plugin that does this:
https://www.npmjs.com/package/payload-query
It just filters out the fields that you want / don't want and you can use it on any payload rest endpoint
Star
Discord
online
Get help straight from the Payload team with an Enterprise License.