Controls, permissions, custom HTML/CSS and reusable designs.
One template format is used by the Wizard, Builder, previews and public renderer on DNN (ASCX and MVC), Umbraco and Oqtane. A new design must be supplied as template data. It must not require a branch in the Wizard keyed by its title, slug, category or CSS prefix.
Layout is flexible; bindings are shared
Templates can use unrelated layouts: an editorial split panel, a compact card, a dark event page, a soft illustration, or native multi-page sections. Keep each design's HTML structure, CSS classes, pictures and responsive rules. The contract standardizes the connection to the form engine, not its appearance.
| Owner |
Responsibility |
fields |
Keys, labels, types, choices, defaults, validation, conditions, composite parts, widget settings and page-break sections |
settings.customHtml |
Layout and token positions; no separately authored data-entry controls |
settings.customCss |
Styles limited to this template's root and field/widget hooks |
settings.customContent |
Editable headings, descriptions, captions and other authored text |
themeAssets / assetBindings |
Managed template pictures and artwork |
customScripts |
Optional root-scoped, idempotent behavior; use native widgets where available |
| Wizard |
Edit those structures, validate the draft and save through the platform adapter |
Set settings.templateContractVersion to 1. Older templates use the generic compatibility reader; their titles never select special Wizard behavior.
Stable layout hooks
Use data-mf-field="email" on the element that owns one field's caption, input token and help. Use data-mf-fields on its parent insertion zone. These attributes have no visual styling: choose any tags, class names, grid, colors or artwork. They let the editor add, clone and remove a field without guessing your design's class names. Keep headings, images, page navigation and submit buttons outside a field wrapper.
<div class="studio-fields" data-mf-fields>
<div class="studio-field" data-mf-field="email">
{{field:email}}
</div>
</div>
For a separately positioned caption, place {{label:email}}, {{required:email}} and {{help:email}} inside that field wrapper and configure the native field to avoid duplicate visible labels. A wrapper must contain exactly its own field token. Row/FlexGrid wrappers own the layout field token; their children stay in the schema. A new field copies a neighboring field wrapper and rebinds its metadata; an empty zone receives a native field wrapper. No new Wizard code is needed for a different design.
Blocks: headings and fields move together
Wrap each logical group in an element with a unique data-mf-block="contact-details". Include its heading, introductory text, grid, field wrappers and related decoration inside the block. Keep the form hero, shared navigation and submit actions outside it. Blocks are independent siblings; use Row/FlexGrid or composite fields for nesting inside a block.
<section class="studio-section" data-mf-block="contact-details">
<h2>{{content:contact_heading}}</h2>
<p>{{content:contact_intro}}</p>
<div class="studio-grid" data-mf-fields>
<div data-mf-field="email">{{field:email}}</div>
<div data-mf-field="phone">{{field:phone}}</div>
</div>
</section>
In Wizard → Fields, Blocks & fields shows each group with its fields. Use the up/down buttons to move the whole block among siblings in the same layout container. Delete … block removes its heading, supporting text and all fields together, including nested Row/composite/widget configuration. Undo block change restores the last block operation in the current draft. No saved form changes until Create succeeds.
Undo is available immediately after a block operation; making another field edit starts a new draft action and clears that block undo, so it cannot overwrite a later field change.
Deleting the last field of a block removes the entire empty group immediately in the design preview and in the saved form. An explicitly informational block with no original fields is retained. A block cannot cross an unrelated sidebar or page boundary; native multi-page steps retain their own page headings and navigation.
For an existing sibling layout that cannot gain a new DOM wrapper without changing CSS, range comments express the same ownership:
<!--mf:blocks:v1-->
<!--mf:block:contact-details-->
<h2>Contact details</h2>
<div>{{field:email}}</div>
<div>{{field:phone}}</div>
<!--/mf:block:contact-details-->
The marker records that block discovery is complete, so deletion does not reinterpret surrounding artwork as a new group. Legacy templates use generic heading/section and repeated separator discovery. New designs should declare ownership explicitly rather than depend on that compatibility inference. There are no rules keyed to a template's name, slug or CSS prefix.
Supported bindings
| Token |
Result |
{{field:email}} |
Native field markup and behavior from the field with key email |
{{label:email}} |
Encoded field label, for an independently positioned caption |
{{required:email}} |
* when the field is required, otherwise empty |
{{help:email}} |
Encoded field help text |
{{form:title}} |
Form title |
{{form:description}} |
Form description |
{{form:submit}} |
Submit button text |
{{content:introduction}} |
Encoded text from customContent.introduction |
{{mf:asset:hero}} |
Managed artwork referenced by the hero binding |
{{script:enhancement}} |
Managed script anchor |
Field and content text is encoded, not executable HTML. Layout belongs in customHtml. Prefer the label already emitted by {{field:key}}; use the metadata tokens only when the design needs its caption elsewhere, and avoid rendering two visible labels. Field keys are stable identifiers; use labels for user-facing wording. Changes to a key must also update any authored CSS selectors or custom script references to that key.
Minimal example
{
"id": "studio-contact",
"slug": "studio-contact",
"title": "Studio contact",
"fields": [
{ "key": "email", "type": "Email", "label": "Your email", "required": true },
{ "key": "message", "type": "Textarea", "label": "Tell us about your project" }
],
"settings": {
"templateContractVersion": 1,
"customHtml": "<section class='studio-contact'><aside><h1>{{form:title}}</h1><p>{{content:intro}}</p></aside><div><div class='studio-contact-fields' data-mf-fields><div data-mf-field='email'>{{field:email}}</div><div data-mf-field='message'>{{field:message}}</div></div><button type='submit'>{{form:submit}}</button></div></section>",
"customCss": ".studio-contact{display:grid;grid-template-columns:1fr 2fr;gap:2rem;max-width:100%;color:#183d34;background:#fff}.studio-contact-fields{min-width:0}.studio-contact .mf-input{max-width:100%}@media(max-width:700px){.studio-contact{grid-template-columns:1fr}}",
"customContent": { "intro": "We would love to hear what you are working on." }
},
"submitButtonText": "Send enquiry"
}
Replace the layout and CSS to create a completely different design. Keep the schema and binding rules. Adding an Address composite or Signature widget means adding its schema entry and a field token; it does not require template-specific Wizard code.
Embedding and interaction rules
- MegaForm supplies the outer form. Do not nest a second
<form> or hard-code an input with its own name.
- Place each native field token once. A Row or FlexGrid token owns its children; do not also render its children separately.
- Use a unique template root class. Do not style
body, html, :root, host navigation or sibling modules. Avoid viewport-width elements, fixed page overlays and negative offsets that leave the module pane.
- Use container-relative sizing,
min-width:0 for grid/flex children, responsive images and a narrow-pane layout. The module may be embedded next to another form or inside a theme column.
- Prefer schema Section page breaks and native widgets over custom navigation or input implementations. Managed scripts must initialize within their supplied form root and tolerate repeated mounts.
- Keep data connections, table mapping and submit workflows separate from design. A downloaded template does not silently grant SQL writes; the Wizard asks the author to choose a data source.
Authoring and QA
The Wizard can edit field properties and template wording, then preview the resulting design at desktop, tablet and mobile widths. Its design preview is a snapshot; public submit, conditional logic and interactive widgets must also be exercised after creation.
From MegaForm.UI, run:
node scripts/check-template-contract.cjs
node scripts/test-wizard.cjs
The contract checker reports missing field/content bindings, duplicate keys, hard-coded controls, nested forms, inline event scripts and CSS that escapes the pane. Version 1 violations fail the build. Legacy records are reported separately rather than silently rewritten by the checker.
The published source catalog contains 82 version 1 templates. upgrade-template-contract.cjs is an explicit, repeatable migration tool: it binds recognized legacy field captions, extracts editable text, annotates identifiable field-only wrappers and verifies equivalent resolved HTML (apart from those editor attributes) before writing the entire batch. It does not rewrite CSS or scripts. Historical/archive templates remain readable through the compatibility path.
upgrade-template-blocks.cjs annotates structurally identifiable field groups across that source catalog and verifies identical markup after removing editor hooks. It preserves CSS, assets and scripts. Templates with one uninterrupted field stack or static content do not acquire invented section headings. Test deletion of a whole group, deletion of its final field, block reorder, undo and the resulting saved public form whenever a design changes.
For every design, verify: unchanged save preserves artwork; edited labels/options/required rules persist; adding/removing a field leaves no empty caption/card; text edits affect only their intended content; desktop/mobile previews match the saved form; two modules do not alter each other's styles; composite values, files and signatures survive a real submission. Keep a screenshot comparison for designs with special positioning or responsive artwork.