Skip to Content

How Blocks Work

Blocks are Puck visual components that merchants can add and customize in the editor. Each block defines:

  1. Editor configuration - UI controls for customization
  2. Render function - How to display the block on the storefront
  3. Category - Where it appears in the editor sidebar

Simple Block Example

// blocks/hero.puck.tsx import { type ComponentConfig } from '@puckeditor/core'; interface HeroProps { title: string; subtitle: string; backgroundImage?: string; } export const category = 'Marketing'; export const config: ComponentConfig<HeroProps> = { label: 'Hero Banner', fields: { title: { type: 'text', label: 'Title', }, subtitle: { type: 'textarea', label: 'Subtitle', }, backgroundImage: { type: 'text', label: 'Background Image URL (optional)', }, }, defaultProps: { title: 'Welcome to our store', subtitle: 'Discover amazing products', }, render: ({ title, subtitle, backgroundImage }) => ( <div className="relative py-24 text-center" style={{ backgroundImage: backgroundImage ? `url(${backgroundImage})` : undefined, backgroundSize: 'cover', }} > <h1 className="text-4xl font-bold">{title}</h1> <p className="mt-2 text-xl text-gray-600">{subtitle}</p> </div> ), };

Complex Block with Editor & Render Separation

For blocks that need interactive features in the editor (like product selection), split them into two files:

blocks/product-grid/ ├── product-grid.edit.puck.tsx # Editor mode with interactivity ├── product-grid.render.puck.tsx # Render mode with data fetching ├── shared.ts # Shared utilities └── index.ts # Exports

Edit Mode (*.edit.puck.tsx)

Edit mode runs in the editor UI ('use client' component):

  • Runs in the editor UI
  • Can have interactive features (pickers, dialogs)
  • Stores lightweight data (IDs, not full objects)
  • Has access to editor context
'use client'; import { type ComponentConfig } from '@puckeditor/core'; interface ProductGridProps { selectedProductIds?: number[]; // Lightweight data selectedProducts?: Product[]; // For preview title: string; } export const config: ComponentConfig<ProductGridProps> = { label: 'Product Grid', fields: { selectedProductIds: { type: 'custom', render: ({ value, onChange }) => <ProductPickerField value={value} onChange={onChange} />, }, title: { type: 'text', label: 'Section Title', }, }, defaultProps: { selectedProductIds: [], title: 'Featured Products', }, render: ({ selectedProductIds, title }) => { return <ProductGridPreview ids={selectedProductIds} title={title} />; }, };

Render Mode (*.render.puck.tsx)

Render mode runs on the published storefront (Server component):

  • Runs on the published storefront
  • Fetches fresh data from the API
  • Uses Suspense for streaming
  • No interactive features
// Server component by default import { Suspense } from 'react'; import { type ComponentConfig } from '@puckeditor/core'; interface ProductGridProps { selectedProductIds?: number[]; title: string; } export const config: ComponentConfig<ProductGridProps> = { label: 'Product Grid', defaultProps: { selectedProductIds: [], title: 'Featured Products', }, render: ({ selectedProductIds, title }) => ( <Suspense fallback={<ProductGridSkeleton />}> <ProductGridAsync ids={selectedProductIds} title={title} /> </Suspense> ), };

Block Field Types

The fields object in block config defines customizable properties:

fields: { // Text input title: { type: 'text', label: 'Title', }, // Multi-line text description: { type: 'textarea', label: 'Description', }, // Select dropdown alignment: { type: 'radio', label: 'Text Alignment', options: [ { label: 'Left', value: 'left' }, { label: 'Center', value: 'center' }, { label: 'Right', value: 'right' }, ], }, // Number input columns: { type: 'number', label: 'Grid Columns', }, // Custom component field products: { type: 'custom', render: ({ value, onChange }) => ( <CustomProductPicker value={value} onChange={onChange} /> ), }, }

Auto-Generated Puck Config

The .storefront/ directory contains auto-generated Puck editor configurations:

  • .storefront/puck.edit.config.tsx - Editor configuration (all blocks in edit mode)
  • .storefront/puck.render.config.tsx - Render configuration (all blocks in render mode)

⚠️ Never edit these files manually. They’re regenerated by the build process.

To regenerate them:

pnpm dev # Runs finqu storefront dev --components=./blocks

The CLI scans the blocks/ directory for files matching *.puck.tsx and generates the configs automatically.

Building Custom Blocks

Block Checklist

  • Create file in blocks/ matching pattern *.puck.tsx
  • Export category string
  • Export config: ComponentConfig<Props>
  • Define fields for editor controls
  • Provide defaultProps
  • Implement render function
  • Rebuild to generate .storefront/ configs

Best Practices

  1. Keep blocks focused - One responsibility per block
  2. Store lightweight data - Use IDs instead of full objects
  3. Use Suspense in render mode - For streaming and loading states
  4. Provide good defaults - Make blocks usable immediately
  5. Add helpful labels - Make editor UI self-documenting
  6. Test in the editor - Verify behavior at different viewports

Next Steps