Importing and Exporting Data

📖 Table of Contents

Overview

SmarkForm manages all form data through two complementary actions: export and import.

  • export reads the current state of a form (or any component inside it) and returns a plain JavaScript value — an object for forms, an array for lists, and a scalar for individual fields.
  • import takes a plain JavaScript value (or a JSON string) and writes it into the form, updating every field it can match by name.

Both actions work recursively: exporting or importing a form exports or imports all its child components in one call, no matter how deeply they are nested. This means you rarely need to address individual fields from JavaScript — you simply read and write the whole form at once.

Two related actions — reset and clear — build on top of import and are covered in the Default Values, clear, and reset section.

All four actions (export, import, reset, clear) are available as HTML trigger buttons via the data-smark attribute as well as via the JavaScript API.

The export Action

When you call export on a component, SmarkForm collects all the current values in that component’s subtree and returns them as a plain, JSON-serialisable value:

Component type Exported value
form / root { fieldName: value, … } plain object
list [ item, … ] array
input / scalar fields The raw field value (string, number, boolean, …)

Return value structure

The structure of the exported value mirrors the nesting of the form:

🔗
🗒️ HTML
⚙️ JS
👁️ Preview
<div id="myForm">
<fieldset data-smark='{"type":"form","name":"order"}'>
    <legend>Order</legend>
    <p>
        <label data-smark>Customer:</label>
        <input data-smark type="text" name="customer">
    </p>
    <p>
        <label data-smark>Notes:</label>
        <textarea data-smark name="notes"></textarea>
    </p>
    <ul data-smark='{"type":"list","name":"items","min_items":0}'>
        <li>
            <input data-smark type="text" name="product" placeholder="Product">
            <input data-smark type="number" name="qty" placeholder="Qty">
            <button data-smark='{"action":"removeItem"}'></button>
        </li>
    </ul>
    <button data-smark='{"action":"addItem","context":"items"}'>➕ Add item</button>
</fieldset>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"), {
    value: {
    "order": {
        "customer": "Jane Smith",
        "notes": "Please wrap as a gift",
        "items": [
            {
                "product": "Widget A",
                "qty": 3
            },
            {
                "product": "Widget B",
                "qty": 1
            }
        ]
    }
}
});

Every example in this section comes with many of the following tabs:

  • HTML: HTML source code of the example.
  • CSS: CSS applied (if any).
  • JS: JavaScript source code of the example.
  • Preview: Live, sandboxed rendering of the example — fully isolated from the page styles.
  • Notes: Additional notes and insights for better understanding. Don't miss it‼️

✨ In the Preview tab, a JSON playground editor is available with handy buttons:

  • ⬇️ Export to export the form data to the JSON playground editor.
  • ⬆️ Import to import data from the JSON playground editor into the form.
  • ♻️ Reset to reset the form to its default values.
  • ❌ Clear to clear the whole form.

💡 The JSON playground editor is part of the SmarkForm form itself — it is just omitted from the code snippets to keep the examples focused on what matters.

🛠️ Between the tab labels and the content there is always an edit toolbar:

  • ✏️ Edit — activates edit mode: each source tab turns into a syntax-highlighted code editor (powered by Ace) pre-filled with the full, merged source. Changes are sandboxed — the original example is not affected.
  • 📋 Include editor — (only visible in edit mode) controls whether the JSON playground editor is included in the preview. When toggled, the HTML and JS editors update instantly so you can see exactly what code is needed to add or remove it.
  • ▶️ Run — (only visible in edit mode) re-renders the Preview from the current editor contents and switches to the Preview tab.

Click ⬇️ Export to see the exported JSON. The structure of the exported object matches the form structure exactly:

{
  "order": {
    "customer": "Jane Smith",
    "notes": "Please wrap as a gift",
    "items": [
      { "product": "Widget A", "qty": 3 },
      { "product": "Widget B", "qty": 1 }
    ]
  }
}

The exportEmpties option

By default, SmarkForm strips empty items from list exports. A list item is considered empty when all its fields are empty (null, undefined, or empty string).

This behaviour is controlled by the exportEmpties option on the list component:

exportEmpties value Behaviour
false (default) Empty list items are omitted from the exported array
true All items, including empty ones, are included in the export
<!-- Empty items will be included in the export -->
<ul data-smark='{"type":"list","name":"slots","exportEmpties":true}'>
  <li>
    <input data-smark type="text" name="value">
  </li>
</ul>

exportEmpties is inherited — a child component inherits the value from its nearest ancestor that sets it explicitly. This means you may need to explicitly set exportEmpties:false on a nested list to override an ancestor’s exportEmpties:true. For example:

<!-- Outer list: export all items, including empty ones -->
<ul data-smark='{"type":"list","name":"sessions","exportEmpties":true}'>
  <li>
    <!-- Inner list: strip empties regardless of the outer setting -->
    <ul data-smark='{"type":"list","name":"tags","exportEmpties":false}'>
      <li><input data-smark type="text" name="tag"></li>
    </ul>
  </li>
</ul>

The exportEmpties option is particularly useful in “save progress” scenarios where you want to preserve the user’s position in a list even if they haven’t filled in all the items yet.

The import Action

import takes a value and writes it into the component. For a form component it distributes each key of the object to the matching child field; for a list it adds or removes items to match the length of the incoming array, then fills each item.

Accepted data formats

The import action accepts:

  • A plain JavaScript object for form components
  • A JSON string that parses to a plain object — SmarkForm parses it automatically
  • An array for list components
  • A scalar value (string, number, boolean) for input fields

Fields that are present in the form but absent from the imported data object receive undefined, which triggers their own reset to default. Fields present in the data object but absent from the form are silently ignored.

🔗
🗒️ HTML
⚙️ JS
👁️ Preview
<div id="myForm">
<p>
    <label data-smark>Name:</label>
    <input data-smark type="text" name="name">
</p>
<p>
    <label data-smark>Email:</label>
    <input data-smark type="email" name="email">
</p>
<p>
    <label data-smark>Role:</label>
    <input data-smark type="text" name="role">
</p>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"), {
    value: {
    "name": "Alice Johnson",
    "email": "alice@example.com",
    "role": "Developer"
}
});

Every example in this section comes with many of the following tabs:

  • HTML: HTML source code of the example.
  • CSS: CSS applied (if any).
  • JS: JavaScript source code of the example.
  • Preview: Live, sandboxed rendering of the example — fully isolated from the page styles.
  • Notes: Additional notes and insights for better understanding. Don't miss it‼️

✨ In the Preview tab, a JSON playground editor is available with handy buttons:

  • ⬇️ Export to export the form data to the JSON playground editor.
  • ⬆️ Import to import data from the JSON playground editor into the form.
  • ♻️ Reset to reset the form to its default values.
  • ❌ Clear to clear the whole form.

💡 The JSON playground editor is part of the SmarkForm form itself — it is just omitted from the code snippets to keep the examples focused on what matters.

🛠️ Between the tab labels and the content there is always an edit toolbar:

  • ✏️ Edit — activates edit mode: each source tab turns into a syntax-highlighted code editor (powered by Ace) pre-filled with the full, merged source. Changes are sandboxed — the original example is not affected.
  • 📋 Include editor — (only visible in edit mode) controls whether the JSON playground editor is included in the preview. When toggled, the HTML and JS editors update instantly so you can see exactly what code is needed to add or remove it.
  • ▶️ Run — (only visible in edit mode) re-renders the Preview from the current editor contents and switches to the Preview tab.

Paste the following JSON into the editor and click ⬆️ Import to see it load into the form:

{"name":"Bob Smith","email":"bob@example.com","role":"Designer"}

The setDefault option

By default, every successful import call updates the component’s defaultValue. This means that after an import, clicking ♻️ Reset restores the imported data rather than the original HTML-defined defaults.

You can opt out of this behaviour by passing setDefault: false:

<!-- Import without updating the reset target (preview mode) -->
<button data-smark='{"action":"import","setDefault":false}'>Preview</button>

<!-- Import and make this the new reset target (load mode) -->
<button data-smark='{"action":"import"}'>Load Data</button>

Summary of setDefault behaviour:

Call Updates default? What reset() restores
import(data) Yes The newly imported data
import(data, {setDefault:false}) No The previous default
import(undefined) No The current default (unchanged)
clear() No The current default (unchanged)
reset() No The current default

setDefault applies only to the originating field: when a form imports data with setDefault: true, only that form updates its own default. Child components (nested forms, lists, and leaf fields) always receive setDefault: false so that their own defaults are not affected.

The focus option

When import is invoked through a trigger button, SmarkForm automatically focuses the form after import. When called programmatically, focus is off by default. Pass focus: true to move keyboard focus after a programmatic import:

await myForm.actions.import(data, { focus: true });

Default Values, clear, and reset

Setting defaults via value

You can pre-populate a form’s default values in HTML using the value property inside data-smark:

<div data-smark='{"name":"profile","value":{"name":"Anonymous","role":"guest"}}'>
  <input data-smark type="text" name="name">
  <input data-smark type="text" name="role">
</div>

This sets defaultValue for the profile form. Calling reset() on it always restores {"name":"Anonymous","role":"guest"}.

Individual input fields can also have a default via the HTML value attribute:

<input data-smark type="text" name="country" value="US">

Do not set both the HTML value attribute and the data-smark value option on the same element at the same time — SmarkForm will throw a VALUE_CONFLICT error during render.

How import updates the default

After a successful import(data) call (with setDefault: true, the default), SmarkForm re-exports the component with exportEmpties: true and stores the result as the new defaultValue:

// After this call, reset() will restore {name:"Alice",role:"admin"}
await myForm.actions.import({ name: "Alice", role: "admin" });

Using exportEmpties: true when capturing the new default ensures that reset() restores the exact same list structure — including empty slots — that was in place right after the import.

Comparing clear and reset

Both clear and reset are built on top of import but behave differently:

Action What it does Updates default?
reset Imports the current defaultValue No
clear Imports the type-level empty value ({} for forms, [] for lists, "" for inputs) No

Use reset to undo user changes and return to the last loaded state.
Use clear to blank every field without regard to defaults — for example, a “New record” button.

🔗
🗒️ HTML
⚙️ JS
👁️ Preview
📝 Notes
<div id="myForm">
<div data-smark='{"type":"form","name":"task","value":{"title":"Buy groceries","priority":"medium"}}'>
    <p>
        <label data-smark>Title:</label>
        <input data-smark type="text" name="title">
    </p>
    <p>
        <label data-smark>Priority:</label>
        <input data-smark type="text" name="priority">
    </p>
    <p>
        <button data-smark='{"action":"clear","context":"task"}'>❌ Clear</button>
        <button data-smark='{"action":"reset","context":"task"}'>♻️ Reset</button>
    </p>
</div>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"));

The task form is initialized with value defaults.

  1. Modify the fields.
  2. Click ❌ Clear — all fields become empty.
  3. Click ♻️ Reset — the original defaults are restored.

Every example in this section comes with many of the following tabs:

  • HTML: HTML source code of the example.
  • CSS: CSS applied (if any).
  • JS: JavaScript source code of the example.
  • Preview: Live, sandboxed rendering of the example — fully isolated from the page styles.
  • Notes: Additional notes and insights for better understanding. Don't miss it‼️

✨ In the Preview tab, a JSON playground editor is available with handy buttons:

  • ⬇️ Export to export the form data to the JSON playground editor.
  • ⬆️ Import to import data from the JSON playground editor into the form.
  • ♻️ Reset to reset the form to its default values.
  • ❌ Clear to clear the whole form.

💡 The JSON playground editor is part of the SmarkForm form itself — it is just omitted from the code snippets to keep the examples focused on what matters.

🛠️ Between the tab labels and the content there is always an edit toolbar:

  • ✏️ Edit — activates edit mode: each source tab turns into a syntax-highlighted code editor (powered by Ace) pre-filled with the full, merged source. Changes are sandboxed — the original example is not affected.
  • 📋 Include editor — (only visible in edit mode) controls whether the JSON playground editor is included in the preview. When toggled, the HTML and JS editors update instantly so you can see exactly what code is needed to add or remove it.
  • ▶️ Run — (only visible in edit mode) re-renders the Preview from the current editor contents and switches to the Preview tab.

Piping Data Between Components

SmarkForm makes it easy to copy data from one component to another using the target option on export and import triggers. This avoids writing any JavaScript for common copy-data workflows.

Using target with export

When an export trigger has a target property, SmarkForm automatically imports the exported value into the target component after the export completes:

<!-- Export "source" and automatically import the result into "destination" -->
<button data-smark='{
    "action": "export",
    "context": "source",
    "target": "/destination"
}'>📋 Copy</button>

This is equivalent to writing the following JavaScript:

const value = await source.export();
await destination.import(value);

Using target with import

Conversely, when an import trigger has a target property, SmarkForm exports from the target first and uses that value as the data to import:

<!-- Export "source", then import the result here -->
<button data-smark='{
    "action": "import",
    "target": "/source"
}'>📥 Load from source</button>

This is useful inside list items for the “duplicate” pattern — export the previous sibling and import it into the current item.

Chaining export and import

The following example shows a practical copy-between-sections workflow. The “Copy to shipping” button exports its context (the billing address) subform directly into its target (the shipping address) subform:

🔗
🗒️ HTML
⚙️ JS
👁️ Preview
📝 Notes
<div id="myForm">
<fieldset data-smark='{"type":"form","name":"billing"}'>
    <legend>Billing address</legend>
    <p>
        <label data-smark>Street:</label>
        <input data-smark type="text" name="street">
    </p>
    <p>
        <label data-smark>City:</label>
        <input data-smark type="text" name="city">
    </p>
    <p>
        <label data-smark>Postcode:</label>
        <input data-smark type="text" name="postcode">
    </p>
</fieldset>
<p>
    <button data-smark='{
        "action":"export",
        "context":"billing",
        "target":"../shipping"
    }'>📋 Copy to shipping</button>
</p>
<fieldset data-smark='{"type":"form","name":"shipping"}'>
    <legend>Shipping address</legend>
    <p>
        <label data-smark>Street:</label>
        <input data-smark type="text" name="street">
    </p>
    <p>
        <label data-smark>City:</label>
        <input data-smark type="text" name="city">
    </p>
    <p>
        <label data-smark>Postcode:</label>
        <input data-smark type="text" name="postcode">
    </p>
</fieldset>
</div>
const myForm = new SmarkForm(document.getElementById("myForm"), {
    value: {
    "billing": {
        "street": "10 Downing Street",
        "city": "London",
        "postcode": "SW1A 2AA"
    },
    "shipping": {
        "street": "",
        "city": "",
        "postcode": ""
    }
}
});

Fill in the billing address, then click 📋 Copy to shipping to copy the data to the shipping address with no JavaScript required.

Every example in this section comes with many of the following tabs:

  • HTML: HTML source code of the example.
  • CSS: CSS applied (if any).
  • JS: JavaScript source code of the example.
  • Preview: Live, sandboxed rendering of the example — fully isolated from the page styles.
  • Notes: Additional notes and insights for better understanding. Don't miss it‼️

✨ In the Preview tab, a JSON playground editor is available with handy buttons:

  • ⬇️ Export to export the form data to the JSON playground editor.
  • ⬆️ Import to import data from the JSON playground editor into the form.
  • ♻️ Reset to reset the form to its default values.
  • ❌ Clear to clear the whole form.

💡 The JSON playground editor is part of the SmarkForm form itself — it is just omitted from the code snippets to keep the examples focused on what matters.

🛠️ Between the tab labels and the content there is always an edit toolbar:

  • ✏️ Edit — activates edit mode: each source tab turns into a syntax-highlighted code editor (powered by Ace) pre-filled with the full, merged source. Changes are sandboxed — the original example is not affected.
  • 📋 Include editor — (only visible in edit mode) controls whether the JSON playground editor is included in the preview. When toggled, the HTML and JS editors update instantly so you can see exactly what code is needed to add or remove it.
  • ▶️ Run — (only visible in edit mode) re-renders the Preview from the current editor contents and switches to the Preview tab.

The context path is resolved relative to where the trigger is placed in the form hierarchy, while target paths are resolved relative to the effective context of the action.

👉 More info at Form Traversing chapter.

Notice that the same effect could have been seamlessly achieved with an import action in the reverse direction.

<button data-smark='{
    "action":"import",
    "context":"shipping",
    "target":"../billing"
}'>📋 Copy to shipping</button>

…or, in both cases, we could just reverse only the action (or only the direction) to achieve Copy from shipping to billing instead of the other way around.

Programmatic API

Using component.actions.export() and component.actions.import()

To export or import data from JavaScript you call the action through the component’s .actions map. This goes through the full @action wrapper, which fires BeforeAction_* and AfterAction_* events and applies all the standard focus/silent/data flow logic:

// Wait for the form to finish rendering before using it
await myForm.rendered;

// Export the whole form
const data = await myForm.actions.export();
console.log(data); // { name: "Alice", … }

// Import data into the form
await myForm.actions.import({ name: "Bob", email: "bob@example.com" });

// Import without updating the default (preview mode)
await myForm.actions.import(data, { setDefault: false });

// Import a specific sub-form
const addressForm = myForm.find("/address");
await addressForm.actions.import({ street: "123 Main St", city: "Springfield" });

Always await myForm.rendered before calling find() or accessing components programmatically. SmarkForm builds its internal component map asynchronously — calls made before rendering is complete will return null.

Calling prototype methods directly

For internal use — such as inside event handlers or other action implementations — you can call the prototype method directly, bypassing the @action wrapper:

// Bypasses events, focus defaults, and BeforeAction cancellation:
const data = await myForm.export();
await myForm.import({ name: "Alice" }, { silent: true });

Direct prototype calls are lower overhead and are the right choice when you are already inside an action handler and do not want to re-fire lifecycle events. However, you must then manage options.silent and options.focus explicitly.

See the Event Handling chapter for a deeper explanation of the @action wrapper and event lifecycle.

Common Patterns

Loading initial data from a server

The typical pattern for pre-populating a form from the server is to wait for the form to finish rendering and then call import:

const myForm = new SmarkForm(document.getElementById("myForm"));

myForm.on("AfterAction_import", (ev) => {
    console.log("Form loaded with:", ev.data);
});

await myForm.rendered;

const response = await fetch("/api/record/42");
const data = await response.json();
await myForm.actions.import(data); // data becomes the new default

Because import defaults to setDefault: true, the user can later click ♻️ Reset to restore the loaded record without re-fetching.

Submitting form data to a backend

The recommended pattern is to place an export trigger on the page and listen for AfterAction_export:

<button data-smark='{"action":"export"}'>💾 Save</button>
const myForm = new SmarkForm(document.getElementById("myForm"), {
    async onAfterAction_export(ev) {
        const payload = ev.data; // Plain JSON-serialisable object
        const res = await fetch("/api/save", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(payload),
        });
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
    },
});

To run validation before allowing the export, use onBeforeAction_export and call ev.preventDefault() to cancel the action. See the Event Handling chapter for more details.

Save and restore draft data

You can persist draft data in localStorage and restore it the next time the user opens the page:

const myForm = new SmarkForm(document.getElementById("myForm"));

await myForm.rendered;

// Restore draft (without making it the reset default)
const draft = localStorage.getItem("myForm.draft");
if (draft) {
    await myForm.actions.import(JSON.parse(draft), { setDefault: false });
}

// Auto-save on every export
myForm.on("AfterAction_export", (ev) => {
    localStorage.setItem("myForm.draft", JSON.stringify(ev.data));
});

Using setDefault: false when restoring a draft ensures that ♻️ Reset still returns to the form’s HTML-defined defaults, not the draft. This lets the user discard a draft by pressing Reset.