Skip to content

Commit d3d8b4e

Browse files
authored
docs: updates custom upload component examples (#14678)
Improves documentation for custom upload components. Fixes #14013 Changes: - Adds "Customizing the Upload UI" section to upload/overview.mdx with working examples - Fixes Upload component example in custom-components/edit-view.mdx - Adds warning about common mistake of using plain input elements - Clarifies component export syntax (default vs named exports) in overview.mdx - Includes advanced examples with custom actions, drawers, and hooks - Adds comparison table for upload collection vs upload field customization
1 parent 910d274 commit d3d8b4e

3 files changed

Lines changed: 281 additions & 10 deletions

File tree

docs/custom-components/edit-view.mdx

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ In addition to swapping out the entire Edit View with a [Custom View](./custom-v
7474

7575
<Banner type="warning">
7676
**Important:** Collection and Globals are keyed to a different property in the
77-
`admin.components` object and have slightly different options. Be sure to use the
78-
correct key for the entity you are working with.
77+
`admin.components` object and have slightly different options. Be sure to use
78+
the correct key for the entity you are working with.
7979
</Banner>
8080

8181
#### Collections
@@ -550,20 +550,21 @@ export function MyDescriptionComponent(props: ViewDescriptionClientProps) {
550550

551551
### Upload
552552

553-
The `Upload` property allows you to render a custom file upload component in the Edit View.
553+
The `Upload` property allows you to render a custom file upload component in the Edit View. This is only available for upload-enabled Collections.
554554

555555
To add an `Upload` component, use the `components.edit.Upload` property in your [Collection Config](../configuration/collections):
556556

557557
```ts
558558
import type { CollectionConfig } from 'payload'
559559

560-
export const MyCollection: CollectionConfig = {
561-
// ...
560+
export const Media: CollectionConfig = {
561+
slug: 'media',
562+
upload: true,
562563
admin: {
563564
components: {
564565
edit: {
565566
// highlight-start
566-
Upload: '/path/to/MyUploadComponent',
567+
Upload: '/path/to/MyUploadComponent#MyUploadServer',
567568
// highlight-end
568569
},
569570
},
@@ -572,15 +573,55 @@ export const MyCollection: CollectionConfig = {
572573
```
573574

574575
<Banner type="warning">
575-
**Note:** The Upload component is only available for Collections.
576+
**Important:** Custom upload components must integrate with Payload's form
577+
system to work correctly. You cannot use a simple `<input type="file" />` as
578+
it won't connect to Payload's upload API. Instead, use Payload's built-in
579+
`<Upload>` component from `@payloadcms/ui` or properly integrate with form
580+
hooks like `useDocumentInfo()`.
576581
</Banner>
577582

578583
Here's an example of a custom `Upload` component:
579584

585+
#### Server Component
586+
587+
```tsx
588+
import React from 'react'
589+
import type {
590+
PayloadServerReactComponent,
591+
SanitizedCollectionConfig,
592+
} from 'payload'
593+
import { CustomUploadClient } from './MyUploadComponent.client'
594+
595+
export const MyUploadServer: PayloadServerReactComponent<
596+
SanitizedCollectionConfig['admin']['components']['edit']['Upload']
597+
> = (props) => {
598+
return (
599+
<div>
600+
<h2>Custom Upload Interface</h2>
601+
<CustomUploadClient {...props} />
602+
</div>
603+
)
604+
}
605+
```
606+
607+
#### Client Component
608+
580609
```tsx
610+
'use client'
581611
import React from 'react'
612+
import { Upload, useDocumentInfo } from '@payloadcms/ui'
582613

583-
export function MyUploadComponent() {
584-
return <input type="file" />
614+
export const CustomUploadClient = () => {
615+
const { collectionSlug, docConfig, initialState } = useDocumentInfo()
616+
617+
return (
618+
<Upload
619+
collectionSlug={collectionSlug}
620+
initialState={initialState}
621+
uploadConfig={'upload' in docConfig ? docConfig.upload : undefined}
622+
/>
623+
)
585624
}
586625
```
626+
627+
For more details on customizing upload components, including examples with custom actions and drawers, see the [Upload documentation](../upload/overview#customizing-the-upload-ui).

docs/custom-components/overview.mdx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ In order to ensure the Payload Config is fully Node.js compatible and as lightwe
6161

6262
Component Paths, by default, are relative to your project's base directory. This is either your current working directory, or the directory specified in `config.admin.importMap.baseDir`.
6363

64-
Components using named exports are identified either by appending `#` followed by the export name, or using the `exportName` property. If the component is the default export, this can be omitted.
64+
Components using named exports are identified either by appending `#` followed by the export name, or using the `exportName` property. If the component is the default export, the `#` and export name can be omitted.
65+
66+
**Both default exports and named exports are fully supported.** Choose the pattern that works best for your codebase.
6567

6668
```ts
6769
import { buildConfig } from 'payload'
@@ -87,6 +89,22 @@ const config = buildConfig({
8789

8890
In this example, we set the base directory to the `src` directory, and omit the `/src/` part of our component path string.
8991

92+
**Examples of component path syntax:**
93+
94+
```ts
95+
// Named export using hash syntax
96+
Button: '/components/Logout#MyComponent'
97+
98+
// Default export (no hash needed)
99+
Button: '/components/Logout'
100+
101+
// Named export using exportName property
102+
Button: {
103+
path: '/components/Logout',
104+
exportName: 'MyComponent',
105+
}
106+
```
107+
90108
### Component Config
91109

92110
While Custom Components are usually defined as a string, you can also pass in an object with additional options:

docs/upload/overview.mdx

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,218 @@ export const Media: CollectionConfig = {
330330
}
331331
```
332332

333+
## Customizing the Upload UI
334+
335+
You can completely customize the upload interface in the Admin Panel by swapping in your own React components. This allows you to modify how files are uploaded, add custom fields, integrate custom actions, or enhance the upload experience.
336+
337+
### Upload Component Configuration
338+
339+
To customize the upload UI for an upload-enabled collection, use the `admin.components.edit.Upload` property in your [Collection Config](../configuration/collections):
340+
341+
```ts
342+
import type { CollectionConfig } from 'payload'
343+
344+
export const Media: CollectionConfig = {
345+
slug: 'media',
346+
upload: true,
347+
admin: {
348+
components: {
349+
edit: {
350+
Upload: '/components/CustomUpload#CustomUploadServer',
351+
},
352+
},
353+
},
354+
fields: [
355+
{
356+
name: 'alt',
357+
type: 'text',
358+
},
359+
],
360+
}
361+
```
362+
363+
### Building Custom Upload Components
364+
365+
Custom upload components must integrate with Payload's form system to work correctly. The recommended approach is to use Payload's built-in `<Upload>` component from `@payloadcms/ui` and wrap it with additional functionality.
366+
367+
<Banner type="warning">
368+
You should not use a simple `<input type="file" />` element
369+
alone. It will not connect to Payload's upload API or form state, resulting
370+
in errors like "400 Bad Request - no file uploaded." Always use Payload's
371+
`<Upload>` component or properly integrate with form hooks.
372+
</Banner>
373+
374+
#### Basic Example
375+
376+
Here's a minimal example showing how to create a custom upload component:
377+
378+
**Server Component** (`/components/CustomUpload.tsx`):
379+
380+
```tsx
381+
import React from 'react'
382+
import type {
383+
PayloadServerReactComponent,
384+
SanitizedCollectionConfig,
385+
} from 'payload'
386+
import { CustomUploadClient } from './CustomUpload.client'
387+
388+
export const CustomUploadServer: PayloadServerReactComponent<
389+
SanitizedCollectionConfig['admin']['components']['edit']['Upload']
390+
> = (props) => {
391+
return (
392+
<div>
393+
<h2>Custom Upload Interface</h2>
394+
<CustomUploadClient {...props} />
395+
</div>
396+
)
397+
}
398+
```
399+
400+
**Client Component** (`/components/CustomUpload.client.tsx`):
401+
402+
```tsx
403+
'use client'
404+
import React from 'react'
405+
import { Upload, useDocumentInfo } from '@payloadcms/ui'
406+
407+
export const CustomUploadClient = () => {
408+
const { collectionSlug, docConfig, initialState } = useDocumentInfo()
409+
410+
return (
411+
<Upload
412+
collectionSlug={collectionSlug}
413+
initialState={initialState}
414+
uploadConfig={'upload' in docConfig ? docConfig.upload : undefined}
415+
/>
416+
)
417+
}
418+
```
419+
420+
#### Advanced Example with Custom Actions
421+
422+
You can add custom actions, drawers, and fields to enhance the upload experience:
423+
424+
```tsx
425+
'use client'
426+
import React from 'react'
427+
import {
428+
Drawer,
429+
DrawerToggler,
430+
TextField,
431+
Upload,
432+
useDocumentInfo,
433+
} from '@payloadcms/ui'
434+
435+
const customDrawerSlug = 'custom-upload-drawer'
436+
437+
const CustomDrawer = () => {
438+
return (
439+
<Drawer slug={customDrawerSlug}>
440+
<h2>Custom Upload Options</h2>
441+
<TextField
442+
field={{
443+
name: 'customField',
444+
label: 'Custom Field',
445+
type: 'text',
446+
}}
447+
path="customField"
448+
/>
449+
</Drawer>
450+
)
451+
}
452+
453+
const CustomDrawerToggler = () => {
454+
return (
455+
<DrawerToggler slug={customDrawerSlug}>
456+
<button type="button">Open Custom Options</button>
457+
</DrawerToggler>
458+
)
459+
}
460+
461+
export const CustomUploadClient = () => {
462+
const { collectionSlug, docConfig, initialState } = useDocumentInfo()
463+
464+
return (
465+
<div>
466+
<CustomDrawer />
467+
<Upload
468+
collectionSlug={collectionSlug}
469+
customActions={[<CustomDrawerToggler key="custom-drawer" />]}
470+
initialState={initialState}
471+
uploadConfig={'upload' in docConfig ? docConfig.upload : undefined}
472+
/>
473+
</div>
474+
)
475+
}
476+
```
477+
478+
### Available Hooks and Components
479+
480+
When building custom upload components, you have access to several useful hooks and components from `@payloadcms/ui`:
481+
482+
| Hook / Component | Description |
483+
| ------------------- | --------------------------------------------------------------------- |
484+
| `useDocumentInfo()` | Get collection slug, document config, and initial state |
485+
| `useField()` | Access and manipulate form field state |
486+
| `useBulkUpload()` | Access bulk upload context |
487+
| `<Upload>` | Main upload component with file selection, drag-and-drop, and preview |
488+
| `<Drawer>` | Modal drawer for additional UI |
489+
| `<DrawerToggler>` | Button to open/close drawers |
490+
| `<TextField>`, etc. | Form field components |
491+
492+
### Custom Upload Fields vs. Custom Upload Collections
493+
494+
It's important to understand the difference between these two customization approaches:
495+
496+
| Approach | Configuration | Use Case |
497+
| ----------------------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
498+
| **Upload Collection Customization** | `admin.components.edit.Upload` | Customize the UI when editing documents in an upload-enabled collection (e.g., the Media collection edit view) |
499+
| **Upload Field Customization** | `admin.components.Field` on an upload field | Customize the field that references uploads in other collections (e.g., a "Featured Image" field on a Posts collection) |
500+
501+
**Example of Upload Field Customization:**
502+
503+
```ts
504+
import type { CollectionConfig } from 'payload'
505+
506+
export const Posts: CollectionConfig = {
507+
slug: 'posts',
508+
fields: [
509+
{
510+
name: 'featuredImage',
511+
type: 'upload',
512+
relationTo: 'media',
513+
admin: {
514+
components: {
515+
Field: '/components/CustomUploadField',
516+
},
517+
},
518+
},
519+
],
520+
}
521+
```
522+
523+
For more details on customizing fields, see [Field Components](../fields/overview#custom-components).
524+
525+
### Component Export Syntax
526+
527+
Custom components are referenced using file paths. Both default exports and named exports are supported:
528+
529+
```ts
530+
// Named export with hash syntax
531+
Upload: '/components/CustomUpload#CustomUploadServer'
532+
533+
// Default export (no hash needed)
534+
Upload: '/components/CustomUpload'
535+
536+
// Alternative: using exportName property
537+
Upload: {
538+
path: '/components/CustomUpload',
539+
exportName: 'CustomUploadServer',
540+
}
541+
```
542+
543+
For more details on component paths, see [Custom Components](../custom-components/overview#component-paths).
544+
333545
## Restricted File Types
334546

335547
Possibly problematic file types are automatically restricted from being uploaded to your application.

0 commit comments

Comments
 (0)