Registry and types
The JS scaffold
Section titled “The JS scaffold”Before registering custom components or columns, publish the scaffold file:
php artisan vendor:publish --tag=lattice-jsThis writes a single resources/js/registry.ts. It defines an app plugin with empty component and
table-column registries and merges it onto the built-in registry with extendRegistry, exporting the
result as registry:
import { extendRegistry, registry as packageRegistry } from "@lattice-php/lattice";import type { Plugin } from "@lattice-php/lattice";
export const registry = extendRegistry(packageRegistry, { name: "app", components: {}, // custom fields and UI components extensions: { "table.columns": {}, // custom column cells },} satisfies Plugin);The generators (lattice:field, lattice:component, lattice:column) append their entries to this
file automatically — fields and components under components, columns under
extensions["table.columns"]. You only need to publish once, and you pass the exported registry to
Provider.
Components and extensions
Section titled “Components and extensions”components contains complete wire nodes. They have type, props, and optional children, and the
core renderer owns their lifecycle. Form fields belong here because they are full nodes rendered in
the form tree.
extensions contains named registries owned by a feature. A table column contributes a cell renderer
to table.columns; a rich-editor extension contributes Tiptap behavior to form.rich-editor. Core
only merges these registries—the table or editor decides how to use them.
Node registry API
Section titled “Node registry API”The node registry maps type strings to RendererComponent functions. Imports come from @lattice-php/lattice.
Plugin objects
Section titled “Plugin objects”A plugin is a plain object that bundles one or more component registrations. Use satisfies Plugin
to check its shape without changing the inferred component keys:
import { eagerComponent } from "@lattice-php/lattice";import type { Plugin } from "@lattice-php/lattice";import { ColorPickerComponent } from "./fields/color-picker";import { RatingComponent } from "./components/rating";
export const appPlugin = { name: "app", components: { "field.color-picker": eagerComponent(ColorPickerComponent), rating: eagerComponent(RatingComponent), },} satisfies Plugin;Loading precompiled plugins
Section titled “Loading precompiled plugins”loadPluginModules imports and validates precompiled plugin URLs. It is a core API, so both regular
apps and the standalone build can load plugins before creating the app:
import { createLatticeApp, loadPluginModules } from "@lattice-php/lattice";
const plugins = await loadPluginModules(["/vendor/acme/plugin.js"]);
createLatticeApp({ plugins });extendRegistry
Section titled “extendRegistry”Merges a plugin into an existing registry, returning a new registry without mutating the original. The
published resources/js/registry.ts already calls it for you — this is the pattern it uses:
import { extendRegistry, registry as packageRegistry } from "@lattice-php/lattice";import type { Plugin } from "@lattice-php/lattice";
export const registry = extendRegistry(packageRegistry, { name: "app", components: {}, extensions: { "table.columns": {}, },} satisfies Plugin);packageRegistry is Lattice’s built-in registry. Pass the extended registry to Provider. Call
extendRegistry again yourself only if you keep additional plugins in their own files.
The built-in registry is a single flat map of eager components; the few heavy ones (the rich editor,
chart, and date inputs) code-split their dependency from inside the component, so you never choose
between an eager and a lazy variant. See Bundle size for the details.
createRegistry
Section titled “createRegistry”Creates a registry from scratch (no built-ins). Only use this if you want to replace the entire built-in component set:
import { createRegistry } from "@lattice-php/lattice";
const minimalRegistry = createRegistry(appPlugin);eagerComponent / lazyComponent
Section titled “eagerComponent / lazyComponent”Components can be registered eagerly (imported at module load time) or lazily (code-split on first render):
import { eagerComponent, lazyComponent } from "@lattice-php/lattice";import type { Plugin } from "@lattice-php/lattice";import { RatingComponent } from "./components/rating";
export const appPlugin = { name: "app", components: { // Eager — bundled with the entry point. rating: eagerComponent(RatingComponent), // Lazy — splits into a separate chunk loaded on demand. "field.color-picker": lazyComponent(async () => ({ default: (await import("./fields/color-picker")).ColorPickerComponent, })), },} satisfies Plugin;Provider and the registry
Section titled “Provider and the registry”Provider supplies the registry to every Lattice component below it in the tree:
import { Provider } from "@lattice-php/lattice";
createRoot(el).render( <Provider registry={appRegistry}> <App {...props} /> </Provider>,);A custom renderer receives its already-rendered child nodes as children. When you need the active component registry directly, use useComponentRegistry:
import { useComponentRegistry } from "@lattice-php/lattice";
const components = useComponentRegistry();Column-cell registry API
Section titled “Column-cell registry API”The column-cell registry maps type strings to ColumnCellComponent functions.
Column plugins
Section titled “Column plugins”Column cell renderers use the same plugin object as components. They go in the named table.columns
extension registry in resources/js/registry.ts (registered bare — columnCell() is optional, see
below):
import { extendRegistry, registry as packageRegistry } from "@lattice-php/lattice";import type { Plugin } from "@lattice-php/lattice";import { StatusBadgeCell } from "./columns/status-badge";
export const registry = extendRegistry(packageRegistry, { name: "app", components: {}, extensions: { "table.columns": { "column.status-badge": StatusBadgeCell, }, },} satisfies Plugin);The same exported registry carries both your components and your column cells — there is no second
registry to merge.
useColumnRegistry
Section titled “useColumnRegistry”Returns the current column registry from inside any component rendered by Lattice:
import { useColumnRegistry } from "@lattice-php/lattice";
const columnRegistry = useColumnRegistry();TypeScript augmentation
Section titled “TypeScript augmentation”The augmentable interfaces
Section titled “The augmentable interfaces”@lattice-php/core exports the shared wire-type interfaces:
ComponentProps— maps a type string to its props shape for fields and UI components.ColumnProps— maps a type string to its props shape for column cells.FilterProps— maps a filter control to its props shape.EffectProps— maps an effect type to its props shape.
EditorExtensionProps remains in @lattice-php/lattice because it belongs to the rich editor.
All of them use TypeScript’s declaration merging. You can augment them manually or let lattice:typescript do it.
php artisan lattice:typescript
Section titled “php artisan lattice:typescript”Run this command whenever your PHP classes gain or lose public properties. It discovers
#[AsComponent] components, #[AsColumn] columns, #[AsFilter] filters, #[AsEffect] effects, and
#[AsEditorExtension] rich-editor extensions:
php artisan lattice:typescriptIt scans the paths listed under discover in config/lattice.php:
'discover' => [ base_path('app'),],And writes an augmentation file to the path configured under typescript.output (default: resources/js/lattice/generated.d.ts):
// This file is generated by `php artisan lattice:typescript`. Do not edit.declare module "@lattice-php/core" { interface ComponentProps { "field.color-picker": { swatches: string | null; }; } interface ColumnProps { "column.status-badge": { colorMap: Record<string, string> | null; }; }}
export {};Without this file, node.props and column.props fall back to Record<string, unknown>. The renderers still work — types are just not narrowed.
See Artisan commands for the full command reference.
Augmenting manually
Section titled “Augmenting manually”If you prefer not to run the generator, augment the interfaces directly in any .d.ts file included in your tsconfig.json:
import "@lattice-php/core";
declare module "@lattice-php/core" { interface ComponentProps { "field.color-picker": { swatches: string | null; }; }}