Here’s how to write a cell data component in Payload CMS to get images to appear in your Collection List. Displaying Images on your Payload CMS Admin is as easy as setting the server’s URL.
This guide uses a custom component to fetch and render the image. Full source code can be found at: aaronksaunders/payload-custom-cell-image-component-1-2025
Setting Server URL
The first thing we need to do is make sure our payload.config.ts
knows where to find our URL.
// payload.config.ts
export default buildConfig({
serverURL: process.env.SERVER_URL || 'http://localhost:3000',
Make sure that your server knows where to retrieve media from by putting this into your config, be that from local or from another host, this would be how it gets set.
If you dont set this, the url for the media will be incorrect and probably not render properly
User List Modifications
To make your User List modifications, the first step is to create a new avatar
field with a relationship to the Media collection. This is defined within your Users.ts
collection configuration:
// Users.ts
{
name: 'avatar',
type: 'relationship',
relationTo: 'media',
admin: {
components: {
Cell: 'src/collections/CustomImageCell',
},
},
},
Important to note that with Payload 3, you now need to specify the full path to the custom cell component, you cannot just import the object like in version 2
Custom Component Creation (CustomImageCell.tsx)
// src/collections/CustomImageCell.tsx
import React from 'react'
import Image from 'next/image'
import { type DefaultServerCellComponentProps } from 'payload'
const MyComponent = async ({ cellData, payload }: DefaultServerCellComponentProps) => {
// thanks to Jarrod for the following simplification, payload
// is already available in the props so no need to load config
// and get payload again
const media = await payload.findByID({
collection: 'media',
id: cellData,
})
console.log(media)
return (
<div
style={{
position: 'relative',
width: '80px',
height: '80px',
}}
>
<Image
src={media.url!}
alt={media.alt}
fill
style={{
objectFit: 'contain',
}}
/>
</div>
)
}
export default MyComponent
The CustomImageCell
component is a server component that asynchronously fetches the Media object using the payload object that is passed in as a component property. Note the use of Next.js's Image
component for optimized image delivery.
The component receives the id of the media as a prop and utilizes it to retrieve all the image data.
Next JS Config Modification (next.config.js)
Lastly, because Next JS can generate an error about the specified URL, it would be useful to add to your remote Patterns.
In your next.config.js
file, you'll need to configure the remotePatterns
array to allow the Image
component to load images from your Payload CMS instance.
// next.config.js
const nextConfig = {
// ... other config
images: {
remotePatterns: [
{
protocol: 'http',
hostname: 'localhost',
port: '3000',
pathname: '/media/**',
},
],
},
};
module.exports = nextConfig;
If the localhost is throwing too many errors, can also modify the .env variable.
This configuration allows images to be loaded from localhost
port 3000, where Payload CMS is running in development. Adapt the protocol, hostname, and pathname to match your specific setup. You may also be able to utilize the variable, depending on your specific version and setup.
Summary:
- Fetching Media Data: The custom component fetches the media data by
ID
. - Rendering the Image: It then utilizes Next.js to render the image.
Links
Top comments (3)
How long have you been working with PayloadCMS?
Since v2 when they went open source. We have deployed it to three clients, we are not a website, design agency though we use it as an application platform for mobile apps and websites. Leverage the integrated platform to help us build more efficiently.
Nice! I have been using it as a CMS to power our new platform we are building, that is similar to Shopify, while our custom CMS is under development. I had it all set up really nice before the update. I was able to migrate every easily except I've been having trouble with the custom logo stuff on the login page. The types are a little different there now. I think I almost got it figured out. Our custom CMS uses lexical just like payloadCMS.