Thenameda Partial Form Recovery for WPForms is a WordPress addon that captures incomplete WPForms field data when a visitor starts filling a form and leaves before submitting. This guide explains the plugin from a developer’s perspective, based on the actual 1.0.0 source under thenameda-partial-form-recovery-for-wpforms.
Focus keyword: WPForms partial form recovery developer guide
Audience: WordPress / WPForms plugin developers
Plugin version analyzed: 1.0.0 · Requires WordPress 6.2+, PHP 7.4+, WPForms Lite or Pro
1. What the plugin does and its main purpose
The plugin’s purpose is lead recovery for abandoned WPForms. When tracking is enabled globally and on a specific form, the front end watches WPForms inputs and, on abandonment signals (mouse leave, link navigation, tab hide, page hide), posts a sanitized snapshot of field values to WordPress via admin-ajax.php.
Stored records live in a custom table ({prefix}wpfrmaf_entries), appear under a top-level admin menu (Partial Recovery), can be opened with a signed recovery URL that refills fields, and can optionally receive a manual reminder email containing that recovery link.
It also ships a shortcode [wpfrmaf_stats] that prints an abandoned-entry count.
2. How the plugin works internally
Architecture is a classic WordPress singleton bootstrap:
- Main file defines constants and waits for
plugins_loaded. - If WPForms is active, it waits for
wpforms_loaded(or runs immediately if that action already fired). wpfrmaf_init()loads helpers/DB, runsWPFRMAF_DB::maybe_upgrade(), then bootsWPFRMAF::instance().- The singleton wires AJAX, frontend, Settings API, shortcode, and (in admin) menus, list table actions, and builder settings.
Runtime split:
- Front JS detects dirty forms and posts payloads.
- AJAX PHP validates nonces, maps fields, inserts/updates rows.
- DB layer encapsulates
$wpdbCRUD. - Admin lists/views/deletes and can send reminder mail.
- Recovery reads
?wpfrmaf_recover=&wpfrmaf_token=, verifies the token, and localizes fill data into JS.
3. Complete plugin folder and file structure
thenameda-partial-form-recovery-for-wpforms/
├── thenameda-partial-form-recovery-for-wpforms.php # Bootstrap, constants, activation hooks
├── uninstall.php # Drop table + delete options
├── readme.txt
├── index.php # Silence directory listing
├── assets/
│ ├── css/admin.css
│ └── js/
│ ├── front.js # Abandon tracking + recovery fill
│ └── admin.js # Delete confirmations
├── includes/
│ ├── class-wpfrmaf.php # Singleton, wpforms_process_complete
│ ├── class-wpfrmaf-helpers.php # Settings, parsing, security helpers
│ ├── class-wpfrmaf-db.php # Custom table + CRUD
│ ├── class-wpfrmaf-ajax.php # track / remove AJAX
│ ├── class-wpfrmaf-frontend.php # Enqueue + localize recovery data
│ ├── class-wpfrmaf-settings.php # Settings API
│ ├── class-wpfrmaf-shortcode.php # [wpfrmaf_stats]
│ ├── class-wpfrmaf-activator.php
│ ├── class-wpfrmaf-deactivator.php
│ └── admin/
│ ├── class-wpfrmaf-admin-menu.php
│ ├── class-wpfrmaf-admin.php # Delete + send_mail actions
│ ├── class-wpfrmaf-list-table.php # WP_List_Table
│ ├── class-wpfrmaf-builder.php # WPForms builder panel
│ └── views/
│ ├── entries-page.php
│ ├── entry-view.php
│ └── settings-page.php
└── languages/
4. Important PHP, JavaScript, CSS, and other files
Bootstrap file
thenameda-partial-form-recovery-for-wpforms.php defines WPFRMAF_VERSION, WPFRMAF_DIR, WPFRMAF_URL, WPFRMAF_BASENAME, checks WPForms via is_plugin_active(), registers activation/deactivation hooks, and starts the plugin only after WPForms is ready.
Core classes
WPFRMAF— wires modules; deletes abandoned rows on successful submit when configured.WPFRMAF_DB—dbDeltaschema, insert/update/query/delete.WPFRMAF_Helpers— option keys, field exclusion list, serializeArray parsing, email extraction, recovery URL/token helpers.WPFRMAF_Ajax—wpfrmaf_track_abandonedandwpfrmaf_remove_abandoned(logged-in +nopriv).WPFRMAF_Frontend— enqueuesfront.jsand localizes AJAX URL, nonces, and fill payload.WPFRMAF_Settings— Settings API registration and sanitization.WPFRMAF_Builder— adds a Partial Recovery section in the WPForms form builder.
JavaScript
assets/js/front.js is the abandonment engine: listens for input/change, WPForms page changes, mouse leave / touch, visibilitychange, pagehide, and outbound link clicks; prefers navigator.sendBeacon when leaving the page; fills recovery fields; clears storage after wpformsAjaxSubmitSuccess.
assets/js/admin.js only confirms single and bulk deletes.
CSS
assets/css/admin.css styles admin list/detail screens. There is no dedicated front-end stylesheet; recovery is script-driven field filling.
5. How the plugin hooks into WordPress and WPForms
WordPress hooks
plugins_loaded→wpfrmaf_bootstrap()register_activation_hook/register_deactivation_hookwp_enqueue_scripts→ front scriptwp_ajax_*/wp_ajax_nopriv_*→ tracking endpointsadmin_menu,admin_init,admin_enqueue_scriptsadmin_noticeswhen WPForms is missingplugin_action_links_{basename}register_setting/ Settings API callbacksadd_shortcode( 'wpfrmaf_stats' )
WPForms hooks / APIs
wpforms_loaded— safe init after WPForms bootstrapswpforms_builder_settings_sections— add builder tabwpforms_form_settings_panel_content— render panel fields viawpforms_panel_field()wpforms_process_complete— delete abandoned rows after successful processingwpforms()->obj( 'form' )->get()— load form JSON for field mapping and recovery fill- Front JS events:
wpformsReady,wpformsPageChange,wpformsAjaxSubmitSuccess
6. How partial form entries are detected and saved
Detection is client-side. After wpformsReady (or DOM ready if a form already exists), WPFRMAFAbandoned.init() binds:
input/changeonform.wpforms-form :input→ mark dirty andserializeArray()wpformsPageChange→ same for multipage formsmouseleave(and touch equivalents) → attempt send- outbound link
mousedown/click→ send before navigation (may intercept same-window navigations) visibilitychange === 'hidden'andpagehide→ send with beacon when possible
Before sending, JS requires a “meaningful” payload: at least one non-empty wpforms[fields]… value. Duplicate payloads are skipped via a signature of form_id|JSON(forms).
On the server, track_abandoned() additionally requires:
- Valid AJAX nonce
- Global
enable_trackingsetting on - Per-form
wpfrmaf_enablesetting on - Resolvable
form_id - For new rows:
has_meaningful_abandoned_data()(email or non-empty mapped fields)
7. Database / storage mechanism
Storage uses a custom table, not a CPT. Table name: {$wpdb->prefix}wpfrmaf_entries.
Created with dbDelta() on activation and whenever wpfrmaf_db_version ≠ WPFRMAF_DB::DB_VERSION (1.0.0).
Schema (columns)
id— primary keyform_id— WPForms form IDemail— extracted email for remindersform_data— JSON map of field_id → valueip_address,page_urlrecover_token— random 32-char password-style tokenemails_sent,fail_countstatus— defaultabandoneddate_created,date_updated
Indexed on form_id, email, status, date_created.
Inserts sanitize email/IP/URL/token and encode arrays with wp_json_encode(). Queries support search across email, page URL, and IP, with allow-listed ORDER BY columns.
On plugin delete, uninstall.php drops the table and deletes wpfrmaf_settings + wpfrmaf_db_version. Deactivation does not delete data.
8. How AJAX requests are handled
Track abandoned — wpfrmaf_track_abandoned
check_ajax_referer( 'wpfrmaf_abandoned_track', 'wpfrmaf_abandoned_nonce' );
Flow:
- Sanitize
formsarray (serializeArray shape). - Parse into
form_id+ field map via regex on names likewpforms[fields][12],…[],…[first]. - Load per-form settings; abort if tracking disabled.
- Map tracked fields (skip excluded types such as payments, captcha, file upload, layout, etc.).
- Extract email from configured email field or auto-detect.
- If an existing entry ID/token is provided and valid for that form, merge non-empty new values and
update(). - Otherwise
insert()with a newrecover_tokenfromwp_generate_password( 32, false ). - Return JSON
{ entry_id: N }. Front JS stores that ID in memory +sessionStoragekeywpfrmaf_entry_{formId}.
Remove abandoned — wpfrmaf_remove_abandoned
Called after successful AJAX submit. Verifies wpfrmaf_remove_abandoned nonce, then deletes by entry ID (+ optional token) or all abandoned rows for a form ID.
Both actions register wp_ajax_ and wp_ajax_nopriv_ so anonymous visitors can be tracked.
9. How the admin settings work
Global settings (Settings API)
Option name: wpfrmaf_settings. Group: wpfrmaf_settings_group. Screen under Partial Recovery → Settings.
Fields:
enable_tracking(default on)delete_on_submit(default on)enable_reminders(default off)reminder_subject/reminder_message(supports{recovery_link})max_reminders(0–10, default 2)
Sanitization coerces checkboxes to 0/1, runs sanitize_text_field / sanitize_textarea_field, and clamps max_reminders.
Per-form settings (WPForms builder)
Stored inside the WPForms form settings JSON:
wpfrmaf_enable— togglewpfrmaf_email_field— email field ID or empty for auto-detect
Helpers also define wpfrmaf_track_fields (allow-list or null = all fields). The builder UI in 1.0.0 exposes enable + email field only; with no track-fields value saved, mapping tracks all non-excluded field types.
Entries UI
WP_List_Table lists abandoned rows with search, sort, bulk delete (admin referer), and row actions View/Delete. Detail view shows fields, recovery link, and optional “Send reminder email” when reminders are enabled and an email exists.
10. How the frontend functionality works
WPFRMAF_Frontend::enqueue_scripts() skips admin and skips when global tracking is off. It localizes wpfrmaf_abandoned with:
ajaxurl,home_url- track + remove nonces
- optional
wpfrmaf_recover, token, andwpfrmaf_fill_fieldsarray
Recovery fill supports textarea, radio, select (including multi via comma-split), checkbox, name (first/middle/last), email (including [primary]), address (address1 from comma-split), and a default input/textarea/select path. Fill runs immediately, after short timeouts, and again on wpformsReady so late-rendered fields still populate.
11. Data flow: interaction → storage → recovery
- Admin enables global tracking and enables Partial Recovery on a form in the builder.
- Visitor loads a page with that WPForms form;
front.jsinitializes. - Visitor types; JS marks dirty and keeps a serialized snapshot.
- Abandonment event fires → POST to
admin-ajax.php?action=wpfrmaf_track_abandoned(XHR or sendBeacon). - PHP validates, maps fields, inserts/updates
wpfrmaf_entries, returnsentry_id. - JS stores entry ID in
sessionStorageso later updates merge into the same row. - Admin views the entry and may send a reminder (
wp_mail) with a URL containingwpfrmaf_recover+wpfrmaf_token. - Visitor opens the link; frontend verifies token, localizes fill data, JS refills the form.
- On successful submit, JS calls
wpfrmaf_remove_abandoned; PHP also may delete abandoned rows for that form onwpforms_process_completewhendelete_on_submitis enabled.
12. Important WordPress and WPForms hooks/functions used
WordPress: check_ajax_referer, wp_send_json_success, wp_create_nonce, wp_verify_nonce, check_admin_referer, current_user_can, wp_mail, wp_safe_redirect, dbDelta, $wpdb->prepare/insert/update/query, register_setting, hash_equals, wp_generate_password, add_query_arg, sessionStorage (client).
WPForms: wpforms_loaded, wpforms_process_complete, builder section/panel hooks, wpforms_panel_field, form object API, front events listed above.
Filter provided for capability: wpfrmaf_admin_capability (default manage_options).
13. Security measures
- Nonces: AJAX track/remove use
check_ajax_referer; admin delete/send mail use action-specific nonces; bulk delete uses list-table referer. - Capabilities: Admin pages and actions require
manage_options(filterable). - Sanitization:
sanitize_text_field,sanitize_email,sanitize_key,sanitize_textarea_field,absint,esc_url_raw,map_deep,wp_unslash. - Escaping: Admin views and shortcode use
esc_html,esc_attr,esc_url,esc_textarea. - Recovery tokens: Compared with
hash_equals; fill data only localized when token matches. - Field exclusions: Payment, captcha, file upload, signature, HTML, pagebreak, layout, repeater, and similar types are never mapped for storage/fill.
- Direct file access:
ABSPATHguards and directoryindex.phpsilencers; uninstall gated byWP_UNINSTALL_PLUGIN.
Note: track/remove AJAX are intentionally available to logged-out users. Protection relies on nonces, per-form enablement, meaningful-data checks, and token verification when updating a recovered entry—not on capability checks.
14. Error handling and validation
- Missing WPForms → admin error notice; plugin does not boot feature classes.
- AJAX failures often return soft success with
entry_id: 0(disabled tracking, missing form, empty payload) to avoid noisy front-end errors. - Invalid recovery token → entry treated as new / fill data cleared.
- Form ID mismatch between stored entry and posted form resets the entry context.
- Reminder send fails closed if reminders disabled, email invalid, or
emails_sent >= max_reminders; incrementsfail_countwhenwp_mailreturns false. - Admin unauthorized access →
wp_die; bad nonce →wp_diewith security message. - JS wraps
sessionStoragein try/catch to ignore private-mode failures.
15. Complete step-by-step workflow
- Activate → create table, seed default settings option if missing, flush rewrites.
- Configure global options under Partial Recovery → Settings.
- Enable on a form: WPForms builder → Settings → Partial Recovery.
- Publish form on a page; front script loads.
- Partial fill → abandon event → AJAX/beacon → DB row.
- Admin reviews under Partial Recovery → Entries.
- Optional reminder from entry detail; visitor returns via recovery URL.
- Complete submit → remove AJAX + optional
wpforms_process_completecleanup. - Delete plugin → uninstall drops table and options.
16. How a developer could build a similar plugin from scratch
- Create a bootstrap file with dependency checks for WPForms.
- On activation, create a custom table with
dbDeltaand store a schema version option. - Add WPForms builder settings via
wpforms_builder_settings_sections+ panel content. - Enqueue a front script that serializes
form.wpforms-formand posts on leave/hide events; usesendBeaconfor unload reliability. - Register
wp_ajax_nopriv_handlers with nonces; sanitize and map only allow-listed fields. - Persist JSON field maps with a random recover token; expose recovery via query args +
hash_equals. - Build an admin
WP_List_Tableover$wpdbqueries. - Hook
wpforms_process_complete(and AJAX success) to remove recovered/abandoned rows. - Use Settings API for global toggles; keep destructive cleanup in
uninstall.phponly.
17. Common issues and troubleshooting
- No rows saved: Confirm WPForms is active, global tracking is on, and the form’s Partial Recovery toggle is enabled. Empty forms never insert.
- Beacon vs XHR: On tab close, beacon may be used; entry ID response may not update
sessionStorageuntil a later XHR update. - Recovery not filling: Token must match; excluded field types are skipped; wait for
wpformsReadyretries. - Reminder not sending: Enable reminders in settings, ensure a valid email was captured, respect
max_reminders, and verify site mail transport (local WAMP often cannot send). - Rows remain after submit: Check
delete_on_submit; Lite may not fire entry creation the same way, but this plugin still hookswpforms_process_completeand front AJAX remove. - Cron expectation: Deactivator clears a hook named
wpfrmaf_send_reminder_emails, but 1.0.0 does not schedule automated reminder cron jobs—reminders are sent manually from the entry screen.
18. Best practices used in the plugin
- Singleton + modular classes with clear responsibilities
- Dependency-aware bootstrap (
plugins_loaded→wpforms_loaded) - Custom table for high-volume partial entries instead of posts
- Schema version option +
maybe_upgrade() - Settings API for nonces/capabilities/sanitization
- Prepared statements and typed
$wpdb->insert/updateformats - Token comparison via
hash_equals - Sensitive field-type exclusion list
- Unload-safe tracking with
sendBeacon - Admin assets loaded only on plugin screens
- Data preserved on deactivate; removed only on uninstall
- i18n-ready strings and text domain
Conclusion
Thenameda Partial Form Recovery for WPForms is a practical reference for building WPForms addons that combine front-end abandonment detection, secure AJAX, a custom $wpdb table, builder settings, Settings API options, and recovery UX. Study front.js for the browser workflow and WPFRMAF_Ajax + WPFRMAF_DB for the server contract—those three files define most of the product behavior.
Plugin listing: Thenameda Partial Form Recovery for WPForms on WordPress.org