FluentCRM: Companies model and contact-company relations
Use this skill when a plugin needs to sync B2B account/company records into FluentCRM or attach contacts to existing companies. Companies are core FluentCRM 3.1.13 code, but the UI/automation surface is behind the experimental company_module setting.
Guard the feature
For UI, automation actions, or user-visible sync flows, check the module flag:
use FluentCrm\App\Services\Helper;
if (!function_exists('FluentCrmApi') || !Helper::isCompanyEnabled()) {
return;
}
Helper::isCompanyEnabled() reads _fluentcrm_experimental_settings['company_module'] === 'yes'. The low-level API does not hard-block writes when the flag is off, so do not create surprise company data from a hidden integration unless the admin opted in.
API entry points
app/Api/config.php registers the API key as companies:
$companiesApi = FluentCrmApi('companies');
Create or update a company:
$company = FluentCrmApi('companies')->createOrUpdate([
'name' => sanitize_text_field($accountName),
'email' => sanitize_email($billingEmail),
'website' => esc_url_raw($website),
'type' => 'Customer',
'industry' => sanitize_text_field($industry),
'owner_id' => (int) $fluentContactId, // Subscriber ID, not WP user ID
'custom_values' => [
'external_account_id' => sanitize_text_field($externalId),
],
]);
Important behavior from Companies::createOrUpdate():
- Existing company lookup is by
idwhen provided, otherwise by exactname. owner_idis a FluentCRMSubscriberID. It is not a WordPress user ID.- Setting
owner_idattaches that contact to the company and sets the contact's primarycompany_idif empty. custom_valuesare formatted byCustomCompanyFieldand stored under serializedCompany.meta['custom_values'].- Create fires
fluent_crm/company_created; update firesfluent_crm/company_updated.
Do not call FluentCrmApi('companies')->getCompany($name) for name lookup. The method name says $idOrName, but the 3.1.13 source checks id for numeric values and email for strings. For name lookup use:
use FluentCrm\App\Models\Company;
$company = Company::where('name', $name)->first();
Data model
Company maps fc_companies. Core fields include:
name, owner_id, industry, type, email, phone, website,
address_line_1, address_line_2, postal_code, city, state, country,
timezone, employees_number, description, logo,
linkedin_url, facebook_url, twitter_url, date_of_start, meta
Relations:
Company::subscribers()usesfc_subscriber_pivotwithobject_type = FluentCrm\App\Models\Company.Company::owner()belongs to aSubscriberthroughowner_id.Company::notes()stores company notes infc_subscriber_noteswith status_company_note_.Subscriber::company()points to the primary company viafc_subscribers.company_id.Subscriber::companies()is the many-to-many relation through the pivot table.
Treat company_id as the primary/display company only. A contact can belong to multiple companies through Subscriber::companies().
Attach and detach contacts
Prefer the API wrapper for bulk changes:
$result = FluentCrmApi('companies')->attachContactsByIds(
[(int) $contactId],
[(int) $companyId]
);
if (!$result) {
// At least one company ID was invalid, no subscribers were found, or input was empty.
}
attachContactsByIds() validates that every requested company ID exists. It attaches all valid companies to each subscriber and sets the first company as primary only when company_id is empty.
Detach:
FluentCrmApi('companies')->detachContactsByIds([$contactId], [$companyId]);
Detach behavior:
- If a detached contact was the company owner,
owner_idis cleared. - If the detached company was the contact's primary
company_id, FluentCRM promotes the first remaining related company or setscompany_id = null.
For a single loaded contact, Subscriber::attachCompanies() and detachCompanies() are safe ORM-level helpers: they guard unsaved subscribers, cast IDs to ints, use per-row INSERT IGNORE / DELETE, refresh the relation, and only fire events for actual changes.
Hooks
Company record hooks:
fluent_crm/company_createdwith($company, $data)fluent_crm/company_updatedwith($company, $data)fluent_crm/before_company_deletewith($company)fluent_crm/company_deletedwith($companyId)fluent_crm/company_type_to_{type}with($company, $oldType)fluent_crm/company_category_to_{industry}with($company, $oldIndustry)
Contact-company pivot hooks are legacy underscore hooks only in 3.1.13:
fluentcrm_contact_added_to_companieswith($companyIds, $subscriber)fluentcrm_contact_removed_from_companieswith($companyIds, $subscriber)
Do not invent slash aliases for the pivot hooks; they are not emitted in the current source.
Query and segment contacts by company
For simple reads:
use FluentCrm\App\Models\Subscriber;
$contacts = Subscriber::with(['companies'])
->filterByCompanies([(int) $companyId])
->get();
ContactsQuery / advanced filters support company-aware properties:
- segment relation
companies company_industrycompany_type
Keep custom controllers bounded and allowlisted. Do not query serialized Company.meta for reporting unless you accept full-table scans.
Automation company actions
Core 3.1.13 registers ApplyCompanyAction and DetachCompanyAction only when Helper::isCompanyEnabled() is true. If a companion action depends on Companies, follow the same guard and seed getBlock()['settings'] with a company field default:
'settings' => [
'company' => null,
],
The built-in action field uses:
'type' => 'option_selectors',
'option_key' => 'companies',
That option key is provided by OptionsController::companies() and returns [{id, title}].
Common mistakes
- Using WP user IDs as
owner_id. It must be a FluentCRMSubscriberID. - Assuming
getCompany('Acme')searches by name. It searches by email for strings. - Setting only
Subscriber.company_idand skipping the pivot relation. The contact then has a primary company but is missing fromCompany::subscribers(). - Writing
Company.metamanually. Usecustom_valuesthrough the API soCustomCompanyFieldformats field values consistently. - Creating visible company integrations while
company_moduleis disabled.
Cross-references
- Use
fluentcrm-contact-modelsfor contact/list/tag basics. - Use
fluentcrm-funnel-actionwhen adding a custom company-aware automation action. - Use
fluentcrm-rest-optionswhen creating a custom company-like picker.
References
- Official documentation: https://developers.fluentcrm.com/database/orm/
- Verified source paths:
fluent-crm/app/Api/config.phpfluent-crm/app/Api/Classes/Companies.phpfluent-crm/app/Models/Company.phpfluent-crm/app/Models/CompanyNote.phpfluent-crm/app/Models/CustomCompanyField.phpfluent-crm/app/Models/Subscriber.phpfluent-crm/app/Services/Helper.phpfluent-crm/app/Http/Controllers/CompanyController.phpfluent-crm/app/Services/Funnel/Actions/ApplyCompanyAction.phpfluent-crm/app/Services/Funnel/Actions/DetachCompanyAction.phpfluent-crm/database/migrations/CompaniesMigrator.phpfluent-crm/database/migrations/Subscribers.php