DEV Community

Cici Yu for Momen

Posted on

How to Save Form Drafts in Momen: Manual vs. Real-Time Approaches

Long or multi-step forms — job applications, insurance claims, multi-page surveys — put user input at risk. A closed tab, a lost connection, or a long pause before submission can wipe out everything someone just typed. Giving users a way to save a "draft" and come back later fixes that.

Momen supports two ways to implement draft saving: a manual method where users click a button to save their progress, and a real-time method that saves automatically whenever an input loses focus. Both projects below are built entirely with Momen's in-product AI Copilot, and both come with an editor link you can clone directly.

Overview

Saving drafts can prevent data loss when users close a form accidentally, lose network connection, or step away for too long before final submission. Momen offers two main ways to implement this: manual saving triggered by a button click, and automatic saving triggered when an input field loses focus.

Method Comparison

Dimension Method 1: Manual Draft Saving Method 2: Real-time Draft Saving
Interaction Core User clicks a "Save Draft" button to store progress Data updates automatically when input fields lose focus
Trigger Timing Explicit, user-controlled Implicit, automatic
Implementation Logic Page variable (form_id) toggles between create and edit modes Pre-retrieve a blank placeholder record, then update on blur events
Resource Consumption Low — only makes requests when the user clicks save Auto-saving triggers more frequent database updates, which may increase server load. Use it where user experience outweighs resource cost.
Use Cases Short forms, or cases where users prefer explicit confirmation Long-form editing, multi-step workflows, high-value inputs

Manual Draft Saving for Forms

Demo Project

Clone the Manual Draft Saving project

Introduction

  • Goal: Build a form system where users can save incomplete data as drafts and resume editing later before final submission.
  • Core Logic: Use a status field (Draft / Submitted / Deleted) to track each record's state, and a page variable form_id to control the UI mode: -1 means creating a new record, any other value means editing an existing draft.
  • Use Cases: Job applications, insurance claim forms, multi-page questionnaires, or any workflow where users may need to return later to complete their input.

Steps

This tutorial uses pre-styled layout blocks from the "Common UI Presets" template page. These preset elements provide basic styling and layout only; they contain no conditional logic, data bindings, or Actionflows. You can copy them directly to skip manual styling and focus on the core logic.

SetupData Model

Table: form — stores draft and submitted records.

Field Name Type Note
id Bigint Auto-generated, unique identifier (system column)
created_at Timestamp Auto-generated (system column)
updated_at Timestamp Auto-generated (system column)
status Text Form state: Draft, Submitted, or Deleted
field_1 Text Custom business field
field_2 Bigint Custom business field
account_id Bigint Foreign key linking to the account table

Field Name

Type

Note

id

Bigint

Auto-generated, unique identifier (system column)

created_at

Timestamp

Auto-generated (system column)

updated_at

Timestamp

Auto-generated (system column)

status

Text

Form state: Draft, Submitted, or Deleted

field_1

Text

Custom business field

field_2

Bigint

Custom business field

account_id

Bigint

Foreign key linking to the account table

Page Entry Logic

  • In the left sidebar, click the Pages tab and add a page named Page Form Drafts.
  • Drag a Button onto the canvas. Change the button text to Create and rename the component to Button Create.
  • Select Button Create and add an action under OnClick.
  • Configure the logic flow:

Add a Condition node.

  • Case: Guest — If getIsLoggedIn is Is false, add a Show toast action: Please log in first.
  • Case: Logged In — Add an Open custom modal node targeting Modal Fill Form.

Modal UI Builder

The modal is the workspace for filling out forms. A page variable tracks whether the user is creating a new record or editing an existing draft.

  • Click the Modal tab in the left sidebar and create a modal named Modal Fill Form.
  • Select the root container of Modal Fill Form, go to the Data tab in the right sidebar, and click + next to Variable.
  • Set Name to form_id, Type to Bigint, and Default value to -1.

form_id controls the form mode. -1 means "Create Mode". When an existing draft is loaded, this variable holds the actual record ID, switching the form to "Edit Mode".

  • Drag two Text Input components onto the modal canvas:

Rename the first input to Input Field 1 and set its type to Text.

  • Rename the second input to Input Field 2 and set its type to Bigint.

  • Select the Button Cancel component. Under its OnClick configuration, add a Close modal node and set Scope to Close top.

Draft List & ActionsDraft List Components

Now that the modal is configured, return to the main page to build the draft list. The list will show all drafts belonging to the logged-in user.

  • Select the Text Current number of drafts component. Bind its Content to Logged in user/form/Count and add a local filter: status Equal to 'Draft'.

Logged in user/form/Count is evaluated when user data loads or refreshes. The draft list uses the Subscription request type, which automatically keeps the UI in sync with database changes.

  • Drag a List component onto the canvas and rename it to List Drafts. Set its Data source to the form table and toggle Request type to Subscription for real-time syncing.

  • Open the Query criteria panel for the list and add two filter rules combined with an And operator:

account_id Equal to Logged in user/id

  • status Equal to 'Draft'

Interactive View Toggles

The draft list is placed inside a conditional view container that can be expanded or collapsed.

  • Select Button Drafts inside the Case Closed state of the Conditional View Drafts container.
  • Add a Switch conditional view action under its OnClick event. Target Conditional View Drafts and set Switch to Case Extended.

  • Switch the container to Case Extended, select its internal Button Drafts, and add a Switch conditional view action under its OnClick event, setting the target back to Case Closed.

Save as Draft Actionflow

The "Save as Draft" logic validates that at least one field is filled, then inserts a new draft record and updates the form_id variable so the user can continue editing.

  • Select Button Save as Draft.

  • Add a Condition node for input validation:

Name the branch Case: Input Empty and use the And operator.

  • Rule: Input Field 1/Value Is null And Input Field 2/Value Is null.
  • Add a Show toast node inside this branch: Please fill in at least one field.

  • In the Case: Input Not Empty branch, add an Insert form node:

Target Table: form.

  • Parameters: Set status to Draft. Bind field_1 to Input Field 1/Value and field_2 to Input Field 2/Value. Bind account_id to Logged in user/id.

  • After the Insert form node, add a Set variable node:

Scope: Page and component

  • Target Variable: form_id
  • Value: Bind to Action result/Insert form/id

  • Add a Show toast node: Draft saved successfully.

Submitting the Form

The "Apply" button determines whether to insert a new submitted record or update an existing draft, based on the form_id variable.

  • Select Button Apply and enter its OnClick action flow.
  • Add an input validation check using the same logic as the draft phase (at least one field must be filled). Under the Case: Input Not Empty branch, add a nested Condition node.

  • Name the branch Case: New Submission and set the expression: Modal Fill Form/Variable/form_id Equal to -1.

  • Path 1: New Submission (form_id == -1):

Add an Insert form node targeting the form table. Set status to Submitted and map the input fields to their corresponding database columns.

  • Path 2: Update Existing Draft to Submitted (form_id != -1):

Add an Update form node targeting the form table.

  • Query criteria: id Equal to Modal Fill Form/Variable/form_id.
  • Parameters: Set status to Submitted. Sync business fields with current input values (Input Field 1/Value and Input Field 2/Value).
  • Append a success toast (Submission successful) and close the modal.

Delete Confirmation Modal

To prevent accidental deletion, a separate confirmation modal is used to pass the user's decision back to the calling Actionflow.

  • Create a new modal named Modal Confirm Delete. Go to the Data tab in the right sidebar and create:

An Output parameter named is_deleted (Boolean) — this will be returned to the caller.

  • A local Variable also named is_deleted (Boolean) — this tracks which button the user clicks inside the modal.

  • Select the modal root, go to the Outputs panel, and configure a Condition tree:

Case 0: If local variable is_deleted Is true, pass True to the Output.

  • Case 1: If local variable is_deleted Is false, pass False to the Output.

  • Select Button NO. On its OnClick event, add a Set variable node to set is_deleted to False, followed by a Close modal (Close top) node.

  • Select Button YES. On its OnClick event, add a Set variable node to set is_deleted to True, followed by a Close modal (Close top) node.

Record Deletion

The deletion logic checks whether the draft being deleted is currently loaded in the form, and shows a confirmation modal only if it is.

  • Select the Text Delete component inside the list item row and open its OnClick event flow.
  • Add a Condition node at the start:

Case: Active Draft — Test if Modal Fill Form/Variable/form_id Equal to List/Data source/Current item/id.

  • Path 1: Delete the draft currently open in the form (requires confirmation):

Add an Open custom modal node targeting Modal Confirm Delete.

  • In the On Modal Closed callback, add a Condition node.
  • Case: Confirm Delete — Check if the output variable Modal/Modal Confirm Delete/is_deleted is Is true.

  • If true, add an Update form node. Query criteria: id Equal to List/Data source/Current item/id. Set status to Deleted.

  • Add a Set variable node. Scope: Page and component. Set form_id back to -1 to reset the form to Create Mode.
  • Add a success toast: Draft successfully deleted.

  • Path 2: Delete a draft that is not currently open (no confirmation):

In the Case: Inactive Draft branch, add an immediate Update form node. Query criteria: id Equal to List/Data source/Current item/id. Set status to Deleted.

  • Add a success toast: Draft successfully deleted.

The draft is soft-deleted (status = Deleted) and remains in the database. The list uses Subscription, so the UI updates automatically when the record changes.

Loading a Draft into the Form

The "Apply" link loads a draft's data back into the form inputs and sets the form_id variable so the user can continue editing.

  • Select the Text Apply link inside the list row template.
  • Under its OnClick event, add a Set variable node. Target form_id and set its value to List/Data source/Current item/id.
  • Add two Set input value nodes to load data back into the UI fields:

Target Input Field 1 and set its value to List/Data source/Current item/field_1.

  • Target Input Field 2 and set its value to List/Data source/Current item/field_2.

Verification

Step 1: Login Required Test

  • Click the Preview icon in the top navigation bar to open the development sandbox.
  • Use the simulator toolbar at the base of the screen and click Restore user to logged out state to clear the session.
  • Click the main page Create button.
  • Expected Result: The form modal stays closed. A warning toast appears: Please log in first.
  • Now log in using the Login simulation panel on the lower action bar — select the simulated user role Logged-in User.

Step 2: Create a New Draft

  • Click the Create button again.
  • Expected Result: Modal Fill Form opens with form_id = -1.
  • Enter text in Input Field 1 and a number in Input Field 2.
  • Click Save as Draft.
  • Expected Result: A success toast appears: Draft saved successfully. The draft list updates and a new record appears. The form_id is now set to the new record's ID (e.g., 1).

Step 3: Save a Second Draft

  • Change the values in the input fields.
  • Click Save as Draft again.
  • Expand the draft list by clicking the Drafts toggle.
  • Expected Result: The draft list now shows two items. The count reads: Current number of drafts: 2.

Step 4: Load a Draft, Then Delete It

  • Click the Apply link on the first draft row (ID 1).
  • Expected Result: The input fields are populated with the values from draft 1.
  • Click the Delete link on the same row.
  • Expected Result: Because this draft is currently open in the form, the Modal Confirm Delete confirmation dialog appears.
  • Click YES.
  • Expected Result: The modal closes. The form_id resets to -1. The draft no longer appears in the list. The draft count decreases by one.

Step 5: Delete a Draft That Is Not Currently Open (If you have multiple drafts)

  • Create a new draft (any values) and save it. Note its ID.
  • Click Apply on a different draft (not the one you just created) to load it into the form.
  • In the draft list, click Delete on the draft you just created (the one that is not currently loaded).
  • Expected Result: The draft is deleted immediately (no confirmation dialog). The form_id remains unchanged (still pointing to the draft you loaded). The list updates and the count decreases.

Step 6: Switch Between Drafts (If you have multiple drafts)

  • Create two drafts (Draft A and Draft B).
  • Click Apply on Draft A. Verify that form_id becomes A's ID and the inputs are populated with A's values.
  • Without saving or deleting, click Apply on Draft B.
  • Expected Result: The inputs now show B's values. The form_id has switched to B's ID. Draft A remains in the list unchanged.

That covers the manual approach. Next, here's the same feature built with real-time saving instead — no explicit "Save Draft" button required.

Real-time Draft Saving for Forms

Demo Project

Clone the Real-time Draft Saving project

Introduction

  • Goal: Build a form system that prevents data loss by saving user inputs in real time, eliminating the need for manual save actions.
  • Use Cases: Long-text editing, multi-step government applications, online examinations, and any scenario where users are at high risk of losing data due to accidental page closure or network interruptions.
  • Core Logic: When users click "Create", the system checks for an existing blank draft. If none exists, it inserts a new record; if one exists, it reuses that ID. When input fields lose focus (On blur), the system automatically updates the record and changes its status from Blank Draft to Filled Draft. A dedicated list panel displays all historical drafts for easy recovery.

Steps

This tutorial uses pre-styled layout blocks from the "Common UI Presets" template page. These preset elements provide basic styling and layout only; they contain no conditional logic, data bindings, or Actionflows. You can copy them directly to skip manual styling and focus on the core logic.

Data Model

To implement the draft system, create a table in the database to store user progress and track status.

Table Name: form

Navigate to the Data tab in the Top Navigation Bar to configure this table.

Field Name

Type

Note

id

Bigint

Auto-generated, unique identifier

status

Text

Enumerated values: Blank Draft, Filled Draft, Submitted, Deleted

field_1

Text

Business input field 1

field_2

Bigint

Business input field 2

account_id

Bigint

Foreign key referencing id in the account table

Relationship Mapping: The account table and form table have a One-to-Many relationship (one account owns multiple form/draft records).

Status Flow:The status field governs the entire lifecycle of a record: - Blank Draft: A placeholder created when the user opens the modal for the first time. - Filled Draft: The user has filled in at least one field and the draft has been auto-saved. - Submitted: The form has been officially submitted. It no longer appears in the draft list. - Deleted: The draft has been soft-deleted. It no longer appears in the draft list.

Page-Level Data Source

Configure a data source at the Page Form Drafts level to check in real time whether a blank draft already exists for the current logged-in user.

  • Open the Data panel on the left sidebar and click + next to Data Sources.
  • Set Name to source_form_empty_draft.
  • Target Table: Select form.
  • Request Type: Query.
  • Limit: 1.
  • Query Criteria (combined with And):

account_id Equal to Logged in user/id

  • status Equal to "Blank Draft"

Reusing Blank Drafts:This logic ensures that each logged-in user has at most one Blank Draft record at any given time. When the user clicks Create again, the system simply reuses the existing blank record instead of generating useless empty entries.

Create Button Actionflow

Select Button Create on the main page and configure its OnClick event. This actionflow handles login validation and blank draft deduplication.

  • Login Check: Add a Condition node.

Name the branch Case: Guest.

  • Condition: getIsLoggedIn is false.
  • Action: Add a Show toast node with the message Please log in first.

  • Blank Draft Check: Under the Case: Logged In branch, add another nested Condition node.

Path 1 - No Blank Draft: Set the condition to source_form_empty_draft/id is null.

Insert Data: Target table form. Set status to "Blank Draft" and bind account_id to Logged in user/id.

  • Open Custom Modal: Select Modal Fill Form. Bind the input parameter empty_draft_form_id to the id returned by the previous Insert action.
  • Refresh Data Source: Target the page-level data source source_form_empty_draft.

  • Path 2 - Blank Draft Exists: Set the condition to always (default branch).

Open Custom Modal: Select Modal Fill Form. Bind the input parameter empty_draft_form_id directly to source_form_empty_draft/id.

  • Refresh Data Source: Target the page-level data source source_form_empty_draft.

Modal State Management

The modal needs to receive and store the draft ID passed from the main page.

  • Input Parameter: Select the root of Modal Fill Form. Go to the Data tab in the right sidebar.

Click + next to Input.

  • Set Name to empty_draft_form_id and Type to Bigint.

  • Local Variable: Still in the Data tab, click + next to Variable.

Set Name to form_id and Type to Bigint. (Default value can be left empty.)

  • On Page Load:

Add a Set variable action.

  • Target: form_id.
  • Value: Bind to Input/empty_draft_form_id.

Input Components and Real-time Saving

Configure the two input fields inside the modal. When users finish typing and move away (blur event), the data is saved automatically.

  • Input Field 1: Select the text input component. Keep Input value type as Text.
  • Input Field 2: Select the text input component. Switch Input value type to Bigint.

  • Configure Auto-Save on Blur for Field 1:

Select Input Field 1 and add an event under On blur.

  • Add a Condition node.

Path 1 - Input is Empty: Set the condition to: Input Field 1/Value Is null AND Input Field 2/Value Is null.

Action: Show toast with message Please fill in at least one field.

  • Path 2 - Input Not Empty: Set the condition to always.

Update Data: Target table form. Query criteria: id Equal to Variable/Modal Fill Form/form_id.

  • Parameters: Set status to "Filled Draft". Bind field_1 to Input Field 1/Value.
  • On Success Action: Show toast with message Draft saved successfully.

  • Configure Auto-Save on Blur for Field 2:

Select Input Field 2 and add an event under On blur.

  • Add a Condition node with the same empty-value logic as above.
  • Path 1 - Input is Empty: Same intercept logic as above.
  • Path 2 - Input Not Empty: Update Data with the same query criteria (id equal to form_id). Set status to "Filled Draft" and bind field_2 to Input Field 2/Value. Show toast Draft saved successfully.

Dual-field Empty Intercept:Both input fields must be empty for the interception to trigger. This validation is configured independently on both On blur events, ensuring users cannot bypass the check by only interacting with one field.

Historical Drafts Panel

Create a panel that displays all saved drafts (status = "Filled Draft") belonging to the current user.

  • Draft Counter:

Select the Text: Current number of drafts component.

  • Bind its Content to Logged in user/form/Count.
  • Add a local filter: status Equal to "Filled Draft".

Logged in user/form/Count updates when user data loads or refreshes. The draft list uses Subscription (see below), so we add a Refresh current user data action inside the list's On subscription success event to keep the counter accurate.

  • Draft List Data Source:

Select the List component inside the modal.

  • Set Request type to Subscription.
  • Target Table: form.
  • Query Criteria (combined with And):

account_id Equal to Logged in user/id.

  • status Equal to "Filled Draft".

  • On subscription success: Add a Refresh current user data action to keep the draft counter accurate.

  • Expand/Collapse State Control:

Select the Conditional View Drafts container. It contains two cases: Case Closed and Case Extended.

  • Expand: Inside Case Closed, select Button Drafts. Under its OnClick, add:

Switch conditional view → Target Conditional View Drafts, switch to Case Extended.

  • Switch conditional view → Target the secondary conditional view (the one wrapping the list), switch to Case Extended.

  • Collapse: Inside Case Extended, select Button Drafts. Under its OnClick, add:

Switch conditional view → Target Conditional View Drafts, switch to Case Closed.

  • Switch conditional view → Target the secondary conditional view, switch to Case Closed.

Synced Conditional Views:This setup uses two conditional view components that must be switched simultaneously. The first controls the button state (Closed/Extended), and the second controls the visibility of the draft list. Using two Switch conditional view actions in the same event ensures they stay in sync.

  • Load Draft (Apply):

Inside the list row, select Text Apply.

  • Under its OnClick, add these actions in order:

Set variable: Target form_id. Set value to Data source/List/Current item/id.

  • Set input value: Target Input Field 1. Set value to Data source/List/Current item/field_1.
  • Set input value: Target Input Field 2. Set value to Data source/List/Current item/field_2.

  • Delete Draft (with Editing Lock):

Inside the list row, select Text Delete.

  • Under its OnClick, add a Condition node.

Path 1 - Editing Protection: Set condition to Variable/Modal Fill Form/form_id Equal to Data source/List/Current item/id.

Action: Show toast with message Cannot delete a draft that is being edited.

  • Path 2 - Safe to Delete: Set condition to always.

Update Data: Target table form. Query criteria: id Equal to Data source/List/Current item/id.

  • Parameters: Set status to "Deleted".
  • On Success Action: Show toast with message Draft deleted successfully.

Form Submission

Configure the final submission and exit actions.

  • Apply Button (Submit):

Select Button Apply inside the modal.

  • Under its OnClick, add an Update Data action.
  • Target Table: form.
  • Query Criteria: id Equal to Variable/Modal Fill Form/form_id.
  • Parameters: Set status to "Submitted".
  • Success Actions:

Show toast: Submission successful.

  • Close modal: Mode CLOSE_ON_TOP.

  • Cancel Button (Exit):

Select Button Cancel.

  • Under its OnClick, add a Close modal action with Mode CLOSE_ON_TOP.

Verification

Step 1: Verify Blank Draft Reuse Logic

  • Use the Login Simulation toolkit at the bottom bar to select a test account.
  • Click the Create button to open the form modal.
  • Leave both input fields completely empty and click Cancel.
  • Click Create again.
  • Expected Result: The system does not create a new record. The Current form ID displayed at the top of the modal is identical to the previous one, confirming the data source successfully executed the reuse logic.

Step 2: Verify Auto-Save on Blur

  • Inside the modal, type some text into Input Field 1.
  • Click any blank area outside the input to trigger the blur event.
  • Expected Result: A toast appears: Draft saved successfully. In the database, the record's status changes from Blank Draft to Filled Draft.
  • Clear Input Field 1 so both fields are completely empty, then click outside to trigger blur.
  • Expected Result: An intercepting toast appears: Please fill in at least one field., and the data is not saved.

Step 3: Verify Draft Recovery and Editing Lock

  • Expand the draft panel and select any historical draft from the list, then click Apply.
  • Expected Result: The values in both input fields refresh to match the historical record. The internal form_id variable updates to the selected record's ID.
  • In the draft list, click Delete next to the draft currently being edited.
  • Expected Result: The system triggers the editing lock protection and shows: Cannot delete a draft that is being edited.

Step 4: Verify Submission Status Change

  • Click Apply (Submit) inside the modal.
  • Expected Result: A toast appears: Submission successful. The modal closes. The submitted record no longer appears in the draft list. In the database, the record's status is now Submitted.

Step 5: Verify Deletion Status Change

  • Click Create to open a new form modal. Fill in at least one field and let it auto-save to create a new Filled Draft.
  • In the draft list, click Delete on this new draft (make sure it is not currently loaded in the editor).
  • Expected Result: A toast appears: Draft deleted successfully. The record disappears from the list. In the database, the record's status is now Deleted.
Field Name Type Note
id Bigint Auto-generated, unique identifier
status Text Enumerated values: Blank Draft, Filled Draft, Submitted, Deleted
field_1 Text Business input field 1
field_2 Bigint Business input field 2
account_id Bigint Foreign key referencing id in the account table

Try It Yourself

Both projects are ready to clone and explore in the Momen editor:

Once you have a project open, try swapping in your own table fields, or combine ideas from both methods — for example, auto-saving on blur while still keeping an explicit "Save Draft" button as a fallback. If you're building forms beyond drafts, dynamic multi-choice forms and the Actionflow guide are good next reads.

Conclusion

Draft saving comes down to one design choice: let the user decide when to save, or save automatically as they type. Momen's AI Copilot can scaffold either pattern — the data model, the Actionflow logic, and the UI state — directly inside the editor. Clone one of the projects above and adapt it to your own form.

Top comments (0)