defineFactory()
defineFactory() registers a builder that turns a typed configuration
object into an array of Blocks. Factories are how you ship parameterised
surface fragments — a hero, a feature grid, an entire pricing section —
that PhiCo and the Studio can drop in with one prompt.
import { defineFactory } from '@pwfabric/sdk'
import { z } from 'zod'
export const LandingPageFactory = defineFactory({
id: 'landing-page',
name: 'Landing Page',
configSchema: z.object({
title: z.string(),
subtitle: z.string().optional(),
ctaLabel: z.string().default('Get started'),
ctaHref: z.string().url(),
}),
build: (config) => [
{ type: 'heading', props: { content: config.title, level: 1, align: 'center' } },
{ type: 'text', props: { content: config.subtitle ?? '', align: 'center' } },
{ type: 'button', props: { label: config.ctaLabel, href: config.ctaHref, variant: 'primary' } },
],
})Signature
function defineFactory<TConfig extends Record<string, unknown>>(
options: FactoryOptions<TConfig>
): FactoryDefinition<TConfig>Options
| Field | Type | Description |
|---|---|---|
id | string (kebab-case) | Stable identifier — landing-page, pricing-section. 2–50 characters, kebab-case. |
name | string | Human-readable label shown in PhiCo and the Studio. |
configSchema | ZodSchema<TConfig> | Zod schema validating the configuration object passed to build. |
build | (config: TConfig) => BlockSpec[] | Pure function that returns a flat or nested array of block specs. Block IDs are auto-generated when the factory is invoked. |
BlockSpec is { type: string; props?: Record<string, unknown>; children?: BlockSpec[] } — the same shape every defineBlock instance produces.
Validation
Config is validated against configSchema every time the factory runs.
The returned FactoryDefinition exposes validate(config) so you can
check inputs ahead of generation:
const result = LandingPageFactory.validate({
title: 'Welcome',
ctaHref: 'not-a-url',
})
if (!result.ok) {
for (const issue of result.errors) {
console.error(issue.path, issue.message)
}
}Composing existing blocks
A factory’s build function returns block specs by type — the same block
types the runtime resolves through the live catalog. You can reference
any first-party block, any installed third-party block, or any
defineBlock-authored block in the same pwpack.
defineFactory({
id: 'pricing-section',
name: 'Pricing Section',
configSchema: z.object({
heading: z.string(),
tiers: z.array(
z.object({
name: z.string(),
price: z.number(),
features: z.array(z.string()),
}),
),
}),
build: (config) => [
{ type: 'heading', props: { content: config.heading, level: 2, align: 'center' } },
{
type: 'grid',
props: { columns: config.tiers.length, gap: 'lg' },
children: config.tiers.map((tier) => ({
type: 'pricing-card',
props: tier,
})),
},
],
})Publishing the factory
Like blocks, factories ship as part of a pwpack. Drop your factory
modules into your pack’s factories/ folder, then pwfabric pwpack publish delivers the bundle. After install, PhiCo and the Studio
surface picker expose every factory automatically.
See also
defineBlock— author a single block type- Starters — first-party factories you can fork