1. Create Google Sheet
- In Google Drive, create a new sheet:
ClientName - Leads
- Add a tab named
Leads
with headers:Timestamp | Name | Email | Phone | Message | CF7_Form_ID
- Copy the Sheet ID from the URL:
https://docs.google.com/spreadsheets/d/THIS_IS_THE_SHEET_ID/edit
2. Apps Script (Webhook)
- Open the sheet → Extensions → Apps Script.
- Paste the following code:
// Apps Script: CF7 -> Google Sheet webhook
const SHARED_SECRET = 'REPLACE_WITH_RANDOM_SECRET';
const SHEET_ID = 'REPLACE_WITH_SHEET_ID';
function doPost(e) {
try {
var payload = {};
if (e.postData && e.postData.type === 'application/json') {
payload = JSON.parse(e.postData.contents || '{}');
} else if (e.parameter && Object.keys(e.parameter).length) {
payload = e.parameter;
} else {
payload = { raw: e.postData && e.postData.contents ? e.postData.contents : '' };
}
if (!payload.token || payload.token !== SHARED_SECRET) {
return ContentService.createTextOutput(JSON.stringify({ status: 'error', message: 'unauthorized' }))
.setMimeType(ContentService.MimeType.JSON);
}
var ss = SpreadsheetApp.openById(SHEET_ID);
var sheet = ss.getSheetByName('Leads') || ss.getSheets()[0];
var row = [
new Date(),
payload['your-name'] || '',
payload['your-email'] || '',
payload['your-phone'] || '',
payload['your-message'] || '',
payload['_cf7_form_id'] || ''
];
sheet.appendRow(row);
return ContentService.createTextOutput(JSON.stringify({ status: 'ok' }))
.setMimeType(ContentService.MimeType.JSON);
} catch (err) {
return ContentService.createTextOutput(JSON.stringify({ status: 'error', message: err.toString() }))
.setMimeType(ContentService.MimeType.JSON);
}
}
- Save → Deploy → New deployment → Web app.
- Execute as: Me
- Who has access: Anyone with link
- Copy the
/exec
URL.
3. Contact Form 7 (Hidden Fields)
In the Form tab of your CF7 form, add:
[hidden token "REPLACE_WITH_RANDOM_SECRET"]
4. WordPress PHP (functions.php or Code Snippets)
Add this code to your theme’s functions.php
or a snippets plugin.
Replace the token and Apps Script URL with your own values.
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
add_action( 'wpcf7_mail_sent', 'cf7_send_to_google_sheet' );
function cf7_send_to_google_sheet( $contact_form ) {
$submission = WPCF7_Submission::get_instance();
if ( ! $submission ) {
return;
}
$posted = $submission->get_posted_data();
$payload = array(
'token' => 'REPLACE_WITH_RANDOM_SECRET',
'your-name' => isset( $posted['your-name'] ) ? (string) $posted['your-name'] : '',
'your-email' => isset( $posted['your-email'] ) ? (string) $posted['your-email'] : '',
'your-phone' => isset( $posted['your-phone'] ) ? (string) $posted['your-phone'] : '',
'your-message' => isset( $posted['your-message'] ) ? (string) $posted['your-message'] : '',
'_cf7_form_id' => $contact_form->id()
);
$url = 'https://script.google.com/macros/s/YOUR_DEPLOYED_EXEC_ID/exec';
$args = array(
'body' => $payload,
'timeout' => 20,
'blocking' => true,
'headers' => array( 'Content-Type' => 'application/x-www-form-urlencoded' )
);
$response = wp_remote_post( $url, $args );
if ( is_wp_error( $response ) ) {
error_log( 'CF7->Sheet error: ' . $response->get_error_message() );
} else {
error_log( 'CF7->Sheet response: ' . wp_remote_retrieve_body( $response ) );
}
}
5. Test
- Submit the CF7 form with test values.
- Open the Google Sheet → confirm a new row was added.
- Check WordPress
debug.log
or Apps Script → Executions for errors if needed.
6. Limits (Google Quotas)
- Writes per day (Apps Script): ~20,000 rows.
- Sheet size: 10M cells (~1M rows with 10 columns).
- Request size: ~50 MB (CF7 data is tiny).
- Execution time: 6 mins/request (your script runs <1s).
7. Best Practices
- Keep Google Sheet private (do not share as “Anyone with link”).
- Keep Apps Script deployment as “Anyone with link” but protect with token check.
- Use strong random secret tokens (rotate if leaked).
- Enable reCAPTCHA in CF7 to reduce spam.
- Archive leads when sheet grows large (>50k rows).
👉 This snippet is generic — safe to store in your docs and reuse for any future project.