Custom blocks
Define a block, register it in a tier and render it.
On this page
A custom block has two parts: a Payload block definition (fields) and a converter (JSX renderer for the frontend).
Complete exampleLink to this section
import type { Block } from 'payload'
import type { CustomBlockConverter } from '@systhemaui/payload'
import type { Upload } from '@/payload-types'
import { Section } from '@systhemaui/react'
import { Image, Video } from '@systhemaui/next'
// 1. Block definition.
export const HeroWithVideoBlock: Block = {
slug: 'heroWithVideo',
interfaceName: 'HeroWithVideo',
labels: { singular: 'Hero with Video', plural: 'Heroes with Video' },
fields: [
{
name: 'video',
type: 'upload',
relationTo: 'uploads',
required: true,
filterOptions: { mimeType: { contains: 'video' } },
},
{
name: 'poster',
type: 'upload',
relationTo: 'uploads',
filterOptions: { mimeType: { contains: 'image' } },
},
{ name: 'title', type: 'text', required: true },
{ name: 'subtitle', type: 'textarea' },
],
}
// 2. Converter.
export const heroWithVideoConverter: CustomBlockConverter = ({ node }) => {
const fields = node.fields as {
video?: Upload | number
poster?: Upload | number
title?: string
subtitle?: string
}
const video = typeof fields.video === 'object' ? fields.video : null
if (!video?.url || !fields.title) return null
return (
<Section className="relative min-h-[60vh]">
<div className="absolute inset-0 -z-10 overflow-hidden">
<Video
src={video.url}
autoPlay
muted
loop
playsInline
objectFit="cover"
className="h-full w-full"
/>
</div>
<div className="relative z-10 text-center">
<h1 className="text-5xl font-bold">{fields.title}</h1>
{fields.subtitle && <p className="mt-4 text-xl">{fields.subtitle}</p>}
</div>
</Section>
)
}Registering the blockLink to this section
import { withSysthema } from '@systhemaui/payload'
import { HeroWithVideoBlock, heroWithVideoConverter } from './blocks/HeroWithVideo'
export default buildConfig(
withSysthema(
{
/* ... */
},
{
customBlocks: [
{
type: 'block', // 'block' or 'inline'
data: HeroWithVideoBlock,
editor: 'root', // 'root' | 'region' | 'fragment' | 'slot' | array | undefined (all)
converter: heroWithVideoConverter,
},
],
},
),
)The editor field controls which Lexical editor level(s) can use the block. See Editor hierarchy below. A block used inside a Card or Columns item must target 'fragment'; see Blocks Using Each Editor. If a block animates its own children, place it in a container with animation disabled or the container cancels those child reveals.
Lexical injects authored alignment as an inline style. Selecting and aligning a whole block node centres the whole block, including a Section. See Lexical editors for the converter contract.
In client-mode live preview a custom block also needs its converter registered in the browser; see Client mode caveats. For the converter signature and common patterns, see Converters.