{"id":340585,"date":"2026-07-16T16:13:37","date_gmt":"2026-07-16T16:13:37","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/cf7-database-google-sheets\/"},"modified":"2026-07-16T16:45:01","modified_gmt":"2026-07-16T16:45:01","slug":"arphost-cf7-submission-archive","status":"publish","type":"plugin","link":"https:\/\/wordpress.org\/plugins\/arphost-cf7-submission-archive\/","author":23526037,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.2.2","stable_tag":"1.2.2","tested":"7.0.1","requires":"6.2","requires_php":"7.4","requires_plugins":null,"header_name":"ARPHost CF7 Submission Archive","header_author":"ARPHost, LLC","header_description":"Saves Contact Form 7 submissions to the WordPress database and optionally forwards them to a Google Sheets webhook (Google Apps Script). Includes an admin submissions browser and CSV export.","assets_banners_color":"","last_updated":"2026-07-16 16:45:01","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/github.com\/arphost-com\/CF7-Submissions","header_author_uri":"https:\/\/arphost.com","rating":0,"author_block_rating":0,"active_installs":0,"downloads":35,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.2.1":{"tag":"1.2.1","author":"hostalot","date":"2026-07-16 16:10:14"},"1.2.2":{"tag":"1.2.2","author":"hostalot","date":"2026-07-16 16:45:01"}},"upgrade_notice":[],"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3610550,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3610550,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":[],"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.2.1","1.2.2"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3610531,"resolution":"1","location":"assets","locale":"","width":1210,"height":1103},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3610531,"resolution":"2","location":"assets","locale":"","width":1240,"height":346},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3610531,"resolution":"3","location":"assets","locale":"","width":983,"height":413}},"screenshots":{"1":"Settings \u2014 database storage toggle, Google Sheets delivery (API or webhook mode), per-form routing, and field mapping with auto-detected CF7 fields.","2":"Submissions list \u2014 filter by form, search fields, per-submission Sheets delivery status, CSV export, view\/resend\/delete actions.","3":"Submission detail \u2014 every captured field with date, form, and Sheets delivery status."}},"plugin_section":[],"plugin_tags":[1152,153,1859,25296,16877],"plugin_category":[59],"plugin_contributors":[271916],"plugin_business_model":[],"class_list":["post-340585","plugin","type-plugin","status-publish","hentry","plugin_tags-contact-form-7","plugin_tags-database","plugin_tags-export","plugin_tags-google-sheets","plugin_tags-submissions","plugin_category-utilities-and-tools","plugin_contributors-hostalot","plugin_committers-hostalot"],"banners":[],"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/arphost-cf7-submission-archive\/assets\/icon-128x128.png?rev=3610550","icon_2x":"https:\/\/ps.w.org\/arphost-cf7-submission-archive\/assets\/icon-256x256.png?rev=3610550","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/arphost-cf7-submission-archive\/assets\/screenshot-1.png?rev=3610531","caption":"Settings \u2014 database storage toggle, Google Sheets delivery (API or webhook mode), per-form routing, and field mapping with auto-detected CF7 fields."},{"src":"https:\/\/ps.w.org\/arphost-cf7-submission-archive\/assets\/screenshot-2.png?rev=3610531","caption":"Submissions list \u2014 filter by form, search fields, per-submission Sheets delivery status, CSV export, view\/resend\/delete actions."},{"src":"https:\/\/ps.w.org\/arphost-cf7-submission-archive\/assets\/screenshot-3.png?rev=3610531","caption":"Submission detail \u2014 every captured field with date, form, and Sheets delivery status."}],"raw_content":"<!--section=description-->\n<p>ARPHost CF7 Submission Archive captures every Contact Form 7 submission and:<\/p>\n\n<ul>\n<li>Stores it in a dedicated database table (survives mail delivery failures \u2014 capture happens before mail is sent)<\/li>\n<li>Optionally forwards it as JSON to a Google Apps Script Web App, which appends it to a Google Sheet<\/li>\n<li>Provides an admin browser (filter by form, search, view detail, delete, resend to Sheets)<\/li>\n<li>Exports submissions to CSV<\/li>\n<li>Tracks per-submission Google Sheets delivery status, with one-click resend<\/li>\n<\/ul>\n\n<p>No Google API keys, OAuth, or service accounts required \u2014 the Sheets integration uses a free Google Apps Script Web App you deploy in about two minutes (companion script included in <code>google-apps-script-example.js<\/code>).<\/p>\n\n<h4>Privacy<\/h4>\n\n<p>IP address and user-agent storage are <strong>off by default<\/strong>. Enable them in Settings only if your privacy policy covers it.<\/p>\n\n<h4>Developer hooks<\/h4>\n\n<ul>\n<li><code>cf7dbgs_capture_submission<\/code> \u2014 filter; return false to skip capturing a submission<\/li>\n<li><code>cf7dbgs_store_fields<\/code> \u2014 filter fields before DB storage<\/li>\n<li><code>cf7dbgs_webhook_payload<\/code> \u2014 filter the JSON payload sent to the webhook<\/li>\n<li><code>cf7dbgs_webhook_args<\/code> \u2014 filter wp_remote_post args<\/li>\n<li><code>cf7dbgs_after_store<\/code> \u2014 action fired after a row is stored<\/li>\n<\/ul>\n\n<h3>External services<\/h3>\n\n<p>This plugin connects to Google services to forward Contact Form 7 submissions to a Google Sheet. Both connections are <strong>off by default<\/strong> (Settings \u2192 <em>Send to Google Sheets<\/em>) and use <em>your own<\/em> Google account\/spreadsheet \u2014 no data passes through any ARPHost server.<\/p>\n\n<h4>Google Sheets API (recommended mode)<\/h4>\n\n<p>When <em>Delivery method<\/em> is set to <em>Google Sheets API<\/em>, the plugin sends a submission's mapped field values directly to the Google Sheets API (<code>sheets.googleapis.com<\/code>) 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 (<code>oauth2.googleapis.com<\/code>).<\/p>\n\n<ul>\n<li>What is sent: the submission's mapped field values (plus <code>formTitle<\/code>\/<code>formId<\/code>), 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 \u2014 no data is sent to any third party you did not configure.<\/li>\n<li>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.<\/li>\n<li>Service: Google Sheets API \/ Google Cloud Platform, operated by Google LLC. <a href=\"https:\/\/developers.google.com\/terms\">Terms of Service<\/a> \u00b7 <a href=\"https:\/\/policies.google.com\/privacy\">Google Privacy Policy<\/a><\/li>\n<\/ul>\n\n<h4>Google Apps Script webhook (alternate mode)<\/h4>\n\n<p>When <em>Delivery method<\/em> is set to <em>Webhook<\/em>, the same submission payload (JSON) is POSTed instead to a Google Apps Script Web App URL that you deploy yourself from <code>google-apps-script-example.js<\/code> (bundled with this plugin). That script runs on Google's infrastructure (<code>script.google.com<\/code> \/ <code>script.googleusercontent.com<\/code>) under your own Google account and appends the row to your sheet.<\/p>\n\n<ul>\n<li>What is sent: the same mapped field values described above (<code>formTitle<\/code>\/<code>formId<\/code> plus form fields, captcha tokens stripped), sent as the POST body.<\/li>\n<li>When it's sent: immediately after a matching submission (if enabled), or on manual \"Resend\".<\/li>\n<li>Service: Google Apps Script, operated by Google LLC. <a href=\"https:\/\/developers.google.com\/terms\">Terms of Service<\/a> \u00b7 <a href=\"https:\/\/policies.google.com\/privacy\">Google Privacy Policy<\/a><\/li>\n<\/ul>\n\n<!--section=installation-->\n<ol>\n<li>Upload the <code>arphost-cf7-submission-archive<\/code> folder to <code>\/wp-content\/plugins\/<\/code>, or install the zip via Plugins \u2192 Add New \u2192 Upload.<\/li>\n<li>Activate the plugin. Contact Form 7 must be active.<\/li>\n<li>Submissions are stored in the database immediately. Find them under <strong>CF7 Submissions<\/strong> in the admin menu.<\/li>\n<\/ol>\n\n<h4>Google Sheets setup \u2014 API mode, no Apps Script (recommended)<\/h4>\n\n<ol>\n<li>Go to https:\/\/console.cloud.google.com \u2192 create (or pick) a project \u2192 APIs &amp; Services \u2192 enable the <strong>Google Sheets API<\/strong>.<\/li>\n<li>IAM &amp; Admin \u2192 Service Accounts \u2192 Create service account (any name, no roles needed) \u2192 Keys \u2192 Add key \u2192 JSON. A .json file downloads.<\/li>\n<li>In WordPress: <strong>CF7 Submissions \u2192 Settings<\/strong> \u2014 choose <em>Google Sheets API<\/em>, paste the JSON file's contents into <em>Service account JSON<\/em>, and put your spreadsheet's ID in <em>Spreadsheet ID<\/em>.<\/li>\n<li>Open the Google Sheet \u2192 Share \u2192 add the service account's email (shown on the settings page after saving) as <strong>Editor<\/strong>.<\/li>\n<\/ol>\n\n<p>That's it. Each form gets its own tab automatically. Use <em>Per-form routing<\/em> to rename tabs or send a form to a different spreadsheet (<code>Form Title=Tab<\/code> or <code>Form Title=SPREADSHEET_ID!Tab<\/code>).<\/p>\n\n<h4>Google Sheets setup \u2014 webhook mode (Apps Script)<\/h4>\n\n<ol>\n<li>Create a Google Sheet with a tab named <code>Submissions<\/code>.<\/li>\n<li>Go to script.google.com, create a project, and paste in <code>google-apps-script-example.js<\/code> (bundled with this plugin). Set your <code>SHEET_ID<\/code>.<\/li>\n<li>Deploy \u2192 New deployment \u2192 Web app \u2192 Execute as <em>you<\/em>, access <em>Anyone<\/em>. Copy the deployment URL.<\/li>\n<li>In WordPress: <strong>CF7 Submissions \u2192 Settings<\/strong> \u2014 enable <em>Send to Google Sheets<\/em> and paste the URL.<\/li>\n<li><p>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 \u2014 so <code>First Name<\/code> matches the CF7 field <code>first-name<\/code>:<\/p>\n\n<p>First Name=firstName\nLast Name=lastName\nyour-email=email\nVolunteer=volunteers\nYard Sign=yardSign<\/p><\/li>\n<\/ol>\n\n<p>The left side must still correspond to the CF7 field <em>name<\/em> (the name inside the form tag, e.g. <code>[email* your-email]<\/code> is <code>your-email<\/code>), not the visible label.<\/p>\n\n<p>Checkbox fields with multiple selections are sent as JSON arrays; single-value fields are sent as plain strings.<\/p>\n\n<h4>Adding a new form later<\/h4>\n\n<p>Nothing special is required. Every payload carries <code>formTitle<\/code>\/<code>formId<\/code>, 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 \u2014 the Settings page lists each form's detected fields.<\/p>\n\n<!--section=faq-->\n<dl>\n<dt id=\"does%20it%20capture%20submissions%20if%20the%20email%20fails%20to%20send%3F\"><h3>Does it capture submissions if the email fails to send?<\/h3><\/dt>\n<dd><p>Yes. Capture hooks <code>wpcf7_before_send_mail<\/code>, so the submission is saved even if SMTP delivery fails.<\/p><\/dd>\n<dt id=\"which%20forms%20are%20captured%3F\"><h3>Which forms are captured?<\/h3><\/dt>\n<dd><p>All CF7 forms. Use the <code>cf7dbgs_capture_submission<\/code> filter to exclude specific forms.<\/p><\/dd>\n<dt id=\"are%20file%20uploads%20stored%3F\"><h3>Are file uploads stored?<\/h3><\/dt>\n<dd><p>Only the posted field values are stored; uploaded files are handled by CF7 as usual and are not copied.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.2.2<\/h4>\n\n<ul>\n<li>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.<\/li>\n<\/ul>\n\n<h4>1.2.1<\/h4>\n\n<ul>\n<li>Moved the field-mapping helper script out of an inline <code>&lt;script&gt;<\/code> tag (admin\/class-cf7dbgs-admin.php) into <code>admin\/js\/cf7dbgs-field-mapping.js<\/code>, enqueued via <code>wp_enqueue_script()<\/code> on the Settings page only. No functional change.<\/li>\n<li>Docs: added the \"External services\" section describing the Google Sheets API \/ OAuth2 and Google Apps Script webhook connections, per WordPress.org plugin review feedback.<\/li>\n<\/ul>\n\n<h4>1.2.0<\/h4>\n\n<ul>\n<li>Renamed plugin to \"ARPHost CF7 Submission Archive\" (slug <code>arphost-cf7-submission-archive<\/code>, text domain to match) per WordPress.org plugin review feedback \u2014 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 (<code>hostalot<\/code>). Added the <code>Requires Plugins: contact-form-7<\/code> header.<\/li>\n<\/ul>\n\n<h4>1.1.9<\/h4>\n\n<ul>\n<li>Fix: text domain reverted (again) to <code>cf7-database-google-sheets<\/code> \u2014 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 named <code>cf7-database-google-sheets<\/code> to match, so the slug is consistent everywhere. The GitLab project\/repo itself stays named <code>cf7-db-gsheets<\/code> \u2014 only the WordPress-facing plugin slug changed.<\/li>\n<\/ul>\n\n<h4>1.1.8<\/h4>\n\n<ul>\n<li>Fix: text domain reverted to <code>cf7-db-gsheets<\/code> (matches the actual plugin slug at the time) \u2014 resolved Plugin Check errors on dev.arphost.com, but broke it on WordPress Playground\/Plugin Check (which expects <code>cf7-database-google-sheets<\/code>). Superseded by 1.1.9.<\/li>\n<\/ul>\n\n<h4>1.1.7<\/h4>\n\n<ul>\n<li>Annotated the whitelisted ORDER BY columns for Plugin Check (documented false positive; $orderby is limited to four hardcoded column names).<\/li>\n<\/ul>\n\n<h4>1.1.6<\/h4>\n\n<ul>\n<li>Text domain renamed to cf7-database-google-sheets to match the WordPress.org slug.<\/li>\n<li>Submissions query rewritten with fully literal prepared statements (resolves all Plugin Check security warnings).<\/li>\n<\/ul>\n\n<h4>1.1.5<\/h4>\n\n<ul>\n<li>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.<\/li>\n<\/ul>\n\n<h4>1.1.4<\/h4>\n\n<ul>\n<li>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.<\/li>\n<\/ul>\n\n<h4>1.1.3<\/h4>\n\n<ul>\n<li>Improvement: bundled Apps Script rewritten as a universal zero-config version \u2014 create it inside the sheet (Extensions &gt; Apps Script), no spreadsheet ID, works with any form automatically, and reports full errors back to WordPress.<\/li>\n<\/ul>\n\n<h4>1.1.2<\/h4>\n\n<ul>\n<li>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\".<\/li>\n<\/ul>\n\n<h4>1.1.1<\/h4>\n\n<ul>\n<li>Fix: Cloudflare Turnstile, hCaptcha, and other captcha tokens are no longer stored or sent to Sheets (previously only reCAPTCHA was excluded).<\/li>\n<\/ul>\n\n<h4>1.1.0<\/h4>\n\n<ul>\n<li>Feature: direct Google Sheets API mode \u2014 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.<\/li>\n<\/ul>\n\n<h4>1.0.8<\/h4>\n\n<ul>\n<li>Docs: bundled Apps Script example is now a multi-form\/multi-sheet router \u2014 per-form tabs (auto-created) plus optional ROUTES config to send any form to a custom tab name or a completely different spreadsheet.<\/li>\n<\/ul>\n\n<h4>1.0.7<\/h4>\n\n<ul>\n<li>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.<\/li>\n<\/ul>\n\n<h4>1.0.6<\/h4>\n\n<ul>\n<li>Feature: \"Auto-map\" button per form in Settings \u2014 reads the form's fields from Contact Form 7 and fills the mapping automatically (email fields \u2192 email, tel fields \u2192 phone, \"your-\" prefixes stripped, names camelCased). Fields already mapped or needing no rename are skipped.<\/li>\n<\/ul>\n\n<h4>1.0.5<\/h4>\n\n<ul>\n<li>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.<\/li>\n<\/ul>\n\n<h4>1.0.4<\/h4>\n\n<ul>\n<li>Feature: forgiving field-map matching \u2014 \"First Name\" now matches the CF7 field \"first-name\" (case-insensitive; spaces and underscores count as hyphens).<\/li>\n<li>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.<\/li>\n<\/ul>\n\n<h4>1.0.3<\/h4>\n\n<ul>\n<li>Fix: single-value arrays from CF7 select fields (e.g. state) are flattened to scalars in the webhook payload \u2014 Apps Script appendRow() fails silently on array values.<\/li>\n<\/ul>\n\n<h4>1.0.2<\/h4>\n\n<ul>\n<li>Fix: Google rejected webhook posts with HTTP 400 \u2014 WP re-POSTs to the Apps Script 302 redirect target, which only accepts GET. The redirect is now followed manually with GET.<\/li>\n<\/ul>\n\n<h4>1.0.1<\/h4>\n\n<ul>\n<li>Fix: Apps Script webhooks that return HTTP 200 with <code>{\"success\":false}<\/code> in the body are now correctly recorded as failed (previously marked \"sent\").<\/li>\n<li>Fix: webhook timeout raised 8s \u2192 15s to survive Apps Script cold starts.<\/li>\n<li>Docs: full field-map example (city\/state\/comments) matching scripts that validate required fields.<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Initial release: DB storage, Google Sheets webhook forwarding, admin browser, CSV export, resend, privacy-safe defaults.<\/li>\n<\/ul>","raw_excerpt":"Save Contact Form 7 submissions to your WordPress database and forward them to Google Sheets via a simple Apps Script webhook.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/340585","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=340585"}],"author":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/hostalot"}],"wp:attachment":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=340585"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=340585"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=340585"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=340585"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=340585"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=340585"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}