DevelopersSDKdefineBlock()

defineBlock()

defineBlock() registers a new block type. Each block has a stable type identifier, a human-readable name, a Zod schema for its props, default values, and a React component that renders it. The runtime validates the schema at publish time and at every PhiCo composition.

import { defineBlock } from '@pwfabric/sdk'
import { z } from 'zod'
 
export const PricingCard = defineBlock({
  type: 'pricing-card',
  name: 'Pricing Card',
  category: 'content',
  icon: '💳',
  schema: z.object({
    title: z.string(),
    price: z.number(),
    currency: z.string().default('USD'),
    features: z.array(z.string()),
    highlighted: z.boolean().default(false),
  }),
  defaultProps: {
    title: 'Basic',
    price: 9.99,
    currency: 'USD',
    features: ['Feature 1', 'Feature 2'],
    highlighted: false,
  },
  component: ({ props }) => (
    <div className={props.highlighted ? 'border-2 border-primary' : 'border'}>
      <h3>{props.title}</h3>
      <p>{props.currency} {props.price}</p>
      <ul>{props.features.map((f, i) => <li key={i}>{f}</li>)}</ul>
    </div>
  ),
})

Signature

function defineBlock<TProps extends Record<string, unknown>>(
  options: CustomBlockOptions<TProps>
): CustomBlockDefinition<TProps>

Options

Required

FieldTypeDescription
typestring (kebab-case)Stable identifier — pricing-card, customer-quote, cta-banner. Must be 2–50 characters, kebab-case, must not collide with built-in block types.
namestringHuman-readable label shown in PhiCo and the Studio block picker.
schemaZodSchema<TProps>Zod schema validating every instance of the block’s props.
defaultPropsTPropsInitial values used when PhiCo inserts the block. Must satisfy schema.
component({ props, children }) => ReactNodeReact component that renders the block.

Optional

FieldTypeDefaultDescription
category'layout' | 'content' | 'media' | 'form' | 'navigation' | 'data' | 'control' | 'panel' | 'text''content'Sidebar grouping in the Studio picker.
iconstring'◆'Glyph or emoji shown in the picker.
canHaveChildrenbooleanfalseIf true, the block accepts a children array of nested blocks.
allowedChildrenstring[]unrestrictedWhen canHaveChildren is true, restrict which child block types are allowed.
allowedParentsstring[]unrestrictedRestrict which parent block types may contain this block.
maxInstancesnumberunlimitedPer-Surface cap on instances of this block.

Schema and defaults

The schema and the defaultProps are validated against each other at defineBlock() call time. A block whose defaults fail its own schema is rejected immediately — you’ll get a thrown Error listing every Zod issue.

defineBlock({
  type: 'testimonial',
  name: 'Customer testimonial',
  schema: z.object({
    quote: z.string().min(10).max(500),
    author: z.string(),
    role: z.string().optional(),
  }),
  defaultProps: {
    quote: 'A great product to work with.',
    author: 'Jane Doe',
    role: 'Product Manager',
  },
  component: ({ props }) => (
    <blockquote>
      <p>"{props.quote}"</p>
      <footer>{props.author}{props.role ? `, ${props.role}` : ''}</footer>
    </blockquote>
  ),
})

Nesting and composition

When canHaveChildren is true, your component receives the rendered children as the second prop:

defineBlock({
  type: 'feature-grid',
  name: 'Feature Grid',
  category: 'layout',
  canHaveChildren: true,
  allowedChildren: ['feature-card', 'text', 'heading'],
  schema: z.object({
    columns: z.number().int().min(1).max(6).default(3),
  }),
  defaultProps: { columns: 3 },
  component: ({ props, children }) => (
    <div className={`grid grid-cols-${props.columns} gap-6`}>
      {children}
    </div>
  ),
})

Publishing the block

Authored blocks ship as a pwpack. Run pwfabric pwpack init, drop your defineBlock(...) modules in the pack source, then pwfabric pwpack publish to push the bundle to the marketplace. Once published and installed into a World, PhiCo can summon your block by name just like a first-party block.

See also