Skip to content

Commit 1b1559b

Browse files
committed
feat: initial commit
0 parents  commit 1b1559b

58 files changed

Lines changed: 17104 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/payload/SKILL.md

Lines changed: 448 additions & 0 deletions
Large diffs are not rendered by default.

.claude/skills/payload/reference/ACCESS-CONTROL-ADVANCED.md

Lines changed: 704 additions & 0 deletions
Large diffs are not rendered by default.

.claude/skills/payload/reference/ACCESS-CONTROL.md

Lines changed: 697 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
# Payload CMS Adapters Reference
2+
3+
Complete reference for database, storage, and email adapters.
4+
5+
## Database Adapters
6+
7+
### MongoDB
8+
9+
```ts
10+
import { mongooseAdapter } from '@payloadcms/db-mongodb'
11+
12+
export default buildConfig({
13+
db: mongooseAdapter({
14+
url: process.env.DATABASE_URL,
15+
}),
16+
})
17+
```
18+
19+
### Postgres
20+
21+
```ts
22+
import { postgresAdapter } from '@payloadcms/db-postgres'
23+
24+
export default buildConfig({
25+
db: postgresAdapter({
26+
pool: {
27+
connectionString: process.env.DATABASE_URL,
28+
},
29+
push: false, // Don't auto-push schema changes
30+
migrationDir: './migrations',
31+
}),
32+
})
33+
```
34+
35+
### SQLite
36+
37+
```ts
38+
import { sqliteAdapter } from '@payloadcms/db-sqlite'
39+
40+
export default buildConfig({
41+
db: sqliteAdapter({
42+
client: {
43+
url: 'file:./payload.db',
44+
},
45+
transactionOptions: {}, // Enable transactions (disabled by default)
46+
}),
47+
})
48+
```
49+
50+
## Transactions
51+
52+
Payload automatically uses transactions for all-or-nothing database operations. Pass `req` to include operations in the same transaction.
53+
54+
```ts
55+
import type { CollectionAfterChangeHook } from 'payload'
56+
57+
const afterChange: CollectionAfterChangeHook = async ({ req, doc }) => {
58+
// This will be part of the same transaction
59+
await req.payload.create({
60+
req, // Pass req to use same transaction
61+
collection: 'audit-log',
62+
data: { action: 'created', docId: doc.id },
63+
})
64+
}
65+
66+
// Manual transaction control
67+
const transactionID = await payload.db.beginTransaction()
68+
try {
69+
await payload.create({
70+
collection: 'orders',
71+
data: orderData,
72+
req: { transactionID },
73+
})
74+
await payload.update({
75+
collection: 'inventory',
76+
id: itemId,
77+
data: { stock: newStock },
78+
req: { transactionID },
79+
})
80+
await payload.db.commitTransaction(transactionID)
81+
} catch (error) {
82+
await payload.db.rollbackTransaction(transactionID)
83+
throw error
84+
}
85+
```
86+
87+
**Note**: MongoDB requires replicaset for transactions. SQLite requires `transactionOptions: {}` to enable.
88+
89+
### Threading req Through Operations
90+
91+
**Critical**: When performing nested operations in hooks, always pass `req` to maintain transaction context. Failing to do so breaks atomicity and can cause partial updates.
92+
93+
```ts
94+
import type { CollectionAfterChangeHook } from 'payload'
95+
96+
// ✅ CORRECT: Thread req through nested operations
97+
const resaveChildren: CollectionAfterChangeHook = async ({ collection, doc, req }) => {
98+
// Find children - pass req
99+
const children = await req.payload.find({
100+
collection: 'children',
101+
where: { parent: { equals: doc.id } },
102+
req, // Maintains transaction context
103+
})
104+
105+
// Update each child - pass req
106+
for (const child of children.docs) {
107+
await req.payload.update({
108+
id: child.id,
109+
collection: 'children',
110+
data: { updatedField: 'value' },
111+
req, // Same transaction as parent operation
112+
})
113+
}
114+
}
115+
116+
// ❌ WRONG: Missing req breaks transaction
117+
const brokenHook: CollectionAfterChangeHook = async ({ collection, doc, req }) => {
118+
const children = await req.payload.find({
119+
collection: 'children',
120+
where: { parent: { equals: doc.id } },
121+
// Missing req - separate transaction or no transaction
122+
})
123+
124+
for (const child of children.docs) {
125+
await req.payload.update({
126+
id: child.id,
127+
collection: 'children',
128+
data: { updatedField: 'value' },
129+
// Missing req - if parent operation fails, these updates persist
130+
})
131+
}
132+
}
133+
```
134+
135+
**Why This Matters:**
136+
137+
- **MongoDB (with replica sets)**: Creates atomic session across operations
138+
- **PostgreSQL**: All operations use same Drizzle transaction
139+
- **SQLite (with transactions enabled)**: Ensures rollback on errors
140+
- **Without req**: Each operation runs independently, breaking atomicity
141+
142+
**When req is Required:**
143+
144+
- All mutating operations in hooks (create, update, delete)
145+
- Operations that must succeed/fail together
146+
- When using MongoDB replica sets or Postgres
147+
- Any operation that relies on `req.context` or `req.user`
148+
149+
**When req is Optional:**
150+
151+
- Read-only lookups independent of current transaction
152+
- Operations with `disableTransaction: true`
153+
- Administrative operations with `overrideAccess: true`
154+
155+
## Storage Adapters
156+
157+
Available storage adapters:
158+
159+
- **@payloadcms/storage-s3** - AWS S3
160+
- **@payloadcms/storage-azure** - Azure Blob Storage
161+
- **@payloadcms/storage-gcs** - Google Cloud Storage
162+
- **@payloadcms/storage-r2** - Cloudflare R2
163+
- **@payloadcms/storage-vercel-blob** - Vercel Blob
164+
- **@payloadcms/storage-uploadthing** - Uploadthing
165+
166+
### AWS S3
167+
168+
```ts
169+
import { s3Storage } from '@payloadcms/storage-s3'
170+
171+
export default buildConfig({
172+
plugins: [
173+
s3Storage({
174+
collections: {
175+
media: true,
176+
},
177+
bucket: process.env.S3_BUCKET,
178+
config: {
179+
credentials: {
180+
accessKeyId: process.env.S3_ACCESS_KEY_ID,
181+
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
182+
},
183+
region: process.env.S3_REGION,
184+
},
185+
}),
186+
],
187+
})
188+
```
189+
190+
### Azure Blob Storage
191+
192+
```ts
193+
import { azureStorage } from '@payloadcms/storage-azure'
194+
195+
export default buildConfig({
196+
plugins: [
197+
azureStorage({
198+
collections: {
199+
media: true,
200+
},
201+
connectionString: process.env.AZURE_STORAGE_CONNECTION_STRING,
202+
containerName: process.env.AZURE_STORAGE_CONTAINER_NAME,
203+
}),
204+
],
205+
})
206+
```
207+
208+
### Google Cloud Storage
209+
210+
```ts
211+
import { gcsStorage } from '@payloadcms/storage-gcs'
212+
213+
export default buildConfig({
214+
plugins: [
215+
gcsStorage({
216+
collections: {
217+
media: true,
218+
},
219+
bucket: process.env.GCS_BUCKET,
220+
options: {
221+
projectId: process.env.GCS_PROJECT_ID,
222+
credentials: JSON.parse(process.env.GCS_CREDENTIALS),
223+
},
224+
}),
225+
],
226+
})
227+
```
228+
229+
### Cloudflare R2
230+
231+
```ts
232+
import { r2Storage } from '@payloadcms/storage-r2'
233+
234+
export default buildConfig({
235+
plugins: [
236+
r2Storage({
237+
collections: {
238+
media: true,
239+
},
240+
bucket: process.env.R2_BUCKET,
241+
config: {
242+
credentials: {
243+
accessKeyId: process.env.R2_ACCESS_KEY_ID,
244+
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
245+
},
246+
region: 'auto',
247+
endpoint: process.env.R2_ENDPOINT,
248+
},
249+
}),
250+
],
251+
})
252+
```
253+
254+
### Vercel Blob
255+
256+
```ts
257+
import { vercelBlobStorage } from '@payloadcms/storage-vercel-blob'
258+
259+
export default buildConfig({
260+
plugins: [
261+
vercelBlobStorage({
262+
collections: {
263+
media: true,
264+
},
265+
token: process.env.BLOB_READ_WRITE_TOKEN,
266+
}),
267+
],
268+
})
269+
```
270+
271+
### Uploadthing
272+
273+
```ts
274+
import { uploadthingStorage } from '@payloadcms/storage-uploadthing'
275+
276+
export default buildConfig({
277+
plugins: [
278+
uploadthingStorage({
279+
collections: {
280+
media: true,
281+
},
282+
options: {
283+
token: process.env.UPLOADTHING_TOKEN,
284+
acl: 'public-read',
285+
},
286+
}),
287+
],
288+
})
289+
```
290+
291+
## Email Adapters
292+
293+
### Nodemailer (SMTP)
294+
295+
```ts
296+
import { nodemailerAdapter } from '@payloadcms/email-nodemailer'
297+
298+
export default buildConfig({
299+
email: nodemailerAdapter({
300+
defaultFromAddress: 'noreply@example.com',
301+
defaultFromName: 'My App',
302+
transportOptions: {
303+
host: process.env.SMTP_HOST,
304+
port: 587,
305+
auth: {
306+
user: process.env.SMTP_USER,
307+
pass: process.env.SMTP_PASS,
308+
},
309+
},
310+
}),
311+
})
312+
```
313+
314+
### Resend
315+
316+
```ts
317+
import { resendAdapter } from '@payloadcms/email-resend'
318+
319+
export default buildConfig({
320+
email: resendAdapter({
321+
defaultFromAddress: 'noreply@example.com',
322+
defaultFromName: 'My App',
323+
apiKey: process.env.RESEND_API_KEY,
324+
}),
325+
})
326+
```

0 commit comments

Comments
 (0)