Skip to content

Laravel CRM Sync makes it effortless to connect and sync your Eloquent models with CRMs like HubSpot and PipeDrive. Define which model properties to sync, and let the package handle real-time, environment-aware synchronization across Sandbox, Production, and more.

License

Notifications You must be signed in to change notification settings

wazzac/sync-model-to-crm

Repository files navigation

GitHub issues GitHub stars GitHub license

Sync Model to CRM

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.


Features

  • 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.

Supported CRMs

  • HubSpot
    • Sync User model to HubSpot Contact object.
    • Sync Entity model to HubSpot Company object.
    • Manage associations between Contact and Company.
    • (Coming soon) Support for Deal and Ticket objects.
  • Planned Support
    • Pipedrive
    • Salesforce
    • Zoho CRM
    • Others

Requirements

  • PHP: 8.2 or higher
  • Laravel: 12.x

How It Works

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.

Required Model Properties

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.

Example: User Model

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
    }
}

Usage

You can trigger a model sync in several ways:

  1. Directly in a Controller:

    (new CrmController())->setModel($user)->execute();
  2. Using the Trait in a Mutator: Call $this->syncToCrm() within your model.

  3. 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);
    }
  4. In a Job: Offload sync logic to a queued job for asynchronous processing.


Installation

  1. Install via Composer:

    composer require wazza/sync-model-to-crm
  2. 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.

  3. Publish Config and Migrations:

    php artisan vendor:publish --tag="sync-modeltocrm-config"
    php artisan vendor:publish --tag="sync-modeltocrm-migrations"
    php artisan migrate
  4. Configure Environment Variables: Add the following to your .env file (see config/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
    
  5. Cache Config:

    php artisan config:clear
    php artisan config:cache
  6. Review Configuration: Adjust the published config file as needed.


Monitoring & Logs

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

Testing

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!


License

This project is open-sourced under the MIT license.

About

Laravel CRM Sync makes it effortless to connect and sync your Eloquent models with CRMs like HubSpot and PipeDrive. Define which model properties to sync, and let the package handle real-time, environment-aware synchronization across Sandbox, Production, and more.

Topics

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •