npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

extended-dynamic-forms

v0.6.2

Published

Extended React JSON Schema Form (RJSF) v6 with custom components, widgets, templates, layouts, and form events

Readme

Extended Dynamic Forms

Extended Dynamic Forms (EDF) extends React JSON Schema Form (RJSF) v6 with Ant Design widgets, JSON-configured conditional logic, multi-step forms and field events. Use it in a React application with JSON Schema for data constraints and uiSchema for presentation.

Installation

npm install extended-dynamic-forms

Install compatible peer dependencies if your application does not already have them:

npm install 'react@^18.0.0' 'react-dom@^18.0.0' 'antd@^5.24.0' 'dayjs@^1.11.13'

React and React DOM 19 are also supported by the peer ranges. Keep their major versions aligned. EDF bundles RJSF 6.11.0 and the AJV validator; you do not need a sibling RJSF checkout.

Use ESM imports and include the exported stylesheet. TypeScript declarations are provided; use an ESNext/Bundler-compatible module configuration.

Features

  • Single-page forms and wizards with step visibility, navigation and validation.
  • Declarative JsonLogic conditions and JSON Patch effects in JSON configuration.
  • Ant Design text, choice, numeric, date/time, file and display widgets.
  • Per-field options, custom widgets, fields, templates and grid layouts.
  • Field focus, blur and change events, submission hooks and webhook configuration.

Basic usage

import { useState } from 'react';
import { ExtendedForm, type RJSFSchema, type UiSchema } from 'extended-dynamic-forms';
import 'extended-dynamic-forms/dist/style.css';

const schema: RJSFSchema = {
  type: 'object',
  required: ['name'],
  properties: {
    name: { type: 'string', title: 'Name', minLength: 1 },
    email: { type: 'string', title: 'Email', format: 'email' },
  },
};

const uiSchema: UiSchema = {
  name: { 'ui:widget': 'text', 'ui:options': { allowClear: true } },
  email: { 'ui:widget': 'email' },
};

export default function ContactForm() {
  const [formData, setFormData] = useState({});

  return (
    <ExtendedForm
      schema={schema}
      uiSchema={uiSchema}
      formData={formData}
      onChange={({ formData }) => setFormData(formData)}
      onSubmit={({ formData }) => console.log('Accepted answers', formData)}
    />
  );
}

EDF supplies its widgets and validator by default. Override the RJSF widgets, fields or templates props when your application needs custom rendering. customWidgets, validator and commonly used RJSF types are exported from extended-dynamic-forms; no private subpath import is needed.

Both ExtendedForm and WizardForm accept an omitted formData prop. It uses a stable empty initial value, and edited answers persist across host re-renders. Pass formData when your host needs to supply answers explicitly.

Multi-step forms

WizardForm detects steps from root properties beginning with STEP_. Define at least two step objects to show wizard navigation; one step renders as a single-page form.

import { WizardForm } from 'extended-dynamic-forms';
import 'extended-dynamic-forms/dist/style.css';

const schema = {
  type: 'object',
  properties: {
    STEP_PERSONAL: {
      type: 'object',
      title: 'Personal details',
      required: ['name'],
      properties: { name: { type: 'string', minLength: 1 } },
    },
    STEP_CONTACT: {
      type: 'object',
      title: 'Contact details',
      properties: { email: { type: 'string', format: 'email' } },
    },
  },
};

export default function RegistrationForm() {
  return (
    <WizardForm
      schema={schema}
      uiSchema={{}}
      onStepChange={(stepIndex, data, stepId) => {
        console.log('Current step', stepIndex, stepId, data);
      }}
      onLiveChange={(flatData) => console.log('Current answers', flatData)}
      onSubmit={({ formData, flatFormData }) => {
        console.log('Accepted step answers', formData);
        console.log('Accepted flat answers', flatFormData);
      }}
    />
  );
}

Conditions use flat answer paths without the STEP_ prefix and can read answers from all wizard steps. A rule on one step can use an answer from another step to show, hide or require a field on its own step. If answer keys collide, the evaluating step's own answers win; distinct keys avoid ambiguity in flat data. One-step forms take flat initial answers only. With two or more configured steps, initial answers may be flat or nested under step keys; explicit nested snapshots take precedence.

Step visibility rules use x-edf:stepRules. Hidden steps are skipped in navigation and excluded from structured submissions unless includeHiddenStepsInSubmit is true; flatFormData retains the all-step context after inactive-null omission. onStepChange reports the visible-step index; onStepChangeDetailed also includes the original index as allStepsIndex. The last visible step can submit, and final submission validates all visible steps, including an earlier step newly revealed by a later answer. Next and Submit stay enabled when the current step has a missing required answer, including alongside other errors. Pressing either reveals all step errors without advancing or submitting invalid answers. Final Submit also reveals errors when it returns to an earlier invalid step; non-required-only error gating is unchanged. Required arrays below minItems also allow an attempt, including requirements applied by a rule and nested object fields. The click reveals the step's feedback without advancing or submitting. Steps with only optional-array minItems errors keep Next/Submit disabled.

Array positions and blank array entries are preserved through editing and navigation. Supplied strings with directly declared pattern, positive minLength, or a valid explicit maxLength keep their characters for validation; other nested/edit blank strings are subject to normalization. JSON Schema required checks presence. A positive minLength rejects an empty string; rejecting whitespace-only answers requires an appropriate non-whitespace pattern. Malformed schema patterns block progression and show configuration errors by default; hiding the error list does not bypass validation.

LayoutGridField is included for step layouts. Configure ui:field as LayoutGridField and use ui:layoutGrid with Ant Design's 24-column spans and gutters. Grouped objects display their own title and description once before their members, using the registered templates. UI titles and descriptions override schema metadata; empty UI values hide that heading, and ui:options.label: false hides both. You can override custom fields and templates.

Form headings and step descriptions

Root uiSchema['ui:options'].label defaults to true. Set it to false to hide the form title and description together on one-step and multi-step forms; field labels are unaffected. An empty root ui:title or ui:description hides only that heading.

The WizardForm prop showStepDescription defaults to true. Multi-step forms show one active-step description inside the form, above the fields, even when progress is hidden. Set it to false to hide that description. It is a component prop, not a persisted uiSchema option, so hosts such as Formstudio must pass it explicitly. One-step forms show no step description.

<WizardForm
  schema={schema}
  uiSchema={{ 'ui:options': { label: false } }}
  showStepDescription={false}
/>

Conditional logic

Put JsonLogic conditions and JSON Patch effects in x-edf:rules on the schema or UI schema. Configuration is JSON-only; JavaScript source strings are not executed.

For example, this schema rule adds a property when accountType is premium:

{
  "type": "object",
  "properties": {
    "accountType": { "type": "string", "enum": ["basic", "premium"] }
  },
  "x-edf:rules": [
    {
      "name": "Premium details",
      "condition": { "==": [{ "var": "accountType" }, "premium"] },
      "effect": [
        { "op": "add", "path": "/properties/premiumFeatures", "value": { "type": "string" } }
      ]
    }
  ]
}

UI rules belong in uiSchema['x-edf:rules']; schema rules belong in schema['x-edf:rules']. Array-item presentation can use ui:dynamicItems with per-item JsonLogic. Conditional requiredness changes validation, not just a visual required marker.

Rule order

Rules in each conditional engine run in ascending numeric order. Use a finite number; negative values and zero are valid. Equal orders retain their array order. Rules without order run after every explicitly ordered rule, keeping their own array order. Evaluation does not reorder the caller's array.

Only matching rules apply effects. Effects run sequentially, so a later effect on the same property wins: hide followed by show reveals a field, and a later widget replacement replaces the earlier widget. Effects within one rule also retain their array order. A false condition does not apply an automatic inverse; authors that need an inverse must supply another rule.

This contract applies separately to the UI, schema, and validation engines. Schema effects determine both rendered constraints and submission validation. The code-only useConditionalValidation hook preserves the same rule order; createCustomValidate then runs the selected validation callbacks in sequence. Callbacks that add errors accumulate them; an earlier error is not automatically removed unless a later callback explicitly changes it.

ExtendedForm applies embedded schema and UI rules with this ordering. WizardForm applies this ordering to each step's local rules followed by inherited root rules, including navigation and final validation. Root rules therefore follow local rules when their orders tie or both omit order. Both local and inherited rules evaluate conditions against all wizard answers, with the evaluating step's own answers taking precedence on key collisions. Effect paths remain relative to that step: effects that name a field on another step are dropped; other effect paths apply to the evaluating step unchanged. Rules may still add fields to their own step. Put a field's rule on its owning step or inherit it from the root.

For example, these UI rules reveal answer as a textarea even though the hide rule appears last in the array:

{
  "x-edf:rules": [
    { "name": "Show", "order": 20, "condition": true,
      "effect": [{ "op": "replace", "path": "/answer/ui:widget", "value": "textarea" }] },
    { "name": "Hide", "order": 10, "condition": true,
      "effect": [{ "op": "replace", "path": "/answer/ui:widget", "value": "hidden" }] }
  ]
}

Widgets and options

Select a registered widget using ui:widget. The same registered key can also be used as ui:field; an explicit field override controls its own rendering.

| Input | Registered keys | | --- | --- | | Text | text, textarea, password | | Email, URL and phone | email, url, tel, telephone | | Number | number, updown | | Choices | choice with dropdown, radio or checkbox presentation | | Date and time | date, time, datetime-local | | Visual inputs | color, range, slider, rating, switch | | Files | file, fileUpload | | Display | title, heading, html, markdown, image, alert, spacer, divider |

Configure supported Ant Design props in ui:options. Container options belong in ui:options.formItem; input styling and container styling are separate. For the unified choice widget, use ui:options.choiceConfig for data sources, presentation and presenter-specific Ant Design options.

{
  "name": {
    "ui:widget": "text",
    "ui:options": {
      "allowClear": true,
      "autoComplete": "name",
      "variant": "outlined",
      "style": { "fontSize": 18 },
      "formItem": { "style": { "marginBottom": 24 } }
    }
  },
  "birthday": {
    "ui:widget": "date",
    "ui:options": { "format": "DD/MM/YYYY", "disableFutureDates": true }
  },
  "score": {
    "ui:widget": "rating",
    "ui:options": { "count": 5, "allowHalf": true }
  }
}
  • Choices can use schema enums or configured static/remote data sources. Single choices emit scalars; multiple choices emit arrays. Remote paging is not implemented.
  • Date options include disablePastDates, disableFutureDates, disableWeekends and range: true for a range picker.
  • Number options include min, max, step, precision, groupSeparator, decimalSeparator, controls, keyboard and changeOnWheel. Display grouping does not become part of numeric answers. Schema bounds validate independently of input clamping.
  • Exact decimals require stringMode: true with a string schema and suitable string/decimal-aware validation. Numeric schemas convert values to JavaScript numbers; numeric JSON Schema bounds do not validate decimal strings.
  • Telephone mask uses 9 for a digit, a for an ASCII letter and * for any character. Mask literals affect display, while answers retain digits and an optional leading +. useCountryMasks: true enables country-pattern fallback; an explicit field mask takes precedence. Letter slots are display-only.
  • URL answers accept HTTP/HTTPS by default. allowedProtocols replaces those defaults; [] rejects every nonempty URL. javascript:, data: and vbscript: remain blocked even if listed. Link previews are restricted to HTTP/HTTPS.
  • File upload options include accept, maxSize, multiple and Ant Upload presentation controls. Use a schema compatible with the selected value mode. Required data-URL uploads show one inline message: "Upload a file." or "Upload at least one file." for multiple files. Host transformErrors or customValidate feedback takes precedence; empty uploads keep the normal label.

Choice presenter-specific Ant Design options use choiceConfig.antd, such as choiceConfig.antd.select for dropdown props.

Values and validation

Empty answers

Clearing Text, Textarea, Email, URL or Telephone emits null by default. Set an explicit own ui:options.emptyValue to override it, including "" or undefined. Other widgets and custom fields have their own clear semantics. Undefined-valued object properties are omitted from JSON, including inside array items; callbacks may contain those properties.

In ExtendedForm and WizardForm, clear an optional field to leave it unanswered: validation treats null object properties as absent, including nested properties and properties inside array items. Put required fields in the parent schema's required array; a required null fails even with a nullable schema. Custom validation receives the actual null and may reject it. Null does not refill a schema default. For primitive array slots, declare a nullable item schema such as items: { type: ['string', 'null'] } to accept cleared text values.

For EDF text widgets in your own native RJSF Form, import nullContractValidator from extended-dynamic-forms and pass validator={nullContractValidator} for the same null acceptance policy. The exported validator is plain AJV: use nullable schemas such as type: ['string', 'null'] with it. A nullable type alone does not bypass enums, dependencies or combinators, or treat required nulls as absent.

Visible, enabled null answers are included in submissions; distinguish them from missing keys. Clearing an array row does not delete it during editing. Hidden or disabled nulls are omitted at submit, which can compact arrays; see Submitted data for values and metadata.

ExtendedForm defaults experimental_defaultFormStateBehavior.requiredBooleanDefault to 'skip': an unanswered required boolean stays absent. An explicit host value (for example 'populateFalse') overrides that default. Authored boolean defaults are preserved.

Stored/default answers are not rewritten on load. Turning off interaction feedback does not disable Submit or Wizard Next validation. Change callbacks can contain incomplete or invalid drafts; only accepted submissions should be stored as completed responses.

Conditional validation feedback

Set ui:options.validation.messages at the field's own UI schema path to supply JSON-safe validation text in ExtendedForm and WizardForm:

{
  "answer": {
    "ui:options": {
      "validation": {
        "messages": {
          "minLength": "Use at least three characters.",
          "format": "Enter a valid email address."
        }
      }
    }
  }
}

Keys are AJV error keywords (required, minLength, maxLength, pattern, format, minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, minItems, maxItems, enum, const, or another error name). Values must be non-empty strings; empty strings and non-string values leave default messages unchanged. Put nested field options under their object path and array-item options under items (or the corresponding tuple item). Declarative ui:dynamicItems uses each item's effective UI options. Native catch-all field and tuple-item UI use additionalProperties and additionalItems, respectively.

EDF folds redundant AJV if wrappers for failed then/else branches when a specific error exists at the same property path; a sole wrapper stays visible. It then replaces matching messages and error-list stack text. Repeated failures with the same configured text appear once per field in each surface. Other keywords without a message keep their defaults, including Required when only minLength is configured. Phone keeps its first-failure widget wording when that check has no message, even when another keyword has one; unconfigured AJV siblings are not added inline. URL and Email widget feedback uses format; Phone pattern feedback uses pattern. Phone's existing customErrorMessage and Choice widgets' existing choiceConfig.validation.messages.required/invalid options retain their meaning.

Use transformErrors(errors, uiSchema) on either component for host code to customise validation feedback. The host runs after folding and keyword replacement, so it can override the configured text. For forms using keyword messages, widget constraints join the errors before this host callback. Field feedback, callbacks, the error list and Wizard validation use the replaced text without changing Next/Submit gating or error classification. edf:configuration errors are never rewritten by keyword messages.

Configuration errors and email feedback

Configuration failures carry name: 'edf:configuration', including errors from the exported nullContractValidator. This marker is the supported way for hosts to recognise configuration failures. Host transformErrors callbacks should preserve name when copying or replacing error objects so Wizard retains its configuration-error feedback path.

Set ui:hideError on Email fields to hide schema and custom validation messages in ExtendedForm and WizardForm. Widget-local blur feedback applies when there are no schema or custom errors.

Date and time answers

| Widget/schema | Accepted answer | | --- | --- | | Single Date with format: "date" | Calendar day, YYYY-MM-DD | | Time with format: "time" | Wall-clock time, HH:mm:ss, without timezone conversion | | DateTime | UTC ISO instant with Z, displayed in the respondent's browser-local timezone | | Calendar Date Range (date with range: true) | Pair of ISO calendar days, even with plain string item schemas |

Date/Time display formats do not change those schema-formatted answers. Native Date/Time string schemas without their respective format retain configured-format output. Explicit showTime / withTime ranges retain formatted time-bearing pairs rather than the UTC DateTime contract.

DateTime rejects nonexistent daylight-saving times and requires an explicit occurrence choice for repeated local times. Restored valid offset-bearing instants and fractional-second precision are preserved until edited. Zone-less or invalid saved DateTime strings require re-entry; no timezone is guessed. Pending invalid edits block submission and wizard progression, even on optional fields, and can emit the Invalid date-time draft marker. DateTime clear emits undefined regardless of emptyValue.

Calendar ranges reject impossible, reversed and incomplete pairs. Clear emits []; optional schemas must allow zero items if clear should be valid. Required ranges reject []. Explicit allowEmpty endpoint options also need schemas that permit an empty string at that endpoint.

Date, calendar Date Range and DateTime accept ui:options.minDate / maxDate as "YYYY-MM-DD" or ISO date-time strings. Bounds are inclusive calendar dates as written, without time-zone shifts; unparseable strings are ignored. Out-of-bounds typed or restored answers block submit and Wizard Next. A function disabledDate takes precedence over convenience flags, while bounds still apply; non-function values are ignored. Single dates also check restored answers against these flags and callbacks before submission or progression.

Events and webhooks

ExtendedForm delivers onFieldFocus, onFieldBlur and onFieldChange events with fieldId and fieldValue. WizardForm currently does not deliver those field callback props; use onLiveChange to subscribe to wizard answer snapshots. Use onSubmit for accepted form answers. Optional webhooks configure event destinations, event names, debounce timing and retries. Keep endpoint authorization and response-storage policy in your host application.

Custom widgets can import useFieldEventHandlers from the root package alongside other event exports. Call the RJSF onChange to update the answer and the field event handler to participate in EDF events.

Both components send webhooks through FormEventHub, using a single JSON envelope with eventType, timestamp, source and formData. Subscriptions support focus, blur, change, submit, validationError and wizard stepChange. Field events also carry fieldId, fieldValue and fieldPath; step events carry fromStep and toStep; validation events carry errors. Submit webhook formData contains the RJSF event for ExtendedForm, flat answers for one-step WizardForm, and step data for multi-step WizardForm.

Submitted data

ExtendedForm and WizardForm send valued hidden, disabled and read-only answers as entered. Hidden and disabled fields are exempt from field validation and Wizard navigation requirements, so their values may be invalid. Their null values are omitted without clearing live answers; showing a field restores its value. Array-level minItems, maxItems and uniqueItems validate the live array.

The submit callback and afterSubmit include optional meta alongside formData when at least one flag list is non-empty. The exported SubmissionMeta type is:

{ hiddenFields: string[]; disabledFields: string[] }

Both arrays are present when meta is emitted. Paths are RFC 6901 JSON Pointers relative to delivered answers, such as /address/street or /items/0/note; structured Wizard paths include the step key. Indices refer to arrays after inactive-null omission. A hidden object and each delivered descendant are listed once, in document order, including nested objects and array items; an inactive root uses "" without descendant expansion. hiddenFields flags delivered values hidden by visibility-capable conditional rules (ui:widget, ui:options or whole-UI replacements), including ui:dynamicItems and base-hidden Show targets. Whole-UI replacements also address descendant fields; other visibility effects address their named target. Static hidden fields outside a hidden object, such as IDs and prefills, are sent without hidden flags unless addressed by these rules. disabledFields includes static and rule-set disabled fields; a field can appear in both lists.

beforeSubmit receives answers after inactive-null omission. Its returned answers are delivered without a second filter. Metadata reflects submit-time field state and is restricted to returned paths, not item identity: reordering an array in this hook can make a flag refer to a different item.

Submit webhooks place meta at the envelope root only when flags exist. meta is reserved: body cannot supply or override it. formDataFields restricts metadata to selected answers, using flat answer pointers for Wizard selections. Other events carry unfiltered answer snapshots without metadata. Standalone FormEventHub callers must supply their own field-state handling. For whole-step exclusion, use structured Wizard answers with includeHiddenStepsInSubmit={false}; hidden-step membership does not produce field flags.

Webhook options

import type { JsonValue, WebhookConfig } from 'extended-dynamic-forms';

const metadata: Record<string, JsonValue> = {
  tenant: 'operations',
  correlation: null,
  label: '{{literal text}}',
};
const webhooks: WebhookConfig[] = [{
  url: 'https://example.com/events?channel=forms#receiver',
  events: ['change', 'submit'],
  body: metadata,
  query: [{ key: 'tag', value: 'customer care' }, { key: 'tag', value: '' }],
  formDataFields: ['contact', 'consent'],
}];
  • body adds fixed extra root properties once. Its recursive JsonValue type accepts JSON primitives, objects, arrays and null. There is no templating or script execution: {{...}} remains literal text. Reserved keys are always ignored, even when absent from the event: eventType, timestamp, source, formData, fieldId, fieldValue, fieldPath, fromStep, toStep, errors and meta.
  • query appends URL-encoded string pairs in order. Existing query values, duplicate keys and empty values are retained; the fragment remains last. An absent or empty list leaves the URL byte-for-byte unchanged.
  • formDataFields selects literal own top-level answer keys for this webhook only. It does not interpret paths, wildcards or renames. An absent list sends all answers; an empty list sends {}. Missing keys stay absent. Selected JSON values retain their complete nested structure and nulls. Null or missing answer data stays as it is. Submit selection uses ExtendedForm's inner answers and WizardForm's accepted flat answer view, not their wrappers. Other events select from their answer data. Selection does not alter other webhooks or callback payloads.

Native FormEventHub callers may supply handleAfterSubmit(data, { formData: answers }) when their canonical submit data is wrapped. Omitting the second argument selects from data; an explicit null or undefined snapshot stays null or missing. The snapshot travels with that event through deferred delivery and retries.

These options apply to every subscribed event without changing methods, headers, debouncing, retries, timeouts or subscriptions. They do not mutate the shared event, another webhook's payload or host/lifecycle callback data. Answer selection is not a redaction control: it filters only formData, not fieldValue, errors, query values or authored body properties. Hosts must apply their own disclosure policy before configuring a destination.

These EDF options do not interpret Formstudio's legacy request_body or form_data_fields storage.

Native RJSF integration

For native hosts that register EDF's TelephoneWidget on their own RJSF 6.11 Form, wrap that Form in the exported RJSF611ErrorStateAdapter, inside FormEventProvider. It preserves the native ref and real change notifications while clearing only widget-owned stale feedback. ExtendedForm and Wizard steps install this boundary internally. See Telephone integration for a native example.

Use the wrapper to clear stale Telephone feedback in native RJSF 6.11 forms. Non-live forms do not acquire schema-only errors from the adapter before Submit; post-submit feedback can clear when corrected.

Known limitations

  • CommonJS require is unsupported; use ESM imports. NodeNext declaration compatibility is not established; use the ESNext/Bundler configuration above.
  • The separate vanilla JavaScript build lacks JsonLogic support. Use the React package for declarative conditional forms.
  • Arrays do not support drag-and-drop.
  • JSON Patch effects support add, remove and replace only, not move, copy or test.

Links and license

MIT. See LICENSE.