--- tags: [payloadcms, tech-patterns] topic: payloadcms sources: - https://payloadcms.com/docs/typescript/overview - https://payloadcms.com/docs/typescript/generating-types - https://payloadcms.com/docs/typescript/ts-plugin created: 2026-05-15 --- # PayloadCMS — TypeScript ## Overview - Payload is built entirely in TypeScript — native first-class support, no bolt-on. - Scaffold with `npx create-payload-app@latest` and pick a TS template. - The generated `payload-types.ts` file wires up automatic type inference across the entire Local API. ## Key Steps / Concepts ### Generating Types Run whenever collections/globals change: ```bash payload generate:types ``` Add an npm script pointing at your config explicitly (needed when config is not at root): ```json { "scripts": { "generate:types": "PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types" } } ``` ### Config Options ```ts // payload.config.ts { typescript: { // Default: path.resolve(__dirname, './payload-types.ts') outputFile: path.resolve(__dirname, './generated-types.ts'), // Disable auto-declare (for cross-repo type sharing) declare: false, // Extend the JSON schema → extend generated types schema: [ ({ jsonSchema }) => { jsonSchema.definitions.MyType = { type: 'object', properties: { title: { type: 'string' } }, required: ['title'], } return jsonSchema }, ], }, } ``` If you disable `declare`, add the declaration manually: ```ts import { Config } from './payload-types' declare module 'payload' { export interface GeneratedTypes extends Config {} } ``` ### Custom Field Interfaces (`interfaceName`) Hoist reusable field types with `interfaceName` on `array`, `block`, `group`, and named `tab` fields: ```ts { type: 'group', name: 'meta', interfaceName: 'SharedMeta', // generates top-level interface fields: [ { name: 'title', type: 'text' }, { name: 'description', type: 'text' }, ], } ``` Generated output: ```ts export interface SharedMeta { title?: string description?: string } export interface Post { meta?: SharedMeta } ``` **Warning:** `interfaceName` values are top-level — naming collisions with collection slugs will break generation. Suffix with field type (e.g. `MetaGroup`). ### External Schema References ```ts typescript: { schema: [ ({ jsonSchema }) => { jsonSchema.definitions.MyType = { $ref: './schemas/my-type.json', // resolved from process.cwd() } return jsonSchema }, ], } ``` ### Exported Types — Reference | Category | Types | |----------|-------| | Config | `Config`, `SanitizedConfig`, `ClientConfig` | | Collections | `CollectionConfig`, `CollectionSlug` | | Globals | `GlobalConfig` | | Fields | `Field`, `TextField`, `RelationshipField`, etc. | | Hooks | `CollectionBeforeChangeHook`, `GlobalAfterReadHook`, etc. | | Form state | `FormState` (renamed from `Fields` in v3) | ## TypeScript Language Service Plugin (Experimental) Plugin `@payloadcms/typescript-plugin` adds IDE intelligence for `PayloadComponent` path strings. ### Features - Path validation — red squiggles for non-existent component paths - Export validation — catches missing named exports, with "Did you mean?" suggestions - Autocomplete — file/dir completions after `/`, export completions after `#` - Go-to-definition — `Cmd+Click` on a path string jumps to source ### Setup ```bash pnpm add -D @payloadcms/typescript-plugin ``` ```json // tsconfig.json { "compilerOptions": { "plugins": [ { "name": "next" }, { "name": "@payloadcms/typescript-plugin" } ] } } ``` **VS Code / Cursor** — must use workspace TypeScript, not bundled: - `Cmd+Shift+P` → "TypeScript: Select TypeScript Version" → "Use Workspace Version" - Then: "TypeScript: Restart TS Server" Add to `.vscode/settings.json` for the whole team: ```json { "js/ts.tsdk.path": "node_modules/typescript/lib", "js/ts.tsdk.promptToUseWorkspaceVersion": true } ``` ### Supported Path Formats | Format | Example | |--------|---------| | Absolute (from `baseDir`) | `'/components/MyField#MyField'` | | Relative | `'./components/MyField#MyField'` | | tsconfig alias | `'@/components/MyField#MyField'` | | Package import | `'@payloadcms/ui/rsc#MyComponent'` | | Default export | `'/components/MyField'` | Both string form and object form (`{ path, exportName }`) are validated. ### baseDir Override Plugin auto-detects `baseDir` by walking up to find `payload.config.ts`. Override when using non-standard layouts: ```json { "compilerOptions": { "plugins": [ { "name": "@payloadcms/typescript-plugin", "baseDir": "./src" } ] } } ``` ## Common Issues & Fixes - **Types not updating** — rerun `payload generate:types`; check `PAYLOAD_CONFIG_PATH` env var - **`declare` errors in consuming repo** — set `declare: false` and add manual declaration - **Plugin not loading** — ensure editor uses workspace TS version, not bundled version - **Naming collision in generated types** — `interfaceName` clashes with a collection slug; rename with type suffix ## Gotchas - The TS plugin only runs in the IDE — it does not affect `tsc` or build output. - Path fields `_h_slugPath` / `_h_titlePath` appear in generated types but are `undefined` at runtime unless `computeHierarchyPaths: true` is passed. - Copying `payload-types.ts` to a frontend repo that does not have `payload` installed requires `declare: false` to avoid TS errors. ## Related - [[wiki/payloadcms/getting-started|Getting Started]] - [[wiki/payloadcms/configuration|Configuration]] - [[wiki/payloadcms/local-api|Local API]]