6.0 KiB
6.0 KiB
Payload CMS Querying Reference
Complete reference for querying data across Local API, REST, and GraphQL.
Query Operators
import type { Where } from 'payload';
// Equals
const equalsQuery: Where = { color: { equals: 'blue' } };
// Not equals
const notEqualsQuery: Where = { status: { not_equals: 'draft' } };
// Greater/less than
const greaterThanQuery: Where = { price: { greater_than: 100 } };
const lessThanEqualQuery: Where = { age: { less_than_equal: 65 } };
// Contains (case-insensitive)
const containsQuery: Where = { title: { contains: 'payload' } };
// Like (all words present)
const likeQuery: Where = { description: { like: 'cms headless' } };
// In/not in
const inQuery: Where = { category: { in: ['tech', 'news'] } };
// Exists
const existsQuery: Where = { image: { exists: true } };
// Near (point fields)
const nearQuery: Where = { location: { near: '-122.4194,37.7749,10000' } };
AND/OR Logic
import type { Where } from 'payload';
const complexQuery: Where = {
or: [
{ color: { equals: 'mint' } },
{
and: [{ color: { equals: 'white' } }, { featured: { equals: false } }],
},
],
};
Nested Properties
import type { Where } from 'payload';
const nestedQuery: Where = {
'author.role': { equals: 'editor' },
'meta.featured': { exists: true },
};
Local API
// Find documents
const posts = await payload.find({
collection: 'posts',
where: {
status: { equals: 'published' },
'author.name': { contains: 'john' },
},
depth: 2,
limit: 10,
page: 1,
sort: '-createdAt',
locale: 'en',
select: {
title: true,
author: true,
},
});
// Find by ID
const post = await payload.findByID({
collection: 'posts',
id: '123',
depth: 2,
});
// Create
const post = await payload.create({
collection: 'posts',
data: {
title: 'New Post',
status: 'draft',
},
});
// Update
await payload.update({
collection: 'posts',
id: '123',
data: {
status: 'published',
},
});
// Delete
await payload.delete({
collection: 'posts',
id: '123',
});
// Count
const count = await payload.count({
collection: 'posts',
where: {
status: { equals: 'published' },
},
});
Threading req Parameter
When performing operations in hooks or nested operations, pass the req parameter to maintain transaction context:
// ✅ CORRECT: Pass req for transaction safety
const afterChange: CollectionAfterChangeHook = async ({ doc, req }) => {
await req.payload.create({
collection: 'audit-log',
data: { action: 'created', docId: doc.id },
req, // Maintains transaction atomicity
});
};
// ❌ WRONG: Missing req breaks transaction
const afterChange: CollectionAfterChangeHook = async ({ doc, req }) => {
await req.payload.create({
collection: 'audit-log',
data: { action: 'created', docId: doc.id },
// Missing req - runs in separate transaction
});
};
This is critical for MongoDB replica sets and Postgres. See ADAPTERS.md#threading-req-through-operations for details.
Access Control in Local API
Important: Local API bypasses access control by default (overrideAccess: true). When passing a user parameter, you must explicitly set overrideAccess: false to respect that user's permissions.
// ❌ WRONG: User is passed but access control is bypassed
const posts = await payload.find({
collection: 'posts',
user: currentUser,
// Missing: overrideAccess: false
// Result: Operation runs with ADMIN privileges, ignoring user's permissions
});
// ✅ CORRECT: Respects user's access control permissions
const posts = await payload.find({
collection: 'posts',
user: currentUser,
overrideAccess: false, // Required to enforce access control
// Result: User only sees posts they have permission to read
});
// Administrative operation (intentionally bypass access control)
const allPosts = await payload.find({
collection: 'posts',
// No user parameter
// overrideAccess defaults to true
// Result: Returns all posts regardless of access control
});
When to use overrideAccess: false:
- Performing operations on behalf of a user
- Testing access control logic
- API routes that should respect user permissions
- Any operation where
userparameter is provided
When overrideAccess: true is appropriate:
- Administrative operations (migrations, seeds, cron jobs)
- Internal system operations
- Operations explicitly intended to bypass access control
See ACCESS-CONTROL.md#important-notes for more details.
REST API
import { stringify } from 'qs-esm';
const query = {
status: { equals: 'published' },
};
const queryString = stringify(
{
where: query,
depth: 2,
limit: 10,
},
{ addQueryPrefix: true },
);
const response = await fetch(`https://api.example.com/api/posts${queryString}`);
const data = await response.json();
REST Endpoints
GET /api/{collection} - Find documents
GET /api/{collection}/{id} - Find by ID
POST /api/{collection} - Create
PATCH /api/{collection}/{id} - Update
DELETE /api/{collection}/{id} - Delete
GET /api/{collection}/count - Count documents
GET /api/globals/{slug} - Get global
POST /api/globals/{slug} - Update global
GraphQL
query {
Posts(
where: { status: { equals: published } }
limit: 10
sort: "-createdAt"
) {
docs {
id
title
author {
name
}
}
totalDocs
hasNextPage
}
}
mutation {
createPost(data: { title: "New Post", status: draft }) {
id
title
}
}
mutation {
updatePost(id: "123", data: { status: published }) {
id
status
}
}
mutation {
deletePost(id: "123") {
id
}
}
Performance Best Practices
- Set
maxDepthon relationships to prevent over-fetching - Use
selectto limit returned fields - Index frequently queried fields
- Use
virtualfields for computed data - Cache expensive operations in hook
context