Skip to content

Partial submissions

Save progress while people fill a form in, see how many finish, and find the field where the rest stop.

With partial submissions on, the embed widget and the hosted form page save what people have typed so far. quickforms uses those saves to build a completion funnel: how many people started, how many finished, how many gave up, and the field they were on when they stopped.

Partials are never validated against the schema and never delivered. No emails, webhooks or destinations are sent for them.

Turning it on

In the dashboard, open the form and switch on Partial submissions. Or set settings.partials through the API or MCP:

Key Default Description
partials.enabled false Save progress from the embed and hosted page.
partials.abandon_after_minutes 30 How long a partial can go without a save before it is marked abandoned. 5 to 1440.
partials.retention_days 30 How long partials are kept. 1 to 90. See retention.
Turn on partialsbash
curl -X PATCH https://YOUR-QUICKFORMS-HOST/api/v1/forms/FORM_ID \
  -H "Authorization: Bearer $QUICKFORMS_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"settings": {"partials": {"enabled": true}}}'

The form’s public GET /f/{form}/schema.json includes "partials": true or false, which is how the embed and hosted page know whether to save progress.

How progress is saved

The embed and hosted page give each visitor an anonymous session ID per form, kept in the browser’s sessionStorage (so it lasts for the tab). No cookies are set.

  • Embed widget: saves about 1.5 seconds after the person stops typing. Control fields (names starting with _) and the Turnstile token are left out.
  • Hosted page: saves about 1.5 seconds after typing stops, and straight away when moving between steps. Files are never sent with progress.

Each save posts the answers so far and the last field the person touched. On submit, the same session ID is sent as the _session control field so the partial can be marked completed. After a successful submit the session is cleared, so the next fill starts a new partial.

POST /f/{form}/progress

You can also save progress from your own form. No authentication is needed: it is a public endpoint like POST /f/{form}.

Field Required Description
session Yes Your anonymous ID for this visitor and form. 8 to 64 characters of A-Z, a-z, 0-9, _ and -.
data Yes An object of answers so far. Can be empty.
last_field No The name of the field the person last touched, up to 64 characters.
link No A personal link token, as an alternative to the X-Quickforms-Link header.

Send X-Quickforms-Sandbox with the form’s sandbox key to save sandbox progress against the draft. Sandbox partials are kept out of the live funnel.

curl -X POST https://YOUR-QUICKFORMS-HOST/f/FORM_ID/progress \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"session": "3f9c2a7e5b1d4c8a", "data": {"name": "Ada Lovelace", "email": "ada@exa"}, "last_field": "email"}'
{ "id": "01jcb6t2w8n4k7q9r3s5v1x0yz", "status": "in_progress" }

What is kept:

  • Only the form’s own fields. Unknown keys and file fields are dropped.
  • Strings are trimmed and capped at 5000 characters. Lists keep up to 100 plain values, each capped at 500 characters. Empty values are dropped.
  • One save can hold up to 64 KB. A larger one gets a 422.
  • A form can start 1000 new sessions an hour by default, set with QUICKFORMS_PARTIALS_NEW_SESSIONS_PER_HOUR. Past that, new sessions get a 429 and existing ones keep saving.
  • last_field is only updated when it names a field on the form.
  • The visitor’s user agent and referer are stored in the partial’s meta.

Each save replaces the partial’s answers for that session. A save to an abandoned partial puts it back to in_progress.

Status When
202 Saved. Returns the partial’s id and status.
204 Ignored: partials are off, the form is inactive or unpublished (and no sandbox key was sent), or a honeypot field in data is filled.
422 session is missing or invalid, or data is missing.
429 More than 120 progress or view requests a minute from one IP.

Completion

When a submission to POST /f/{form} includes _session, the partial with that session ID is marked completed, with completed_at and the new submission_id. _session is a control field, so it is never stored in the submission’s data.

Once a partial is completed it stays that way. Late saves for the same session are ignored.

JSON submission with _sessionjson
{
  "name": "Ada Lovelace",
  "email": "ada@example.com",
  "message": "Hello",
  "_session": "3f9c2a7e5b1d4c8a"
}

Statuses

Status Meaning
in_progress Saved recently and not submitted yet.
abandoned No save for abandon_after_minutes. abandoned_at is set.
completed The real submission arrived with the same _session.

Retention

A scheduled job (quickforms:abandon-partials) runs every five minutes. It:

  • Marks in_progress partials abandoned once their last save is older than the form’s abandon_after_minutes.
  • Deletes in_progress and abandoned partials whose last save is older than the form’s retention_days.
  • Deletes completed partials once they were completed more than retention_days ago.

Funnel

GET /api/v1/forms/{form}/funnel

Started, completed and abandoned counts for partials created in the window, plus where people stop: the last_field of abandoned partials, most common first.

Query Description
days 1 to 365, default 30.
sandbox true for sandbox partials. Live partials by default.
curl -G https://YOUR-QUICKFORMS-HOST/api/v1/forms/FORM_ID/funnel \
  -H "Authorization: Bearer $QUICKFORMS_TOKEN" \
  -H "Accept: application/json" \
  --data-urlencode "days=30"
{
  "data": {
    "started": 4,
    "completed": 1,
    "abandoned": 3,
    "in_progress": 0,
    "completion_rate": 0.25,
    "drop_off": [
      { "field": "name", "label": "Name", "abandoned": 2 },
      { "field": "email", "label": "Email", "abandoned": 1 }
    ]
  }
}

started is completed + abandoned + in_progress. completion_rate is completed / started, rounded to three decimals, or null when nothing has started. label is the field’s label from the schema, or its name if it has none.

Partials API

All partials endpoints use a team API key. Anything outside the API key’s team returns 404.

GET /api/v1/forms/{form}/partials

A form’s partials, most recently active first.

Query Description
status in_progress, abandoned or completed. Omit for all.
sandbox true or false. Omit for both.
per_page 1 to 100, default 25.
page Page number.
curl -G https://YOUR-QUICKFORMS-HOST/api/v1/forms/FORM_ID/partials \
  -H "Authorization: Bearer $QUICKFORMS_TOKEN" \
  -H "Accept: application/json" \
  --data-urlencode "status=abandoned"
{
  "data": [
    {
      "id": "01jcb6t2w8n4k7q9r3s5v1x0yz",
      "form_id": "01jc8xk2m5t9qw3e7r4y6u8v0p",
      "status": "abandoned",
      "sandbox": false,
      "data": { "name": "Ada Lovelace", "email": "ada@exa" },
      "fields_filled": 2,
      "last_field": "email",
      "link_id": null,
      "submission_id": null,
      "meta": { "user_agent": "Mozilla/5.0 ...", "referer": "https://yoursite.com/contact" },
      "started_at": "2026-11-06T10:20:00.000000Z",
      "last_activity_at": "2026-11-06T10:21:30.000000Z",
      "abandoned_at": "2026-11-06T10:55:00.000000Z",
      "completed_at": null
    }
  ],
  "links": { "first": "...?page=1", "last": "...?page=1", "prev": null, "next": null },
  "meta": { "current_page": 1, "from": 1, "last_page": 1, "per_page": 25, "to": 1, "total": 1 }
}
Field Description
data The answers saved so far.
fields_filled How many fields have a value.
last_field The last field the person touched.
link_id The personal link the partial came through, otherwise null.
submission_id The submission that completed it, otherwise null.
started_at When the first save arrived.
last_activity_at When the latest save arrived.

GET /api/v1/partials/{partial}

One partial, in the same shape, wrapped in data.

DELETE /api/v1/partials/{partial}

Deletes a partial. Returns 204.

Delete a partialbash
curl -X DELETE https://YOUR-QUICKFORMS-HOST/api/v1/partials/PARTIAL_ID \
  -H "Authorization: Bearer $QUICKFORMS_TOKEN" \
  -H "Accept: application/json"

Dashboard

The form page shows a Funnel panel for the last 30 days of live visitors: started, completed, abandoned and in progress, the completion rate, where people stop, and the recent partials, filterable by status. It shows while partials are on, or while there are partials from when they were.

MCP

The MCP server has a read-only list-partial-submissions tool. It takes form_id, and optionally status (in_progress, abandoned or completed), sandbox and days (the funnel window, 1 to 365, default 30). It returns the funnel in the shape above plus up to 50 of the most recently active partials in data.

Turn partials on with settings.partials (enabled, abandon_after_minutes, retention_days) on create-form or update-form.

Type to search…

↑↓ navigate↵ selectEsc close