Use your first block
From the add command to a live page — the editor side of the flow, start to finish.
You've run payload-components add (see Installation for the CLI side). The wiring is already done — here's the editor journey, from the Payload admin to a live page, using Feature Grid Basic as the example. Every component follows the same path.
Why the block is already there
add didn't just copy files. It registered the block in your Pages layout
field and mapped it in RenderBlocks, then regenerated types and the admin
import map. So the block shows up in the admin block picker with no extra
wiring — that's the whole point of the wrapper CLI.
Open a page in the admin. Start your project (pnpm dev) and go to /admin.
Open an existing Page or create a new one — blocks live on the page's layout field.
Add the block — it's already in the picker. In the layout field, click Add Block.
Feature Grid Basic is listed alongside the starter blocks, because add registered it for you.

Fill the content. Select the block and complete its fields — eyebrow, title, description, and a few items. They come straight from the content model; required fields are marked.

Publish. Save and publish the page. There's nothing else to wire — the block is fully typed and registered.
See it live. The page renders the block through RenderBlocks on the frontend.
Here's the component you just configured:
import type { Block } from 'payload'import { featureFields } from '@/blocks/shared/featureFields'import { linkGroup } from '@/fields/linkGroup'export const FeatureGridBasic: Block = { slug: 'featureGridBasic', interfaceName: 'FeatureGridBasicBlock', fields: [ // Shared feature core (eyebrow, title, description). Variant-specific fields // follow; edit the shared shape in @/blocks/shared/featureFields. ...featureFields, { name: 'items', type: 'array', required: true, minRows: 3, maxRows: 6, admin: { initCollapsed: true, }, fields: [ { name: 'title', type: 'text', required: true, }, { name: 'description', type: 'textarea', required: true, }, ], }, linkGroup({ overrides: { admin: { initCollapsed: true, }, maxRows: 1, }, }), ], labels: { plural: 'Feature Grid Basic Blocks', singular: 'Feature Grid Basic', },}import React from 'react'import type { FeatureGridBasicBlock as FeatureGridBasicBlockData } from '@/payload-types'import { CMSLink } from '@/components/Link'import { Badge } from '@/components/ui/badge'import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'import { cn } from '@/utilities/ui'type Props = FeatureGridBasicBlockData & { id?: string className?: string disableInnerContainer?: boolean}export const FeatureGridBasicBlock: React.FC<Props> = ({ className, description, disableInnerContainer, eyebrow, id, items, links, title,}) => { return ( <section className={cn('container', className)} id={id ? `block-${id}` : undefined}> <div className="overflow-hidden rounded-frame border border-border/70 bg-card/35 px-6 py-10 sm:px-8 lg:px-12 lg:py-14"> <div className={cn('flex flex-col gap-8', { 'mx-auto max-w-5xl': !disableInnerContainer, })} > <div className="flex max-w-3xl flex-col gap-4"> {eyebrow ? ( <Badge variant="outline" className="w-fit rounded-full px-3 py-1 uppercase tracking-eyebrow"> {eyebrow} </Badge> ) : null} <h2 className="text-4xl font-medium tracking-display text-balance sm:text-5xl">{title}</h2> {description ? ( <p className="text-base leading-7 text-muted-foreground sm:text-lg">{description}</p> ) : null} </div> {items && items.length > 0 ? ( <div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3"> {items.map((item, index) => ( <Card key={item.id ?? `${item.title}-${index}`} className="border-border/70 bg-background/85 shadow-none"> <CardHeader className="gap-3 p-5"> <CardTitle className="text-xl tracking-title">{item.title}</CardTitle> </CardHeader> <CardContent className="p-5 pt-0"> <CardDescription className="text-sm leading-7 text-muted-foreground"> {item.description} </CardDescription> </CardContent> </Card> ))} </div> ) : null} {links && links.length > 0 ? ( <div className="flex flex-col gap-3 sm:flex-row"> {links.map(({ link }, index) => ( <CMSLink key={index} appearance={link.appearance === 'outline' ? 'outline' : 'default'} {...link} /> ))} </div> ) : null} </div> </div> </section> )}import type { Field } from 'payload'/** * Shared field core for the Feature component family. * * Every feature variant (feature-grid-basic, feature-split, feature-bento, * feature-steps, …) spreads these section-heading fields first and then * appends its own variant-specific shape — the item array and CTA links, * which differ per layout. Editing the shared eyebrow/title/description here * updates every installed feature block at once, so the family never drifts * field-by-field across a repo. * * Installed once per repo at `src/blocks/shared/featureFields.ts`; re-running * `payload-components add feature-*` never overwrites a copy you have already edited. */export const featureFields: Field[] = [ { name: 'eyebrow', type: 'text', }, { name: 'title', type: 'text', required: true, }, { name: 'description', type: 'textarea', },]