---
title: "Custom blocks"
description: "Define a block, register it in a tier and render it."
requested_language: ar
language: en
translation_notice: "This page isn't translated yet"
url: https://docs.systhema.app/ar/payload/custom-blocks
version: unreleased (main)
docs_index: https://docs.systhema.app/ar/llms.txt
---
> This page isn't translated yet. Showing English.


A custom block has two parts: a Payload **block definition** (fields) and a **converter** (JSX renderer for the frontend).

## Complete example

```tsx title="src/blocks/HeroWithVideo.tsx"
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 block

```ts title="src/payload.config.ts"
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](https://docs.systhema.app/ar/payload/editor/tiers.md) below. A block used inside a Card or Columns item must target `'fragment'`; see [Blocks Using Each Editor](https://docs.systhema.app/ar/payload/editor/tiers.md#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](https://docs.systhema.app/ar/payload/editor/tiers.md) for the converter contract.

In client-mode live preview a custom block also needs its converter registered in the browser; see [Client mode caveats](https://docs.systhema.app/ar/payload/frontend/live-preview/client-mode.md#client-mode-caveats). For the converter signature and common patterns, see [Converters](https://docs.systhema.app/ar/payload/custom-blocks/converters.md).
