Skip to content

Form versioning and migrations

Since 0.1.13, QForm Builder provides a canonical, versioned persistence format. It lets the following areas evolve independently:

  • the document envelope;
  • the FormKit/Quasar schema;
  • the serialized theme.

The engine remains compatible with raw schema arrays and historical unversioned autosave states.

Current versions

ts
import {
  QFORM_BUILDER_DOCUMENT_VERSION,
  QFORM_BUILDER_SCHEMA_VERSION,
  QFORM_BUILDER_THEME_VERSION,
} from '@vevedh/qform-builder-layer/migrations'

The 0.1.13 values are:

txt
QFORM_BUILDER_DOCUMENT_VERSION = 1
QFORM_BUILDER_SCHEMA_VERSION = 1
QFORM_BUILDER_THEME_VERSION = 2

The theme version is independent from the schema version. Theme migration 1 to 2 adds the background, effects, motion, and icons groups introduced by the advanced Theme Builder.

Canonical document

json
{
  "format": "qform-builder",
  "documentVersion": 1,
  "schemaVersion": 1,
  "themeVersion": 2,
  "exportedAt": "2026-06-27T15:00:00.000Z",
  "schema": [],
  "values": {},
  "settings": {
    "formName": "Form",
    "preview": {
      "width": 640,
      "isFullWidth": false
    },
    "previewMode": "editing",
    "columns": "default",
    "theme": {}
  },
  "metadata": {
    "source": "form-builder"
  }
}

The complete document is now the recommended export mode. Schema only remains available for historical integrations.

Pure API

ts
import {
  createQFormBuilderDocument,
  migrateQFormBuilderDocument,
  parseQFormBuilderDocument,
  stringifyQFormBuilderDocument,
} from '@vevedh/qform-builder-layer/migrations'

const document = createQFormBuilderDocument({
  schema,
  values,
  settings,
}, {
  metadata: {
    name: 'Support form',
    source: 'nfz-forms',
  },
})

const json = stringifyQFormBuilderDocument(document)
const result = parseQFormBuilderDocument(json)

console.log(result.sourceKind)
console.log(result.migrations)
console.log(result.warnings)

migrateQFormBuilderDocument() accepts:

  • a historical raw schema array;
  • a { schema: [...] } envelope;
  • a historical { schema, values, settings, savedAt } state;
  • a canonical versioned document.

The result records exactly what the source included:

ts
result.included.values
result.included.settings

This prevents an old schema-only export from clearing current values or theme settings.

Component API

ts
const builder = ref<FormBuilderPublicApi | null>(null)

const document = builder.value?.exportDocument({
  metadata: { source: 'admin-ui' },
})

builder.value?.importDocument(document)
builder.value?.importDocument(json) // JSON strings are accepted too

Import options:

ts
builder.value?.importDocument(legacySchema, {
  applyValues: false,
  applySettings: false,
})

By default:

  • the schema is always applied;
  • values are only applied when the source contained values;
  • settings and theme are only applied when the source contained settings.

Events:

vue
<FormBuilder
  @document-import="auditImport"
  @document-export="auditExport"
/>

document-import exposes the source, versions, executed migrations, warnings, and the groups that were actually applied.

Migration registry

ts
import {
  qFormBuilderMigrationRegistry,
  resolveQFormBuilderMigrationPath,
} from '@vevedh/qform-builder-layer/migrations'

const themePath = resolveQFormBuilderMigrationPath('theme', 1)

Each entry has a stable identifier, scope, source version, target version, and description. resolveQFormBuilderMigrationPath() resolves a contiguous deterministic path and fails when a step is missing or cyclic. An unsupported future version is rejected explicitly; it is never interpreted as an older version.

Autosave

Autosave now writes the canonical document. Reading remains compatible with the historical unversioned format. An existing localStorage entry is therefore migrated on the next save without a destructive prerequisite step.

Security

The engine:

  • reuses sanitizeQFormBuilderSchema();
  • rejects __proto__, prototype, and constructor keys;
  • rejects functions, symbols, non-finite numbers, and custom prototypes;
  • bounds depth, value count, string size, and JSON size;
  • filters settings and metadata through a closed contract;
  • normalizes themes and icons through the existing allowlists;
  • rejects future versions instead of silently downgrading them.

Document values must remain JSON-compatible. Browser File, Blob, and FileList objects are not embedded in .qform.json: upload binaries separately, then persist only a validated identifier or metadata. If a non-serializable value reaches autosave, the previous local snapshot is preserved and the error event receives AUTOSAVE_SERIALIZATION_FAILED.

The backend remains responsible for authentication, RBAC, quotas, concurrency control, and server-side validation before persistence.

Policy for future migrations

Every persisted format change must:

  1. increment only the version of the affected scope;
  2. add a pure deterministic migration to the registry;
  3. preserve the source object;
  4. document defaults and any potential data loss;
  5. add a runtime test, browser scenario, and FR/EN migration note;
  6. reject unknown future versions.

QForm Builder — Nuxt 4, Quasar and FormKit layer for dynamic forms.