ARPHost CF7 Submission Archive
ARPHost CF7 Submission Archive
Description
ARPHost CF7 Submission Archive captures every Contact Form 7 submission and:
- Stores it in a dedicated database table (survives mail delivery failures — capture happens before mail is sent)
- Optionally forwards it as JSON to a Google Apps Script Web App, which appends it to a Google Sheet
- Provides an admin browser (filter by form, search, view detail, delete, resend to Sheets)
- Exports submissions to CSV
- Tracks per-submission Google Sheets delivery status, with one-click resend
No Google API keys, OAuth, or service accounts required — the Sheets integration uses a free Google Apps Script Web App you deploy in about two minutes (companion script included in google-apps-script-example.js).
Privacy
IP address and user-agent storage are off by default. Enable them in Settings only if your privacy policy covers it.
Developer hooks
cf7dbgs_capture_submission— filter; return false to skip capturing a submissioncf7dbgs_store_fields— filter fields before DB storagecf7dbgs_webhook_payload— filter the JSON payload sent to the webhookcf7dbgs_webhook_args— filter wp_remote_post argscf7dbgs_after_store— action fired after a row is stored
External services
This plugin connects to Google services to forward Contact Form 7 submissions to a Google Sheet. Both connections are off by default (Settings Send to Google Sheets) and use your own Google account/spreadsheet — no data passes through any ARPHost server.
Google Sheets API (recommended mode)
When Delivery method is set to Google Sheets API, the plugin sends a submission’s mapped field values directly to the Google Sheets API (sheets.googleapis.com) using a Google Cloud service account you create and paste into Settings. Before that, it exchanges the service account’s key for a short-lived OAuth2 access token via Google’s token endpoint (oauth2.googleapis.com).
- What is sent: the submission’s mapped field values (plus
formTitle/formId), sent as a new row appended to the spreadsheet/tab you configured. Anti-spam/captcha tokens (reCAPTCHA, hCaptcha, Turnstile) are stripped before sending. For authentication, a signed JWT derived from your own service-account credentials is sent to obtain the access token — no data is sent to any third party you did not configure. - When it’s sent: immediately after a matching Contact Form 7 submission (if enabled), or when an admin manually clicks “Resend” on a stored submission.
- Service: Google Sheets API / Google Cloud Platform, operated by Google LLC. Terms of Service · Google Privacy Policy
Google Apps Script webhook (alternate mode)
When Delivery method is set to Webhook, the same submission payload (JSON) is POSTed instead to a Google Apps Script Web App URL that you deploy yourself from google-apps-script-example.js (bundled with this plugin). That script runs on Google’s infrastructure (script.google.com / script.googleusercontent.com) under your own Google account and appends the row to your sheet.
- What is sent: the same mapped field values described above (
formTitle/formIdplus form fields, captcha tokens stripped), sent as the POST body. - When it’s sent: immediately after a matching submission (if enabled), or on manual “Resend”.
- Service: Google Apps Script, operated by Google LLC. Terms of Service · Google Privacy Policy
Installation
- Upload the
arphost-cf7-submission-archivefolder to/wp-content/plugins/, or install the zip via Plugins Add New Upload. - Activate the plugin. Contact Form 7 must be active.
- Submissions are stored in the database immediately. Find them under CF7 Submissions in the admin menu.
Google Sheets setup — API mode, no Apps Script (recommended)
- Go to https://console.cloud.google.com create (or pick) a project APIs & Services enable the Google Sheets API.
- IAM & Admin Service Accounts Create service account (any name, no roles needed) Keys Add key JSON. A .json file downloads.
- In WordPress: CF7 Submissions Settings — choose Google Sheets API, paste the JSON file’s contents into Service account JSON, and put your spreadsheet’s ID in Spreadsheet ID.
- Open the Google Sheet Share add the service account’s email (shown on the settings page after saving) as Editor.
That’s it. Each form gets its own tab automatically. Use Per-form routing to rename tabs or send a form to a different spreadsheet (Form Title=Tab or Form Title=SPREADSHEET_ID!Tab).
Google Sheets setup — webhook mode (Apps Script)
- Create a Google Sheet with a tab named
Submissions. - Go to script.google.com, create a project, and paste in
google-apps-script-example.js(bundled with this plugin). Set yourSHEET_ID. - Deploy New deployment Web app Execute as you, access Anyone. Copy the deployment URL.
- In WordPress: CF7 Submissions Settings — enable Send to Google Sheets and paste the URL.
-
Optionally add a field mapping so your CF7 field names become friendlier payload keys. Matching is forgiving: case-insensitive, and spaces/underscores count as hyphens — so
First Namematches the CF7 fieldfirst-name:First Name=firstName
Last Name=lastName
your-email=email
Volunteer=volunteers
Yard Sign=yardSign
The left side must still correspond to the CF7 field name (the name inside the form tag, e.g. [email* your-email] is your-email), not the visible label.
Checkbox fields with multiple selections are sent as JSON arrays; single-value fields are sent as plain strings.
Adding a new form later
Nothing special is required. Every payload carries formTitle/formId, and the companion Apps Script creates a new sheet tab (named after the form) with columns built from the fields automatically. Optionally add field-map lines if you want friendlier payload keys — the Settings page lists each form’s detected fields.
Screenshots

Settings — database storage toggle, Google Sheets delivery (API or webhook mode), per-form routing, and field mapping with auto-detected CF7 fields.

Submissions list — filter by form, search fields, per-submission Sheets delivery status, CSV export, view/resend/delete actions.

Submission detail — every captured field with date, form, and Sheets delivery status.
Faq
Yes. Capture hooks wpcf7_before_send_mail, so the submission is saved even if SMTP delivery fails.
All CF7 forms. Use the cf7dbgs_capture_submission filter to exclude specific forms.
Only the posted field values are stored; uploaded files are handled by CF7 as usual and are not copied.
Reviews
Changelog
1.2.2
- Added WordPress.org directory screenshots (settings, submissions list, submission detail) with a readme Screenshots section; CI now syncs the assets/ folder to the WordPress.org SVN assets directory. No functional changes.
1.2.1
- Moved the field-mapping helper script out of an inline
<script>tag (admin/class-cf7dbgs-admin.php) intoadmin/js/cf7dbgs-field-mapping.js, enqueued viawp_enqueue_script()on the Settings page only. No functional change. - Docs: added the “External services” section describing the Google Sheets API / OAuth2 and Google Apps Script webhook connections, per WordPress.org plugin review feedback.
1.2.0
- Renamed plugin to “ARPHost CF7 Submission Archive” (slug
arphost-cf7-submission-archive, text domain to match) per WordPress.org plugin review feedback — the prior name/slug led with the Contact Form 7 (“CF7”) trademark and was too close to other CF7/Sheets integration plugin names. No functional changes. Contributors line corrected to the actual WordPress.org account (hostalot). Added theRequires Plugins: contact-form-7header.
1.1.9
- Fix: text domain reverted (again) to
cf7-database-google-sheets— this is the canonical slug going forward (matches the plugin’s readable name and is what WordPress Playground / Plugin Check expects). The plugin’s packaged folder and zip are now also namedcf7-database-google-sheetsto match, so the slug is consistent everywhere. The GitLab project/repo itself stays namedcf7-db-gsheets— only the WordPress-facing plugin slug changed.
1.1.8
- Fix: text domain reverted to
cf7-db-gsheets(matches the actual plugin slug at the time) — resolved Plugin Check errors on dev.arphost.com, but broke it on WordPress Playground/Plugin Check (which expectscf7-database-google-sheets). Superseded by 1.1.9.
1.1.7
- Annotated the whitelisted ORDER BY columns for Plugin Check (documented false positive; $orderby is limited to four hardcoded column names).
1.1.6
- Text domain renamed to cf7-database-google-sheets to match the WordPress.org slug.
- Submissions query rewritten with fully literal prepared statements (resolves all Plugin Check security warnings).
1.1.5
- Plugin Check compliance: table names now use the %i identifier placeholder in all queries (requires WordPress 6.2+), removed the deprecated load_plugin_textdomain() call, and annotated intentional custom-table queries.
1.1.4
- Improvement: sheet timestamps now include the timezone. Apps Script writes Mountain Time via a TIME_ZONE constant (e.g. “2026-07-13 08:43:21 MDT”); API mode uses the WordPress timezone setting with zone label.
1.1.3
- Improvement: bundled Apps Script rewritten as a universal zero-config version — create it inside the sheet (Extensions > Apps Script), no spreadsheet ID, works with any form automatically, and reports full errors back to WordPress.
1.1.2
- Improvement: the bundled Apps Script now reports the real sheet-write error to WordPress (e.g. bad spreadsheet ID) instead of a generic “Failed to save to sheet”.
1.1.1
- Fix: Cloudflare Turnstile, hCaptcha, and other captcha tokens are no longer stored or sent to Sheets (previously only reCAPTCHA was excluded).
1.1.0
- Feature: direct Google Sheets API mode — no Apps Script required. Paste a service-account JSON key, share the sheet with the service account, done. Auto-creates one tab per form, manages columns, supports per-form routing to custom tabs or entirely different spreadsheets from the Settings screen. Webhook mode remains available.
1.0.8
- Docs: bundled Apps Script example is now a multi-form/multi-sheet router — per-form tabs (auto-created) plus optional ROUTES config to send any form to a custom tab name or a completely different spreadsheet.
1.0.7
- UI: the submissions list and detail views now show human-friendly field labels (“First Name”, “Email”, “Phone”) instead of raw CF7 field names (“first-name”, “your-email”, “tel-269”). Uses your field map, then humanizes.
1.0.6
- Feature: “Auto-map” button per form in Settings — reads the form’s fields from Contact Form 7 and fills the mapping automatically (email fields email, tel fields phone, “your-” prefixes stripped, names camelCased). Fields already mapped or needing no rename are skipped.
1.0.5
- Feature: every webhook payload now includes formTitle and formId, so one webhook can serve many forms (e.g. one Google Sheet tab per form). Resends include them too.
1.0.4
- Feature: forgiving field-map matching — “First Name” now matches the CF7 field “first-name” (case-insensitive; spaces and underscores count as hyphens).
- Feature: the Settings page now lists every Contact Form 7 form’s detected field names; click a field to add it to the mapping box.
1.0.3
- Fix: single-value arrays from CF7 select fields (e.g. state) are flattened to scalars in the webhook payload — Apps Script appendRow() fails silently on array values.
1.0.2
- Fix: Google rejected webhook posts with HTTP 400 — WP re-POSTs to the Apps Script 302 redirect target, which only accepts GET. The redirect is now followed manually with GET.
1.0.1
- Fix: Apps Script webhooks that return HTTP 200 with
{"success":false}in the body are now correctly recorded as failed (previously marked “sent”). - Fix: webhook timeout raised 8s 15s to survive Apps Script cold starts.
- Docs: full field-map example (city/state/comments) matching scripts that validate required fields.
1.0.0
- Initial release: DB storage, Google Sheets webhook forwarding, admin browser, CSV export, resend, privacy-safe defaults.