237 lines
4.8 KiB
Markdown
237 lines
4.8 KiB
Markdown
---
|
|
title: Custom Endpoints
|
|
description: Custom REST API endpoints with authentication and helpers
|
|
tags: [payload, endpoints, api, routes, webhooks]
|
|
---
|
|
|
|
# Payload Custom Endpoints
|
|
|
|
## Basic Endpoint Pattern
|
|
|
|
Custom endpoints are **not authenticated by default**. Always check `req.user`.
|
|
|
|
```typescript
|
|
import { APIError } from 'payload'
|
|
import type { Endpoint } from 'payload'
|
|
|
|
export const protectedEndpoint: Endpoint = {
|
|
path: '/protected',
|
|
method: 'get',
|
|
handler: async (req) => {
|
|
if (!req.user) {
|
|
throw new APIError('Unauthorized', 401)
|
|
}
|
|
|
|
// Use req.payload for database operations
|
|
const data = await req.payload.find({
|
|
collection: 'posts',
|
|
where: { author: { equals: req.user.id } },
|
|
})
|
|
|
|
return Response.json(data)
|
|
},
|
|
}
|
|
```
|
|
|
|
## Route Parameters
|
|
|
|
```typescript
|
|
export const trackingEndpoint: Endpoint = {
|
|
path: '/:id/tracking',
|
|
method: 'get',
|
|
handler: async (req) => {
|
|
const { id } = req.routeParams
|
|
|
|
const tracking = await getTrackingInfo(id)
|
|
|
|
if (!tracking) {
|
|
return Response.json({ error: 'not found' }, { status: 404 })
|
|
}
|
|
|
|
return Response.json(tracking)
|
|
},
|
|
}
|
|
```
|
|
|
|
## Request Body Handling
|
|
|
|
```typescript
|
|
// Manual JSON parsing
|
|
export const createEndpoint: Endpoint = {
|
|
path: '/create',
|
|
method: 'post',
|
|
handler: async (req) => {
|
|
const data = await req.json()
|
|
|
|
const result = await req.payload.create({
|
|
collection: 'posts',
|
|
data,
|
|
})
|
|
|
|
return Response.json(result)
|
|
},
|
|
}
|
|
|
|
// Using helper (handles JSON + files)
|
|
import { addDataAndFileToRequest } from 'payload'
|
|
|
|
export const uploadEndpoint: Endpoint = {
|
|
path: '/upload',
|
|
method: 'post',
|
|
handler: async (req) => {
|
|
await addDataAndFileToRequest(req)
|
|
|
|
// req.data contains parsed body
|
|
// req.file contains uploaded file (if multipart)
|
|
|
|
const result = await req.payload.create({
|
|
collection: 'media',
|
|
data: req.data,
|
|
file: req.file,
|
|
})
|
|
|
|
return Response.json(result)
|
|
},
|
|
}
|
|
```
|
|
|
|
## Query Parameters
|
|
|
|
```typescript
|
|
export const searchEndpoint: Endpoint = {
|
|
path: '/search',
|
|
method: 'get',
|
|
handler: async (req) => {
|
|
const url = new URL(req.url)
|
|
const query = url.searchParams.get('q')
|
|
const limit = parseInt(url.searchParams.get('limit') || '10')
|
|
|
|
const results = await req.payload.find({
|
|
collection: 'posts',
|
|
where: {
|
|
title: {
|
|
contains: query,
|
|
},
|
|
},
|
|
limit,
|
|
})
|
|
|
|
return Response.json(results)
|
|
},
|
|
}
|
|
```
|
|
|
|
## CORS Headers
|
|
|
|
```typescript
|
|
import { headersWithCors } from 'payload'
|
|
|
|
export const corsEndpoint: Endpoint = {
|
|
path: '/public-data',
|
|
method: 'get',
|
|
handler: async (req) => {
|
|
const data = await fetchPublicData()
|
|
|
|
return Response.json(data, {
|
|
headers: headersWithCors({
|
|
headers: new Headers(),
|
|
req,
|
|
}),
|
|
})
|
|
},
|
|
}
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
```typescript
|
|
import { APIError } from 'payload'
|
|
|
|
export const validateEndpoint: Endpoint = {
|
|
path: '/validate',
|
|
method: 'post',
|
|
handler: async (req) => {
|
|
const data = await req.json()
|
|
|
|
if (!data.email) {
|
|
throw new APIError('Email is required', 400)
|
|
}
|
|
|
|
return Response.json({ valid: true })
|
|
},
|
|
}
|
|
```
|
|
|
|
## Endpoint Placement
|
|
|
|
### Collection Endpoints
|
|
|
|
Mounted at `/api/{collection-slug}/{path}`.
|
|
|
|
```typescript
|
|
export const Orders: CollectionConfig = {
|
|
slug: 'orders',
|
|
endpoints: [
|
|
{
|
|
path: '/:id/tracking',
|
|
method: 'get',
|
|
handler: async (req) => {
|
|
// Available at: /api/orders/:id/tracking
|
|
const orderId = req.routeParams.id
|
|
return Response.json({ orderId })
|
|
},
|
|
},
|
|
],
|
|
}
|
|
```
|
|
|
|
### Global Endpoints
|
|
|
|
Mounted at `/api/globals/{global-slug}/{path}`.
|
|
|
|
```typescript
|
|
export const Settings: GlobalConfig = {
|
|
slug: 'settings',
|
|
endpoints: [
|
|
{
|
|
path: '/clear-cache',
|
|
method: 'post',
|
|
handler: async (req) => {
|
|
// Available at: /api/globals/settings/clear-cache
|
|
await clearCache()
|
|
return Response.json({ message: 'Cache cleared' })
|
|
},
|
|
},
|
|
],
|
|
}
|
|
```
|
|
|
|
### Root Endpoints
|
|
|
|
Mounted at `/api/{path}`.
|
|
|
|
```typescript
|
|
export default buildConfig({
|
|
endpoints: [
|
|
{
|
|
path: '/hello',
|
|
method: 'get',
|
|
handler: () => {
|
|
// Available at: /api/hello
|
|
return Response.json({ message: 'Hello!' })
|
|
},
|
|
},
|
|
],
|
|
})
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Always check authentication** - Custom endpoints are not authenticated by default
|
|
2. **Use `req.payload` for operations** - Ensures access control and hooks execute
|
|
3. **Use helpers for common tasks** - `addDataAndFileToRequest`, `headersWithCors`
|
|
4. **Throw `APIError` for errors** - Provides consistent error responses
|
|
5. **Return Web API `Response`** - Use `Response.json()` for consistent responses
|
|
6. **Validate input** - Check required fields, validate types
|
|
7. **Log errors** - Use `req.payload.logger` for debugging
|