Skip to main content

Exporting Form Definitions

Get the current form definition from the builder for saving, storing, or transmitting to a server.

Choosing a serialization format

The builder's Export action produces *.esheet.yaml by default. Choose eSheet JSON in the export dialog when a downstream system needs JSON; that produces *.esheet.json.

Use YAML for committed form layouts. It produces reviewable diffs, supports comments, and keeps multi-line help text readable. Use JSON for API payloads and other machine-to-machine interchange. Both files contain the same FormDefinition, and existing .esheet.json layouts continue to import and render.

Using the onChange Callback

The simplest way to export is via the onChange prop:

function FormEditor() {
const handleChange = (definition: FormDefinition) => {
// Save to your backend, localStorage, etc.
console.log(JSON.stringify(definition, null, 2));
};

return <EsheetBuilder definition={initialDef} onChange={handleChange} />;
}

The onChange callback fires on every change (field edits, reorder, add, remove). The provided FormDefinition is a clean tree -- no internal state, no response data.

Export Format

The exported FormDefinition contains only structural data. The on-disk YAML form looks like this:

id: my-form
title: My Form
description: Optional description
pages:
- id: page-1
fields:
- id: q1
fieldType: text
question: Your name
required: true
inputType: string

For a wire/API payload, serialize the same object as JSON:

fetch('/api/forms', {
method: 'PUT',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(definition),
});

What's included:

  • Form identifier (id)
  • Form title and description
  • All field definitions with their properties
  • Conditional rules
  • Section hierarchy (nested fields)

What's NOT included:

  • User responses / answers
  • Internal normalized state
  • UI selection or mode state

Hydration

Internally, the builder uses a normalized flat state for O(1) operations. When exporting, hydrateDefinition() converts this back to the nested tree structure expected by FormDefinition.

This happens automatically when using onChange -- you always receive a standard FormDefinition.

Debouncing

The onChange callback fires on every keystroke and edit. For network-intensive operations (e.g., auto-saving to a server), consider debouncing:

import { useMemo } from 'react';

function FormEditor() {
const debouncedSave = useMemo(
() =>
debounce((def: FormDefinition) => {
fetch('/api/forms', {
method: 'PUT',
body: JSON.stringify(def),
});
}, 500),
[]
);

return <EsheetBuilder definition={initialDef} onChange={debouncedSave} />;
}

Programmatic Store Access

For advanced use cases, access the FormStore directly via React context:

import { useFormStore } from '@esheet/builder';

function ExportButton() {
const form = useFormStore();

const handleExport = () => {
const definition = form.getState().hydrateDefinition();
// Use the definition...
};

return <button onClick={handleExport}>Export</button>;
}
warning

useFormStore() only works within the EsheetBuilder component tree where the FormStoreContext is provided.