Skip to content

Releases: edmundhung/conform

v1.19.0

13 Apr 23:24
a9481c5

Choose a tag to compare

Breaking Changes (Future APIs)

  • Removed deprecated exports from @conform-to/react/future (#1183)

    • FormOptionsProvider component
    • BaseMetadata type
    • BaseErrorShape type
    • CustomTypes type
    • CustomMetadata type
    • CustomMetadataDefinition type

    The deprecated schema option in the future useForm hook is also removed. If you were validating with a schema, pass the schema as the first argument: useForm(schema, options) instead of useForm({ schema, ... }).

  • Improved ErrorShape handling through out the future APIs. form.errors and field.errors now use your ErrorShape directly instead of always wrapping it in an array. (#1183)

    If you were using a custom ErrorShape, either by specifying it in the generics or through the isError option from configureForms, update it from ErrorShape to ErrorShape[] to keep the array structure you had before. For example, if you previously used string, change it to string[] to keep the errors as arrays of strings:

    const forms = configureForms({
    -  isError: shape<string>(),
    +  isError: shape<string[]>(),
    });
  • report() now accepts either schema issues or an explicit error payload, but not both in the same error object. If you were combining them before, append the extra message as another issue instead. (#1183)

    const result = schema.safeParse(submission.payload);
    const isEmailUnique = await checkEmailUnique(submission.payload.email);
    
    if (!result.success || !isEmailUnique) {
      const issues = !result.success ? [...result.error.issues] : [];
    
    + if (!isEmailUnique) {
    +   issues.push({
    +     message: 'Email is already taken',
    +     path: ['email'],
    +   });
    + }
    
      return {
        result: report(submission, {
          error: {
            issues,
    -       fieldErrors: {
    -         email: !isEmailUnique ? ['Email is already taken'] : [],
    -       },
          },
        }),
      };
    }

What's Changed

  • Added support for customizing serializer based on field name (#1184)

    The serialize options now let you customize serialization based on ctx.name, both globally through configureForms() and per form through useForm(), while delegating everything else back to Conform's default serializer with ctx.defaultSerialize.

    This changes the serializer callback signature to:

    serialize(value, ctx) {
      // Custom serialization for a specific field name
      if (ctx.name === 'metadata') {
        return typeof value === 'string' || value == null
          ? value
          : JSON.stringify(value);
      }
      return ctx.defaultSerialize(value);
    }

Full Changelog: v1.18.0...v1.19.0

v1.18.0

04 Apr 10:28
0b7197e

Choose a tag to compare

Breaking Changes (Future APIs)

  • Renamed the path helpers in @conform-to/dom/future exports for consistency across the API surface. If you're using these helpers directly, you'll need to update your imports and call sites. (#1172)

    getPathSegments -> parsePath
    formatPathSegments -> formatPath
    appendPathSegment -> appendPath
    getValueAtPath -> getPathValue
    setValueAtPath -> setPathValue

What's Changed

  • Added support for structured values in future useControl(). Custom inputs can now register a hidden <fieldset> as their base control, which makes it much easier to build components that work with objects and nested arrays. This release also adds a new BaseControl component to render those hidden native controls for you, and exposes field.defaultPayload so structured controls can be initialized from the field's default value. (#1173)

    const control = useControl({
      defaultValue: field.defaultPayload,
      parse(payload) {
        return DateRangeSchema.parse(payload);
      },
    });
    
    <BaseControl
      type="fieldset"
      name={field.name}
      ref={control.register}
      defaultValue={control.defaultValue}
    />
    <DateRangePicker
      value={control.payload}
      onChange={(value) => control.change(value)}
    />
  • Added coerceStructure() to the future Zod and Valibot APIs. Unlike coerceFormValue(), this API focuses on converting form string values into typed data without running the schema's validation rules, which makes it useful when you want to read the current form state as typed values during rendering or interaction. (#1169)

    import { coerceStructure } from '@conform-to/zod/v4/future';
    
    const schema = z.object({
      age: z.number().min(0).max(120),
      subscribe: z.boolean(),
    });
    
    const value = coerceStructure(schema).parse({
      age: '42',
      subscribe: '',
    });
    
    // { age: 42, subscribe: false }
  • Added configureCoercion() to the future Zod and Valibot APIs so coercion behavior can be customized in one place and reused across both coerceFormValue() and coerceStructure(). This replaces the older defaultCoercion and customize options on coerceFormValue(). (#1169)

    import { configureCoercion } from '@conform-to/zod/v4/future';
    
    const { coerceFormValue, coerceStructure } = configureCoercion({
      stripEmptyString: (value) => {
        const trimmed = value.trim();
        return trimmed === '' ? undefined : trimmed;
      },
      type: {
        number: (text) => Number(text.trim().replace(/,/g, '')),
      },
    });
  • Improved datetime-local handling in future APIs. Date values now serialize to datetime strings without a trailing Z, and timezone-less datetime strings are now coerced as UTC by the Zod and Valibot integrations. This better matches how datetime-local inputs behave in the browser and avoids the confusion that can come from mixing local-looking inputs with explicit UTC suffixes. No changes were made to the v1 APIs. (#1174)

  • Improved recursive schema support across the schema integrations. Zod constraint resolution now works correctly for nested recursive schemas, including getter-based schemas and z.lazy(). Valibot now also resolves constraints more reliably for recursive schemas and normalizes indexed lookups like items[0].name to the corresponding structural path. coerceFormValue() for Valibot also now handles v.lazy() schemas correctly. (#1166)

    This means nested fields inside tree-like structures, recursive collections, and lazily-defined schemas should now derive constraints and coercion behavior more reliably than before.

  • parseSubmission() now preserves empty strings and files by default by changing stripEmptyValues to false. This is a behavioral improvement because empty values can carry meaning, such as distinguishing "the user cleared this field" from "this field was never submitted". The option is now deprecated and will be removed in a future minor release. If you're already using Zod or Valibot integration, empty-value stripping is already handled during validation where appropriate. (#1171)

  • Fixed the Valibot coerceFormValue() return type to match the schema object it actually returns. Previously, TypeScript could imply that the returned schema still exposed properties from the original schema shape even when that was not true at runtime. There are no runtime behavior changes here, but the typings are now more honest and prevent misleading property access in TypeScript. (#1169)

  • Fixed native form submission after async client validation. Previously, Conform could re-dispatch the submit event immediately after async validation finished, but browsers could still ignore that follow-up submission. Submission is now resumed on the next task so the original native form flow can continue more reliably. (#1177)

    This primarily helps forms that rely on async client-side validation but still expect normal browser submission semantics once validation succeeds.

Documentation / Examples

  • Added an Astro example (#1177)

Full Changelog: v1.17.1...v1.18.0

v1.17.1

15 Feb 22:55
5e811c4

Choose a tag to compare

What's Changed

  • Fixed coerceFormValue return type to no longer pretend to be the original schema type in TypeScript. This means properties like .shape that don't exist on the coerced schema will now correctly produce a type error instead of failing silently at runtime. (#1164)
  • Fixed coerceFormValue and getZodConstraint causing stack overflow when used with Zod v4's getter-based recursive schemas (#1165)

Full Changelog: v1.17.0...v1.17.1

v1.17.0

10 Feb 00:17
815bf88

Choose a tag to compare

What's Changed

  • Added PreserveBoundary component for preserving form values when fields are unmounted during client-side navigation (e.g., multi-step wizards, form dialogs, virtualized lists). (#1148)

    import { PreserveBoundary } from '@conform-to/react/future';
    
    {step === 1 ? (
      <PreserveBoundary name="step-1">
        <input name="name" />
        <input name="email" />
      </PreserveBoundary>
    ) : (
      <PreserveBoundary name="step-2">
        <input name="address" />
        <input name="city" />
      </PreserveBoundary>
    )}
  • Added dynamic behavior options for array intents. insert and remove can now change behavior based on validation errors using onInvalid and from options. (#1154)

    intent.insert({
      name: fields.tags.name,
      // Read and validate a value from another field before inserting.
      // If valid, the value is inserted and the source field is cleared.
      // If invalid, the error is shown on that field.
      from: fields.newTag.name,
      // Cancel the insert if it causes array validation errors (e.g., exceeding max items)
      onInvalid: 'revert',
    });
    
    intent.remove({
      name: fields.tags.name,
      index: 0,
      // Cancel the removal if it causes validation errors (e.g., going below min items)
      onInvalid: 'revert',
      // Or use 'insert' to continue removing but insert a new item at the end
      // onInvalid: 'insert',
    });

    These options require the validation error to be available synchronously. See the docs for guidance on combining with async validation.

  • getConstraints (zod v4 and valibot) now derives an accept constraint from file schemas with MIME type configuration by @transparent-citizen. (#1151)

    import { getConstraints } from '@conform-to/zod/v4/future';
    
    const schema = z.object({
      avatar: z.file().mime(['image/png', 'image/jpeg']),
    });
    
    getConstraints(schema);
    // { avatar: { required: true, accept: 'image/png,image/jpeg' } }
  • Fixed missing 'use client' directive on internal modules that caused React Server Component frameworks to incorrectly treat parts of @conform-to/react as server code. (#1157)

New Contributors

Full Changelog: v1.16.0...v1.17.0

v1.16.0

26 Jan 19:40
105cdba

Choose a tag to compare

Breaking Changes (Future APIs)

  • The useFormData() selector is now only called when the form is available. When unavailable, the hook returns undefined or the new fallback option if provided. (#1142)

    // Before
    const value = useFormData(formRef, (formData) => {
      if (formData === null) return '';
      return formData.get('name') ?? '';
    });
    
    // After
    const value = useFormData(formRef, (formData) => formData.get('name') ?? '', {
      fallback: '',
    });

What's Changed

  • Introduced the configureForms future API, a factory function that creates form hooks with custom configuration and full type inference. It replaces FormOptionsProvider (now deprecated). (#1131)

    import { configureForms } from '@conform-to/react/future';
    import { getConstraints } from '@conform-to/zod/v3/future';
    
    const { useForm, useField, FormProvider } = configureForms({
      shouldValidate: 'onBlur',
      getConstraints,
      extendFieldMetadata(metadata) {
        return {
          get textFieldProps() {
            return {
              name: metadata.name,
              defaultValue: metadata.defaultValue,
              'aria-invalid': !metadata.valid,
            };
          },
        };
      },
    });
  • Added getConstraints and isSchema to @conform-to/zod and @conform-to/valibot future exports. getConstraints replaces getZodConstraint and getValibotConstraint (now deprecated). (#1131)

  • Fixed unbound-method TypeScript ESLint error by using arrow function syntax for change, focus, and blur methods in the Control type (#1129)

Documentation

  • Fixed the number of properties returned by useForm hook in the docs by @DarkstarXDD (#1136)
  • Added guidance for Zod v4 import path (#1146)

New Contributors

Full Changelog: v1.15.1...v1.16.0

v1.15.1

18 Dec 23:41
e1f9169

Choose a tag to compare

What's Changed

  • Added support for nullable constraints in getZodConstraint (#1126)
  • Fixed useControl not reflecting the input's default value in the DOM (#1121)
  • Fixed useControl not dispatching a change event when calling control.change() with the input's default value (#1122)
  • Fixed parseWithZod and parseWithValibot incorrectly treating falsy result values as errors (#1115)

New Contributors

Full Changelog: v1.15.0...v1.15.1

v1.15.0

10 Dec 00:47
a7c9e99

Choose a tag to compare

What's Changed

  • Added a getFieldValue helper to extract and validate field values from FormData or URLSearchParams. (#1112)

    import { getFieldValue } from '@conform-to/react/future';
    
    // Basic: returns `unknown`
    const email = getFieldValue(formData, 'email');
    
    // With type guard: returns `string`, throws if not a string
    const name = getFieldValue(formData, 'name', { type: 'string' });
    
    // File type: returns `File`, throws if not a File
    const avatar = getFieldValue(formData, 'avatar', { type: 'file' });
    
    // Object type: parses nested fields into `{ city: unknown, ... }`
    const address = getFieldValue<Address>(formData, 'address', { type: 'object' });
    
    // Array: returns `unknown[]`
    const tags = getFieldValue(formData, 'tags', { array: true });
    
    // Array of objects: returns `Array<{ name: unknown, ... }>`
    const items = getFieldValue<Item[]>(formData, 'items', {
      type: 'object',
      array: true,
    });
    
    // Optional: returns `string | undefined`, no error if missing
    const bio = getFieldValue(formData, 'bio', { type: 'string', optional: true });

    It also infers types from the field name:

    import { useForm, useFormData, getFieldValue } from '@conform-to/react/future';
    
    function Example() {
      const { form, fields } = useForm();
      // Retrieves the value of the `address` fieldset as an object, e.g. `{ city: unknown; ... }`
      const address = useFormData(form.id, (formData) =>
        getFieldValue(formData, fields.address.name, { type: 'object' }),
      );
    
      // ...
    }

Full Changelog: v1.14.1...v1.15.0

v1.14.1

04 Dec 11:22
fe389a8

Choose a tag to compare

What's Changed

  • Relaxed the FormConfig type to allow both lastResult and onSubmit to be optional (#1116)

Full Changelog: v1.14.0...v1.14.1

v1.14.0

01 Dec 22:07
5f30e77

Choose a tag to compare

Breaking Changes (Future APIs)

  • The intendedValue option in the report helper has been renamed to value and now works as the defaultValue when resetting the form. Previously, this option was ignored when resetting and the form would always reset to the default value. You can now use the value option to update or reset forms to a specific value. (#1079)

    // Update form to a specific value after submission
    return {
      result: report(submission, {
        value: updatedValue,
      }),
    };
    
    // Reset form to a specific value after submission
    return {
      result: report(submission, {
        reset: true,
        value: defaultValue,
      }),
    };
  • parseSubmission now strips empty values by default. This makes it easier to work with schemas directly (without coerceFormValue) since you no longer need extra validation like .min(1) for required fields. You can set stripEmptyValues: false to preserve empty values if needed. (#1110)

    const formData = new FormData();
    // Empty text input
    formData.append('name', '');
    // Empty file input
    formData.append('files[]', new File([], ''));
    parseSubmission(formData);
    // { payload: {} }
    parseSubmission(formData, { stripEmptyValues: false });
    // { payload: { name: '', files: [new File([], '')] } }

What's Changed

  • Schema-first future useForm hook with improved type inference (#1106)

    The schema option is now promoted to the first argument of useForm for better type inference:

    // Before: schema in options
    const { form, fields } = useForm({
      schema: mySchema,
      onSubmit(event, { value }) {
        // value type inference could be inconsistent
      },
    });
    
    // After: schema as first argument
    const { form, fields } = useForm(mySchema, {
      onSubmit(event, { value }) {
        // value is fully typed based on your schema
      },
    });
    • onValidate is now required when not using a schema
    • Either onSubmit or lastResult must be provided (Relaxed the type to allow both to be optional in v1.14.1)

    The old API with schema in options still works but is now deprecated. It will be removed in the next minor release.

  • Fixed parseSubmission array handling for entries ending with []. Previously, when multiple form entries had the same name ending with [] (e.g., todos[]), all items were incorrectly pushed as a single nested array element. Now they are correctly spread as individual array items. (#1108)

    const formData = new FormData();
    formData.append('todos[]', 'Buy milk');
    formData.append('todos[]', 'Walk dog');
    formData.append('todos[]', 'Write tests');
    
    parseSubmission(formData);
    // Before (incorrect): { todos: [['Buy milk', 'Walk dog', 'Write tests']] }
    
    // After (correct): { todos: ['Buy milk', 'Walk dog', 'Write tests'] }

Improvements

  • You can now import from @conform-to/zod/v3 if you need to work with v3 schema using zod v4. (Thanks @kesoji - #1090)
  • Fixed type inference for getFieldset() with interface declarations (#1097)
  • Moved lastResult logic from an effect to the render phase. Your form component may now render twice within a single lifecycle when needed, but state updates that previously spanned two separate lifecycles now complete in one. (#1103)
  • Improved FormOptions and ValidationAttributes types compatibility with exactOptionalPropertyTypes setting in tsconfig. (#1105)

Full Changelog: v1.13.3...v1.14.0

v1.13.3

07 Nov 12:35
ba4ced8

Choose a tag to compare

What's Changed

Full Changelog: v1.13.2...v1.13.3