# Fluentcrm Companies Model

> Work with FluentCRM 3.x Companies / account records from companion plugins. Covers the experimental company_module flag, FluentCrmApi('companies'), Company model fields, createOrUpdate, owner_id as Subscriber ID, custom company fields in meta.custom_values, primary company vs many-to-many company membership, attachContactsByIds / detachContactsByIds, company hooks, and company-aware contact filters. Use when a plugin must sync organizations/accounts, attach contacts to companies, read company segments, add automation company actions, or audit code touching fc_companies, company_id, attachCompanies, or fluent_crm/company_ hooks.

- Skill: `lonsdale201/fluentcrm-companies-model` (Agent Skill)
- Install (CLI): `npx skillmds@latest add lonsdale201/fluentcrm-companies-model`
- Raw SKILL.md: https://api.skillmd.com/api/skills/lonsdale201/fluentcrm-companies-model/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: Lonsdale201 (https://skillmd.com/u/lonsdale201)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/lonsdale201/fluentcrm-companies-model

---


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

```php
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`:

```php
$companiesApi = FluentCrmApi('companies');
```

Create or update a company:

```php
$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 `id` when provided, otherwise by exact `name`.
- `owner_id` is a FluentCRM `Subscriber` ID. It is not a WordPress user ID.
- Setting `owner_id` attaches that contact to the company and sets the contact's primary `company_id` if empty.
- `custom_values` are formatted by `CustomCompanyField` and stored under serialized `Company.meta['custom_values']`.
- Create fires `fluent_crm/company_created`; update fires `fluent_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:

```php
use FluentCrm\App\Models\Company;

$company = Company::where('name', $name)->first();
```

## Data model

`Company` maps `fc_companies`. Core fields include:

```php
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()` uses `fc_subscriber_pivot` with `object_type = FluentCrm\App\Models\Company`.
- `Company::owner()` belongs to a `Subscriber` through `owner_id`.
- `Company::notes()` stores company notes in `fc_subscriber_notes` with status `_company_note_`.
- `Subscriber::company()` points to the primary company via `fc_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:

```php
$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:

```php
FluentCrmApi('companies')->detachContactsByIds([$contactId], [$companyId]);
```

Detach behavior:

- If a detached contact was the company owner, `owner_id` is cleared.
- If the detached company was the contact's primary `company_id`, FluentCRM promotes the first remaining related company or sets `company_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_created` with `($company, $data)`
- `fluent_crm/company_updated` with `($company, $data)`
- `fluent_crm/before_company_delete` with `($company)`
- `fluent_crm/company_deleted` with `($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_companies` with `($companyIds, $subscriber)`
- `fluentcrm_contact_removed_from_companies` with `($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:

```php
use FluentCrm\App\Models\Subscriber;

$contacts = Subscriber::with(['companies'])
    ->filterByCompanies([(int) $companyId])
    ->get();
```

`ContactsQuery` / advanced filters support company-aware properties:

- segment relation `companies`
- `company_industry`
- `company_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:

```php
'settings' => [
    'company' => null,
],
```

The built-in action field uses:

```php
'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 FluentCRM `Subscriber` ID.
- Assuming `getCompany('Acme')` searches by name. It searches by email for strings.
- Setting only `Subscriber.company_id` and skipping the pivot relation. The contact then has a primary company but is missing from `Company::subscribers()`.
- Writing `Company.meta` manually. Use `custom_values` through the API so `CustomCompanyField` formats field values consistently.
- Creating visible company integrations while `company_module` is disabled.

## Cross-references

- Use `fluentcrm-contact-models` for contact/list/tag basics.
- Use `fluentcrm-funnel-action` when adding a custom company-aware automation action.
- Use `fluentcrm-rest-options` when creating a custom company-like picker.

## References

- Official documentation: <https://developers.fluentcrm.com/database/orm/>
- Verified source paths:
  - `fluent-crm/app/Api/config.php`
  - `fluent-crm/app/Api/Classes/Companies.php`
  - `fluent-crm/app/Models/Company.php`
  - `fluent-crm/app/Models/CompanyNote.php`
  - `fluent-crm/app/Models/CustomCompanyField.php`
  - `fluent-crm/app/Models/Subscriber.php`
  - `fluent-crm/app/Services/Helper.php`
  - `fluent-crm/app/Http/Controllers/CompanyController.php`
  - `fluent-crm/app/Services/Funnel/Actions/ApplyCompanyAction.php`
  - `fluent-crm/app/Services/Funnel/Actions/DetachCompanyAction.php`
  - `fluent-crm/database/migrations/CompaniesMigrator.php`
  - `fluent-crm/database/migrations/Subscribers.php`

