refineddigital / cms-form-builder-hubspot
HubSpot integration for Form Builder
Package info
github.com/refined-digital/cms-form-builder-hubspot
pkg:composer/refineddigital/cms-form-builder-hubspot
Requires
- php: ^8.4
- refineddigital/cms: ^1.36|dev-master
- refineddigital/cms-form-builder: ^1.13|dev-master
README
A HubSpot integration for RefinedCMS Form Builder. On submit, the form's mapped field values create or update a contact via the HubSpot CRM v3 API. Mapping is done per field using the field's Merge Field value; the normal submission email still sends unless you turn it off in the Integrations panel.
Requires refineddigital/cms-form-builder. No SDK — it uses Laravel's HTTP client.
Install
composer require refineddigital/cms-form-builder-hubspot php artisan refinedCMS:install-form-builder-hubspot
The install command publishes the config (config/hubspot.php) and appends HUBSPOT_TOKEN= to your .env (only if missing — it won't overwrite an existing value).
HubSpot setup
- In HubSpot go to Settings → Integrations → Private Apps and create a private app.
- Give it the
crm.objects.contacts.writescope. - Copy the access token into
HUBSPOT_TOKENin your.env.
The token is the only credential, and it is global to the site — there is nothing to configure per form.
Connecting a form
Once the package is installed it appears automatically in every form's Integrations panel.
- Edit the form and open the Integrations tab.
- Toggle HubSpot on.
- Open each field you want sent (Fields tab → the field → Merge Field) and enter the HubSpot internal property name —
email,firstname,lastname,phone,company, or any custom property's internal name. - Back on Integrations, click Configure and toggle off any field you don't want sent. Each row shows its merge field next to the name, so a row with no tag isn't mapped and won't be sent whatever you do with its toggle.
Fields with an empty Merge Field are not sent. Blank submitted values are dropped rather than written as empty strings. A form whose Configure modal has never been saved sends every mapped field.
The Merge Field box only appears in the field editor once at least one integration is enabled on the form.
Emails are not this integration's concern — use the form's own Email Notifications section. There is no Send Email toggle here, and no ordering: HubSpot properties are keyed by name, so the row order is irrelevant.
Finding a property's internal name
In HubSpot: Settings → Properties, open the property, and use the name shown under Internal name (not the label). It is usually lowercase with underscores.
Behaviour
- Upsert by email. If a field is mapped to
email, the contact is upserted on that address (POST /crm/v3/objects/contacts/batch/upsertwithidProperty: email), so a repeat submitter updates their existing contact instead of failing with a 409. Without an email mapped there is no identity to match on, so a plain contact create is used. - Checkbox / multi-select values are joined with
;, which is HubSpot's delimiter for multi-select properties. - Failures never block a submission. HTTP errors are caught and reported via
report(), never surfaced to the visitor — HubSpot being down will not cost you a lead or stop the notification email. Returning a failure result (or throwing) would abort the whole submission; we deliberately don't. - No token, no call. If
HUBSPOT_TOKENis unset the integration is a no-op.
Config
config/hubspot.php:
return [ 'token' => env('HUBSPOT_TOKEN'), ];
How it works
The service provider registers the integration with the core FormBuilderIntegrationAggregate, which is what makes it show up in the panel. It registers 'sortable' => false (order is meaningless for named properties) and 'config_tab' => false (no Send Email toggle, no settings), so the Configure modal is just the field on/off list.
Process implements FormBuilderIntegrationInterface; when an enabled form is submitted, the core form-builder calls Process::process($request, $form, $settings), which walks the form's fields, keeps the ones that have a merge field and are enabled in $settings['config']['fields'], drops blanks, and POSTs the result to the contacts endpoint.
The integration does not send email — notifications are the form's own responsibility.