Skip to content
Advanced Features

Multi-step drafts

Build multi-step forms with Qubeforms encrypted drafts. Create and update saved progress, protect draft tokens, handle expiration, and complete the final submission.

1. Plan your draft flow

Drafts let you save text fields before the visitor sends the final submission. They are available on every plan and use the same public form key and allowed origins as submissions. A draft is not an inbox submission and does not send notifications or webhooks. Build your own step controls and save on meaningful transitions rather than on every keystroke. File attachments must be sent with the final submission; draft endpoints do not save files.

2. Create a draft

Send JSON to POST /v1/{publicKey}/drafts, including at least one ordinary form field. _cf_step is an optional positive step number and defaults to 1. Keep the configured honeypot empty. Replace the public key and approved origin in this example. HTTP 201 returns draft_id, draft_token, and expires_in in seconds. Save both identifiers for later requests; the token is returned when the draft is created.

curl --fail-with-body \
  'https://api.qubeforms.io/v1/cf_pk_YOUR_KEY/drafts' \
  --header 'Origin: https://www.example.com' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --data '{"email":"alex@example.com","_cf_step":1,"fax_number":""}'

3. Save the next step

Use PATCH or PUT on /v1/{publicKey}/drafts/{draftId} and send the token in X-Qubeforms-Draft-Token. The examples use QUBEFORMS_DRAFT_ID and QUBEFORMS_DRAFT_TOKEN as local variables containing the create response. Updates merge supplied fields into saved values; omitted fields remain. Neither method replaces the whole draft. The recorded step can increase but does not decrease. A successful update returns HTTP 200 with saved: true, the draft ID, and the step.

curl --fail-with-body --request PATCH \
  "https://api.qubeforms.io/v1/cf_pk_YOUR_KEY/drafts/${QUBEFORMS_DRAFT_ID}" \
  --header 'Origin: https://www.example.com' \
  --header 'Content-Type: application/json' \
  --header "X-Qubeforms-Draft-Token: ${QUBEFORMS_DRAFT_TOKEN}" \
  --data '{"message":"Please contact me about a project.","_cf_step":2}'

4. Finish the submission

Send the final request to POST /v1/{publicKey}/submissions with _cf_draft_id and _cf_draft_token. Saved fields are merged with the final fields, and final values override matching saved values. Normal submission limits, origin rules, and spam checks apply; include a fresh CAPTCHA response if the form requires it. Use FormData or multipart encoding for attachments. The draft is removed after the submission is stored successfully. This example completes the draft without attachments.

curl --fail-with-body \
  'https://api.qubeforms.io/v1/cf_pk_YOUR_KEY/submissions' \
  --header 'Origin: https://www.example.com' \
  --header 'Accept: application/json' \
  --form "_cf_draft_id=${QUBEFORMS_DRAFT_ID}" \
  --form "_cf_draft_token=${QUBEFORMS_DRAFT_TOKEN}" \
  --form 'message=Please contact me about a project.'

5. Handle expiration and resuming

The default draft lifetime is 72 hours from creation; use the returned expires_in rather than assuming a fixed duration. Updating a draft does not extend its deadline. There is currently no customer GET endpoint to retrieve a draft’s saved fields and no automatic resume-email feature. Your form must retain the state it needs to redisplay previous steps. Keep the draft token private to that visitor or your backend, never in a public URL or shared log. If you persist state across page reloads, choose storage appropriate to the sensitivity of the fields and clear it after completion.

6. Handle draft errors and quotas

HTTP 403 indicates an origin restriction. HTTP 404 means the draft or form is unavailable, the token is wrong, or the draft belongs to another form. HTTP 410 indicates an expired draft that still exists in storage; once purged it returns 404. HTTP 422 can mean invalid fields or a completed honeypot. HTTP 429 means a save or storage limit was reached; follow Retry-After. Defaults allow 60 draft requests per form/IP per 10 minutes and daily draft creation ceilings of 500 on Free, 2,500 on Starter, 10,000 on Pro, and 25,000 on Business. Existing draft updates do not consume a new creation slot.

7. Test before publishing

Create a draft, update one value while omitting another, and finish it. Confirm the final submission contains both the preserved field and the updated value. Test a wrong token and an expired draft, and give the visitor a clear way to restart. Keep file selections in the browser until final submission; after a reload the visitor may need to select them again. If a final response is uncertain, check the inbox before resubmitting.

Keep private credentials private

Public form keys can appear in browser code. Read keys, management credentials, and webhook signing secrets must stay on the server.