Pricing Enterprise
A single wide enterprise panel — price and CTA beside an included-features list and an editable wall of trust logos.
import type { Block } from 'payload'import { planFields, pricingFields } from '@/blocks/shared/pricingFields'export const PricingEnterprise: Block = { slug: 'pricingEnterprise', interfaceName: 'PricingEnterpriseBlock', fields: [ // Shared pricing heading (eyebrow, title, description). Variant-specific // fields follow; edit the shared shape in @/blocks/shared/pricingFields. ...pricingFields, { name: 'plans', type: 'array', required: true, // A single enterprise plan rendered as one wide panel. minRows: 1, maxRows: 1, admin: { initCollapsed: true, }, fields: planFields, }, { // Editable trust logos — uploaded images with an accessible name and an // optional link, mirroring the shared logo-cloud shape so editors insert // their own brand marks instead of shipping hardcoded SVGs. name: 'logos', type: 'array', minRows: 0, maxRows: 8, admin: { initCollapsed: true, }, fields: [ { name: 'logo', type: 'upload', relationTo: 'media', required: true, }, { name: 'name', type: 'text', required: true, }, { name: 'href', type: 'text', // Logos link out to arbitrary customer sites, so this can't reuse the // embed/form allowlists in shared/safeUrls — but still reject anything // that isn't an absolute http(s) URL (e.g. a `javascript:` payload). validate: (value: unknown) => { if (value === null || value === undefined || value === '') return true try { const { protocol } = new URL(String(value)) return protocol === 'https:' || protocol === 'http:' ? true : 'Use an absolute http(s) URL.' } catch { return 'Use an absolute http(s) URL.' } }, }, ], }, ], labels: { plural: 'Pricing Enterprise Blocks', singular: 'Pricing Enterprise', },}import { Check } from 'lucide-react'import React from 'react'import type { PricingEnterpriseBlock as PricingEnterpriseBlockData } from '@/payload-types'import { CMSLink } from '@/components/Link'import { Media } from '@/components/Media'import { Badge } from '@/components/ui/badge'import { cn } from '@/utilities/ui'type Props = PricingEnterpriseBlockData & { id?: string className?: string disableInnerContainer?: boolean}export const PricingEnterpriseBlock: React.FC<Props> = ({ className, description, disableInnerContainer, eyebrow, id, logos, plans, title,}) => { const plan = plans?.[0] 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-10', { 'mx-auto max-w-5xl': !disableInnerContainer, })} > <div className="mx-auto flex max-w-2xl flex-col gap-4 text-center"> {eyebrow ? ( <Badge variant="outline" className="mx-auto 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> {plan ? ( <div className="rounded-frame border border-border/70 bg-background/60 shadow-none"> <div className="grid items-center gap-12 divide-y divide-border/70 p-8 sm:p-12 md:grid-cols-2 md:divide-x md:divide-y-0"> <div className="flex flex-col items-center gap-4 text-center md:pr-12"> <h3 className="text-2xl font-semibold tracking-title">{plan.name}</h3> {plan.description ? ( <p className="text-base text-muted-foreground">{plan.description}</p> ) : null} <div className="my-4 flex items-baseline justify-center gap-1"> <span className="text-5xl font-bold">{plan.price}</span> {plan.period ? ( <span className="text-base text-muted-foreground">{plan.period}</span> ) : null} </div> {plan.links && plan.links.length > 0 ? ( <div className="flex flex-col gap-2"> {plan.links.map(({ link }, linkIndex) => ( <CMSLink key={linkIndex} {...link} appearance="default" /> ))} </div> ) : null} </div> <div className="flex flex-col gap-6 md:pl-12"> {plan.features && plan.features.length > 0 ? ( <ul className="flex flex-col gap-4 text-sm"> {plan.features.map((item, featureIndex) => ( <li key={item.id ?? `${item.feature}-${featureIndex}`} className="flex items-center gap-2" > <Check aria-hidden="true" className="size-3" /> {item.feature} </li> ))} </ul> ) : null} {logos && logos.length > 0 ? ( <div className="flex flex-wrap items-center gap-x-10 gap-y-6 pt-2"> {logos.map((item, index) => { const logo = ( <Media resource={item.logo} imgClassName="h-6 w-auto object-contain" /> ) return ( <div className="flex items-center" key={item.id ?? `${item.name}-${index}`}> {item.href ? ( <a aria-label={item.name} href={item.href}> {logo} </a> ) : ( logo )} </div> ) })} </div> ) : null} </div> </div> </div> ) : null} </div> </div> </section> )}import type { Field } from 'payload'import { linkGroup } from '@/fields/linkGroup'/** * Shared field core for the Pricing component family. * * Every pricing variant (pricing-cards, pricing-cards-muted, pricing-cards-cta, * pricing-split, pricing-enterprise) spreads `pricingFields` for the section * heading and reuses `planFields` for the editable plan array. Editing the * shared shape here updates every installed pricing block at once, so the * family never drifts field-by-field across a repo. * * Each plan carries its own price, period, blurb, a `featured` flag (which * drives the emerald "Popular" highlight), a list of included features, and a * single CTA link group — so editors manage the whole pricing table from the * admin instead of shipping hardcoded copy. * * The layout structure is adapted from tailark/blocks (MIT) pricing blocks, * retokenized onto this repo's monochrome + emerald design system. * * Installed once per repo at `src/blocks/shared/pricingFields.ts`; re-running * `payload-components add pricing-*` never overwrites a copy you have already edited. */export const pricingFields: Field[] = [ { name: 'eyebrow', type: 'text', }, { name: 'title', type: 'text', required: true, }, { name: 'description', type: 'textarea', },]/** * Subfields for one plan, reused as the `fields` of every variant's `plans` * array. `period` is optional (a one-off enterprise price may omit "/ mo"), * `featured` marks the highlighted column, and `link` is a single CTA group. */export const planFields: Field[] = [ { name: 'name', type: 'text', required: true, }, { name: 'price', type: 'text', required: true, }, { name: 'period', type: 'text', }, { name: 'description', type: 'text', }, { name: 'featured', type: 'checkbox', defaultValue: false, }, { name: 'features', type: 'array', required: true, minRows: 1, maxRows: 12, admin: { initCollapsed: true, }, fields: [ { name: 'feature', type: 'text', required: true, }, ], }, linkGroup({ overrides: { admin: { initCollapsed: true, }, maxRows: 1, }, }),]Installation
npx payload-components add pricing-enterpriseCopy the files straight from the registry, then wire the Payload fragments by hand:
pnpm dlx shadcn@latest add https://www.payload-components.xyz/r/pricing-enterprise.jsonWhat it installs
Copies 3 source files into your project:
src/blocks/shared/pricingFields.tssharedsrc/blocks/PricingEnterprise/config.tssrc/blocks/PricingEnterprise/Component.tsx
…and makes 4 edits to wire the block into your project:
| Registers the block | src/collections/Pages/index.ts |
| Maps the renderer | src/blocks/RenderBlocks.tsx |
| Regenerates types | src/payload-types.ts |
| Regenerates the admin import map | src/app/(payload)/admin/importMap.js |
pricingFields.ts is the shared field core for this family — every variant composes it. Editing it updates each installed block at once, and re-running an install never overwrites a copy you have changed.Re-running the install converges: it detects existing wiring, skips it, and records install state in .payload-components/state.json.
Content model
The first three fields come from the shared pricingFields base; plans is built from the
shared planFields and renders a single enterprise plan. logos is specific to this variant —
an editable wall of uploaded trust logos, like the Logo Cloud family, so editors insert their
own brand marks.
Prop
Type
Each item in plans carries:
Prop
Type
Each item in logos carries:
Prop
Type
Usage
PricingEnterprise block to its layout.RenderBlocks on the frontend, fully typed — no extra wiring.Requirements
- Target
- payload-website-starter
- Payload
- v3
- Next.js
- 15 / 16
- shadcn UI
- badge
Your project must already expose components.json, src/payload.config.ts, src/blocks/RenderBlocks.tsx, src/collections/Pages/index.ts — the surfaces payload-components add patches. The CLI verifies this against the support matrix before touching anything.
In this family
pricing-cardspricing-cards-mutedpricing-cards-ctapricing-splitpricing-enterprisecurrentLayout adapted from tailark/blocks (MIT), retokenized onto the Payload Components design system.