Building a contact form in Next.js is straightforward.
You create a form component, add your input fields, wire up some state with useState, and handle the submission with a fetch call. Clean. Predictable. Very Next.js.
But then comes the question every Next.js developer eventually faces:
Where does the data actually go?
The obvious answer is an API route. You create a pages/api/contact.ts file or an app/api/contact/route.ts file, write a handler that receives the submission, and then figure out how to store it somewhere. A database. A Google Sheet. An email. Something.
For a side project or a client site, that is a lot of infrastructure to set up and maintain just to collect a few contact form submissions. You need a database or a third-party email service. You need environment variables. You need error handling.
You need to think about rate limiting and spam.
And if you want those submissions in Google Sheets specifically, you also need to figure out the Google Sheets API, OAuth tokens, service accounts, and all the complexity that comes with that integration.
There is a simpler way.
This guide shows you how to send Next.js form submissions directly
to Google Sheets using Formgrid. No API routes. No database. No Google Apps Script. No Zapier. Just your form component pointing at a Formgrid endpoint and a Google Sheet shared with one email address.
Who This Is For
This guide is for Next.js developers who want form submissions in Google Sheets without building and maintaining backend infrastructure:
Next.js developers building portfolio sites, landing pages, or client sites who need a contact form that works reliably without spinning up a database or a serverless function just to collect
enquiries.
Indie hackers building Next.js projects who want submissions organised in a spreadsheet without paying for Zapier or writing their own Google Sheets integration.
Freelancers who build Next.js sites for clients and want a form backend that is easy to set up, requires zero maintenance, and puts submissions somewhere the client can actually see them without logging into a developer tool.
Frontend developers who want to keep their Next.js project statically deployed on Vercel or Netlify without needing a separate API server just to handle form submissions.
If you have a Next.js form and you want submissions in Google Sheets, this guide gets you there in one sitting.
The Problem With Building Your Own API Route
The standard Next.js approach to form handling is an API route. It looks clean, and it is the right solution for many cases. But for a simple contact form, it introduces more complexity than the problem warrants.
Here is what you end up building:
An API route to receive the submission
A way to store or forward the data
Error handling for failed requests
Rate limiting to prevent spam abuse
Environment variables for API keys
Deployment configuration
Ongoing maintenance when things break
Then, if you want the data in Google
Sheets specifically, you also need:
A Google Cloud project
A service account with credentials
The Google Sheets API enabled
OAuth scope configuration
Token refresh handling
The actual Sheets API integration code
That is a significant amount of infrastructure for what is essentially a contact form. Every piece of it is something you wrote and something you own.
When it breaks at 2 am because an OAuth token expired, it is your
problem to fix.
Formgrid is all of that infrastructure already built and maintained for you. You point your form at an endpoint. The data lands in your Sheet. Done.
What You Get With Formgrid
Once your Next.js form is connected to formgrid.dev and your Google Sheet is linked, here is what happens on every submission:
Every entry appears as a new row in your spreadsheet automatically. No manual export. No dashboard to check every day.
Column headers are created automatically from your form field names on the very first submission. You do not need to set up the spreadsheet structure. Name your fields clearly and Formgrid
handles the rest.
The integration works in real time. The moment someone submits your form, the row appears in Google Sheets within seconds. Not on a schedule. Instantly.
You also get email notifications on every submission. Spam protection is built in through honeypot fields and rate limiting. And every submission is saved to your Formgrid dashboard as a backup, so if anything ever goes wrong with your Sheet, you have a full record
to fall back on.
What You Will Need
Before starting, make sure you have the following in place:
A Formgrid account:
Google Sheets integration is available on all plans, including free. The free plan syncs your first 10 rows per form.
The Premium plan at $8 per month syncs up to 500 rows per month. The Business plan at $29 per month syncs unlimited rows automatically.
Sign up for free at formgrid.dev.
No credit card required.
A Next.js project with a form:
Any Next.js form works whether you are using the Pages Router or the
App Router. The approach is the same for both.
A Google account:
You will need access to Google Sheets to create the spreadsheet that will receive your submissions.
How This Works
Instead of sending your form data to a Next.js API route, you send it directly to a Formgrid endpoint URL using a standard HTML POST or a fetch call from your form component.
Formgrid receives the submission, saves it to your dashboard, sends you an email notification, and writes a new row to your connected Google Sheet instantly.
No API route needed in your Next.js project. No server code. No database. Just your form component and a Formgrid endpoint.
Part One: Set Up Your Formgrid Form and Get Your Endpoint URL
Step 1: Create a Form in Your Formgrid Dashboard
Log in to your Formgrid account at formgrid.dev. From your dashboard, create a new form
and give it a name that matches what you are collecting. For example
"Contact Form" or "Project Enquiries."
You are not building a form inside Formgrid here. You are registering
a form entry in your dashboard so that Formgrid knows where to route
the incoming submissions from your Next.js project. Your actual form
component stays exactly as it is.
Step 2: Copy Your Formgrid Endpoint URL
Once your form is created, open it in your Formgrid dashboard. You will see your unique endpoint URL displayed
prominently. It will look like this:
https://formgrid.dev/api/f/your-form-id
Copy this URL. You will need it when you update your Next.js form component.
This URL is permanent. It does not change when you update your form
settings or connect integrations. You set it once and never need to
touch it again.
Part Two: Update Your Next.js Form Component
There are two ways to connect your Next.js form to Formgrid, depending on how your form is currently built.
Option A: Using a Standard HTML Form
If your form is a standard HTML form with a POST action, this is the simplest approach.
Just set the action attribute to your Formgrid endpoint, and the
method to POST:
export default function ContactForm() {
return (
<form
action="https://formgrid.dev/api/f/your-form-id"
method="POST"
>
<input
type="text"
name="name"
placeholder="Your Name"
required
/>
<input
type="email"
name="email"
placeholder="Your Email"
required
/>
<textarea
name="message"
placeholder="Your Message"
/>
{/* Honeypot field for spam protection */}
<input
type="text"
name="_honey"
style={{ display: 'none' }}
/>
<button type="submit">
Send Message
</button>
</form>
)
}
That is the entire change. One attribute on the form tag. Your
form now sends submissions to Formgrid on submit.
One important detail: Formgrid reads the name attribute of each input
field and uses those values as the column headers in your Google Sheet. Make sure every field has a clear descriptive name attribute.
Option B: Using fetch with a React Controlled Form
If your form is a controlled React component using useState and you want to handle the submission with fetch to show a custom success state, use this approach instead:
'use client'
import { useState } from 'react'
export default function ContactForm() {
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [message, setMessage] = useState('')
const [status, setStatus] = useState
'idle' | 'submitting' | 'success' | 'error'
>('idle')
const handleSubmit = async (
e: React.FormEvent
) => {
e.preventDefault()
setStatus('submitting')
try {
const formData = new FormData()
formData.append('name', name)
formData.append('email', email)
formData.append('message', message)
const response = await fetch(
'https://formgrid.dev/api/f/your-form-id',
{
method: 'POST',
body: formData
}
)
if (response.ok) {
setStatus('success')
setName('')
setEmail('')
setMessage('')
} else {
setStatus('error')
}
} catch (error) {
setStatus('error')
}
}
if (status === 'success') {
return (
<div>
<p>Thank you. Your message
has been received.</p>
</div>
)
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
name="name"
placeholder="Your Name"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
<input
type="email"
name="email"
placeholder="Your Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<textarea
name="message"
placeholder="Your Message"
value={message}
onChange={(e) =>
setMessage(e.target.value)}
/>
<button
type="submit"
disabled={status === 'submitting'}
>
{status === 'submitting'
? 'Sending...'
: 'Send Message'}
</button>
{status === 'error' && (
<p>Something went wrong.
Please try again.</p>
)}
</form>
)
}
Option C: Using the App Router With Server Actions
If you are using the Next.js App Router and prefer Server Actions, here is how to forward the submission to Formgrid from a server action:
'use server'
export async function submitContactForm(
formData: FormData
) {
const response = await fetch(
'https://formgrid.dev/api/f/your-form-id',
{
method: 'POST',
body: formData
}
)
if (!response.ok) {
throw new Error('Submission failed')
}
return { success: true }
}
Then, in your form component:
import { submitContactForm } from
'./actions'
export default function ContactForm() {
return (
<form action={submitContactForm}>
<input
type="text"
name="name"
placeholder="Your Name"
required
/>
<input
type="email"
name="email"
placeholder="Your Email"
required
/>
<textarea
name="message"
placeholder="Your Message"
/>
<button type="submit">
Send Message
</button>
</form>
)
}
All three options send the same data to the same Formgrid endpoint. Use whichever pattern matches how your form is currently built.
Step 3: Test That Submissions Are Reaching Formgrid
Before connecting Google Sheets, confirm that submissions are reaching Formgrid correctly.
Run your Next.js project locally or deploy it and
submit a test entry through your form.
Open your Formgrid dashboard and check the submissions list for your
form. The test entry should appear within a few seconds.
If the submission does not appear, open your browser developer tools,
go to the Network tab, submit the form again, and look for the POST
request to formgrid.dev. A 200 response means the submission was
received. Any other status code means there is a configuration
issue to sort out before moving on.
Part Three: Connect Google Sheets
Step 4: Open the Integrations Tab in Formgrid
In your Formgrid dashboard, open the form you just connected and click on the Integrations tab at the top of the page.
You will see the Google Sheets integration section. If you are within
your plan limits the Connect interface, which is active and ready to use.
Step 5: Create a Blank Google Sheet
Click the Create blank Google Sheet
button. This opens a fresh blank spreadsheet in Google Sheets in a
new browser tab.
Give your sheet a clear, identifiable name. Something like "Contact Form Submissions" or "Next.js Project Enquiries 2026" works well.
Do not add any column headers or set up any structure in the spreadsheet. Formgrid creates the column headers automatically from your Next.js form field names on the very first submission. The sheet should be empty when you connect it.
Step 6: Share the Sheet With the Formgrid Service Account
In your Google Sheet, click the Share button in the top right
corner. The share dialog will open.
You need to add the Formgrid service account email address as an Editor. Go back to your Formgrid dashboard, where the service account email is displayed with a Copy button
next to it.
Copy it directly from there to avoid any chance of a typing error. Paste the email into the share dialog and make sure you select Editor access, not Viewer. Formgrid needs
Editor access to write new rows to your sheet. If you add it as a Viewer the connection will fail with a
permissions error.
Click Send or Done to confirm.
Step 7: Paste Your Sheet URL Into Formgrid
Go back to your Formgrid dashboard.
Copy the full URL of your Google Sheet from the browser address bar. The URL will look like this:
https://docs.google.com/spreadsheets/d/
1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74
OgVE2upms/edit
Paste the full URL into the sheet URL field in your Formgrid dashboard. Make sure the URL contains the full
spreadsheet ID, which is the long alphanumeric string between /d/
and /edit.
Step 8: Choose Whether to Sync Existing Submissions
Before connecting, you will see this option:
Sync existing submissions to this sheet? If you already have submissions, Formgrid can add them all to your
Google Sheets now, so your entire history is in one place.
[ ] Yes, sync my existing submissions
If you have been collecting submissions through Formgrid for a while and want your full history in the sheet from day one, check this box. If you only want new submissions going forward, leave it unchecked.
Step 9: Click Connect
Click the Connect Google Sheets button.
Formgrid will verify that it can access your sheet and that the service account has the correct permissions. If everything is in order, you will see a success confirmation:
Connected successfully
Your sheet is ready. Every new submission will appear as a new
row automatically.
Part Four: Verify the Full Flow Is Working
Step 10: Submit Another Test Entry Through Your Next.js Form
Submit another test entry through your form. Use realistic-looking data so it is easy to identify in your spreadsheet.
Open your Google Sheet. Within a few seconds, you should see:
Row 1: Column headers created automatically from your Next.js form field name attributes.
Row 2: Your test submission data with a timestamp in the final column showing exactly when the submission was received.
From this point forward, every submission from your Next.js form
will appear as a new row in your Google Sheet in real time. You never
need to log into Formgrid, export anything, or take any manual action.
What Happens on Every Submission
Here is the complete flow from the
moment a visitor submits your Next.js
form to the moment a row appears in
your spreadsheet:
Visitor fills in your Next.js form
and clicks Submit
↓
The form sends a POST request to
your Formgrid endpoint
↓
Formgrid receives and saves the
submission to your dashboard
↓
Email notification sent to you
and any other configured recipients
↓
A new row added to your Google
Sheet instantly
↓
Spam protection runs in the
background to filter out
bot submissions
Your submission is available in three places simultaneously: your Formgrid dashboard, your email inbox, and your Google Sheet.
Managing Your Google Sheets Connection
Once connected, the Integrations tab
in your Formgrid dashboard, you have full control:
Pause the integration:
Use the Active toggle to pause syncing at any time. New submissions are still saved to your dashboard and email notifications still go out, but new rows are not written to your sheet.
Disconnect:
Removes the connection entirely. Your existing sheet data stays exactly as it is. New submissions will not sync until you reconnect.
Open Sheet:
Takes you directly to your connected Google Sheet with a single click.
How This Compares to Building Your Own API Route
| Custom API Route | Formgrid | |
|---|---|---|
| Setup time | 1 to 2 hours | Under 5 minutes |
| Code to maintain | Yes | None |
| Google Sheets API | You build it | Built in |
| Spam protection | You build it | Built in |
| Email notifications | You build it | Included |
| Deployment config | Required | None |
| When it breaks | Your problem | Formgrid's problem |
How This Compares to Using Zapier
| Zapier Route | Formgrid Native | |
|---|---|---|
| Extra monthly cost | $19.99/month | Included in all plans |
| Setup steps | 6 to 8 steps across two tools | 3 steps in one place |
| Real time sync | Depends on Zap frequency | Instant |
| Failure points | Two services that can break | One |
| Spam protection | None built in | Honeypot and rate limiting |
| Email notifications | Requires extra Zap | Included |
A Real Example: A Freelancer Who Stopped Writing API Routes
for Contact Forms
James is a freelance Next.js developer. He builds sites for clients across various industries. Every client site needs a contact form. Every contact form used to mean a new API route, a new email service integration, and a new Google Sheets setup that he built from scratch each time.
He was spending 2 to 3 hours per project just on form infrastructure.
Code he would never touch again once the site was live, but code he was still responsible for when it broke.
He switched to Formgrid for all his client contact forms. Now the setup takes 5 minutes per project. He points the form at a Formgrid endpoint, shares a Google Sheet with the Formgrid service account, and clicks connect. The client gets a spreadsheet that
fills itself and sends an email notification on every enquiry.
He saves roughly 2 hours per project. Across 15 client projects a year, that is 30 hours of development time he now spends on work that actually charges at his hourly rate.
Troubleshooting
Form submissions not appearing in Formgrid at all:
Check that your fetch call or form action is pointing to your Formgrid endpoint and that the method is POST, not GET. Open your browser developer tools, go to the Network tab, submit the form, and look for the POST request to formgrid.dev. A 200 response means
the submission was received. Any other status code means something is
misconfigured.
CORS error in the browser console:
If you are seeing a CORS error, it means you are likely trying to send
a JSON body with a Content-Type of application/json. Formgrid expects
FormData or URL-encoded form data, not JSON. Switch to using FormData
in your fetch call as shown in Option B above.
"Could not access this sheet" error when connecting:
This means Formgrid does not have write access to your sheet.
Open in Google Sheets, click Share, and confirm that the Formgrid service account email is listed as an Editor. If it is listed as a Viewer, remove it and re-add it with Editor access, then try connecting again.
Column headers missing or showing unexpected values:
Column headers come from the name attributes of your form input fields. If a column is missing, check that the corresponding input has a name attribute set. If a header looks incorrect, update the name attribute and resubmit a test entry.
Submissions appearing in Formgrid but not in Google Sheets:
Open the Integrations tab in your Formgrid dashboard and check that
the Google Sheets integration is showing as Active. If it shows as paused, click the toggle to resume. If it shows as Active but submissions are still not appearing, try disconnecting and reconnecting the integration.
Server Action forwarding not working:
If you are using a Server Action to forward submissions to Formgrid, make sure you are passing the FormData object directly to the fetch body. Do not convert it to JSON. Formgrid reads FormData directly.
What Formgrid Includes
Google Sheets integration is available on all Formgrid plans, including free.
Free plan at $0 per month:
Google Sheets sync (first 10 rows
per form. One time.)
Form builder with shareable link
Form endpoint URL
Email notifications
25 submissions per month
Premium plan at $8 per month:
Google Sheets sync (up to 500 rows
per month)
Everything in the free plan
1,000 submissions per month
File uploads up to 1GB per file
CSV export
Advanced spam protection
Remove Formgrid branding
Business plan at $29 per month:
Google Sheets sync (unlimited rows.
Every submission syncs automatically.)
Everything in Premium
15,000 submissions per month
Webhooks: connect to Zapier, Make,
Slack, Notion, Airtable, and more
Workflow automations
Custom HTML email templates
Auto-responder emails to form submitters
Multiple notification recipients
Custom email subject lines
Priority support with direct access
to the founder
No contracts. Cancel at any time.
👉 Start free at formgrid.dev
No credit card required.
Final Thoughts
Next.js makes it easy to build form components. Formgrid makes it
easy to do something useful with what those forms collect.
You do not need an API route. You do not need a database. You do not need to build a Google Sheets integration. You do not need Zapier.
Point your form at a Formgrid endpoint. Share a Google Sheet with one email address. Click connect.
Every submission lands in your spreadsheet automatically and in real time from that moment forward.
Full disclosure: I built Formgrid.
Written as honestly as I could.
Let me know in the comments if
anything is unclear or looks off.















Top comments (0)