QUESTPIE
Build Your WorkspaceBlocks

Blocks

Content blocks for page builders — define server-side, render client-side.

Blocks are reusable content components for visual page building. Define them server-side with fields and admin metadata, then render them client-side with React components. Block content is stored as JSONB in the database.

Architecture

Server: block("hero")           Client: HeroRenderer
  .fields({ title, image })  →    Receives { values, data }
  .admin({ label, icon })        Returns JSX
  .prefetch({ with: {...} })

How Blocks Work

  1. Define blocks in blocks/ using the file convention — each block has fields, admin config, and optional data prefetching
  2. Use f.blocks() on a collection field to enable block content
  3. Register renderers on the client — React components that render each block type
  4. Admin shows a drag-and-drop block editor; frontend renders the block tree with prefetched data

Block Content Structure

Block content is stored as JSONB with this structure:

type BlockContent = {
  _tree: BlockNode[]                           // Hierarchical tree of blocks
  _values: Record<string, Record<string, any>> // Field values per block instance
  _data?: Record<string, Record<string, any>>  // Prefetched relation data
}

type BlockNode = {
  id: string          // Unique instance ID
  type: string        // Block type name (e.g., "hero", "columns")
  children: BlockNode[] // Nested blocks
}

Block Categories

Blocks can be organized into categories in the block editor:

CategoryPurposeExamples
layoutStructural blocksColumns, Grid, Container, Section
contentText contentHeading, Paragraph, List, Quote
mediaVisual contentImage, Video, Gallery
sectionsPre-built sectionsHero, Features, Testimonials, CTA
interactiveUser interactionForm, Accordion, Tabs

Custom categories are supported — use any string as the category name.

Quick Example

Server — define a hero block:

src/questpie/server/blocks/hero.ts
import { block } from "#questpie/factories"

export default block("hero")
  .fields(({ f }) => ({
    title: f.text(255).required(),
    subtitle: f.textarea(),
    backgroundImage: f.upload(),
    ctaLabel: f.text(100),
    ctaLink: f.url(),
  }))
  .admin({
    label: "Hero Section",
    icon: "ph:star",
    category: "sections",
  })

Collection — use blocks field:

export default collection("pages")
  .fields(({ f }) => ({
    title: f.text(255).required(),
    content: f.blocks(),
  }))

Client — render the block:

src/questpie/admin/blocks/hero.tsx
export default function HeroRenderer({ values }) {
  return (
    <section style={{ backgroundImage: `url(${values.backgroundImage?.url})` }}>
      <h1>{values.title}</h1>
      <p>{values.subtitle}</p>
      {values.ctaLabel && <a href={values.ctaLink}>{values.ctaLabel}</a>}
    </section>
  )
}

Sections

  • Defining Blocks — Server-side block definitions with fields and admin config
  • Renderers — Client-side React components for rendering blocks
  • Prefetch — Prefetching relation data for blocks

On this page