If you have ever wanted to create a stunning custom contact form but didn't want to deal with backend servers, databases, or complex integrations, this guide is for you.
I'll show you how to build a professional contact form that:
- Submits to Google Forms (free google sheet storage)
- Creates tasks in your CRM (ClickUp, in this case)
- Looks completely custom - no ugly Google Forms UI
- Works with just frontend code - no backend required
Why This Approach?
Most developers either use Google Forms directly (which looks basic) or build a full backend with database. But there's a middle ground:
Benefits:
- Free storage - Google Sheets handles all your form data
- No backend costs - Everything runs in the browser
- Instant CRM integration - Every submission becomes an actionable task
- Complete design control - Build any UI you want
- Easy maintenance - No server to manage
Step 1: Create Your Google Form
First, create a normal Google Form with all the fields you need:
- Go to Google Forms
- Create a new form
- Add fields: Name, Email, Phone, Message, etc.
- Important: Set it to not require sign-in
For example, my form has:
- Full Name (Short answer)
- Email (Short answer)
- Phone Number (Short answer)
- Requirement/Message (Paragraph)
Step 2: Get the Prefilled Link
Here's the magic trick. Google Forms has a hidden feature called "Get pre-filled link":
- Open your form
- Click the three dots menu (⋮) in the top right
- Select "Get pre-filled link"
- Fill in dummy values for each field
- Click "Get Link"
You'll get a URL like:
https://docs.google.com/forms/d/e/FORM_ID/viewform?entry.123456=John&entry.789012=test@email.com
Notice the entry.XXXXXX parameters? Those are your field IDs.
Step 3: Convert to Form Action URL
Change the URL from viewform to formResponse:
Before:
https://docs.google.com/forms/d/e/YOUR_FORM_ID/viewform?entry.123=test
After:
https://docs.google.com/forms/d/e/YOUR_FORM_ID/formResponse
This is your form submission endpoint.
Step 4: Map Your Field IDs
From the prefilled link, note down each field's entry ID:
// My form field mappings:
// entry.1112309323 = Full Name
// entry.1832877796 = Email
// entry.533660756 = Phone Number
// entry.1198180553 = Message/RequirementStep 5: Build Your Custom Form
Now create your React component with a beautiful UI:
const ContactForm = () => {
const [formData, setFormData] = useState({
firstName: "",
lastName: "",
email: "",
phone: "",
message: "",
});
return (
<form
method="POST"
action="https://docs.google.com/forms/d/e/YOUR_FORM_ID/formResponse"
onSubmit={handleSubmit}
>
{/* Hidden combined name field */}
<input
type="hidden"
name="entry.1112309323"
value={`${formData.firstName} ${formData.lastName}`}
/>
{/* Email field */}
<input
type="email"
name="entry.1832877796"
value={formData.email}
onChange={(e) =>
setFormData((prev) => ({ ...prev, email: e.target.value }))
}
required
/>
{/* Phone field */}
<input
type="tel"
name="entry.533660756"
value={formData.phone}
onChange={(e) =>
setFormData((prev) => ({ ...prev, phone: e.target.value }))
}
required
/>
{/* Message field */}
<textarea
name="entry.1198180553"
value={formData.message}
onChange={(e) =>
setFormData((prev) => ({ ...prev, message: e.target.value }))
}
required
/>
<button type="submit">Send Message</button>
</form>
);
};Step 6: Prevent the Redirect to Google Forms
When you submit a form to Google Forms, it automatically redirects users to a Google Forms success page. This breaks the user experience on your custom site.
The solution? A hidden iframe that captures the redirect:
// Add a hidden iframe to your component
<iframe name="hidden_iframe" style={{ display: "none" }}></iframe>
// Target the iframe in your form
<form
method="POST"
target="hidden_iframe"
action="https://docs.google.com/forms/d/e/YOUR_FORM_ID/formResponse"
onSubmit={handleSubmit}
>How it works:
- The
target="hidden_iframe"attribute tells the browser to load the response inside the iframe - Instead of your page redirecting to Google Forms, the success page loads in the hidden iframe
- Your user stays on your beautiful custom form
- You can show your own success message
This is a clever workaround that keeps users on your site while still successfully submitting to Google Forms in the background.
Step 7: Add CRM Integration
Now for the powerful part - creating tasks in your CRM automatically. I'm using ClickUp, but you can adapt this for any CRM with an API.
First, get your ClickUp API key:
- Go to ClickUp Settings → Apps
- Generate an API token
- Find your List ID (from the URL when viewing a list)
const submitToClickUp = async (formData) => {
const fullName = `${formData.firstName} ${formData.lastName}`;
const fullPhone = `${dialingCodes[formData.countryCode]}${formData.phone}`;
const clickUpData = {
name: `New Lead: ${fullName}`,
description: `
Name: ${fullName}
Email: ${formData.email}
Phone: ${fullPhone}
Requirement:
${formData.message}
Submitted: ${new Date().toLocaleString()}
`,
assignees: [YOUR_USER_ID],
due_date: Date.now() + 24 * 60 * 60 * 1000, // 24 hours from now
};
const response = await fetch(
"https://api.clickup.com/api/v2/list/YOUR_LIST_ID/task",
{
method: "POST",
headers: {
Authorization: "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify(clickUpData),
}
);
return response.ok;
};Step 8: Submit to Both Simultaneously
Use Promise.allSettled to submit to both Google Forms and ClickUp at the same time:
const handleSubmit = async (e) => {
e.preventDefault();
setIsSubmitting(true);
const form = e.target;
const data = new FormData(form);
try {
const [googleFormsResult, clickUpResult] = await Promise.allSettled([
// Google Forms submission
fetch(form.action, {
method: "POST",
mode: "no-cors",
body: data,
}),
// ClickUp submission
submitToClickUp(formData),
]);
// Show success message
setIsSuccess(true);
// Reset form after 3 seconds
setTimeout(() => {
setFormData({
firstName: "",
lastName: "",
email: "",
phone: "",
message: "",
});
setIsSuccess(false);
}, 3000);
} catch (error) {
console.error("Error:", error);
}
};The Complete Flow
- User fills out your beautiful custom form
- On submit, data goes to both:
- Google Forms → stored in Google Sheets (your backup/database)
- ClickUp API → creates an actionable task with all details
- Google's success page loads in the hidden iframe (invisible to user)
- You show your own custom success message
- You get a notification in ClickUp
- Data is safely stored in Google Sheets
Pro Tips
Add Phone Number Validation:
const validatePhone = (phone) => {
const phoneRegex = /^[0-9]{7,15}$/;
return phoneRegex.test(phone);
};Add Country Code Selector:
const dialingCodes = {
US: "+1",
IN: "+91",
UK: "+44",
CA: "+1",
};Show Loading States:
{
isSubmitting ? (
<div className="animate-spin">⏳</div>
) : isSuccess ? (
<div>✅ Message Sent!</div>
) : (
<div>Send Message</div>
);
}Advantages of This Approach
- Zero backend costs - No servers, no databases to pay for
- Instant setup - Can be done in 30 minutes
- Reliable - Google's infrastructure handles storage
- Flexible - Easy to add more CRM integrations
- Transparent - All submissions visible in Google Sheets
- No vendor lock-in - Your data is in Google Sheets, easy to export
Limitations to Know
- API keys are exposed in frontend (use environment variables in production)
- Google Forms has rate limits (but they're quite high)
- No server-side validation (add client-side validation carefully)
- Because of
mode: "no-cors", you can't programmatically confirm Google Forms submission status
Taking It Further
You can extend this to:
- Send email notifications using EmailJS
- Integrate with Slack, Discord, or Telegram
- Connect to Notion databases
- Trigger Zapier workflows
- Add to multiple CRMs simultaneously
Real-World Impact
I've used this exact setup for client projects. Every form submission:
- Gets logged in Google Sheets (searchable database)
- Creates a ClickUp task (assigned to sales team)
- Due date set for 24-hour follow-up
No backend. No database. Just smart frontend integration.
The best part? Non-technical team members can access responses directly in Google Sheets, and developers can still use the CRM API for automation.
This approach has saved me countless hours of backend development and hosting costs. Try it out for your next project - you might never go back to traditional form backends.
Questions or improvements? Let me know!
