OverviewWhen a visitor fills in the form and clicks submit, the form sends their details to yourFAB CRM dashboard, where they appear as a new lead. Once it's saved, the form resets and thanks the visitor; if anything goes wrong, a short error message is shown so they can try again. Every lead is tied to yourapplication id, so FAB CRM knows which account it belongs to. Copy yours from the CS app underPage Pilot app → pagepilot.fabbuilder.com/tenantand paste it into the form's IntegrationGetting the form working takes three short steps. Follow them in order and you'll be capturing leads in a few minutes.
That's all you need for a working form. To stop spam submissions, you can also add Google reCAPTCHA — see the The full componentCopy this in as-is. The list of inputs lives in a single
LeadForm.tsx A complete, ready-to-use lead capture form.
Already have a form?If you've already built your own form, you don't need the component above — just call this helper with your form values to send the lead
import { useState } from 'react';
// Replace YOUR_WORKSPACE_ID with your application id.
const LEAD_API = 'https://cs.fabbuilder.com/api/tenant/YOUR_WORKSPACE_ID';
// Dynamic field config — add / remove / reorder inputs here.
const FIELDS = [
{ name: 'firstName', label: 'First Name', type: 'text' },
{ name: 'businessEmail', label: 'Email', type: 'email' },
{ name: 'phoneNumber', label: 'Phone Number', type: 'tel' },
{ name: 'companyName', label: 'Company Name', type: 'text' },
{ name: 'message', label: 'Message', textarea: true },
];
const EMPTY = Object.fromEntries(FIELDS.map((f) => [f.name, '']));
export default function LeadForm() {
const [values, setValues] = useState(EMPTY);
const [status, setStatus] = useState('idle'); // idle | submitting | success | error
const [error, setError] = useState('');
const onChange = (e) => {
const { name, value } = e.target;
setValues((v) => ({ ...v, [name]: value }));
};
const onSubmit = (e) => {
e.preventDefault();
setStatus('submitting');
setError('');
const payload = {
firstName: values.firstName,
phone: values.phoneNumber,
email: values.businessEmail,
companyName: values.companyName,
description: `${values.companyName}-${values.message}`,
source: 'website',
};
const body = {
data: {
...payload,
messageInfo: { text: values.message },
tags: ['CX'],
sourceDetail: { pageUrl: window.location.href },
},
};
const qs = new URLSearchParams(payload).toString();
const xhr = new XMLHttpRequest();
xhr.open('POST', `${LEAD_API}/lead?${qs}`, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = () => {
if (xhr.readyState !== XMLHttpRequest.DONE) return;
if (xhr.status === 200) {
setStatus('success');
setValues(EMPTY);
} else {
const data = JSON.parse(xhr.responseText || '{}');
setStatus('error');
setError(data.message || 'Submission failed. Please try again.');
}
};
xhr.onerror = () => {
setStatus('error');
setError('Network error. Please check your connection and try again.');
};
xhr.send(JSON.stringify(body));
};
if (status === 'success') return <p>Thanks — we got your details!</p>;
return (
<form onSubmit={onSubmit}>
{FIELDS.map((f) => (
<div key={f.name}>
<label htmlFor={f.name}>{f.label}</label>
{f.textarea ? (
<textarea id={f.name} name={f.name} value={values[f.name]} onChange={onChange} />
) : (
<input
id={f.name}
name={f.name}
type={f.type || 'text'}
value={values[f.name]}
onChange={onChange}
/>
)}
</div>
))}
{error && <p>{error}</p>}
<button type="submit" disabled={status === 'submitting'}>
{status === 'submitting' ? 'Submitting…' : 'Submit'}
</button>
</form>
);
}
Integrate using AIPrefer to let your AI assistant do the work? Copy the prompt below into Cursor, Claude, or GitHub Copilot and it will build the form and connect it to FAB CRM for you.
Integrate using AI Copy this prompt into Cursor, Claude, or GitHub Copilot and let it wire PagePilot into your codebase, step by step.
You are helping me add a lead capture form to my React app. Build a single, self-contained component called LeadForm that submits to the FAB CRM lead API. Follow every instruction below.
GOAL
Create LeadForm.tsx — a contact form that, on submit, creates a lead in FAB CRM.
API
- Endpoint: const LEAD_API = "https://cs.fabbuilder.com/api/tenant/YOUR_WORKSPACE_ID";
- Replace YOUR_WORKSPACE_ID with my workspace/application id (found in the CS app under Settings -> General Settings: https://pagepilot.fabbuilder.com/tenant).
- Method: POST, header Content-Type: application/json, sent with XMLHttpRequest.
FIELDS (drive these from a single config array so they're easy to change)
- firstName (label "First Name", text)
- businessEmail (label "Email", email)
- phoneNumber (label "Phone Number", tel)
- companyName (label "Company Name", text)
- message (label "Message", textarea)
ON SUBMIT
1. Build a flat payload that maps the inputs to the API's field names:
firstName -> firstName
phoneNumber -> phone
businessEmail -> email
companyName -> companyName
description -> `${companyName}-${message}`
source -> "website"
2. Build the request body as: { data: { ...payload, messageInfo: { text: message }, tags: ["CX"], sourceDetail: { pageUrl: window.location.href } } }
3. Append the flat payload to the URL as a query string AND send the body as JSON:
POST `${LEAD_API}/lead?${new URLSearchParams(payload)}`
STATES & UX
- Track a status: "idle" | "submitting" | "success" | "error".
- While submitting, disable the submit button and show a loading label.
- On status 200: clear the form and show a friendly thank-you message.
- On any other status: read the error message from xhr.responseText (JSON, "message" field) and show it inline.
- On a network error (xhr.onerror): show a generic "check your connection" message.
CONSTRAINTS
- Use plain React with hooks only — no form libraries, no antd, no styled-components.
- Keep it dependency-free apart from React.
- Add short comments explaining each step.
Form fieldsOut of the box the form asks for the details below. The fields are listed in one place at the top of the component, so you can drop any you don't need or add new ones to suit your use case.
What gets sentYou usually won't need to touch this, but it helps to know what leaves the browser. Before sending, the form renames its inputs to the names FAB CRM expects (for example
The request How the form's inputs are packaged before they're sent.
// Form input → API field mapping
const payload = {
firstName: values.firstName, // firstName -> firstName
phone: values.phoneNumber, // phoneNumber -> phone
email: values.businessEmail, // businessEmail -> email
companyName: values.companyName, // companyName -> companyName
description: `${values.companyName}-${values.message}`,
source: 'website',
// token: gReCaptchaToken, // optional reCAPTCHA token
};
// Sent BOTH as the query string AND nested under "data" in the body.
const body = {
data: {
...payload,
messageInfo: { text: values.message }, // message -> messageInfo.text
tags: ['CX'],
sourceDetail: { pageUrl: window.location.href },
},
};
const qs = new URLSearchParams(payload).toString();
const xhr = new XMLHttpRequest();
xhr.open('POST', `${LEAD_API}/lead?${qs}`, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(body));
Two extra pieces of information ride along automatically: string[]optionaldefault: ['CX']tags— a label that helps you group and filter leads in FAB CRM. { pageUrl: string }optionaldefault: window.location.hrefsourceDetail— records which page the lead came from, so you know where your leads are coming from. Add your own tracking details here if you like.
Stopping spam (optional)Public forms attract bots. If you start seeing junk submissions, add Google reCAPTCHA: it quietly checks each submission in the background and attaches a one-time
Adding reCAPTCHA Get a token when the form is submitted, then send it along with the lead.
import { useGoogleReCaptcha } from 'react-google-recaptcha-v3';
function LeadFormWithCaptcha() {
const { executeRecaptcha } = useGoogleReCaptcha();
const onSubmit = async (e) => {
e.preventDefault();
if (!executeRecaptcha) return; // not ready yet
// Generate a v3 token for this action…
const token = await executeRecaptcha('enquiryFormSubmit');
// …and include it in the payload.
await submitLead(values, { token });
};
// …render the same form, calling onSubmit
}
// Wrap your app once with the provider:
// <GoogleReCaptchaProvider reCaptchaKey="YOUR_SITE_KEY">
// <App />
// </GoogleReCaptchaProvider>
After submittingHere's what the visitor experiences once they hit submit, and how the form handles each outcome:
API referenceThe technical details, for reference. If you're using the component or helper above, this is all handled for you — these tables are here for anyone integrating the endpoint directly. Endpoint
Body fields (under `data`)
|