Rex Automaton
All posts
Operations & Admin AutomationAugust 4, 202610 min read

How to Send Contact Form 7 to Google Sheets with File Uploads

We wired Contact Form 7 to a Google Sheet and Drive so every submission is logged and file uploads are forwarded by email with Drive links. This guide shows the exact webhook and Apps Script we run in production.

By Jacky Lei

We built a production-safe flow that takes every Contact Form 7 submission straight into a Google Sheet, uploads any attached files to Google Drive, and forwards an email to your team with the submission data and secure file links. It is for WordPress sites that cannot afford to miss a lead because an email failed or a large attachment was stripped. This post shows the exact architecture, code, and gotchas we solved.

Contact Form 7 to Google Sheets automation is: a webhook from WordPress to a Google Apps Script that sanitizes fields, appends a row to a Sheet, streams uploaded files to Drive, and sends a confirmation or internal forward so nothing is missed.

The problem it solves

Most sites run CF7 as email only. When email hiccups or an attachment exceeds limits, you lose the submission. CF7 core does not store entries by default. The Flamingo add-on stores messages and exports CSV, but it does not put structured rows into your live sheet or move files into Drive for easy sharing.

Manual CF7 email onlyAutomated CF7 to Sheets + Drive
Submissions can disappear when email fails.Every submission is logged to a Sheet first, then emailed.
Attachments hit size caps or get stripped.Files upload to Drive. Email contains links and optional small attachments.
No central reporting.One live Sheet becomes your source of truth.
Re-keying into CRMs and trackers.Sheet rows feed downstream automations without copy paste.

Two CF7 facts matter for reliability: CF7 core does not store submissions unless you add Flamingo, and file attachments are subject to size limits. CF7 warns about a 25 MB total attachment cap and advises using a specialized service for large files. Source: contactform7.com/file-uploading-and-attachment.

How the automation works

The flow is lightweight and WordPress friendly. We do not depend on third-party CF7 cloud apps. We post directly to a Google Apps Script web app that writes to Sheets and Drive.

  • Contact Form 7 on WordPress: your existing form, including file fields. CF7 AJAX posts through the WordPress REST API. If the REST API is disabled or misrouted, CF7 submissions can break. Source: contactform7.com/faq.
  • WordPress sender: a small functions.php hook packages submitted fields and any uploaded files, then POSTs JSON to our Apps Script URL. If you prefer no code, a CF7 to Webhook add-on works too.
  • Apps Script engine: receives JSON, sanitizes values for spreadsheet safety, appends a row to your Google Sheet, converts base64 files to Blobs, writes them to Drive, then emails a forward with links and optionally small attachments.
  • Flamingo as a safety net: stores the raw message inside WordPress so even if your Google quota blips, the entry is still retrievable. Source: wordpress.org/plugins/flamingo.

CF7 submissions to Google Sheets and Drive with email forwarding: CF7 form posts via a WordPress sender to an Apps Script engine that writes to Google Sheets, uploads files to Drive, and sends an email with Drive links

Step-by-step: how to build it

Answer first: you will deploy an Apps Script web app, then point CF7 at it. Apps Script sanitizes and stores the data and files, and sends the forward.

1) Prepare CF7 and add Flamingo

Install and activate Flamingo so CF7 messages are stored in WordPress. CF7 does not store submissions otherwise. Source: contactform7.com/save-submitted-messages-with-flamingo.

Add or confirm your file field in CF7. Example CF7 markup:

[text* your-name]
[email* your-email]
[textarea your-message]
[file your-file limit:25mb]
[submit "Send"]

CF7 warns about a 25 MB total attachment cap. For anything large, plan to store in Drive and email links. Source: contactform7.com/file-uploading-and-attachment.

Key gotcha: CF7 relies on the WordPress REST API for AJAX. Make sure the REST route is reachable at /wp-json. Language-prefixed REST URLs or security plugins that block REST can break submissions. Source: contactform7.com/faq.

2) Create the Google Apps Script web app

Create a new Apps Script project and add the following. This receives JSON, sanitizes values, appends a row, and uploads base64 files to Drive.

// File: Code.gs
const SHEET_ID = 'YOUR_SHEET_ID';
const DRIVE_FOLDER_ID = 'YOUR_DRIVE_FOLDER_ID';
const FORWARD_TO = 'team@example.com';
 
function doPost(e) {
  try {
    const data = JSON.parse(e.postData.contents);
    const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName('Submissions') ||
                  SpreadsheetApp.openById(SHEET_ID).insertSheet('Submissions');
 
    const row = buildRow(data);
    sheet.appendRow(row.values);
 
    const fileLinks = saveFiles(data.files || []);
    sendForward(row.map, fileLinks);
 
    return ContentService.createTextOutput(JSON.stringify({ ok: true }))
      .setMimeType(ContentService.MimeType.JSON);
  } catch (err) {
    return ContentService.createTextOutput(JSON.stringify({ ok: false, error: String(err) }))
      .setMimeType(ContentService.MimeType.JSON);
  }
}
 
function buildRow(data) {
  const ts = new Date();
  const map = {
    timestamp: ts.toISOString(),
    name: sanitize(data.fields?.name || data.fields?.['your-name'] || ''),
    email: sanitize(data.fields?.email || data.fields?.['your-email'] || ''),
    message: sanitize(data.fields?.message || data.fields?.['your-message'] || ''),
  };
  const values = [map.timestamp, map.name, map.email, map.message];
  return { map, values };
}
 
function sanitize(v) {
  if (typeof v !== 'string') return v;
  // Spreadsheet formula injection guard: prefix risky starts with an apostrophe
  if (/^[=+\-@]/.test(v)) return "'" + v;
  return v;
}
 
function saveFiles(files) {
  const folder = DriveApp.getFolderById(DRIVE_FOLDER_ID);
  const links = [];
  files.forEach(f => {
    try {
      const blob = Utilities.newBlob(Utilities.base64Decode(f.base64), f.mimeType || 'application/octet-stream', f.filename || 'upload');
      const driveFile = folder.createFile(blob);
      links.push({ name: driveFile.getName(), url: driveFile.getUrl(), size: blob.getBytes().length });
    } catch (e) {
      links.push({ name: f.filename || 'upload', url: 'ERROR: ' + String(e) });
    }
  });
  return links;
}
 
function sendForward(rowMap, fileLinks) {
  const subject = `New CF7 submission: ${rowMap.name || 'Unknown'}`;
  const body = [
    'A new Contact Form 7 submission was received.',
    '',
    `Name: ${rowMap.name}`,
    `Email: ${rowMap.email}`,
    '',
    'Message:',
    rowMap.message,
    '',
    'Files:',
    ...(fileLinks.length ? fileLinks.map(l => `- ${l.name}: ${l.url}`) : ['- none'])
  ].join('\n');
 
  // Attach only small files to avoid email caps. Always include links above.
  const smallBlobs = [];
  try {
    fileLinks.forEach(l => {
      // We cannot re-fetch from Drive without permissions. If you want attachments, pass base64 along too.
      // This example sticks to link-only for large files to avoid the 25 MB email cap CF7 warns about.
    });
  } catch (_) {}
 
  MailApp.sendEmail({ to: FORWARD_TO, subject, htmlBody: body.replace(/\n/g, '<br>'), body });
}

Deploy as a Web App: Deploy, New deployment, type Web app, execute as Me, allow Anyone with the link. Copy the Web App URL.

3) Add a WordPress sender hook for CF7

Add this to your theme's functions.php or a small mu-plugin. It packages posted fields and base64-encodes uploaded files, then POSTs JSON to your Apps Script URL.

// File: functions.php
add_action('wpcf7_before_send_mail', function($contact_form) {
  if (!class_exists('WPCF7_Submission')) return;
  $sub = WPCF7_Submission::get_instance();
  if (!$sub) return;
 
  $posted = $sub->get_posted_data();
  $uploads = $sub->uploaded_files(); // [field_name => file_path or array]
 
  $payload = [
    'fields' => [
      'your-name' => isset($posted['your-name']) ? $posted['your-name'] : '',
      'your-email' => isset($posted['your-email']) ? $posted['your-email'] : '',
      'your-message' => isset($posted['your-message']) ? $posted['your-message'] : ''
    ],
    'files' => []
  ];
 
  foreach ($uploads as $field => $paths) {
    $pathsArr = is_array($paths) ? $paths : [$paths];
    foreach ($pathsArr as $path) {
      if (!$path || !file_exists($path)) continue;
      $payload['files'][] = [
        'filename' => basename($path),
        'mimeType' => function_exists('mime_content_type') ? mime_content_type($path) : 'application/octet-stream',
        'base64' => base64_encode(file_get_contents($path))
      ];
    }
  }
 
  $resp = wp_remote_post('https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/exec', [
    'timeout' => 12,
    'headers' => [ 'Content-Type' => 'application/json' ],
    'body' => wp_json_encode($payload)
  ]);
 
  // Optional: log non-200s with error_log for ops visibility
  if (is_wp_error($resp) || wp_remote_retrieve_response_code($resp) >= 300) {
    error_log('CF7->AppsScript push failed: ' . (is_wp_error($resp) ? $resp->get_error_message() : wp_remote_retrieve_body($resp)));
  }
});

If you prefer no code, use a CF7 webhook add-on and point it at the Apps Script URL. Zapier does not offer a native CF7 app. Use Webhooks by Zapier or a CF7 webhook plugin. Source: zapier.com and apps.make.com for CF7 via webhooks.

4) Append a header row and test

In your Sheet, add a header row for Submissions: Timestamp, Name, Email, Message. Submit your CF7 form with and without a file. Confirm that:

  • A new row appears per submission.
  • A Drive file is created per upload in your folder.
  • The forward email contains the Drive links.

5) Harden spreadsheet safety and logging

CF7 posted values can include leading equals or plus. That can trigger a spreadsheet formula if pasted raw. We prefixed risky cells with an apostrophe in sanitize. CF7 published a note about spreadsheet vulnerabilities and why you should sanitize before feeding rows to a sheet. Source: contactform7.com/2020/01/15/heads-up-about-spreadsheet-vulnerabilities.

Add a second Sheet tab named Logs and push minimal event logs if you want an audit trail. You can also enable Apps Script Stackdriver logs for error triage.

function logEvent(kind, msg) {
  const ss = SpreadsheetApp.openById(SHEET_ID);
  const sheet = ss.getSheetByName('Logs') || ss.insertSheet('Logs');
  sheet.appendRow([new Date().toISOString(), kind, msg]);
}

6) Set email policy for large files

CF7 warns about a 25 MB total attachment cap in outgoing mail. To avoid bounces, we forward link-only by default. If you must attach small files, include a file size field in your JSON and attach only below a threshold.

function attachIfSmall(files) {
  const max = 4 * 1024 * 1024; // 4 MB
  return files.filter(f => f.size && f.size <= max).map(f => UrlFetchApp.fetch(f.url).getBlob());
}

We found links are safer and faster to route. If your team needs direct attachments, keep the threshold conservative and still include Drive links in the body.

Where it gets complicated

  • REST dependency and CF7 AJAX: If /wp-json is blocked or rewritten, front-end CF7 submissions can silently fail. Confirm REST health before you blame CF7. Source: contactform7.com/faq.
  • No native CF7 storage: Without Flamingo, a mail server glitch can lose everything. Flamingo stores messages and CSV exports if you need a backstop. Source: wordpress.org/plugins/flamingo.
  • Zapier and Make paths are webhook based: Zapier does not ship a native CF7 app. A Make module exists but relies on add-ons. Webhooks work well, but file handling requires either base64 or a later pull from your server.
  • Attachment size and deliverability: CF7 warns of a 25 MB cap. Even when under caps, multiple attachments can harm deliverability. Drive links in the body keep mail light and reliable. Source: contactform7.com/file-uploading-and-attachment.
  • Spreadsheet injection risk: Pasting raw values can create active formulas. Always sanitize with a leading apostrophe for any cell that starts with equals, plus, minus, or at. Source: contactform7.com heads-up post on spreadsheet vulnerabilities.

What this actually changes

For a multi-location services business on WordPress, this removed two failure modes: lost submissions from email issues and missing attachments from size limits. In production it logs every form to a Sheet, stores files in Drive, and forwards a readable email with links. We also left Flamingo active as a last-ditch archive because CF7 core does not store messages by itself. Sources: contactform7.com and the Flamingo plugin page.

Frequently asked questions

Does Contact Form 7 have a native Zapier app?

No. Zapier lists CF7 as not having a native app. You can use Webhooks by Zapier or a CF7 webhook add-on to POST submissions to Zapier. Make also relies on webhooks or add-ons for CF7. Sources: zapier.com and apps.make.com.

How do you get file uploads into Google Sheets?

You do not. Sheets is for rows, not binary blobs. We upload files to Google Drive and write the Drive URLs into the row. The forward email includes those links and optionally small attachments.

Can I do this without writing PHP?

Yes, with a CF7 to Webhook plugin and an Apps Script web app. The tradeoff: you will need to configure field mappings and ensure your webhook plugin can include files, often as base64, or expose a later fetch URL.

Will this work if my WordPress REST API is disabled?

CF7 AJAX relies on the WordPress REST API. If REST is blocked or misrouted, submissions can fail. Fix REST routing and test /wp-json first. Source: contactform7.com/faq.

How big can file uploads be with CF7?

CF7 warns about a 25 MB total attachment cap on outgoing email. For larger files, store in a specialized service such as Drive and email links. Source: contactform7.com/file-uploading-and-attachment.

Do I still need Flamingo if I am logging to Sheets?

We keep Flamingo on as a safety net. CF7 core does not store messages. Flamingo gives you an in-WordPress archive and CSV export in case an external service is down. Source: wordpress.org/plugins/flamingo.

If you want this running on your site with proper sanitization and file handling, we can wire it to your exact form in a day. See our workflow automation services at /services#workflow-automation, and if you also run forms on other platforms, our related guide on routing Jotform uploads to Sheets shows the same Drive-first pattern at /blog/jotform-to-google-sheets-with-attachments. Or skip the build and book a 15-minute call.

Want us to build this for you?

15-minute discovery call. No pitch. We tell you what to automate first.

Book a Discovery Call

Related reading