fayzfayz sdk
PTEN

Customization

The promise is "nothing locked in": a Fayz app goes from pure config to fully bespoke without ever forking the SDK. Customization is a seven-level ramp, each level strictly additive — someone sitting at level 6 keeps getting SDK upgrades for everything they didn't touch.

The ladder

LevelWhat changesWhereCode?
1 · Configlabels, currency, flags, plugin configapp.manifest.jsonno
2 · Themebrand + tokenstheme in the manifestno
3 · Recomposereorder/add/remove blocks, new pagessurfaces.*.pages[].blocksno
4 · Slotsinject widgets into named zoneswidget refs in the manifestsometimes
5 · Overridereplace an SDK component/block by idsrc/registry.tsxyes
6 · Custom pages/blocksbespoke React, routed by the manifestsrc/registry.tsx + componentsyes
7 · Your own pluginyour own entities/migrations/nav/blocksa plugin packageyes

Levels 1–4 are platform-editable — the Fayz UI / AI agent edits the manifest live, no code and no deploy. Levels 5–7 are code in your repository, but confined to src/registry.tsx + your own components — never copies of SDK pages.

Levels 1–4: data only

You've already seen config and theming in Configuration and Theming. Recomposing (level 3) means assembling a page as a block tree — pure data:

{
  "pages": [
    { "path": "/", "blocks": [
      { "type": "hero", "props": { "variant": "banner" } },
      { "type": "products", "props": { "title": "Highlights", "limit": 4 } }
    ]}
  ]
}

Slots (level 4) inject widgets into named zones of the shell (shell.topbar.end, shell.sidebar.footer, page.after, shell.floating, …). You reference a registered widget; for a widget of your own, register it (level 5/6) and cite it by id.

Level 5: override by id

Every SDK component or block has a registry id. Re-register that id in src/registry.tsx and your version wins (last-registration-wins). It receives the same typed props as the original — so the SDK can evolve the internals behind the props contract without breaking you:

// src/registry.tsx
import { registerComponent, registerBlock } from '@fayz-ai/core'
import { BrandedDetailHeader } from './components/BrandedDetailHeader'
import { FancyHero } from './components/FancyHero'

registerComponent('crud.detail-header', BrandedDetailHeader) // swaps out an SDK component
registerBlock('hero', FancyHero)                             // swaps out the built-in hero block

Level 6: custom: pages and blocks

React that's entirely yours, in the custom: namespace (the platform never generates custom: ids — they only come from your repository). Register it and reference it by id in the manifest. Your component runs with the full SDK context (data provider, tenant, permissions, i18n):

// src/registry.tsx
import { registerBlock, registerPage } from '@fayz-ai/core'
import { WineStory } from './blocks/WineStory'
import { HarvestBoard } from './pages/HarvestBoard'

registerBlock('custom:wine-story', WineStory)
registerPage('custom:harvest-board', HarvestBoard)

The register* functions (registerComponent, registerBlock, registerPage) come from @fayz-ai/core — the import above compiles today. The src/lib/fayz-runtime.ts the scaffold generates is, for now, an anchor screen (just a renderApp placeholder) and does not re-export these functions yet; the scaffold will re-export them soon. Until then, import directly from @fayz-ai/core.

{
  "pages": [
    { "path": "/harvest", "component": "custom:harvest-board" },
    { "path": "/", "blocks": [{ "type": "custom:wine-story", "props": { "year": 2019 } }] }
  ]
}

Level 7: your own plugin

When the customization is a reusable capability (entities, migrations, navigation, blocks), the ceiling is a plugin — the same PluginManifest contract the official plugins use. There's nothing an SDK plugin can do that yours can't. That's the subject of Building plugins.

Why upgrades stay safe

  • Overrides are keyed by id and receive the original's typed props → SDK internals change without breaking you.
  • Your own code stays confined to src/registry.tsx + your components + your plugins; you never copy an SDK page, so an upgrade touches everything you did not customize and leaves your overrides intact.

There's no "eject" by design. If you feel like you need to fork an SDK page, that's an SDK gap worth reporting — not a supported flow.


Next: the second consumption path in Headless.