Save & Resume
Core Forms allows users to save their progress on long forms and resume later from a unique URL. Partial entries are tracked automatically, and drafts expire after a configurable period.
Enabling Save & Resume
Enable the feature per form in Settings > Save & Resume:
- Open the form editor in WordPress admin.
- Go to the Settings tab.
- Check Enable Save & Resume.
- Optionally configure: - Draft expiry (days) -- default is 30. - Show save button -- displays an explicit "Save Progress" button. - Auto-save -- saves automatically on input changes (enabled by default).
How It Works
Draft Token
When a user begins filling out a form with Save & Resume enabled, Core Forms generates a unique draft token:
- 64-character hexadecimal string generated via
bin2hex(random_bytes(32)). - Stored in the
draft_tokencolumn of thewp_cf_submissionstable. - The submission row is marked with
status = 'draft'until final submission.
Example token: a3f8c1d9e4b7...2f1a (64 chars)
Database Schema
The wp_cf_submissions table includes these draft-related columns:
draft_token VARCHAR(64) NULL -- Unique resume token
status VARCHAR(20) 'new' -- 'draft' for saved progress, 'new' for completed
updated_at DATETIME NULL -- Last auto-save timestamp
Auto-Save Behavior
When auto-save is enabled, Core Forms saves the current form state automatically:
On Input Change (5-Second Debounce)
After a user stops typing or changes a field value, a 5-second debounce timer starts. When it fires, the current form data is saved via AJAX:
// Simplified auto-save logic
let saveTimer;
form.addEventListener('input', function () {
clearTimeout(saveTimer);
saveTimer = setTimeout(() => saveDraft(form), 5000);
});
On Step Change
In multi-step forms, a draft save is triggered immediately when the user navigates to the next or previous step, without waiting for the debounce timer.
On Page Unload
When the user navigates away or closes the tab, Core Forms uses navigator.sendBeacon() to send a final save request. This ensures partial entries are captured even if the user abandons the form:
window.addEventListener('beforeunload', function () {
const data = new FormData(form);
data.append('action', 'cf_save_draft');
data.append('_cf_draft_token', token);
navigator.sendBeacon(cfAjax.url, data);
});
AJAX Endpoints
Save Draft: cf_save_draft
Saves the current form data as a draft.
Request:
POST /wp-admin/admin-ajax.php
Content-Type: application/x-www-form-urlencoded
action=cf_save_draft
_cf_nonce=<nonce>
_cf_form_id=<form_id>
_cf_draft_token=<token> // Empty on first save (server generates one)
first_name=Jane
email=jane@example.com
Response (first save):
{
"success": true,
"data": {
"draft_token": "a3f8c1d9e4b7...2f1a",
"resume_url": "https://example.com/contact/?cf_resume=a3f8c1d9e4b7...2f1a",
"message": "Your progress has been saved."
}
}
Response (subsequent saves):
{
"success": true,
"data": {
"draft_token": "a3f8c1d9e4b7...2f1a",
"message": "Progress updated."
}
}
Load Draft: cf_load_draft
Loads a saved draft to pre-fill form fields.
Request:
POST /wp-admin/admin-ajax.php
Content-Type: application/x-www-form-urlencoded
action=cf_load_draft
_cf_nonce=<nonce>
_cf_form_id=<form_id>
_cf_draft_token=a3f8c1d9e4b7...2f1a
Response:
{
"success": true,
"data": {
"fields": {
"first_name": "Jane",
"email": "jane@example.com"
},
"current_step": 2,
"updated_at": "2026-04-08 14:30:00"
}
}
Resuming a Draft
Users resume their form by visiting the form page with the cf_resume query parameter:
https://example.com/contact/?cf_resume=a3f8c1d9e4b7...2f1a
When Core Forms detects this parameter:
- Calls
cf_load_draftvia AJAX with the token. - Pre-fills all saved field values into the form.
- In multi-step forms, navigates to the last active step.
- Displays a notice: "Welcome back! Your saved progress has been restored."
Sharing the Resume URL
After the first auto-save, the resume URL is displayed to the user in a dismissible notice below the form. Users can copy this URL to return later or continue on another device.
Draft Expiry
Drafts expire after a configurable number of days (default: 30). Expired drafts are cleaned up automatically.
Configuration
Set the expiry period per form in Settings, or globally via filter:
// Change default draft expiry to 14 days
add_filter('cf_draft_expiry_days', function ($days, $form_id) {
return 14;
}, 10, 2);
Auto-Cleanup
Core Forms registers a daily cron event (cf_cleanup_expired_drafts) that deletes draft submissions older than the configured expiry:
// Runs daily via wp_cron
function cf_cleanup_expired_drafts() {
global $wpdb;
$table = $wpdb->prefix . 'cf_submissions';
$wpdb->query(
$wpdb->prepare(
"DELETE FROM {$table} WHERE status = 'draft' AND updated_at < %s",
gmdate('Y-m-d H:i:s', strtotime("-{$expiry_days} days"))
)
);
}
Partial Entry Tracking
Even without Save & Resume enabled, Core Forms can track partial entries using the sendBeacon unload handler. This is useful for analytics -- understanding where users abandon your forms.
To enable partial entry tracking without the full Save & Resume feature:
add_filter('cf_track_partial_entries', '__return_true');
Partial entries are saved with status = 'partial' and do not generate a resume URL. They appear in the submissions list with a "Partial" badge.
Security Considerations
- Draft tokens are cryptographically random (64 hex chars = 256 bits of entropy).
- Tokens are not tied to a user session, so drafts work for anonymous users.
- The
cf_load_draftendpoint validates that the token matches the requestedform_id. - Rate limiting applies to save/load endpoints (default: 30 requests per minute).
- Expired drafts are permanently deleted, including any associated file uploads.
Example: Multi-Step Form with Save & Resume
{
"settings": {
"save_resume": {
"enabled": true,
"auto_save": true,
"show_save_button": true,
"expiry_days": 14
}
},
"layout": {
"type": "multi-step",
"steps": [
{ "id": "basics", "title": "Basic Info", "fields": ["name", "email"] },
{ "id": "details", "title": "Details", "fields": ["company", "role", "team_size"] },
{ "id": "project", "title": "Project", "fields": ["description", "budget", "timeline"] }
]
}
}
With this configuration, users filling out a lengthy project intake form can save at any step and return later via their unique resume URL.