Easily synchronize your Laravel Eloquent models with remote CRM providers such as HubSpot, Pipedrive, and more. This package allows you to define which model properties and relationships should be kept in sync with external CRM objects, providing a seamless integration experience.
- Flexible Property Mapping: Define which model attributes map to CRM fields.
- Multi-Environment Support: Sync to multiple CRM environments (e.g., sandbox, production).
- Relationship Sync: Automatically associate related models (e.g., User to Company).
- Customizable Sync Triggers: Initiate syncs via observers, mutators, or queued jobs.
- Extensible: Easily add support for additional CRM providers.
- HubSpot
- Sync
User
model to HubSpotContact
object. - Sync
Entity
model to HubSpotCompany
object. - Manage associations between
Contact
andCompany
. - (Coming soon) Support for
Deal
andTicket
objects.
- Sync
- Planned Support
- Pipedrive
- Salesforce
- Zoho CRM
- Others
- PHP: 8.2 or higher
- Laravel: 12.x
Define a set of properties on your Eloquent models to control how and when they sync with your CRM provider. After the first successful sync, the CRM object’s primary key is stored in a mapping table for efficient future updates.
Add the following properties to your model to enable CRM synchronization:
Property | Type | Description |
---|---|---|
$syncModelCrmEnvironment |
string/array/null | CRM environments to sync with (e.g., ['sandbox', 'production'] ). Defaults to config value. |
$syncModelCrmPropertyMapping |
array (required) | Maps local model attributes to CRM fields. |
$syncModelCrmUniqueSearch |
array (required) | Defines unique fields for identifying CRM records. |
$syncModelCrmRelatedObject |
string | CRM object type (e.g., contact ). Defaults to config mapping. |
$syncModelCrmDeleteRules |
array (required) | Rules for handling deletes (soft/hard) in the CRM. |
$syncModelCrmActiveRules |
array (required) | Rules for restoring/reactivating records in the CRM. |
$syncModelCrmAssociateRules |
array (optional) | Defines associations with other CRM objects. |
Below is a sample User
model configured for CRM synchronization:
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use App\Models\Entity;
use Illuminate\Database\Eloquent\SoftDeletes;
use Wazza\SyncModelToCrm\Http\Controllers\CrmProviders\HubSpotController;
use Wazza\SyncModelToCrm\Traits\crmTrait;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable, SoftDeletes;
// include this if you wish to use the `Mutators function` or
// $this->syncToCrm() directly as appose to the observer method
use crmTrait;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
/**
* Function that will be used to return the relationship data
* @return type
*/
public function entity()
{
return $this->belongsTo(Entity::class)->withTrashed();
}
// --------------------------------------------------------------
// Sync Model to CRM
// --------------------------------------------------------------
/**
* The CRM provider environment/s to use (e.g. production, sandbox, etc.)
* Use an array to sync to multiple environments.
* `null` (or not defined) will take the default from the config file.
*
* @var string|array|null
*/
public $syncModelCrmEnvironment = ['sandbox']; // ..or ['sandbox','production']
/**
* Mapping array for local and CRM properties
* This will be the primary property used to cycle through the crm providers
* and properties to sync the model to the CRM.
* Required - if not provided, the sync will process will be skipped (no Exceptions will be thrown)
*
* @var array
*/
public $syncModelCrmPropertyMapping = [
'hubspot' => [
'name' => 'firstname',
'email' => 'email',
// ... add all the properties that you would like to sync
],
];
/**
* Unique filters for the CRM to locate the record if there is no internal mapping available.
* Not required, but strongly encouraged to be configured as to avoid any duplicate record creation in the crm
*
* @var array
*/
public $syncModelCrmUniqueSearch = [
'hubspot' => [
'email' => 'email', // this will ensure that the search filter is unique
],
];
/**
* The CRM object to sync this model to.
* This is the CRM object type (e.g. contact, company, deal, etc.)
* If this is null or not provided, the `object_table_mapping` key will be used from the config file.
*
* @var string
*/
public $syncModelCrmRelatedObject = 'contact';
/**
* The CRM Delete rules to follow.
* i.e. if Soft-delete is applicable, what should the CRM record be updated to?
* if Hard-delete is used, the record will be deleted/archived in the CRM.
*
* @var array
*/
public $syncModelCrmDeleteRules = [
'hard_delete' => [
'hubspot' => false,
],
'soft_delete' => [
'hubspot' => [
'lifecyclestage' => 'other',
'hs_lead_status' => 'DELETED',
],
]
];
/**
* The CRM Active/Restore rules to follow.
* These will be the rules to follow for any new entries that are not soft-deleted.
*
* @var array
*/
public $syncModelCrmActiveRules = [
'hubspot' => [
'lifecyclestage' => 'customer',
'hs_lead_status' => 'OPEN',
],
];
/**
* The CRM Associations to sync.
* This is used to associate the model with other CRM objects.
*
* @var array
*/
public $syncModelCrmAssociateRules = [
[
'assocMethod' => 'entity', // App\Models\Entity::class
'provider' => [
'hubspot' => [
[
'association_category' => HubSpotController::ASSOCIATION_CATEGORY__HUBSPOT_DEFINED,
'association_type_id' => HubSpotController::ASSOCIATION_TYPE_ID__CONTACT_TO_COMPANY_PRIMARY,
],
[
'association_category' => HubSpotController::ASSOCIATION_CATEGORY__HUBSPOT_DEFINED,
'association_type_id' => HubSpotController::ASSOCIATION_TYPE_ID__CONTACT_TO_COMPANY,
],
],
],
],
];
// --------------------------------------------------------------
// Custom Methods to initiate a sync
// --------------------------------------------------------------
/**
* (1) Register the observer in the AppServiceProvider boot method
*
* public function boot(): void
* {
* // register the observer/s
* // ...refer the the template examples in the sync-model-to-crm repo for a observer working copy
* \App\Models\User::observe(\App\Observers\UserObserver::class);
* }
*/
/**
* (2) Mutators function (Laravel 5.4 or above)
*
* Laravel provides mutators which are methods that can be defined on a model to modify
* attributes before they are saved. You can create a custom mutator named save that
* first calls the original save method using parent::save() and then performs your
* additional action.
*
* @param array $options
* @return void
*/
public function save(array $options = [])
{
parent::save($options);
// lets call the syncModelToCrm method to sync the model to the CRM.
// refer to the trait for all the available methods
$this->syncToCrmPatch(); // -- disabled as we are currently using the observer method
}
}
You can trigger a model sync in several ways:
-
Directly in a Controller:
(new CrmController())->setModel($user)->execute();
-
Using the Trait in a Mutator: Call
$this->syncToCrm()
within your model. -
Via an Observer: Register an observer to automatically sync after save, update, delete, or restore events.
namespace App\Observers; use App\Models\User; use Wazza\SyncModelToCrm\Http\Controllers\CrmController; use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit; class UserObserver implements ShouldHandleEventsAfterCommit { /** * Handle the User "created" event. */ public function created(User $user): void { echo ('create...'); (new CrmController()) ->setModel($user) ->setAttemptCreate() ->execute(true); echo ('created...'); } /** * Handle the User "updated" event. */ public function updated(User $user): void { echo ('update...'); (new CrmController()) ->setModel($user) ->setAttemptUpdate() ->execute(true); echo ('updated...'); } /** * Handle the User "deleted" event. * Run when a user is soft-deleted. */ public function deleted(User $user) { echo ('delete...'); (new CrmController()) ->setModel($user) ->setAttemptDelete() ->execute(); echo ('deleted...'); } /** * Handle the User "restored" event. * Soft-delete has been reversed. */ public function restored(User $user): void { echo ('restore...'); (new CrmController()) ->setModel($user) ->setAttemptRestore() ->execute(); echo ('restored...'); } /** * Handle the User "force deleted" event. */ public function forceDeleted(User $user): void { echo ('forceDeleted...'); } /** * Handle the User "saved" event. * */ public function saved(User $user): void { // echo ('saving...'); // (new CrmController()) // ->setModel($user) // ->setAttemptAll() // open for anything... // ->execute(true); // echo ('saved...'); } }
Register the observer in your
AppServiceProvider
:public function boot(): void { \App\Models\User::observe(\App\Observers\UserObserver::class); }
-
In a Job: Offload sync logic to a queued job for asynchronous processing.
-
Install via Composer:
composer require wazza/sync-model-to-crm
-
Register the Service Provider (if not auto-discovered): Add to
bootstrap/providers.php
:return [ App\Providers\AppServiceProvider::class, Wazza\SyncModelToCrm\Providers\SyncModelToCrmServiceProvider::class, ];
If your package supports Laravel auto-discovery, this step may be optional.
-
Publish Config and Migrations:
php artisan vendor:publish --tag="sync-modeltocrm-config" php artisan vendor:publish --tag="sync-modeltocrm-migrations" php artisan migrate
-
Configure Environment Variables: Add the following to your
.env
file (seeconfig/sync_modeltocrm.php
for details):SYNC_MODEL_TO_CRM_DB_PRIMARY_KEY_FORMAT=int SYNC_MODEL_TO_CRM_HASH_SALT=Ey4cw2BHvi0HmGYjyqYr SYNC_MODEL_TO_CRM_HASH_ALGO=sha256 SYNC_MODEL_TO_CRM_LOG_INDICATOR=sync-modeltocrm SYNC_MODEL_TO_CRM_LOG_LEVEL=3 SYNC_MODEL_TO_CRM_PROVIDER=hubspot SYNC_MODEL_TO_CRM_ENVIRONMENT=sandbox SYNC_MODEL_TO_CRM_PROVIDER_HUBSPOT_SANDBOX_URI=https://api.hubapi.com/crm/v4/ SYNC_MODEL_TO_CRM_PROVIDER_HUBSPOT_SANDBOX_TOKEN=xxx-xxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
-
Cache Config:
php artisan config:clear php artisan config:cache
-
Review Configuration: Adjust the published config file as needed.
Set your desired log level in the config file (1
= high, 3
= low verbosity).
Monitor sync activity in your Laravel log file:
tail -f storage/logs/laravel.log | grep sync-modeltocrm
Run the test suite with:
./vendor/bin/pest
Example output:
PASS Tests\Unit\EnvTest
✓ it should have the correct environment variables set 1.11s
PASS Tests\Unit\ExampleTest
✓ it contains a successful example unit test 0.33s
PASS Tests\Unit\ModelTest
✓ it can create a new SmtcExternalKeyLookup model record 0.31s
✓ it can mass assign data to the SmtcExternalKeyLookup model 0.25s
✓ it can update the SmtcExternalKeyLookup model record 0.35s
PASS Tests\Feature\ExampleTest
✓ it contains a successful example feature test 0.24s
Tests: 6 passed (25 assertions)
Duration: 3.84s
More tests and features coming soon!
This project is open-sourced under the MIT license.