Select Your Region

Region-Based Optimized Content

Sanity CMS Contact Form Integration in 2025 | Step-by-Step Guide

Learn how to integrate a contact form with Sanity CMS. step-by-step guide covers setup, configuration & best practices to connect forms seamlessly with Sanity backend

Sanity CMS Contact Form Integration in 2025_ Step-by-Step Guide

In the dynamic landscape of modern web development, user interaction is paramount. A contact form is not just a mere element on a website; it's a critical gateway for lead generation, customer support, and direct communication. For businesses leveraging a headless CMS like Sanity, integrating a robust and secure contact form is essential for capturing valuable user data and fostering engagement. This guide will provide a clear, step-by-step approach to Sanity CMS contact form integration in 2025, covering everything from project setup and schema definition to frontend integration and secure data handling.

As a leading IT agency specializing in Headless Solutions and Jamstack development, RW Infotech understands the nuances of creating high-performance, scalable web experiences. We'll demystify the process, offering practical insights, code examples, and best practices to ensure your Sanity-powered website effectively collects and manages user submissions. Whether you're a developer, content manager, or business owner, this article will equip you with the knowledge to implement a seamless and efficient contact form solution.

Understanding Headless CMS and Contact Forms

Before diving into the technicalities, let's establish a foundational understanding of the key concepts at play. A headless CMS like Sanity separates the content management backend from the frontend presentation layer. This architecture offers unparalleled flexibility, allowing developers to use their preferred frameworks (React, Next.js, Vue, Svelte, etc.) while content editors manage data centrally. Contact forms, in this context, become a mechanism to push user-generated data back into the CMS or a related service.

The primary benefit of integrating a contact form with Sanity is the ability to centralize form submissions alongside your other content. This means marketing teams can easily access, review, and manage leads or inquiries directly within the Sanity Studio, streamlining workflows and improving data governance. It also opens up possibilities for content-driven automation, where form submissions can trigger specific actions or content updates.

Setting Up Your Sanity Project for Form Data

The first step in any Sanity CMS integration involves preparing your backend. This section details how to set up your Sanity project and define the schema for your contact form submissions.

Initializing Your Sanity Project

If you don't already have a Sanity project, you can create one using the Sanity CLI. Open your terminal and run:

npm install -g @sanity/cli
sanity init
Copy

Follow the prompts to create a new project, select a schema template (e.g., "Clean project with no predefined schemas"), and choose your preferred dataset configuration. This will set up your Sanity Studio, which is where you'll define and manage your content schemas.

Defining the Contact Form Submission Schema

To store contact form data, you need to define a document type in your Sanity schema. This schema will dictate the structure of each submission. Navigate to your Sanity project's schemas directory (e.g., ./schemas/) and create a new file, for instance, submission.js.

Here’s an example of a basic contact form submission schema:

// schemas/submission.js
export default {
  name: 'submission',
  title: 'Form Submission',
  type: 'document',
  fields: [
    {
      name: 'name',
      title: 'Name',
      type: 'string',
      validation: Rule => Rule.required()
    },
    {
      name: 'email',
      title: 'Email',
      type: 'string',
      validation: Rule => Rule.required().email()
    },
    {
      name: 'subject',
      title: 'Subject',
      type: 'string',
    },
    {
      name: 'message',
      title: 'Message',
      type: 'text',
      validation: Rule => Rule.required()
    },
    {
      name: 'sourcePage',
      title: 'Source Page',
      type: 'string',
      description: 'The URL of the page where the form was submitted.'
    },
    {
      name: 'submittedAt',
      title: 'Submitted At',
      type: 'datetime',
      options: {
        dateFormat: 'YYYY-MM-DD',
        timeFormat: 'HH:mm',
        calendarTodayLabel: 'Today'
      },
      readOnly: true // This field will be set programmatically
    }
  ],
  preview: {
    select: {
      name: 'name',
      email: 'email',
      subject: 'subject',
      submittedAt: 'submittedAt'
    },
    prepare(selection) {
      const {name, email, subject, submittedAt} = selection
      return {
        title: `${name} (${email})`,
        subtitle: `${subject || 'No Subject'} - ${new Date(submittedAt).toLocaleDateString()}`
      }
    }
  }
}
Copy

After creating this file, remember to add it to your schema.js file (typically ./schemas/schema.js) within the types array:

// schemas/schema.js
import schemaTypes from 'all:part:@sanity/base/schema-type'
import createSchema from 'part:@sanity/base/schema-creator'

import submission from './submission' // Import your new schema

export default createSchema({
  name: 'default',
  types: schemaTypes.concat([
    submission, // Add it to the array
    // ... other schemas
  ]),
})
Copy

Run sanity start in your studio directory to see the new "Form Submission" document type available in your Sanity Studio. This completes the backend setup for receiving form data.

Frontend Integration: Building Your Contact Form

With the Sanity backend ready, the next step is to build the actual contact form on your frontend. This guide will use React/Next.js as an example, but the principles apply to any modern JavaScript framework.

Creating the Form Component

First, create a simple React component for your contact form. This component will handle form state, input changes, and the submission logic.

// components/ContactForm.js (Example for React/Next.js)
import React, { useState } from 'react';

const ContactForm = () => {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    subject: '',
    message: '',
  });
  const [status, setStatus] = useState(''); // 'idle', 'loading', 'success', 'error'

  const handleChange = (e) => {
    setFormData({ ...formData, [e.target.name]: e.target.value });
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    setStatus('loading');

    try {
      const response = await fetch('/api/submit-form', { // Your API endpoint
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          ...formData,
          sourcePage: window.location.href, // Capture source page
        }),
      });

      if (response.ok) {
        setStatus('success');
        setFormData({ name: '', email: '', subject: '', message: '' }); // Clear form
      } else {
        const errorData = await response.json();
        setStatus(`error: ${errorData.message || 'Unknown error'}`);
      }
    } catch (error) {
      console.error('Submission error:', error);
      setStatus(`error: ${error.message}`);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="contact-form">
      <div>
        <label htmlFor="name">Name:</label>
        <input
          type="text"
          id="name"
          name="name"
          value={formData.name}
          onChange={handleChange}
          required
        />
      </div>
      <div>
        <label htmlFor="email">Email:</label>
        <input
          type="email"
          id="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
          required
        />
      </div>
      <div>
        <label htmlFor="subject">Subject:</label>
        <input
          type="text"
          id="subject"
          name="subject"
          value={formData.subject}
          onChange={handleChange}
        />
      </div>
      <div>
        <label htmlFor="message">Message:</label>
        <textarea
          id="message"
          name="message"
          value={formData.message}
          onChange={handleChange}
          required
        />
      </div>
      <button type="submit" disabled={status === 'loading'}>
        {status === 'loading' ? 'Submitting...' : 'Send Message'}
      </button>

      {status === 'success' && <p className="success-message">Thank you for your message!</p>}
      {status.startsWith('error') && <p className="error-message">{status}</p>}
    </form>
  );
};

export default ContactForm;
Copy

This component captures user input and, upon submission, sends a POST request to a backend API endpoint (/api/submit-form). This endpoint will be responsible for interacting with Sanity.

Handling Form Submissions with an API Route

Directly exposing your Sanity API credentials on the frontend is a security risk. Instead, we'll create an API route (e.g., a Next.js API route or a serverless function) that acts as an intermediary, securely handling the interaction with Sanity.

Creating the API Route for Sanity Integration

For Next.js, create a file like pages/api/submit-form.js. This file will contain the logic to receive form data, validate it (optional but recommended), and then send it to Sanity.

// pages/api/submit-form.js (Example for Next.js API Route)
import sanityClient from '@sanity/client';

// Initialize Sanity client with your project details
// IMPORTANT: Use environment variables for these credentials in production!
const client = sanityClient({
  projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
  dataset: process.env.NEXT_PUBLIC_SANITY_DATASET,
  apiVersion: '2023-01-01', // Use a consistent API version
  token: process.env.SANITY_WRITE_TOKEN, // A token with write permissions
  useCdn: false, // Don't use CDN for writes
});

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ message: 'Method Not Allowed' });
  }

  const { name, email, subject, message, sourcePage } = req.body;

  // Basic server-side validation (enhance as needed)
  if (!name || !email || !message) {
    return res.status(400).json({ message: 'Name, email, and message are required.' });
  }

  try {
    const submission = {
      _type: 'submission', // Matches your Sanity schema document type name
      name,
      email,
      subject: subject || 'No Subject',
      message,
      sourcePage: sourcePage || 'Unknown',
      submittedAt: new Date().toISOString(),
    };

    const result = await client.create(submission);

    console.log('Form submission successful:', result);
    return res.status(200).json({ message: 'Form submitted successfully!', id: result._id });
  } catch (error) {
    console.error('Error submitting form to Sanity:', error);
    return res.status(500).json({ message: 'Failed to submit form.', error: error.message });
  }
}
Copy

Security Note: It is absolutely crucial to use environment variables for your Sanity API token (SANITY_WRITE_TOKEN). This token grants write access to your Sanity project, so it must never be exposed on the client-side. For Next.js, NEXT_PUBLIC_ variables are exposed to the browser, while others are server-side only. Your SANITY_WRITE_TOKEN should not start with NEXT_PUBLIC_.

Generating a Sanity Write Token

To get a SANITY_WRITE_TOKEN, navigate to your Sanity project dashboard (manage.sanity.io), go to "Settings" -> "API" -> "Tokens". Create a new token and ensure it has "Editor" permissions (or at least "Create" and "Update" for your submission document type). Copy this token and add it to your .env.local file in your Next.js project:

# .env.local
NEXT_PUBLIC_SANITY_PROJECT_ID="your-project-id"
NEXT_PUBLIC_SANITY_DATASET="your-dataset-name"
SANITY_WRITE_TOKEN="your-generated-sanity-write-token"
Copy

Restart your development server after adding environment variables.

Advanced Considerations and Best Practices

While the basic integration is now complete, there are several advanced considerations to enhance your

Spam Protection

Contact forms are prime targets for spam. Implement measures like reCAPTCHA (v2 or v3) or hCaptcha on your frontend. The API route should then verify the reCAPTCHA token before processing the submission to Sanity. This adds a crucial layer of defense against automated bots.

// Example: reCAPTCHA verification in your API route
// (Requires installing a package like 'google-recaptcha-verify' or similar)
const verifyRecaptcha = async (token) => {
  const response = await fetch(`https://www.google.com/recaptcha/api/siteverify?secret=${process.env.RECAPTCHA_SECRET_KEY}&response=${token}`, {
    method: 'POST',
  });
  const data = await response.json();
  return data.success;
};

// ... inside your API route handler
const { name, email, subject, message, sourcePage, recaptchaToken } = req.body;

if (!recaptchaToken || !(await verifyRecaptcha(recaptchaToken))) {
  return res.status(400).json({ message: 'reCAPTCHA verification failed.' });
}
// ... proceed with Sanity submission
Copy

Remember to get a reCAPTCHA secret key and add it to your server-side environment variables.

Email Notifications

Storing submissions in Sanity is excellent for management, but you'll likely want to receive immediate email notifications. Integrate a transactional email service like SendGrid, Mailgun, or Nodemailer (with an SMTP service) into your API route. After successfully writing to Sanity, trigger an email send to your team.

// Example: Sending an email after Sanity submission (using Nodemailer)
import nodemailer from 'nodemailer';

const transporter = nodemailer.createTransport({
  host: process.env.EMAIL_HOST,
  port: process.env.EMAIL_PORT,
  secure: process.env.EMAIL_SECURE === 'true', // Use 'true' for 465, 'false' for other ports
  auth: {
    user: process.env.EMAIL_USER,
    pass: process.env.EMAIL_PASS,
  },
});

// ... inside your API route, after client.create(submission)
try {
  await transporter.sendMail({
    from: process.env.EMAIL_FROM,
    to: process.env.EMAIL_TO, // Your recipient email
    subject: `New Form Submission: ${subject}`,
    html: `
      <p><strong>Name:</strong> ${name}</p>
      <p><strong>Email:</strong> ${email}</p>
      <p><strong>Subject:</strong> ${subject}</p>
      <p><strong>Message:</strong> ${message}</p>
      <p><strong>Source Page:</strong> ${sourcePage}</p>
      <p><strong>Submitted At:</strong> ${new Date().toLocaleString()}</p>
    `,
  });
  console.log('Notification email sent successfully.');
} catch (emailError) {
  console.error('Error sending notification email:', emailError);
}
Copy

Ensure all email service credentials are kept in server-side environment variables.

Data Validation and Sanitization

Always validate and sanitize user input on both the client-side (for better UX) and, more importantly, the server-side (for security). Use libraries like yup or zod for robust schema validation in your API routes. Sanitize inputs to prevent XSS attacks by escaping HTML or using a library like dompurify if you intend to display the raw input.

Error Handling and Logging

Implement comprehensive error handling in your API routes and frontend. Log errors to a service like Sentry or LogRocket for easier debugging and monitoring. Provide clear, user-friendly error messages on the frontend without exposing sensitive backend details.

avatar
Are you ready?

Hi, my name is Jaswinder, let's talk about your business needs.

I will do my best to find a reliable solution for you!

Performance Optimization for Forms

For high-traffic forms, consider optimizing the submission process. This might involve asynchronous processing of submissions (e.g., using a queue service like AWS SQS or Google Cloud Pub/Sub) to decouple the form submission from the Sanity write operation and email sending. This ensures a fast response to the user even if external services are slow.

Conclusion

Integrating a contact form with Sanity CMS in 2025 is a straightforward yet powerful way to enhance user engagement and streamline data management. By carefully defining your schema, building a secure API endpoint, and implementing best practices for validation, spam protection, and notifications, you can create a robust and efficient system for collecting valuable user information. This approach not only centralizes your form submissions within a flexible headless CMS but also leverages the performance and scalability benefits of modern web architectures like Jamstack.

At RW Infotech, we specialize in crafting bespoke Headless Solutions and Jamstack websites that empower businesses. Our expertise in Sanity CMS migrations, full-stack development, and AI automation allows us to design and implement sophisticated contact form integrations that are not only secure and performant but also tailored to your specific business needs. Whether you require advanced spam protection, custom data workflows, CRM integrations, or comprehensive digital marketing strategies for your headless site, our team is equipped to deliver. Partner with RW Infotech to transform your website's user interaction into a seamless, data-driven experience that fuels your growth.

Faq's

Frequently Asked Questions

Find answers to the most common questions about Sanity CMS Contact Form Integration.