P
ProFormsDocs

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:

HTML
<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:

HTML
<!-- 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>
AttributeValuesDescription
data-formstring (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).

๐Ÿ’ก
Changes made via the API or dashboard are reflected on the next page load โ€” no cache purging needed. The form's version number increments on every update and drives cache invalidation automatically.

Multiple Forms on One Page

Add multiple script tags โ€” each renders independently:

HTML
<!-- 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:

  1. The site's custom recaptchaSiteKey (if configured via the Sites API or Site Settings)
  2. 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:

ValueBehavior
"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.
๐Ÿ’ก
If you're using the platform global key, make sure domain verification is disabled in your Google reCAPTCHA admin console so the key works on any domain your forms are embedded on.

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

  1. The embed loads gtag.js with allow_enhanced_conversions: true
  2. On form submission, it auto-detects email, phone, name, and address fields by field type
  3. User data is SHA-256 hashed client-side (via Web Crypto API) before being sent to Google
  4. The conversion event fires with hashed user_data attached for improved attribution matching

Setup via API

cURL
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 TypeGoogle user_data KeyProcessing
emailsha256_email_addressLowercased, trimmed, SHA-256 hashed
phonesha256_phone_numberNon-digit characters stripped (except +), SHA-256 hashed
nameaddress.sha256_first_name / sha256_last_nameSplit name auto-detected; full name split on first space. SHA-256 hashed.
addressaddress.street / city / region / postal_code / countryPassed 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_emailA Text field (type: text) labeled "Email Address"
A Phone field (type: phone) with any custom labelA Number field (type: number) labeled "Phone"
A Name field (type: name) in either full or split formatTwo separate Text fields for first/last name
An Address field (type: address) with all sub-fieldsIndividual Text fields for street, city, etc.
โš ๏ธ
If your form uses generic Text fields for collecting email or phone data, Enhanced Conversions won't detect them. Switch those fields to the proper Email or Phone field type in the builder to enable automatic detection.

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.
๐Ÿ’ก
Enhanced Conversions are off by default. Set 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:

dataLayer Event
{
  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
๐Ÿ’ก
Use isolated mode (default) for most sites โ€” it prevents CSS conflicts and ensures your form looks exactly as designed. Switch to openmode if you need the form to inherit your site's font family or want to style it entirely with your own CSS.

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

ClassLegacyElement
.pf-wrapper.proform-wrapperOutermost container (centers the form, applies max-width)
.pf-form.proformThe <form> element
.pf-header.proform-headerForm header area (title + description)
.pf-body.proform-bodyField grid container โ€” CSS Grid with 6 columns. Controls gap between all fields.
.pf-field.proform-fieldIndividual 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.

ClassGrid SpanWidth
.pf-width-halfspan 3 of 650% โ€” two fields side by side
.pf-width-thirdspan 2 of 633% โ€” three fields in a row
(no modifier)span 6 of 6100% โ€” full width (default)

Form Fields

ClassLegacyElement
.pf-label.proform-labelField label
.pf-required.proform-requiredRequired asterisk (*)
.pf-help.proform-helpHelp text below a field
.pf-error.proform-errorValidation error message
.pf-input.proform-inputText, number, email, phone, date, time inputs
.pf-textarea.proform-textareaTextarea
.pf-select.proform-selectSelect dropdown
.pf-file.proform-fileFile upload input
.pf-file-wrapper.proform-file-wrapperFile upload container (drag zone + button)

Choice Fields

ClassLegacyElement
.pf-option.proform-optionIndividual radio/checkbox option row
.pf-options-grid.proform-options-gridOptions grid container (radio/checkbox layout)
.pf-card-option.proform-card-optionCard-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

ClassLegacyElement
.pf-name-split.proform-name-splitSplit name field container (First + Last side by side)
.pf-address-group.proform-address-groupAddress field group wrapper
.pf-address-row.proform-address-rowAddress sub-row (City+State, Zip+Country)
.pf-address-sublabel.proform-address-sublabelSmall label below each address input

Content & UI

ClassLegacyElement
.pf-heading.proform-headingHeading element
.pf-paragraph.proform-paragraphParagraph text
.pf-divider.proform-dividerHorizontal divider
.pf-disclaimer.proform-disclaimerDisclaimer text above submit button
.pf-submit.proform-submitSubmit button
.pf-success.proform-successSuccess/thank-you message container
.pf-success-icon.proform-success-iconSuccess checkmark icon
.pf-recaptcha-attr.proform-recaptcha-attrreCAPTCHA text attribution

State Classes

ClassApplied ToWhen
.pf-error.pf-fieldField has a validation error (also added as .proform-error-state)
.pf-selected.pf-card-optionCard option is selected
.pf-hidden.pf-fieldField hidden by conditional logic
.pf-loading.pf-formForm 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.

VariableDefaultControls
--pf-bg#ffffffForm background color
--pf-text#111827Text color
--pf-border#d1d5dbInput border color
--pf-border-width1pxInput border width
--pf-error#ef4444Error text & border color
--pf-primary#3b82f6Primary/accent color (checkboxes, radio fills)
--pf-success#10b981Success state color
--pf-btn-color#111827Submit button background
--pf-btn-text#ffffffSubmit button text color
--pf-btn-radius8pxSubmit button border-radius
--pf-radius8pxInput border-radius
--pf-fontsystem-uiFont family
--pf-font-size15pxBase font size
--pf-label-font-size15pxLabel font size
--pf-label-weight500Label font weight
--pf-heading-font-size24pxHeading element font size
--pf-input-height44pxInput minimum height
--pf-form-max-width600pxMaximum form width
--pf-box-shadownoneForm container box shadow
--pf-padding12pxInput inner padding
--pf-field-spacing18pxGap between fields (row-gap and column-gap)
--pf-form-padding20pxInner 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.

โš ๏ธ
In open embed mode, your site's CSS can override the grid gap. If side-by-side fields appear with no spacing, add this to your form's Custom CSS: .pf-body { gap: 16px !important; }
Example: Custom CSS
/* 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;
}