Payload CMS是一个完全基于TypeScript和Node.js构建的开头无头内容管理系统,它的图片处理能力是内置的,不需要额外安装插件。很多人在选型时会把Payload和Strapi、Directus放在一起比较,其中Payload的Upload集合机制是最有特色的设计之一:它把上传的文件本身当作一种集合文档来管理,天然支持图片与任意内容集合之间的关联引用。本文将以图片上传为切入点,完整讲解在Node.js项目中利用Payload CMS实现图片上传、缩略图生成、动态访问与权限控制的完整流程。

一、搭建环境并创建Media集合
首先需要初始化一个Payload项目。官方脚手架会自动创建Next.js工程并集成Payload,执行以下命令即可:
npx create-payload-app@latest cd my-payload-app npm run dev
安装完成后,在src/collections目录下新建Media.ts文件。Payload中所有集合都通过配置对象定义,图片上传集合的核心是把upload属性配置好。一个典型的Media集合定义如下:
import type { CollectionConfig } from 'payload'
export const Media: CollectionConfig = {
slug: 'media',
access: {
read: () => true, // 图片资源通常允许公开读取
},
fields: [
{
name: 'alt',
type: 'text',
required: true,
},
{
name: 'caption',
type: 'text',
},
],
upload: {
staticDir: 'media', // 图片存储在本地的 media 目录
imageSizes: [
{ name: 'thumbnail', width: 400, height: 300, position: 'centre' },
{ name: 'card', width: 768, height: 1024, position: 'centre' },
{ name: 'hero', width: 2048, height: 2048, position: 'centre' },
],
mimeTypes: ['image/*'], // 只允许上传图片类型
},
}这段配置做了三件事:定义了alt和caption两个附加字段,方便SEO和前台展示;通过imageSizes声明了三档自动裁剪尺寸,上传原图后Payload会借助sharp自动生成对应尺寸的缩略图;通过mimeTypes限制只接受图片文件,避免用户上传脚本或其他危险文件。需要注意的是,如果项目里安装了Payload但没有自动装上sharp依赖,需要手动执行npm install sharp,否则缩略图生成会静默失败。
定义好集合后,还要在payload.config.ts中注册它。同时在开发阶段建议开启disableLocalStorage: false保持默认行为,让图片落到本地磁盘,便于调试观察。生产环境则建议换成云存储适配器,后文会提到。
二、通过REST API上传图片
Payload自动为每个集合生成完整的REST接口,上传图片本质上是向/api/media发送一个multipart/form-data的POST请求。下面用Node.js原生的fetch配合FormData演示调用方式:
// scripts/upload.js
const fs = require('fs')
const path = require('path')
async function uploadImage(filePath, altText) {
const form = new FormData()
const buffer = fs.readFileSync(filePath)
form.append('file', new Blob([buffer]), path.basename(filePath))
form.append('alt', altText)
form.append('_payload', JSON.stringify({ alt: altText }))
const res = await fetch('http://localhost:3000/api/media', {
method: 'POST',
headers: {
Authorization: `Users JWT-collection ${process.env.PAYLOAD_TOKEN}`,
},
body: form,
})
const data = await res.json()
console.log('上传结果:', JSON.stringify(data, null, 2))
}
uploadImage('./images/cover.jpg', '文章封面图')这里有几个细节值得注意。第一,字段数据要通过_payload以JSON字符串形式随表单一起提交,这是Payload处理混合上传的约定;第二,如果集合的create权限没有放开,需要携带认证头,最简单的方式是先在管理后台生成一个API Key或登录获取JWT;第三,返回结果中的url和sizes字段分别对应原图地址和各尺寸图片地址,前端可以根据屏幕宽度按需取用,比如列表页取thumbnail,详情页取hero。
上传成功后,访问http://localhost:3000/media/xxx/cover.jpg即可拿到原图,访问http://localhost:3000/media/xxx/cover-400x300.jpg则拿到缩略图。Payload还内置了图片动态处理参数,直接在URL后拼接?width=300&height=200就能实时缩放,非常灵活。
三、在内容集合中关联图片并使用Local API
图片上传只是第一步,实际业务中更常见的是让文章、商品等内容引用图片。这通过upload类型的字段实现。假设有一个Posts集合:
export const Posts: CollectionConfig = {
slug: 'posts',
admin: {
useAsTitle: 'title',
},
fields: [
{ name: 'title', type: 'text', required: true },
{
name: 'cover',
type: 'upload',
relationTo: 'media', // 关联到 Media 集合
required: true,
},
{
name: 'gallery',
type: 'array',
fields: [
{ name: 'image', type: 'upload', relationTo: 'media' },
],
},
],
}配置完成后,管理后台的编辑界面会出现图片选择器,支持直接拖拽上传或从媒体库中挑选,编辑体验比传统表单友好得多。数据层面,cover字段存储的是Media文档的ID,查询文章时通过?depth=1参数即可自动展开图片完整信息,而不需要前端再发一次请求去查图片详情。
如果你的业务代码和Payload运行在同一个Node.js进程中,比如在Next.js的自定义路由里,使用Local API会比走HTTP更高效,省去了网络开销和序列化成本:
import { getPayload } from 'payload'
import config from '@/payload.config'
const payload = await getPayload({ config })
// 直接在本地上传文件
const result = await payload.create({
collection: 'media',
data: { alt: '本地接口上传的图片' },
file: {
data: fs.readFileSync('./images/photo.png'),
mimetype: 'image/png',
name: 'photo.png',
size: fs.statSync('./images/photo.png').size,
},
})
// 查询文章并展开封面图
const posts = await payload.find({
collection: 'posts',
depth: 1,
limit: 10,
})Local API的一个隐藏优势是绕过了REST层的权限校验(除非显式传入overrideAccess: false),因此在服务端内部调用时非常方便,但也要警惕不要把它暴露给不受信任的输入,否则容易造成越权写入。
四、生产环境部署与常见问题
本地磁盘存储只适合开发环境,一旦应用部署到容器或Serverless平台,文件系统往往是只读或随时会被重置的。Payload官方提供了@payloadcms/storage-s3等存储适配器,切换到S3、腾讯云COS或阿里云OSS只需安装适配器并在配置中挂载插件,代码层面几乎零改动:
import { s3Storage } from '@payloadcms/storage-s3'
export default buildConfig({
collections: [Media, Posts],
plugins: [
s3Storage({
bucket: process.env.S3_BUCKET,
config: {
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
},
},
}),
],
})接入适配器后,上传的图片会自动写入对象存储,返回的url也会指向云端地址,可以进一步配合CDN加速分发。需要注意的是密钥一定要放在环境变量中,切勿写进代码仓库。
最后总结几个高频踩坑点。其一,上传大图片报413错误,这通常是反向代理层(如Nginx的client_max_body_size)限制了请求体大小,与Payload本身无关;其二,缩略图不生成,先检查sharp是否正确安装、Node版本是否在18以上;其三,图片字段在查询时只返回ID,多半是depth参数没设置,REST请求默认depth为2,Local API默认也是2,但显式传0时会只保留ID。理解了这些细节,Payload的图片管理能力基本就能覆盖绝大多数内容型项目的需求了。
Payload CMSNode.js图片上传修改时间:2026-09-03 16:31:30