Dynamic Form Builder
PulseGate provides a single script at /pulse.js that loads both session tracking and the form builder. Add one script tag, call PulseGate.init(), then configure and render forms with PulseGateForm.configure() and PulseGateForm.render(). The endpoint defaults to the script’s origin, so no URL config is required for typical setups.
- Single script:
https://your-worker.dev/pulse.js— use this for new integrations. PulseGate.init()— initializes tracking and sets the form endpoint to the script origin (default path/lead.capture).PulseGateForm.configure()— sets common config (hidden fields, theme) once.PulseGateForm.render()— renders form(s) into all elements matching the target selector.- Hidden fields from
configure()andrender()are merged — per-render values win on conflicts. - Default submit mode is traditional POST — browser follows the lead API’s
301redirect to the thank-you page.
Scripts are minified in CI; run npm run build:form locally if you change the form or tracker source.
Quick Start (single script)
Add the script, call PulseGate.init(), then configure and render your form. Omit endpoint to use the default /lead.capture on the script’s origin.
<div class="lead-form"></div>
<script src="https://your-worker.dev/pulse.js"></script>
<script>
PulseGate.init();
PulseGateForm.configure({
hidden: { SOURCE: 'WebSite', TEAM: 'sales' }
});
PulseGateForm.render({
target: '.lead-form',
hidden: { PROJECT: 'SunsetVillas', LOCATION: 'pune' },
countryCode: { enabled: true, required: true, defaultValue: '+91' },
fields: [
{ name: 'FIRSTNAME', label: 'Full Name', type: 'text', required: true },
{ name: 'EMAIL', label: 'Email', type: 'email', required: true },
{ name: 'PHONE', label: 'Phone', type: 'tel', required: true }
],
submitText: 'Enquire Now'
});
</script>Every .lead-form div gets a form with hidden fields SOURCE, TEAM, PROJECT, LOCATION. On submit, the browser POSTs to the worker’s /lead.capture endpoint and follows the 301 to the thank-you page.
Minimal integration (PROJECT, LOCATION, DOMAIN)
The form always includes FIRSTNAME, PHONE, EMAIL, and COUNTRYCODE. Default TEAM is umikoindia. DOMAIN is auto-detected from the page if not set. So integrators typically only need to pass PROJECT and LOCATION (and optionally DOMAIN):
<div class="lead-form"></div>
<script src="https://your-worker.dev/pulse.js"></script>
<script>
PulseGate.init();
PulseGateForm.render({
target: '.lead-form',
hidden: { PROJECT: 'Sunset Villas', LOCATION: 'Pune' },
submitText: 'Enquire Now'
});
</script>No need to pass fields or countryCode unless you want to customize; TEAM defaults to umikoindia, DOMAIN is taken from window.location.hostname.
Endpoint: Omit endpoint to use default /lead.capture on the script origin. To override: PulseGate.init({ endpoint: '...' }) or PulseGate.init({ formEndpoint: '...' }), or set a path or full URL in PulseGateForm.configure({ endpoint: '/lead.capture' }). The legacy path /lead is deprecated (sunset 2026-06-01).
Alternative: separate scripts (tracker.js + form.js)
If you prefer to load tracking and form separately (e.g. tracker in head, form later), use /tracker.js and /form.js and set the session endpoint explicitly. With separate scripts you must set PulseGateForm.configure({ endpoint: '...' }) to a full URL or path (e.g. '/lead.capture'), since there is no PulseGate.init() to set it from the script origin.
<div class="lead-form"></div>
<!-- Replace your-worker.dev with your PulseGate worker URL -->
<script src="https://your-worker.dev/pulse.js"></script>
<script>
PulseGateForm.configure({
hidden: { SOURCE: 'WebSite', TEAM: 'sales' }
});
PulseGateForm.render({
target: '.lead-form',
hidden: { PROJECT: 'SunsetVillas', LOCATION: 'pune' },
countryCode: { enabled: true, required: true, defaultValue: '+91' },
fields: [
{ name: 'FIRSTNAME', label: 'Full Name', type: 'text', required: true },
{ name: 'EMAIL', label: 'Email', type: 'email', required: true },
{ name: 'PHONE', label: 'Phone', type: 'tel', required: true }
],
submitText: 'Enquire Now'
});
</script>Multiple Different Forms on One Page
Use different class names for different form configs. With the single script, call PulseGate.init() once, then configure and multiple render() calls:
<div class="hero-form"></div>
<div class="sidebar-callback"></div>
<div class="hero-form"></div> <!-- same config as the first one -->
<script src="https://your-worker.dev/pulse.js"></script>
<script>
PulseGate.init();
PulseGateForm.configure({
hidden: { SOURCE: 'WebSite', TEAM: 'sales' }
});
// All .hero-form elements get the full enquiry form
PulseGateForm.render({
target: '.hero-form',
hidden: { PROJECT: 'SunsetVillas', LOCATION: 'pune' },
countryCode: { enabled: true, required: true, defaultValue: '+91' },
fields: [
{ name: 'FIRSTNAME', label: 'Full Name', type: 'text', required: true },
{ name: 'EMAIL', label: 'Email', type: 'email', required: true },
{ name: 'PHONE', label: 'Phone', type: 'tel', required: true }
],
submitText: 'Enquire Now'
});
// All .sidebar-callback elements get a compact callback form
PulseGateForm.render({
target: '.sidebar-callback',
hidden: { PROJECT: 'GreenMeadows', LOCATION: 'mumbai' },
fields: [
{ name: 'FIRSTNAME', label: 'Name', type: 'text', required: true },
{ name: 'PHONE', label: 'Phone', type: 'tel', required: true }
],
submitText: 'Call Me Back'
});
</script>Both forms inherit SOURCE and TEAM from configure(), but have different PROJECT and LOCATION values.
How Hidden Fields Merge
configure({ hidden: { SOURCE: 'WebSite', TEAM: 'sales' } })
+
render({ hidden: { PROJECT: 'Villas', TEAM: 'marketing' } })
=
Final hidden fields: { SOURCE: 'WebSite', TEAM: 'marketing', PROJECT: 'Villas' }Per-render values override global values when the same key exists.
Default and configurable fields
Form fields (always included) — FIRSTNAME, EMAIL, COUNTRYCODE, and PHONE are always on the form by default. Add more via fields or hidden as needed.
What integrators typically set — For most integrations you only need to pass PROJECT, LOCATION, and DOMAIN in hidden. TEAM defaults to umikoindia; DOMAIN is auto-detected from the page hostname if omitted.
| Field | Type | Description |
|---|---|---|
FIRSTNAME | visible | Full name |
EMAIL | visible | Email address |
COUNTRYCODE | visible | Country dial code (selector; enabled by default) |
PHONE | visible | Phone number |
TEAM | hidden | Team assignment (default 'umikoindia' if not set in configure() / render()) |
Configurable hidden fields (set per form) — Pass these via hidden in configure() or render():
| Field | Description |
|---|---|
LOCATION | Location or site (e.g. 'Whitefield, Bangalore') |
PROJECT | Project or campaign name (e.g. 'Sky Towers') |
DOMAIN | Source domain (auto-detected from window.location.hostname if not set) |
You can override TEAM in configure() or render() and add custom hidden fields as needed.
Google Ads & Campaign Tracking
The script auto-captures campaign parameters from the page URL:
| Parameter | Source | Description |
|---|---|---|
gclid | Google Ads | Google Click Identifier |
gbraid | Google Ads | App-to-web measurement |
wbraid | Google Ads | Web-to-app measurement |
utm_source | Any | Campaign source |
utm_medium | Any | Campaign medium |
utm_campaign | Any | Campaign name |
utm_term | Any | Campaign keyword |
utm_content | Any | Campaign content variant |
fbclid | Meta Ads | Facebook Click Identifier |
msclkid | Bing Ads | Microsoft Click Identifier |
KEYWORD auto-resolution (when not set in hidden fields):
utm_termpresent → uses its valuegclid/gbraid/wbraid→google-adsfbclid→facebook-adsmsclkid→bing-ads
Campaign params are sent as hidden fields prefixed with __ (e.g. __gclid, __utm_source).
API Reference
PulseGateForm.configure(config)
Sets global defaults inherited by all render() calls. Call once after PulseGate.init() when using the single script.
| Option | Type | Description |
|---|---|---|
endpoint | string | With pulse.js, omit to use default /lead.capture on script origin. Otherwise: full URL or path (e.g. '/lead.capture') resolved against script origin. |
hidden | object | Common hidden fields (SOURCE, TEAM, etc.) |
theme | object | Visual customization |
submitText | string | Default button label |
submitMode | string | Default submit mode |
showBranding | boolean | Show "Powered by PulseGate" |
inputClassName | string | Extra CSS class(es) applied to all inputs (for host-site styling) |
showLabels | boolean | Whether to render field labels (default true); set false to hide and control layout via CSS |
submitClassName | string | Extra CSS class(es) applied to the submit/CTA button (for host-site styling) |
Any option set in configure() can be overridden per render() call.
PulseGateForm.render(config)
Renders form(s) into all elements matching target. Returns a single instance object or an array if multiple elements matched.
| Option | Type | Default | Description |
|---|---|---|---|
target | string | Element | — | Required. CSS selector (class or ID) or DOM element |
hidden | object | {} | Per-form hidden fields (merged with global). Use for LOCATION, PROJECT, DOMAIN, or to override TEAM. |
fields | array | FIRSTNAME, EMAIL, COUNTRYCODE, PHONE | Visible field definitions; add or replace as needed |
countryCode | object | { enabled: true } | Country dial code field; set enabled: false to disable |
submitMode | string | 'redirect' | 'redirect' = traditional POST, 'ajax' = XHR |
title | string | — | Form heading |
subtitle | string | — | Subheading |
submitText | string | 'Submit' | Button label |
loadingText | string | 'Submitting...' | Button text during submit |
successTitle | string | 'Thank You!' | Success heading (ajax mode) |
successMessage | string | auto | Success text (ajax mode) |
theme | object | — | Visual customization (merged with global) |
showBranding | boolean | true | Show branding footer |
inputClassName | string | — | Extra CSS class(es) on all inputs (e.g. 'my-input' or 'form-control input-lg') for host-site styling |
showLabels | boolean | true | Whether to show field labels; set false to hide labels (form gets class pg-form-no-labels for your CSS) |
submitClassName | string | — | Extra CSS class(es) on the submit/CTA button (e.g. 'btn btn-primary') for host-site styling |
onBeforeSubmit | function | — | Return false to cancel submit |
onSuccess | function(data) | — | Called after submit |
onError | function(error) | — | Called on failure (ajax mode) |
Field Definition
| Property | Type | Default | Description |
|---|---|---|---|
name | string | — | Required. POST field name (FIRSTNAME, EMAIL, etc.) |
label | string | — | Required. Visible label |
type | string | 'text' | text, email, tel, number, select, textarea |
required | boolean | false | Must have value |
placeholder | string | — | Placeholder text |
minLength | number | — | Min characters |
maxLength | number | — | Max characters |
pattern | string | — | Custom regex |
validationMessage | string | 'Invalid format' | Pattern error message |
options | array | — | For select: string[] or { value, label }[] |
rows | number | — | For textarea: visible rows |
defaultValue | string | — | Pre-filled value or default selected option |
className | string | — | Extra CSS class(es) for this field’s input (in addition to global inputClassName) |
countryCode Option
The country dial code selector is enabled by default and posts COUNTRYCODE to the lead API. When COUNTRYCODE and PHONE are adjacent, they render inline in one row on desktop and stack on mobile. Set countryCode: { enabled: false } to disable.
If defaultValue is not set, the form defaults from the user’s locale (e.g. navigator.language / navigator.languages) so the country code select is pre-filled by location when possible.
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Include country code field; set to false to disable |
required | boolean | false | Mark country code field required |
defaultValue | string | from locale | Selected dial code (+1, +91, etc.); omit to use locale-based default |
position | string | 'beforePhone' | 'beforePhone' or 'afterPhone' |
name | string | 'COUNTRYCODE' | Field name sent to API |
label | string | 'Country Code' | Label text |
placeholder | string | 'Select country code' | Select placeholder |
options | array | built-in global list (alphabetical) | Custom dial-code options |
className | string | — | Extra CSS class(es) for the country code select only (e.g. a narrower dropdown); merged with global inputClassName |
Common Hidden Fields
| Field | Default / behaviour | Purpose |
|---|---|---|
TEAM | Default 'umikoindia' | Team assignment; override in configure() or render() if needed |
LOCATION | Configurable | Set in hidden (e.g. 'Whitefield, Bangalore') |
PROJECT | Configurable | Set in hidden (e.g. project or campaign name) |
DOMAIN | Auto-detected from hostname | Source domain; override in hidden if needed |
SOURCE | Optional | Lead source (e.g. 'WebSite', 'FB'); set in hidden |
COUNTRYCODE | From visible selector | Sent from the country code field |
KEYWORD | Auto-detected from URL | Marketing keyword (from UTM/gclid/fbclid etc.) |
Theme Options
| Property | Default | Description |
|---|---|---|
primaryColor | #2563eb | Button and focus color |
borderRadius | 8px | Corner rounding |
fontFamily | inherit | Font stack |
backgroundColor | #ffffff | Form background |
textColor | #1f2937 | Input text color |
labelColor | #374151 | Label color |
borderColor | #d1d5db | Input border |
errorColor | #dc2626 | Error color |
successBg | #f0fdf4 | Success background |
successColor | #166534 | Success text |
Complete configuration sample
Below is a single example that includes every configuration option. Copy it and remove or change only what you need.
<div class="lead-form"></div>
<script src="https://your-worker.dev/pulse.js"></script>
<script>
PulseGate.init();
// Optional: set defaults once for all forms on the page
PulseGateForm.configure({
endpoint: '/lead.capture', // or full URL; omit to use script origin
hidden: {
SOURCE: 'WebSite',
TEAM: 'umikoindia' // default; override per render if needed
},
theme: {
primaryColor: '#2563eb',
borderRadius: '8px',
fontFamily: 'inherit',
backgroundColor: '#ffffff',
textColor: '#1f2937',
labelColor: '#374151',
borderColor: '#d1d5db',
errorColor: '#dc2626',
successBg: '#f0fdf4',
successColor: '#166534'
},
submitText: 'Submit',
submitMode: 'redirect', // 'redirect' | 'ajax'
showBranding: true,
inputClassName: '', // e.g. 'form-control'
showLabels: true,
submitClassName: '' // e.g. 'btn btn-primary'
});
PulseGateForm.render({
target: '.lead-form', // required: selector or DOM element
// Hidden fields (PROJECT, LOCATION, DOMAIN are what you usually set)
hidden: {
PROJECT: 'Sunset Villas',
LOCATION: 'Pune',
DOMAIN: 'example.com', // omit to use window.location.hostname
TEAM: 'umikoindia', // omit to use default
SOURCE: 'WebSite'
},
// Visible fields (omit to use defaults: FIRSTNAME, EMAIL, COUNTRYCODE, PHONE)
// Per field you can also use: minLength, maxLength, pattern, validationMessage, options (select), rows (textarea), defaultValue
fields: [
{ name: 'FIRSTNAME', label: 'Full Name', type: 'text', required: true, placeholder: 'Your name', className: '' },
{ name: 'EMAIL', label: 'Email', type: 'email', required: true, placeholder: 'you@example.com' },
{ name: 'PHONE', label: 'Phone', type: 'tel', required: true, placeholder: '+91 98765 43210' }
// COUNTRYCODE is added automatically unless countryCode.enabled is false
],
// Country code select (omit to use defaults: enabled, locale-based default)
countryCode: {
enabled: true,
required: false,
defaultValue: '+91', // omit to use locale-based default
position: 'beforePhone', // 'beforePhone' | 'afterPhone'
name: 'COUNTRYCODE',
label: 'Country Code',
placeholder: 'Select country code',
options: [], // omit to use built-in list
className: '' // e.g. 'country-code-select'
},
submitMode: 'redirect', // 'redirect' | 'ajax'
title: 'Get in touch',
subtitle: 'We\'ll get back to you within 24 hours.',
submitText: 'Enquire Now',
loadingText: 'Submitting...',
successTitle: 'Thank You!',
successMessage: 'We received your information and will get back to you soon.',
theme: {}, // merged with configure() theme
showBranding: true,
inputClassName: 'my-input',
showLabels: true,
submitClassName: 'my-cta',
// Callbacks (optional)
onBeforeSubmit: function () { return true; }, // return false to cancel
onSuccess: function (data) { console.log('Success', data); },
onError: function (err) { console.error('Error', err); }
});
</script>Minimal version (only what you usually need):
PulseGate.init();
PulseGateForm.render({
target: '.lead-form',
hidden: { PROJECT: 'My Project', LOCATION: 'Pune' },
submitText: 'Enquire Now'
});Embedding on external sites
When embedding the form on different websites, you can align styling with the host site using:
inputClassName— Extra class name(s) applied to every input, select, and textarea. Use your site’s existing form classes (e.g.form-control,my-site-input). Space-separated values are supported.submitClassName— Extra class name(s) applied to the submit/CTA button (e.g.btn btn-primary).showLabels— Set tofalseto hide labels and control layout entirely with your CSS (e.g. placeholders only). When labels are hidden, the form wrapper gets the classpg-form-no-labelsso you can target it (e.g. reduce spacing).- Per-field
className— On any field object, setclassNameto add extra class(es) to that field’s input only. countryCode.className— In thecountryCodeconfig, setclassNameto style the country code select separately (e.g.countryCode: { enabled: true, className: 'country-code-select' }).
Example: match a Bootstrap-style site and hide labels:
PulseGateForm.render({
target: '.lead-form',
inputClassName: 'form-control',
submitClassName: 'btn btn-primary',
showLabels: false,
fields: [
{ name: 'FIRSTNAME', label: 'Full Name', type: 'text', required: true, placeholder: 'Your name' },
{ name: 'EMAIL', label: 'Email', type: 'email', required: true, placeholder: 'Email' },
{ name: 'PHONE', label: 'Phone', type: 'tel', required: true, placeholder: 'Phone' }
],
submitText: 'Submit'
});Your CSS can target .pg-form .form-control, .pg-form .pg-submit (or your CTA class), and .pg-form-no-labels .pg-field as needed.
Custom CSS for submit button and inputs
You can style the submit button and inputs in two ways:
1. Add your own classes and style them
Pass inputClassName and submitClassName in render() (or configure()), then add CSS on your page that targets those classes:
<style>
.my-input { border-radius: 6px; padding: 10px 14px; font-size: 1rem; }
.my-cta { background: #059669; border-radius: 8px; padding: 12px 24px; }
.my-cta:hover { background: #047857; }
</style>
<div class="lead-form"></div>
<script src="https://your-worker.dev/pulse.js"></script>
<script>
PulseGate.init();
PulseGateForm.render({
target: '.lead-form',
inputClassName: 'my-input',
submitClassName: 'my-cta',
hidden: { PROJECT: 'My Project', LOCATION: 'Pune' },
submitText: 'Submit'
});
</script>The form adds your classes in addition to pg-input and pg-submit, so your rules apply (use the same specificity or higher if needed).
2. Target the built-in classes
Every input/select/textarea has class pg-input; the submit button has pg-submit. The form is wrapped in .pg-form. Add your own styles that target these:
.pg-form .pg-input {
border-radius: 8px;
padding: 0.75rem 1rem;
border: 1px solid #e5e7eb;
}
.pg-form .pg-submit {
background: linear-gradient(180deg, #2563eb, #1d4ed8);
border-radius: 8px;
font-weight: 600;
}Scope with your container (e.g. .lead-form .pg-input) if you have multiple forms on the page.
Submit Modes
redirect (default)
Traditional form POST. Browser submits application/x-www-form-urlencoded data and follows the lead API's 301 to the thank-you page. This is the standard mode for marketing websites.
ajax
XHR POST with JSON body. The page doesn't navigate — the form shows an inline success message. Useful for SPAs or modals.
Return Value
render() returns:
- Single match —
{ formId, reset() } - Multiple matches — Array of
{ formId, reset() }
Call reset() to restore a form to its initial state.