Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 50 additions & 9 deletions docs/custom-components/edit-view.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ In addition to swapping out the entire Edit View with a [Custom View](./custom-v

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

#### Collections
Expand Down Expand Up @@ -550,20 +550,21 @@ export function MyDescriptionComponent(props: ViewDescriptionClientProps) {

### Upload

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

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

```ts
import type { CollectionConfig } from 'payload'

export const MyCollection: CollectionConfig = {
// ...
export const Media: CollectionConfig = {
slug: 'media',
upload: true,
admin: {
components: {
edit: {
// highlight-start
Upload: '/path/to/MyUploadComponent',
Upload: '/path/to/MyUploadComponent#MyUploadServer',
// highlight-end
},
},
Expand All @@ -572,15 +573,55 @@ export const MyCollection: CollectionConfig = {
```

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

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

#### Server Component

```tsx
import React from 'react'
import type {
PayloadServerReactComponent,
SanitizedCollectionConfig,
} from 'payload'
import { CustomUploadClient } from './MyUploadComponent.client'

export const MyUploadServer: PayloadServerReactComponent<
SanitizedCollectionConfig['admin']['components']['edit']['Upload']
> = (props) => {
return (
<div>
<h2>Custom Upload Interface</h2>
<CustomUploadClient {...props} />
</div>
)
}
```

#### Client Component

```tsx
'use client'
import React from 'react'
import { Upload, useDocumentInfo } from '@payloadcms/ui'

export function MyUploadComponent() {
return <input type="file" />
export const CustomUploadClient = () => {
const { collectionSlug, docConfig, initialState } = useDocumentInfo()

return (
<Upload
collectionSlug={collectionSlug}
initialState={initialState}
uploadConfig={'upload' in docConfig ? docConfig.upload : undefined}
/>
)
}
```

For more details on customizing upload components, including examples with custom actions and drawers, see the [Upload documentation](../upload/overview#customizing-the-upload-ui).
20 changes: 19 additions & 1 deletion docs/custom-components/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ In order to ensure the Payload Config is fully Node.js compatible and as lightwe

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`.

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.
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.

**Both default exports and named exports are fully supported.** Choose the pattern that works best for your codebase.

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

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

**Examples of component path syntax:**

```ts
// Named export using hash syntax
Button: '/components/Logout#MyComponent'

// Default export (no hash needed)
Button: '/components/Logout'

// Named export using exportName property
Button: {
path: '/components/Logout',
exportName: 'MyComponent',
}
```

### Component Config

While Custom Components are usually defined as a string, you can also pass in an object with additional options:
Expand Down
212 changes: 212 additions & 0 deletions docs/upload/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,218 @@ export const Media: CollectionConfig = {
}
```

## Customizing the Upload UI

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.

### Upload Component Configuration

To customize the upload UI for an upload-enabled collection, use the `admin.components.edit.Upload` property in your [Collection Config](../configuration/collections):

```ts
import type { CollectionConfig } from 'payload'

export const Media: CollectionConfig = {
slug: 'media',
upload: true,
admin: {
components: {
edit: {
Upload: '/components/CustomUpload#CustomUploadServer',
},
},
},
fields: [
{
name: 'alt',
type: 'text',
},
],
}
```

### Building Custom Upload Components

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.

<Banner type="warning">
You should not use a simple `<input type="file" />` element
alone. It will not connect to Payload's upload API or form state, resulting
in errors like "400 Bad Request - no file uploaded." Always use Payload's
`<Upload>` component or properly integrate with form hooks.
</Banner>

#### Basic Example

Here's a minimal example showing how to create a custom upload component:

**Server Component** (`/components/CustomUpload.tsx`):

```tsx
import React from 'react'
import type {
PayloadServerReactComponent,
SanitizedCollectionConfig,
} from 'payload'
import { CustomUploadClient } from './CustomUpload.client'

export const CustomUploadServer: PayloadServerReactComponent<
SanitizedCollectionConfig['admin']['components']['edit']['Upload']
> = (props) => {
return (
<div>
<h2>Custom Upload Interface</h2>
<CustomUploadClient {...props} />
</div>
)
}
```

**Client Component** (`/components/CustomUpload.client.tsx`):

```tsx
'use client'
import React from 'react'
import { Upload, useDocumentInfo } from '@payloadcms/ui'

export const CustomUploadClient = () => {
const { collectionSlug, docConfig, initialState } = useDocumentInfo()

return (
<Upload
collectionSlug={collectionSlug}
initialState={initialState}
uploadConfig={'upload' in docConfig ? docConfig.upload : undefined}
/>
)
}
```

#### Advanced Example with Custom Actions

You can add custom actions, drawers, and fields to enhance the upload experience:

```tsx
'use client'
import React from 'react'
import {
Drawer,
DrawerToggler,
TextField,
Upload,
useDocumentInfo,
} from '@payloadcms/ui'

const customDrawerSlug = 'custom-upload-drawer'

const CustomDrawer = () => {
return (
<Drawer slug={customDrawerSlug}>
<h2>Custom Upload Options</h2>
<TextField
field={{
name: 'customField',
label: 'Custom Field',
type: 'text',
}}
path="customField"
/>
</Drawer>
)
}

const CustomDrawerToggler = () => {
return (
<DrawerToggler slug={customDrawerSlug}>
<button type="button">Open Custom Options</button>
</DrawerToggler>
)
}

export const CustomUploadClient = () => {
const { collectionSlug, docConfig, initialState } = useDocumentInfo()

return (
<div>
<CustomDrawer />
<Upload
collectionSlug={collectionSlug}
customActions={[<CustomDrawerToggler key="custom-drawer" />]}
initialState={initialState}
uploadConfig={'upload' in docConfig ? docConfig.upload : undefined}
/>
</div>
)
}
```

### Available Hooks and Components

When building custom upload components, you have access to several useful hooks and components from `@payloadcms/ui`:

| Hook / Component | Description |
| ------------------- | --------------------------------------------------------------------- |
| `useDocumentInfo()` | Get collection slug, document config, and initial state |
| `useField()` | Access and manipulate form field state |
| `useBulkUpload()` | Access bulk upload context |
| `<Upload>` | Main upload component with file selection, drag-and-drop, and preview |
| `<Drawer>` | Modal drawer for additional UI |
| `<DrawerToggler>` | Button to open/close drawers |
| `<TextField>`, etc. | Form field components |

### Custom Upload Fields vs. Custom Upload Collections

It's important to understand the difference between these two customization approaches:

| Approach | Configuration | Use Case |
| ----------------------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| **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) |
| **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) |

**Example of Upload Field Customization:**

```ts
import type { CollectionConfig } from 'payload'

export const Posts: CollectionConfig = {
slug: 'posts',
fields: [
{
name: 'featuredImage',
type: 'upload',
relationTo: 'media',
admin: {
components: {
Field: '/components/CustomUploadField',
},
},
},
],
}
```

For more details on customizing fields, see [Field Components](../fields/overview#custom-components).

### Component Export Syntax

Custom components are referenced using file paths. Both default exports and named exports are supported:

```ts
// Named export with hash syntax
Upload: '/components/CustomUpload#CustomUploadServer'

// Default export (no hash needed)
Upload: '/components/CustomUpload'

// Alternative: using exportName property
Upload: {
path: '/components/CustomUpload',
exportName: 'CustomUploadServer',
}
```

For more details on component paths, see [Custom Components](../custom-components/overview#component-paths).

## Restricted File Types

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