Klaviyo
Subscribe or unsubscribe profiles and record Klaviyo events from a form.
Klaviyo is an add-on for Forms. Once your account is connected, every form you edit gains a Klaviyo panel in its sidebar. An action can subscribe or unsubscribe a profile, or record an event that starts a Klaviyo flow. You decide which submitted fields map to profile details and event properties.
Nothing else about the form changes: validation, messages, redirects, notification emails and the Submissions list all behave as they did before.
Where to find it
Two places, because there are two kinds of setting. The account connection is site-wide, at XPAC → Integrations on the Klaviyo tab. Which lists a particular form adds to is per-form, in the Klaviyo panel of the document sidebar while you edit that form.
Forms has to be active
The plugin does nothing on its own. Without XPAC Forms it registers no settings tab, no panel and no sending, and shows an admin notice asking for Forms instead.
Connecting your account
Create an API key in Klaviyo, under Settings → API keys. It has to be a private key — the plugin reads your lists and adds people to them, and the short public key that goes in a website's tracking snippet cannot do either.
Paste it into the Api Key field at XPAC → Integrations → Klaviyo and save. That single key serves every form on the site.
Missing credentials do not hide your actions
The Klaviyo panel and its saved actions remain visible when the key is missing or revoked. Account-backed pickers show the connection error, and a submission still creates its Forms entry before the queued delivery is marked failed. Save a private key with the required scopes, then retry it from XPAC → Forms → Deliveries.
Adding a form to a list
Edit the form and open the Klaviyo panel. If your key is good you will see an empty list with a round + button (Add action); if it is not, you will see Klaviyo's own error message instead.
Press +. Choose whether to subscribe, unsubscribe or track an event, then complete the controls for that action.
Choose the list when the action needs one and map a form field to Email address. Event actions also need a metric name; email-and-SMS consent also needs a phone-number mapping.
Press Save, then update the form. Updating the form is what stores the action.
Add another action for another list or event. With the Conditional Logic plugin installed, each action can also carry conditions so it runs only for matching submissions.
Editing and removing an action
Each row shows Create contact, a pencil (Edit action) that reopens the window, and a red X (Remove action) that asks Are you sure you want to remove the item? before acting.
Use the switch on a row to pause or resume that action without losing its settings. The X removes the selected row after confirmation; other actions keep their order and settings.
What reaches Klaviyo
Only mapped answers are sent. Map an email field to Email address for every action, and add any profile fields, location fields or named custom properties you need. For an event, profile mappings identify the person while custom properties become the event's properties.
Consent and list confirmation
Record only consent the form actually collected
A subscribe action can record consent for email, email and SMS, or neither. The SMS choice requires a phone-number mapping. No consent merely adds the profile to the list; it does not make that profile marketable. The integration does not prove that consent was freely given, so make the form wording and stored answer do that.
The chosen list's Klaviyo setting controls confirmation. A single-opt-in list joins immediately; a double-opt-in list sends Klaviyo's confirmation and joins only after the recipient clicks. The editor displays the setting Klaviyo reports for the list.
Deliveries, retries and duplicate limits
Forms writes the entry first and schedules Klaviyo afterwards. The visitor does not wait, and a remote failure cannot erase the submission. The Deliveries screen retains the provider's explanation. Accepted profile, list, consent and event steps are marked done, so a retry resumes the failed step without repeating completed siblings.
Configuration, authentication and validation failures reported as 400/401/403/404/410/422 are left failed for an administrator to fix. Rate limits, 5xx replies and transport failures are retried after about 1, 5, 30 and 120 minutes, for at most five attempts. Restore the private key or action configuration, then retry a permanent failure manually.
Profile upserts and list membership operations converge on Klaviyo state. Each tracked
event also carries a stable unique_id based on its Forms entry and action, which
Klaviyo uses to collapse a replay of that event.
For developers
The API key is stored in the xpac option under the klaviyo_api_key key. Its
password control masks ordinary settings-screen display without changing the stored
option name or value. It is read during xpac_forms_init and is never sent to the form
editor.
Per-form actions live under a klaviyo key in the form's form_settings post meta on
the xpac-form post type, contributed to the Forms meta schema at Addon.php:207-234
and therefore readable and writable over the REST API at /wp-json/wp/v2/xpac-form/<id>:
{
"klaviyo": {
"items": [
{
"status": true,
"action": "subscribe",
"list": "AbCdEf"
}
]
}
}action may be subscribe, unsubscribe or event. Subscribe actions also store
consent and customSource; unsubscribe actions store scope; event actions store
metric; all three store explicit map rows. Items are declared with
additionalProperties: true, which is how
Conditional Logic's condition object rides along without appearing in the schema.
status is declared and the editor always writes it true, but the panel passes no
withDisableFeature, so no toggle is rendered and there is no UI path to false.
One route backs the editor UI, requiring manage_options:
GET /wp-json/xpac/v1/form/klaviyo/listsConnection failures are returned as REST errors, and the editor renders their message beside the list control without hiding saved actions.
The requests
Client targets https://a.klaviyo.com/api/, authenticates with Klaviyo's private-key
header and sends the 2026-07-15 API revision. Its timeout defaults to ten seconds and
is filterable with xpac_klaviyo_request_timeout. The delivery path upserts profiles,
changes list subscription state or posts events according to the action. Exceptions
keep the HTTP status for the shared delivery classifier.
Addon::subscribe() is still attached through xpac_forms_submit_callbacks, but the
Forms dispatcher queues it only after the entry has been written. Each accepted
sub-operation records a delivery key. Events additionally send their stable entry and
action identity as Klaviyo's unique_id.
Hooks and blocks
The plugin registers no blocks and fires no PHP hooks of its own. That is a fact about its shape rather than a gap in this page: it is a thin adapter, and all of its behaviour is contributed to hooks Forms already owns. It attaches to five of them:
| Hook | Fired by | Used for |
|---|---|---|
xpac_forms_init | packages/Forms/Bootstrap.php:93, on init at priority 200 | Reads the key, registers the editor script, then wires the four below |
xpac_forms_post_meta_schema | Forms/core/base/PostType.php:126 | The shape of its per-form settings |
xpac_forms_post_meta_default_values | Forms/core/base/PostType.php:118 | An empty items array |
xpac_forms_post_script_dependencies | Forms/core/base/PostType.php:329 | Loads its panel ahead of the Forms editor script |
xpac_forms_submit_callbacks | Forms/core/base/Submission.php:402, as xpac_forms_{$action}_callbacks with $action of submit | The send itself |
The last one is worth noting for anyone auditing hook names: it is fired under a
runtime-composed name, so a search for the literal string xpac_forms_submit_callbacks
in Forms finds nothing. It is the same stage all eleven Forms integrations run in.
Outside Forms it also uses plugins_loaded, rest_api_init and admin_notices.
On the editor side the panel is contributed from JavaScript through the
xpac-forms-form-panels filter (assets/packages/klaviyo/index.js:16) with no
priority, which is why its position among the other add-ons' panels is not fixed. Its
action window can be extended through the xpac-forms-after-settings-modal-content
filter (assets/packages/klaviyo/Components/Modal.js:58), which receives the action's
current options with an onChange callback and is passed xpac-klaviyo as the module
it is filtering. Conditional Logic's condition editor is the one thing on it today.
Nothing in the send path is licence-gated.