DevelopersSDKdefineFactory()

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

FieldTypeDescription
idstring (kebab-case)Stable identifier — landing-page, pricing-section. 2–50 characters, kebab-case.
namestringHuman-readable label shown in PhiCo and the Studio.
configSchemaZodSchema<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