@juspay/deformed
v0.2.4
Published
Deformed — a schema-driven form engine with React bindings
Downloads
994
Keywords
Readme
Features
- Schema-first — Define entire forms as JSON: fields, validation, conditional logic, API calls, and output transformations
- Headless core —
@juspay/deformed-coreis UI-agnostic; plug in any rendering layer - React integration —
@juspay/deformed-reactprovides hooks, context providers, and a full renderer built on React Hook Form + shadcn/ui - 30+ field types — text, number, select, combobox, date, datepicker, switch, checkbox, radiogroup, slider, toggle, togglegroup, codemirror, sqlquery, button, tabs, accordion, wizard, array, stack, object, permutation, tagsinput, formattedlistinput, plus a family of read-only
preview*displays (text, tag, taglist, table, card, infobox, json) - Validation engine — Zod-powered with 10+ rules, dynamic required via logic rules, and automatic skip-when-hidden
- Logic engine — 12 comparison operators + AND/OR/NOT combinators for conditional visibility, disabling, and dependencies
- Action system — 10 action types with conditional execution, debounce, and recursive chaining
- Template engine —
{{field}}interpolation with dot-path access and pipe fallbacks in URLs, request bodies, and display text - Output transformation — Field mapping, request body templating, and custom transforms for API submission
- Dynamic options — Static, API-fetched, and cascading dropdown options with
dataKeyextraction anddataTransformer - Custom component registry — Register your own components and use them in schemas
Packages
| Package | Description |
| ------------------------------------------ | ------------------------------------------------------------------------------------- |
| @juspay/deformed-core | Schema types, validation engine (Zod), logic evaluator, template engine, output utils |
| @juspay/deformed-react | React hooks, context providers, field components (shadcn/ui), form renderer |
| docs | Documentation site (Docusaurus) |
Installation
The umbrella package bundles core + react in a single install:
npm install @juspay/deformed
# or: yarn add @juspay/deformed
# or: pnpm add @juspay/deformedYou can also install directly from the GitHub repo (useful for testing
unreleased commits — the prepare script builds on install):
npm install github:juspay/deformed
# or pin a branch / tag / commit:
npm install github:juspay/deformed#main
npm install github:juspay/deformed#v0.1.14If you prefer the individual packages, those are still published too:
npm install @juspay/deformed-core @juspay/deformed-reactPeer dependencies: react >= 19, react-dom >= 19 · Node >= 22
Agent Skill
For AI coding assistants (Claude Code, Cursor, etc.), this repo also ships a bundled agent skill that teaches the assistant how to author and edit Deformed schemas. It follows the agentskills.io format and installs via the vercel-labs/skills CLI:
# Direct path to the skill in this repo
npx skills add https://github.com/juspay/deformed/tree/main/skills/deformedOnce installed, the assistant gains:
- The mental model and authoring rules from
SKILL.md. - A full type reference in
references/REFERENCE.mdand a 13-recipe cookbook inreferences/EXAMPLES.md, both loaded on demand via the skill's progressive-disclosure model. - A Node CLI at
scripts/validate.mjsthat validates aFormSchemaJSON against the published Zod definition. The script importsvalidateSchemafrom@juspay/deformed-core, so it reports identically to the MCP server'sdeformed_validate_schematool. Host environments can bind it to a slash command (e.g./validate path/to/schema.json).
For environments that prefer MCP, the same validation surface is also available via the @juspay/deformed-mcp-server package (deformed_validate_schema, deformed_generate_schema, deformed_edit_schema, deformed_get_field_reference).
Quick Start
1. Define a schema
import { FormSchema } from '@juspay/deformed-core';
const schema: FormSchema = {
title: 'Contact Form',
fields: [
{
type: 'text',
name: 'name',
label: 'Full Name',
validation: { required: 'Name is required' },
},
{
type: 'text',
name: 'email',
label: 'Email',
inputType: 'email',
validation: { required: true, email: 'Enter a valid email' },
},
{
type: 'select',
name: 'subject',
label: 'Subject',
options: [
{ label: 'General', value: 'general' },
{ label: 'Support', value: 'support' },
{ label: 'Sales', value: 'sales' },
],
},
{
type: 'text',
name: 'message',
label: 'Message',
multiline: true,
rows: 4,
validation: { required: true, minLength: { value: 10, message: 'At least 10 characters' } },
},
],
};2. Render with React
import { DeformedProvider, DeformedRenderer, useDeformed } from '@juspay/deformed-react';
function ContactForm() {
const methods = useDeformed(schema);
return (
<DeformedProvider>
<DeformedRenderer schema={schema} methods={methods} />
</DeformedProvider>
);
}DeformedProvider wires the registry, options, loading, and error contexts.
DeformedRenderer takes the parsed schema and the React Hook Form methods
returned by useDeformed.
3. Or use the hook for full control
import { useDeformed } from '@juspay/deformed-react';
function CustomForm() {
const methods = useDeformed(schema);
return (
<form onSubmit={methods.handleSubmit((data) => console.log(data))}>
{/* Build your own UI using methods.control, methods.register, etc. */}
</form>
);
}Schema Overview
A form schema is a JSON object that describes the entire form:
{
title?: string;
description?: string;
fields: FormNode[]; // Fields and containers
actions?: FormButtonSchema[]; // Form-level buttons
api?: {
submitUrl: string;
method: 'POST' | 'PUT' | 'PATCH';
headers?: Record<string, string>;
bodyTemplate?: Record<string, any>; // Supports {{field}} interpolation
outputMapping?: OutputFieldMapping[]; // Source → target field mapping
};
}Field Types
Input Fields
| Type | Key Props |
| -------------------- | -------------------------------------------------------------- |
| text | inputType (text/email/password/url/tel), multiline, rows |
| number | min, max, step |
| select | options (static or API), multi, searchable |
| combobox | options, multi, searchable |
| date | minDate, maxDate, format |
| datepicker | minDate, maxDate, format |
| switch | labelPosition |
| checkbox | labelPosition |
| radiogroup | options, orientation |
| slider | min, max, step |
| toggle | variant, size |
| togglegroup | options, selectionMode, orientation, variant, size |
| codemirror | language, theme |
| sqlquery | SQL editor with parsed-array output |
| button | variant, size |
| tabs | tabs[] (label, fields, hidden), defaultActiveIndex |
| accordion | items[] (title, fields, hidden), multiple, collapsible |
| wizard | steps[] (title, description, fields), orientation |
| array | fields[], itemLabel |
| stack | repeated row template with shared add/remove |
| object | nested-object grouping with shared-value sync |
| permutation | cartesian-product field combinator |
| tagsinput | free-form tag input |
| formattedlistinput | textarea split into a structured list |
Read-only / Preview Fields
previewtext, previewtag, previewtaglist, previewtable, previewcard,
previewinfobox, previewjson — bind to a sourceField and render its value
in different display modes.
Layout
| Type | Description |
| ----------- | ---------------------------------------------------------- |
| container | Groups fields with optional className and hidden logic |
Fields and containers can be nested to create any layout structure.
Validation
Validation rules are declared per-field in the schema and compiled to Zod schemas at runtime.
{
type: 'text',
name: 'username',
validation: {
required: 'Username is required',
minLength: { value: 3, message: 'At least 3 characters' },
maxLength: 50,
pattern: { value: '^[a-zA-Z0-9_]+$', message: 'Alphanumeric and underscores only' },
},
}Available Rules
| Rule | Type | Description |
| ----------- | ------------------------------ | --------------------------------------- |
| required | boolean \| string | Required field, optional custom message |
| min | number \| {value, message} | Minimum numeric value |
| max | number \| {value, message} | Maximum numeric value |
| minLength | number \| {value, message} | Minimum string length |
| maxLength | number \| {value, message} | Maximum string length |
| pattern | string \| {value, message} | Regex validation |
| email | boolean \| string | Email format |
| url | boolean \| string | URL format |
| uuid | boolean \| string | UUID format |
| custom | (value) => boolean \| string | Custom validator function |
Hidden fields are automatically skipped during validation.
Logic Engine
Control field visibility and state with declarative logic rules:
{
type: 'text',
name: 'companyName',
label: 'Company Name',
// Only visible when userType equals "business"
hidden: {
not: { var: 'userType', op: 'equals', value: 'business' }
},
}Operators
equals · notEquals · greaterThan · greaterThanOrEqual · lessThan · lessThanOrEqual · contains · notContains · in · notIn · isEmpty · isNotEmpty
Combinators
{
and: [rule1, rule2];
}
{
or: [rule1, rule2];
}
{
not: rule;
}Logic rules can also be expressed as string DSL: "field == value", "field != value", etc.
Action System
Define actions that execute on field events:
{
type: 'select',
name: 'country',
options: [...],
events: {
onChange: [
{
type: 'api_call',
config: {
url: '/api/states?country={{country}}',
method: 'GET',
onSuccess: [
{ type: 'set_options', target: 'state', dataKey: 'data' },
{ type: 'clear_value', target: ['state', 'city'] },
],
},
},
],
},
}Action Types
| Type | Description |
| ------------- | ------------------------------------------------------------------------------- |
| set_value | Set a field's value |
| clear_value | Clear one or more fields |
| set_options | Update a select field's options |
| transform | Evaluate a template expression against form state and write it to a field |
| api_call | Make an API request with onSuccess/onError chaining |
| fetch_data | Re-fire a host's deferred fetch by name (paired with manual select/container) |
| custom | Invoke a custom-registered action by name |
| submit | Submit the form |
| reset | Reset the form |
| navigate | Navigate to a URL |
All actions support condition (conditional execution) and debounce.
API Integration
Dynamic Options from API
{
type: 'select',
name: 'category',
options: {
api: '/api/categories',
method: 'GET',
dataKey: 'data.categories', // Dot-path to array in response
labelKey: 'name',
valueKey: 'id',
dataTransformer: (item) => ({ label: item.title, value: item.code }),
},
}Form Submission
{
fields: [...],
api: {
submitUrl: '/api/forms/submit',
method: 'POST',
headers: { 'X-Custom': 'value' },
bodyTemplate: {
user: { name: '{{name}}', email: '{{email}}' },
metadata: { submitted_at: '{{_now}}' },
},
outputMapping: [
{ source: 'fullName', target: 'user.name' },
{ source: 'age', transform: (v) => parseInt(v) },
],
},
}Architecture
deformed/
├── packages/
│ ├── core/ # @juspay/deformed-core
│ │ └── src/
│ │ ├── schema/ # Type definitions (field, form, action, logic, container)
│ │ ├── validation/ # Zod-based validation engine
│ │ ├── logic/ # Logic rule evaluator + string DSL parser
│ │ └── utils/ # Template interpolation, output transforms
│ └── react/ # @juspay/deformed-react
│ └── src/
│ ├── hooks/ # useDeformed, useLogic, useTemplate, useActionExecutor
│ ├── context/ # DeformedContext, OptionsContext, LoadingContext
│ ├── components/
│ │ ├── ui/ # shadcn/ui base components
│ │ ├── fields/ # Field type components
│ │ └── renderer/ # FormNodeRenderer, DeformedRenderer
│ └── utils/ # Submit handler
└── apps/
└── docs/ # Docusaurus documentation siteCore Design Principles
- Schema as truth — The JSON schema is the single source of truth for the form
- Separation of concerns — Core engine knows nothing about React or DOM
- Composable — Use the full renderer, individual hooks, or mix and match
- Extensible — Custom components via registry, custom actions, custom validators
Development
This is a Turborepo monorepo.
Prerequisites
- Node.js >= 18
- npm >= 10 (or bun)
Setup
git clone <repo-url>
cd deformed
npm installCommands
| Command | Description |
| ---------------------- | ------------------------------------ |
| npm run dev | Start all packages in dev/watch mode |
| npm run build | Build all packages |
| npm run lint | Lint all packages |
| npm run lint:fix | Auto-fix lint issues |
| npm run format | Format all files with Prettier |
| npm run format:check | Check formatting |
| npm run clean | Clean all build outputs |
Package-specific
# Build just core
cd packages/core && npm run build
# Build just react
cd packages/react && npm run build
# Run docs locally
cd apps/docs && npm startTech Stack
- TypeScript — Full type safety across core and react packages
- React Hook Form — Form state management
- Zod — Runtime validation schema generation
- shadcn/ui + Radix — Accessible UI components
- dnd-kit — Drag-to-reorder rows in
ArrayField(opt-in viasortable: true); primitives re-exported for custom DnD UIs - Turborepo — Monorepo build orchestration
- Vite — React package bundling
- Docusaurus — Documentation site
License
Private — Internal use only.
