JavaScript Embed
The recommended way to embed ProForms on any website. Add a single script tag and the form renders itself โ full styling, validation, auto-formatting, and submission handling included. No dependencies, no build step.
Basic Embed
Drop this snippet wherever you want the form to appear:
<script src="https://proforms.io/embed.js" data-form="YOUR_FORM_UUID"></script>The script inserts the form immediately after itself. Copy the snippet from your form's Share tab in the builder.
Embed Modes
Control how the form's CSS interacts with your site using data-mode:
<!-- Isolated (default) โ Shadow DOM, fully scoped, no CSS bleed -->
<script src="https://proforms.io/embed.js"
data-form="YOUR_FORM_UUID"
data-mode="isolated"></script>
<!-- Open โ inherits your site's CSS, useful for tight design integration -->
<script src="https://proforms.io/embed.js"
data-form="YOUR_FORM_UUID"
data-mode="open"></script>| Attribute | Values | Description |
|---|---|---|
| data-form | string (required) | Your form UUID |
| data-mode | "isolated" | "open" | Shadow DOM isolation (default: "isolated") |
Caching & Updates
The embed fetches your form config from /api/v1/f/:uuid on every page load. Responses use Cache-Control: no-cache with an ETag based on the form version, so the browser revalidates on every load but only downloads new data when the form has changed (304 Not Modified otherwise).
version number increments on every update and drives cache invalidation automatically.Multiple Forms on One Page
Add multiple script tags โ each renders independently:
<!-- Contact form -->
<script src="https://proforms.io/embed.js" data-form="FORM_UUID_1"></script>
<!-- Newsletter signup -->
<script src="https://proforms.io/embed.js" data-form="FORM_UUID_2"></script>reCAPTCHA v3
When settings.security.recaptchaEnabled is true on a form, the embed automatically loads the reCAPTCHA v3 script and attaches a token to every submission. No extra configuration needed.
The embed resolves the reCAPTCHA site key using this priority order:
- The site's custom
recaptchaSiteKey(if configured via the Sites API or Site Settings) - The ProForms platform global key (fallback โ works on any domain)
Server-side verification uses the matching secret key in the same priority order. The score threshold is controlled per-form via settings.security.recaptchaThreshold (default: 0.5).
Badge Display
Google requires either the floating reCAPTCHA badge or a text attribution to be visible. Control this per-form with settings.security.recaptchaBadgeMode:
| Value | Behavior |
|---|---|
| "text" | Default. Hides the floating badge. Shows a centered one-line attribution at the bottom: "Protected by reCAPTCHA ยท Privacy ยท Terms" |
| "badge" | Shows Google's default floating corner badge. No text attribution added. |
| "none" | Hides both badge and text. You're responsible for adding your own reCAPTCHA attribution. |
Google Ads Enhanced Conversions
ProForms natively supports Google Ads conversion tracking with Enhanced Conversions โ no GTM required. Add your Google Ads ID and Conversion Label in the Tracking tab, and ProForms handles everything automatically.
How It Works
- The embed loads
gtag.jswithallow_enhanced_conversions: true - On form submission, it auto-detects email, phone, name, and address fields by field type
- User data is SHA-256 hashed client-side (via Web Crypto API) before being sent to Google
- The conversion event fires with hashed
user_dataattached for improved attribution matching
Setup via API
curl -X PUT https://proforms.io/api/v1/forms/:id \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"settings": {
"tracking": {
"googleAdsId": "AW-123456789",
"googleAdsConversionLabel": "AbCdEfGhIjKlMn",
"googleAdsEnhancedConversions": true // opt-in
}
}
}'What Gets Sent
ProForms maps form field types to Google's user_data schema automatically:
| Field Type | Google user_data Key | Processing |
|---|---|---|
| sha256_email_address | Lowercased, trimmed, SHA-256 hashed | |
| phone | sha256_phone_number | Non-digit characters stripped (except +), SHA-256 hashed |
| name | address.sha256_first_name / sha256_last_name | Split name auto-detected; full name split on first space. SHA-256 hashed. |
| address | address.street / city / region / postal_code / country | Passed as plaintext (Google's spec for address fields) |
Compatible Field Types
Enhanced Conversions detection is based on the field typeyou choose in the form builder โ not the field label, key, or ID. Renaming a field's key or label has no effect on detection. As long as the field type is one of the four above, it works.
| Will Work โ | Won't Work โ |
|---|---|
An Email field (type: email) with key renamed to my_email | A Text field (type: text) labeled "Email Address" |
| A Phone field (type: phone) with any custom label | A Number field (type: number) labeled "Phone" |
| A Name field (type: name) in either full or split format | Two separate Text fields for first/last name |
| An Address field (type: address) with all sub-fields | Individual Text fields for street, city, etc. |
Important Notes
- Privacy-first: All PII (email, phone, name) is SHA-256 hashed in the browser before being sent to Google. Raw values are never transmitted to Google's servers.
- Minimum data: At least one email or phone field is required for enhanced matching. If neither exists, a standard conversion fires instead.
- Multiple fields: If your form has multiple email or phone fields, the last one in the form is used.
- Hidden fields: Fields hidden by conditional logic at the time of submission are excluded.
- Google Ads requirements: You must enable Enhanced Conversions in your Google Ads account (Settings โ Conversions โ Enhanced Conversions) for the data to be used.
- Both IDs required: You need both the Google Ads ID (
AW-XXXXXXXXX) and the Conversion Label from your conversion action. Without the label, no conversion event fires.
googleAdsEnhancedConversions: true (or toggle it on in the Tracking tab) to send hashed user data with conversion events.GTM / dataLayer
Even without configuring Google Ads directly in ProForms, every submission pushes a proforms_submission event to the dataLayer with user data fields for GTM-based Enhanced Conversions:
{
event: "proforms_submission",
proforms_form_id: "uuid",
proforms_form_name: "Contact Form",
proforms_user_email: "user@example.com", // if email field exists
proforms_user_phone: "+18015551234", // if phone field exists
proforms_user_first_name: "John", // if name field exists
proforms_user_last_name: "Doe"
}Use these dataLayer variables in your GTM Enhanced Conversions tag to pass user data to Google Ads without any custom code.
What the Embed Handles
- Field rendering โ all field types including file upload
- Client-side validation with error messages
- Email field format validation (standard, strict, or none)
- Phone field auto-formatting and digit-only enforcement
- Conditional logic (show/hide fields based on values)
- Theme CSS variables from your form settings
- Custom CSS injection (scoped to shadow DOM in isolated mode)
- reCAPTCHA v3 with hybrid key resolution and honeypot spam protection
- Google Analytics, Facebook Pixel, and Google Ads conversion tracking
- Google Ads Enhanced Conversions with automatic field detection and SHA-256 hashing
- UTM parameter capture
- File upload with progress
- Multi-step form navigation
- Thank you message or redirect after submission
CSS Class Reference
Target these classes in your customCss to style form elements. Short .pf-* aliases are recommended. Legacy .proform-* names also work.
Structure & Layout
| Class | Legacy | Element |
|---|---|---|
| .pf-wrapper | .proform-wrapper | Outermost container (centers the form, applies max-width) |
| .pf-form | .proform | The <form> element |
| .pf-header | .proform-header | Form header area (title + description) |
| .pf-body | .proform-body | Field grid container โ CSS Grid with 6 columns. Controls gap between all fields. |
| .pf-field | .proform-field | Individual field wrapper (default: spans all 6 columns = full width) |
Field Width Modifiers
Applied alongside .pf-field to control column span in the 6-column grid. On screens โค480px, all fields collapse to full width.
| Class | Grid Span | Width |
|---|---|---|
| .pf-width-half | span 3 of 6 | 50% โ two fields side by side |
| .pf-width-third | span 2 of 6 | 33% โ three fields in a row |
| (no modifier) | span 6 of 6 | 100% โ full width (default) |
Form Fields
| Class | Legacy | Element |
|---|---|---|
| .pf-label | .proform-label | Field label |
| .pf-required | .proform-required | Required asterisk (*) |
| .pf-help | .proform-help | Help text below a field |
| .pf-error | .proform-error | Validation error message |
| .pf-input | .proform-input | Text, number, email, phone, date, time inputs |
| .pf-textarea | .proform-textarea | Textarea |
| .pf-select | .proform-select | Select dropdown |
| .pf-file | .proform-file | File upload input |
| .pf-file-wrapper | .proform-file-wrapper | File upload container (drag zone + button) |
Choice Fields
| Class | Legacy | Element |
|---|---|---|
| .pf-option | .proform-option | Individual radio/checkbox option row |
| .pf-options-grid | .proform-options-grid | Options grid container (radio/checkbox layout) |
| .pf-card-option | .proform-card-option | Card-style option (bordered) |
| .pf-cols-1 | โ | Options grid: 1 column |
| .pf-cols-2 | โ | Options grid: 2 columns |
| .pf-cols-3 | โ | Options grid: 3 columns |
| .pf-cols-4 | โ | Options grid: 4 columns |
Compound Fields
| Class | Legacy | Element |
|---|---|---|
| .pf-name-split | .proform-name-split | Split name field container (First + Last side by side) |
| .pf-address-group | .proform-address-group | Address field group wrapper |
| .pf-address-row | .proform-address-row | Address sub-row (City+State, Zip+Country) |
| .pf-address-sublabel | .proform-address-sublabel | Small label below each address input |
Content & UI
| Class | Legacy | Element |
|---|---|---|
| .pf-heading | .proform-heading | Heading element |
| .pf-paragraph | .proform-paragraph | Paragraph text |
| .pf-divider | .proform-divider | Horizontal divider |
| .pf-disclaimer | .proform-disclaimer | Disclaimer text above submit button |
| .pf-submit | .proform-submit | Submit button |
| .pf-success | .proform-success | Success/thank-you message container |
| .pf-success-icon | .proform-success-icon | Success checkmark icon |
| .pf-recaptcha-attr | .proform-recaptcha-attr | reCAPTCHA text attribution |
State Classes
| Class | Applied To | When |
|---|---|---|
| .pf-error | .pf-field | Field has a validation error (also added as .proform-error-state) |
| .pf-selected | .pf-card-option | Card option is selected |
| .pf-hidden | .pf-field | Field hidden by conditional logic |
| .pf-loading | .pf-form | Form is submitting (shows spinner) |
CSS Variables
These CSS custom properties are set automatically from your form's theme settings. Override them in customCss for full control.
| Variable | Default | Controls |
|---|---|---|
| --pf-bg | #ffffff | Form background color |
| --pf-text | #111827 | Text color |
| --pf-border | #d1d5db | Input border color |
| --pf-border-width | 1px | Input border width |
| --pf-error | #ef4444 | Error text & border color |
| --pf-primary | #3b82f6 | Primary/accent color (checkboxes, radio fills) |
| --pf-success | #10b981 | Success state color |
| --pf-btn-color | #111827 | Submit button background |
| --pf-btn-text | #ffffff | Submit button text color |
| --pf-btn-radius | 8px | Submit button border-radius |
| --pf-radius | 8px | Input border-radius |
| --pf-font | system-ui | Font family |
| --pf-font-size | 15px | Base font size |
| --pf-label-font-size | 15px | Label font size |
| --pf-label-weight | 500 | Label font weight |
| --pf-heading-font-size | 24px | Heading element font size |
| --pf-input-height | 44px | Input minimum height |
| --pf-form-max-width | 600px | Maximum form width |
| --pf-box-shadow | none | Form container box shadow |
| --pf-padding | 12px | Input inner padding |
| --pf-field-spacing | 18px | Gap between fields (row-gap and column-gap) |
| --pf-form-padding | 20px | Inner padding of the form body |
Layout: Side-by-Side Fields
The form body (.pf-body) is a CSS Grid with 6 columns. Fields set to "Half Width" in the builder get .pf-width-half (span 3), creating side-by-side layouts. The gap between all fields โ including between side-by-side columns โ is controlled by --pf-field-spacing.
.pf-body { gap: 16px !important; }/* Adjust spacing between all fields */
.pf-body {
gap: 24px;
}
/* Style inputs */
.pf-input, .pf-textarea, .pf-select {
border-radius: 12px;
}
/* Style the submit button */
.pf-submit {
background: #1a1a1a;
border-radius: 12px;
}
/* Uppercase labels */
.pf-label {
font-weight: 500;
text-transform: uppercase;
font-size: 11px;
letter-spacing: 0.05em;
}
/* Override CSS variables */
.pf-form {
--pf-field-spacing: 24px;
--pf-radius: 12px;
--pf-btn-color: #1a1a1a;
}