Skip to content

Commit d4d029f

Browse files
authored
docs: document exported TypeScript type helpers for plugins (#17336)
adds a Type Helpers section to the TypeScript overview outlining the generic helpers exported from `payload` (`CollectionSlug`,`DataFromCollectionSlug`, `TypedUser`, etc.) and how they resolve via `GeneratedTypes`
1 parent 2a69863 commit d4d029f

2 files changed

Lines changed: 99 additions & 0 deletions

File tree

docs/plugins/build-your-own.mdx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,49 @@ export interface PluginTypes {
273273

274274
If possible, include [JSDoc comments](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#types-1) to describe the options and their types. This allows a developer to see details about the options in their editor.
275275

276+
### Consuming generated types
277+
278+
A common question when building a plugin is how to reference the type of a collection that belongs to the _end user_. At authoring time you don't know which collections the user has, so it's tempting to hand-copy a subset of their interfaces — but that is fragile and quickly drifts out of sync.
279+
280+
Instead, use Payload's generic [type helpers](../typescript/overview#type-helpers). Because a consuming project augments the `GeneratedTypes` interface when it runs `payload generate:types`, helpers like `CollectionSlug` and `DataFromCollectionSlug` resolve to the user's _actual_ collections wherever your plugin's source is type-checked in their project:
281+
282+
```ts
283+
import type {
284+
CollectionSlug,
285+
DataFromCollectionSlug,
286+
CollectionAfterChangeHook,
287+
} from 'payload'
288+
289+
export interface PluginTypes {
290+
/**
291+
* Collections to enable this plugin for
292+
*/
293+
collections?: CollectionSlug[]
294+
}
295+
296+
// Stays generic over whichever collection slug is passed in
297+
const createHook = <TSlug extends CollectionSlug>(slug: TSlug) => {
298+
const hook: CollectionAfterChangeHook<DataFromCollectionSlug<TSlug>> = ({
299+
doc,
300+
}) => {
301+
// `doc` is typed to the user's collection at `slug`
302+
return doc
303+
}
304+
return hook
305+
}
306+
```
307+
308+
<Banner type="info">
309+
Within your plugin's own repository, `GeneratedTypes` is not augmented, so
310+
these helpers fall back to loose types. To develop against real types,
311+
generate them inside your `dev/` project (see the `dev:generate-types` script
312+
in the [plugin template](#plugin-template)) — the concrete types materialize
313+
once the plugin is installed in a project that has run `payload
314+
generate:types`.
315+
</Banner>
316+
317+
If your plugin needs to contribute its _own_ reusable types into the user's generated file, extend the JSON schema via [`typescript.schema`](../typescript/generating-types#custom-generated-types).
318+
276319
## Best practices
277320

278321
In addition to the setup covered above, here are other best practices to follow:

docs/typescript/overview.mdx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,59 @@ Payload exports a number of types that you may find useful while writing your ow
3434
- [Collection hooks](/docs/hooks/collections#typescript)
3535
- [Global hooks](/docs/hooks/globals#typescript)
3636
- [Field hooks](/docs/hooks/fields#typescript)
37+
38+
## Type Helpers
39+
40+
Beyond the concrete interfaces in your generated `payload-types.ts` (`Post`, `User`, etc.), Payload exports a set of **generic type helpers** from the `payload` package. Instead of referencing a single collection by name, these resolve dynamically against _all_ of your collections and globals — which makes them especially useful in [Plugins](../plugins/build-your-own#consuming-generated-types), reusable [Hooks](../hooks/overview), and [Access Control](../access-control/overview) functions, where the exact collection may not be known ahead of time.
41+
42+
### How they resolve
43+
44+
When you run [`payload generate:types`](./generating-types), the generated file augments a global `GeneratedTypes` interface inside the `payload` module:
45+
46+
```ts
47+
// payload-types.ts (generated)
48+
declare module 'payload' {
49+
export interface GeneratedTypes extends Config {}
50+
}
51+
```
52+
53+
Every helper below is generic over this augmented interface. Because the augmentation lives in _your_ project, the helpers resolve to _your_ actual collections wherever your code is type-checked — including inside a third-party plugin's source once it is installed in your project. If `GeneratedTypes` has not been augmented (for example, in a plugin's own repository before it is consumed), the helpers gracefully fall back to loose types (`string`, index signatures) rather than erroring.
54+
55+
### Available helpers
56+
57+
| Helper | Resolves to |
58+
| --------------------------------------- | ------------------------------------------------------------------------------------------ |
59+
| `CollectionSlug` | A union of all your collection slugs, e.g. `'posts' \| 'users' \| 'media'`. |
60+
| `DataFromCollectionSlug<TSlug>` | The full document type for a collection (the shape returned after read). |
61+
| `RequiredDataFromCollectionSlug<TSlug>` | The data shape accepted by `create` (system fields like `id`/`createdAt` become optional). |
62+
| `SelectFromCollectionSlug<TSlug>` | The `select` type for a collection. |
63+
| `TypedCollection` | A map of every collection slug to its document type. |
64+
| `GlobalSlug` | A union of all your global slugs. |
65+
| `TypedGlobal` | A map of every global slug to its data type. |
66+
| `TypedUser` | The user type — a union across all auth-enabled collections. |
67+
| `TypedLocale` | A union of your configured locale codes (or `string` if localization is disabled). |
68+
| `DefaultDocumentIDType` | Your database's default ID type (`string` or `number`). |
69+
70+
### Example
71+
72+
```ts
73+
import type {
74+
CollectionSlug,
75+
DataFromCollectionSlug,
76+
CollectionAfterChangeHook,
77+
} from 'payload'
78+
79+
// Constrain a value to any valid collection slug
80+
const collections: CollectionSlug[] = ['posts', 'users']
81+
82+
// Write a hook that stays generic over whichever slug it is attached to
83+
function createAuditHook<TSlug extends CollectionSlug>(slug: TSlug) {
84+
const hook: CollectionAfterChangeHook<DataFromCollectionSlug<TSlug>> = ({
85+
doc,
86+
}) => {
87+
// `doc` is fully typed to the collection at `slug`
88+
return doc
89+
}
90+
return hook
91+
}
92+
```

0 commit comments

Comments
 (0)