Mailchimp
Add the people who fill in your forms to a Mailchimp audience, with form fields mapped onto the fields of that audience.
Mailchimp is an add-on for Forms. Once your account is connected, every form you edit gains a Mailchimp panel in its sidebar where you describe one or more actions. When the form is submitted each action adds the person to the audience you chose, filling that audience's own fields from the answers you mapped onto them.
Nothing else about the form changes: validation, messages, redirects, notification emails and the Submissions list all behave as they did before. Mailchimp delivery runs after the entry is stored, so a provider refusal cannot erase it.
Where to find it
Two places, because there are two kinds of setting. The account connection is site-wide, at XPAC → Integrations on the Mailchimp tab, under the heading Mailchimp Settings. What each form sends is per-form, in the Mailchimp panel of the document sidebar while you edit that form. That panel sits below the six panels Forms provides itself, among the other add-ons, and its position within that group is not fixed.
Forms has to be active
The plugin does nothing on its own. Without XPAC Forms it registers no settings tab and no panel, and shows an admin notice asking for Forms instead.
Connecting your account
Create an API key in Mailchimp, from your account's API keys screen (Account & settings → Extras → API keys). The plugin reads your list of audiences, reads each audience's fields, and adds contacts, so a key with normal account access is all it needs.
Paste it into the Api Key box at XPAC → Integrations → Mailchimp and save. That single key serves every form on the site.
Paste the whole key, including the part after the dash
A Mailchimp key ends in a dash and a short code — …-us14, …-us21. That code is
how the plugin works out which Mailchimp server your account lives on, so a key
pasted without it is sent to a different server than the one holding your
audiences, and Mailchimp refuses everything. There is no warning: the key looks
saved, and the form panel simply shows an error where your audiences should be.
Missing credentials do not hide your actions
The Mailchimp 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. An old delivery cannot be redirected through a newly connected account. Restore the exact key that was accepted and retry it from XPAC → Forms → Deliveries, or abandon it and submit again after connecting the intended account.
Adding people to an audience
Edit the form, open the Mailchimp panel and press the round + button (Add action). The Create contact modal opens.
Pick the audience under Select audience. The list comes from Mailchimp when the panel opens.
Map the fields. Each row pairs a Local field from your form with a Remote field in the audience, and the round + button adds another row. One of those rows has to be the email address — see the warning below.
Press Save in the modal — it stays greyed out until you have changed something, and Discard closes the modal without keeping the changes — then update the form. Updating the form is what saves the action.
Create contact is the only kind of action there is, so a saved action always means "add this person to this audience". A form can hold as many actions as you like — two audiences from a single submission — and with the Conditional Logic plugin installed an action can also carry conditions, which is how someone is added only when their answers warrant it.
The controls
| Control | What it sets |
|---|---|
| Select audience | Which Mailchimp audience the contact is added to. Read from your account each time the panel opens. |
| Local field | The field in your form that supplies a value. Listed under its label, or under its field name where the label is blank. |
| Remote field | The field in the audience that receives it. EMAIL is always first in the list; the rest are the audience's own fields, exactly as they are named in Mailchimp. |
One row must send the email address
Mailchimp cannot add anyone without an email address, so one mapping row has to have EMAIL on its Remote field side, with the form's email field on the other. The action editor enforces it: Save stays disabled and says what is missing, and a warning sits under the mapping rows until you add it.
The row on its own is not quite enough — mark that form field required as well. If the row is there but the visitor left the field blank there is no address to send, so no contact is created and the action is recorded as a failed delivery.
Choose confirmation for new contacts
Add the contact as defaults to Pending — send a confirmation email on newly created actions. Choose Subscribed — no confirmation email only when the form itself is enough to enrol the person immediately. The choice applies only to an address Mailchimp has never seen; an existing member keeps their current status while mapped fields are updated.
Old actions saved before this control existed keep their historical immediate
subscribed behaviour until you edit them. In either mode, make consent explicit
on the form and retain the Forms entry that records it.
Existing contacts are updated, not silently re-subscribed
The integration uses Mailchimp's member upsert. A returning address receives the new mapped values and tags but keeps its existing subscription status, including an unsubscribed status. Re-enrolment therefore needs to happen through a consent flow Mailchimp accepts rather than by submitting the form again.
Keep multi-answer fields off the Email row
Multiple selected answers are flattened into one comma-separated display value for a merge field. That works for an ordinary text merge field, but it is not an email address, so map a single Email field to EMAIL. A File Upload answer is not uploaded into Mailchimp and should stay in the form's own record.
Two rows on the same audience field: only one survives
One answer can feed several audience fields — map the same Local field to two different Remote fields and both are sent. The other direction does not work, because an audience field holds one value: two rows aimed at the same Remote field send only the first one, and nothing warns you. A form carrying Email and Alternate email therefore sends the first as the contact's address.
Deliveries, retries and duplicate limits
Forms writes the entry first and schedules Mailchimp afterwards. The visitor does not wait, and a remote failure cannot erase the submission. The Deliveries screen retains Mailchimp's explanation. The member upsert and its separate tag operation each record completion, so a retry resumes the missing step and does not resend successful sibling actions.
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. Each delivery keeps the accepted audience, mapping and conditions in encrypted retry context. It also keeps a site-keyed fingerprint of the accepted Mailchimp key, not the key itself. Restore that key to retry a remote failure; editing the action or connecting another account applies only to a new submission.
Member upsert and tag assignment describe desired audience state, making normal replays safe. Mailchimp does not expose a separate request idempotency key for these operations; Forms' per-action and per-step completion markers preserve continuity across retries.
Check your actions after editing a form
Renaming or deleting a form field, and renaming or deleting an audience field in Mailchimp, both leave the action pointing at something that no longer exists. Depending on which half went missing, the result is either a submission that quietly adds nobody or a form that fails for everyone. Nothing in the editor flags either, so reopen the action after any such change.
Editing and removing an action
Each row in the panel shows the action's name — Create contact — with a pencil (Edit action) that reopens the modal, 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.
For developers
The API key is stored in the shared xpac option under the mailchimp_api_key key
(packages/Mailchimp/Addon.php:96-101, read once per request at :160-163). The
settings module is registered with show_in_rest left at its default false
(packages/Shared/Settings/Settings.php:481, :511), so the key is not published on
/wp/v2/settings, and the module's own read route
(/wp-json/wp-react-settings/xpac/read) answers null rather than the values —
its callback is Settings::get(), which REST hands a WP_REST_Request and which
returns null for anything that is not a string, array or null
(packages/Shared/Settings/Rest.php:27-31, Settings.php:603-624). Where the key
is readable is the settings screen itself: every value in the module is printed into
a data-values attribute on that page (packages/Shared/Settings/MenuPage.php:204),
which the module's capability gates at manage_options.
Per-form actions live in the form's form_settings post meta on the xpac-form
post type, under a mailchimp key, registered on the Forms meta schema
(Addon.php:210-251) and therefore readable and writable over the REST API at
/wp-json/wp/v2/xpac-form/<id>. The registered default is an empty items array
(Addon.php:262-269):
{
"mailchimp": {
"items": [
{
"status": true,
"action": "subscribe",
"list": "a1b2c3d4e5",
"map": [
{ "local": "email", "remote": "EMAIL" },
{ "local": "name", "remote": "FNAME" }
]
}
]
}
}action is only ever subscribe; the editor has no control for it and the
submission path skips anything else (Addon.php:425). Items are declared
additionalProperties: true, which is what lets Conditional Logic store its own
condition key on an item. status is in the schema, unlike some sibling
add-ons, but it carries no default and is not required, and Forms' shared
action-item filter reads $item['status'] unguarded
(packages/Forms/core/base/Utils.php:56) into a bool parameter on Conditional
Logic's callback (packages/ConditionalLogic/Forms/Addon.php:285-289) — so write
status explicitly if you write this meta by hand.
Mapping is reduced to remote => local — keyed on the audience
field, which is the side that can hold only one value, so two rows aimed at one
remote collapse to the first while one local feeding several remotes is kept. The
row whose remote is EMAIL supplies email_address, flattened through
flattenValue() so a multi-answer field is joined rather than sent as a list; every
other row becomes a merge_fields entry under its remote name, and the contact is
posted through a member upsert. New actions default subscriberStatus to pending,
which asks Mailchimp to send confirmation. Actions saved before that setting existed
default to subscribed for compatibility. Tags are applied as a separate desired-state
request.
Two routes back the editor UI, both requiring manage_options
(Addon.php:276-300):
GET /wp-json/xpac/v1/form/mailchimp/lists
GET /wp-json/xpac/v1/form/mailchimp/<list-id>/fieldsThe list segment is matched as [a-z0-9]+, which covers Mailchimp audience ids.
TouchBasePro's route also accepts uppercase letters, underscores and hyphens so an
unfamiliar provider-issued id reaches the provider instead of becoming a local 404.
Both routes return REST errors for missing or refused credentials, and the editor renders their messages without hiding the form's saved actions.
Neither response is cached across page loads, so every form-editor load fetches the audience list; within a load the fields are fetched once per distinct audience, not once per configured action.
Client derives the Mailchimp data centre from the key suffix, authenticates with
Basic auth, and talks to API 3.0. Its timeout defaults to ten seconds and is filterable
through xpac_mailchimp_request_timeout. Responses retain their HTTP status for the
shared delivery classifier. Contacts use PUT /lists/{id}/members/{subscriber_hash};
tags use the member tags endpoint.
subscribe() remains registered on xpac_forms_submit_callbacks, but Forms queues it
only after the entry exists. Successful contact and tag calls record separate stable
delivery keys, which is why a later attempt can skip completed work.
This plugin registers no blocks and fires no hooks of its own in PHP — it has
no apply_filters or do_action call site anywhere in packages/Mailchimp. That is
what a Forms add-on of this kind looks like rather than an omission: its behaviour is
reached through the hooks Forms already fires. It attaches to five of them —
xpac_forms_init to bootstrap, xpac_forms_post_meta_schema and
xpac_forms_post_meta_default_values for the shape of its settings,
xpac_forms_post_script_dependencies to load its editor panel, and
xpac_forms_submit_callbacks for the send — plus WordPress core's 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, with no priority declared, which is why it sorts
below every core Forms panel (assets/packages/forms/hooks/useSortedAndFilteredPanels.js:18-21)
and why its position relative to other add-ons' panels is not stable. The one filter
this package fires anywhere is in that JavaScript: the action modal is extensible
through xpac-forms-after-settings-modal-content
(assets/packages/mailchimp/Components/Modal.js:101-109), which receives the
action's current options with an onChange callback and is passed xpac-mailchimp
as the module being filtered. Conditional Logic is the only listener today.
Nothing in the send path is licence-gated; the only licensing call is the updater
(packages/Mailchimp/Bootstrap.php:51-55).