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
| Field | Type | Description |
|---|---|---|
type | string (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. |
name | string | Human-readable label shown in PhiCo and the Studio block picker. |
schema | ZodSchema<TProps> | Zod schema validating every instance of the block’s props. |
defaultProps | TProps | Initial values used when PhiCo inserts the block. Must satisfy schema. |
component | ({ props, children }) => ReactNode | React component that renders the block. |
Optional
| Field | Type | Default | Description |
|---|---|---|---|
category | 'layout' | 'content' | 'media' | 'form' | 'navigation' | 'data' | 'control' | 'panel' | 'text' | 'content' | Sidebar grouping in the Studio picker. |
icon | string | '◆' | Glyph or emoji shown in the picker. |
canHaveChildren | boolean | false | If true, the block accepts a children array of nested blocks. |
allowedChildren | string[] | unrestricted | When canHaveChildren is true, restrict which child block types are allowed. |
allowedParents | string[] | unrestricted | Restrict which parent block types may contain this block. |
maxInstances | number | unlimited | Per-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
defineFactory— generate a composed block tree from configuration- Blocks reference — the 45 first-party blocks PhiCo knows by default