Developer Guide - Getting Started

SkunkCRM is built with extensibility in mind. This guide will help you get started with developing extensions, integrations, and customizations for SkunkCRM.

Architecture Overview

SkunkCRM follows WordPress plugin best practices and provides multiple extension points:

  • Hooks & Filters: Over 100 action and filter hooks for maximum extensibility
  • REST API: Comprehensive REST API for integrations
  • Database Access: Direct database access with custom tables
  • Class System: Object-oriented architecture with dependency injection

Quick Start

1. Basic Hook Usage

The simplest way to extend SkunkCRM is through WordPress hooks:

// Add to your theme's functions.php or custom plugin

// Log when contacts are created
add_action('skunkcrm_after_contact_create', function($contact_id, $data) {
    error_log("New contact created: {$data['name']} (ID: $contact_id)");
});

// Modify contact data before saving
add_filter('skunkcrm_contact_data_before_save', function($data, $contact_id) {
    // Auto-format phone numbers
    if (isset($data['phone'])) {
        $data['phone'] = preg_replace('/[^0-9]/', '', $data['phone']);
        $data['phone'] = preg_replace('/(\d{3})(\d{3})(\d{4})/', '($1) $2-$3', $data['phone']);
    }
    return $data;
}, 10, 2);

// Add custom contact statuses
add_filter('skunkcrm_contact_status_options', function($statuses) {
    $statuses['vip'] = 'VIP Customer';
    $statuses['partner'] = 'Business Partner';
    return $statuses;
});

2. REST API Integration

::rest-api-banner::

3. Custom Plugin Structure

Create a structured plugin to extend SkunkCRM:

<?php
/**
 * Plugin Name: SkunkCRM Extension
 * Description: Custom extensions for SkunkCRM
 * Version: 1.0.0
 */

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

class SkunkCRM_Extension {
    
    public function __construct() {
        add_action('plugins_loaded', [$this, 'init']);
    }
    
    public function init() {
        // Ensure SkunkCRM is active
        if (!class_exists('SkunkCRM')) {
            add_action('admin_notices', [$this, 'missing_skunkcrm_notice']);
            return;
        }
        
        // Initialize your extensions
        $this->init_hooks();
        $this->init_rest_api();
        $this->init_admin_pages();
    }
    
    private function init_hooks() {
        // Contact management hooks
        add_action('skunkcrm_after_contact_create', [$this, 'on_contact_created'], 10, 2);
        add_filter('skunkcrm_contact_data_before_save', [$this, 'modify_contact_data'], 10, 2);
        
        // Automation hooks
        add_filter('skunkcrm_automation_action_types', [$this, 'add_custom_actions']);
        add_action('skunkcrm_before_automation_execute', [$this, 'log_automation'], 10, 3);
    }
    
    private function init_rest_api() {
        add_action('rest_api_init', [$this, 'register_custom_endpoints']);
    }
    
    private function init_admin_pages() {
        add_action('admin_menu', [$this, 'add_admin_menu']);
    }
    
    public function on_contact_created($contact_id, $data) {
        // Custom logic when contact is created
        $this->sync_to_external_system($contact_id, $data);
    }
    
    public function modify_contact_data($data, $contact_id) {
        // Add custom validation or data transformation
        if (isset($data['email']) && !empty($data['email'])) {
            $data['email_domain'] = substr(strrchr($data['email'], "@"), 1);
        }
        return $data;
    }
    
    public function add_custom_actions($actions) {
        $actions['send_to_crm'] = 'Send to External CRM';
        $actions['create_calendar_event'] = 'Create Calendar Event';
        return $actions;
    }
    
    public function register_custom_endpoints() {
        register_rest_route('skunkcrm-extension/v1', '/sync', [
            'methods' => 'POST',
            'callback' => [$this, 'sync_endpoint'],
            'permission_callback' => [$this, 'check_permissions']
        ]);
    }
    
    public function sync_endpoint($request) {
        // Custom API endpoint
        $contact_id = $request->get_param('contact_id');
        
        // Sync logic here
        $result = $this->sync_contact($contact_id);
        
        return new WP_REST_Response($result, 200);
    }
    
    public function missing_skunkcrm_notice() {
        echo '<div class="notice notice-error"><p>SkunkCRM Extension requires SkunkCRM to be installed and activated.</p></div>';
    }
    
    private function sync_to_external_system($contact_id, $data) {
        // Implement external system integration
    }
    
    private function sync_contact($contact_id) {
        // Implement sync logic
        return ['success' => true, 'contact_id' => $contact_id];
    }
    
    public function check_permissions() {
        return current_user_can('manage_options');
    }
}

new SkunkCRM_Extension();

Common Extension Patterns

1. External System Integration

class SkunkCRM_Mailchimp_Integration {
    
    private $api_key;
    private $list_id;
    
    public function __construct($api_key, $list_id) {
        $this->api_key = $api_key;
        $this->list_id = $list_id;
        
        // Sync contacts to Mailchimp when created/updated
        add_action('skunkcrm_after_contact_create', [$this, 'sync_to_mailchimp'], 10, 2);
        add_action('skunkcrm_after_contact_update', [$this, 'update_in_mailchimp'], 10, 3);
        add_action('skunkcrm_after_contact_delete', [$this, 'remove_from_mailchimp'], 10, 2);
    }
    
    public function sync_to_mailchimp($contact_id, $data) {
        if (empty($data['email'])) {
            return;
        }
        
        $mailchimp_data = [
            'email_address' => $data['email'],
            'status' => 'subscribed',
            'merge_fields' => [
                'FNAME' => $this->get_first_name($data['name']),
                'LNAME' => $this->get_last_name($data['name']),
                'COMPANY' => $data['company'] ?? '',
                'PHONE' => $data['phone'] ?? ''
            ],
            'tags' => ['skunkcrm']
        ];
        
        $this->mailchimp_api_call("lists/{$this->list_id}/members", $mailchimp_data);
    }
    
    public function update_in_mailchimp($contact_id, $new_data, $old_data) {
        // Update subscriber in Mailchimp
        if (!empty($new_data['email'])) {
            $subscriber_hash = md5(strtolower($new_data['email']));
            $this->mailchimp_api_call(
                "lists/{$this->list_id}/members/{$subscriber_hash}",
                ['merge_fields' => ['FNAME' => $this->get_first_name($new_data['name'])]],
                'PATCH'
            );
        }
    }
    
    private function mailchimp_api_call($endpoint, $data, $method = 'POST') {
        $url = "https://us1.api.mailchimp.com/3.0/{$endpoint}";
        
        return wp_remote_request($url, [
            'method' => $method,
            'headers' => [
                'Authorization' => 'Basic ' . base64_encode('user:' . $this->api_key),
                'Content-Type' => 'application/json'
            ],
            'body' => json_encode($data)
        ]);
    }
}

2. Custom Automation Actions

class SkunkCRM_Custom_Automation_Actions {
    
    public function __construct() {
        add_filter('skunkcrm_automation_action_types', [$this, 'add_action_types']);
        add_filter('skunkcrm_custom_action_type', [$this, 'handle_custom_action'], 10, 4);
    }
    
    public function add_action_types($actions) {
        $actions['send_sms'] = 'Send SMS';
        $actions['create_deal'] = 'Create Deal';
        $actions['assign_to_user'] = 'Assign to User';
        $actions['add_to_sequence'] = 'Add to Email Sequence';
        return $actions;
    }
    
    public function handle_custom_action($result, $action_type, $config, $contact) {
        switch ($action_type) {
            case 'send_sms':
                return $this->send_sms($config, $contact);
            case 'create_deal':
                return $this->create_deal($config, $contact);
            case 'assign_to_user':
                return $this->assign_to_user($config, $contact);
            case 'add_to_sequence':
                return $this->add_to_sequence($config, $contact);
        }
        return $result;
    }
    
    private function send_sms($config, $contact) {
        if (empty($contact['phone'])) {
            return ['success' => false, 'error' => 'No phone number'];
        }
        
        $message = str_replace('{name}', $contact['name'], $config['message']);
        
        // Use Twilio, TextMagic, or other SMS service
        return $this->sms_service_send($contact['phone'], $message);
    }
    
    private function create_deal($config, $contact) {
        global $wpdb;
        
        $deal_data = [
            'title' => str_replace('{contact_name}', $contact['name'], $config['title']),
            'amount' => $config['amount'] ?? 0,
            'stage' => $config['stage'] ?? 'prospecting',
            'contact_id' => $contact['id'],
            'assigned_to' => $config['assigned_to'] ?? get_current_user_id(),
            'created_at' => current_time('mysql')
        ];
        
        $result = $wpdb->insert(
            $wpdb->prefix . 'skunk_deals',
            $deal_data,
            ['%s', '%f', '%s', '%d', '%d', '%s']
        );
        
        return [
            'success' => $result !== false,
            'deal_id' => $wpdb->insert_id
        ];
    }
}

3. Contact Data Enhancement

class SkunkCRM_Data_Enrichment {
    
    public function __construct() {
        add_filter('skunkcrm_contact_data_before_save', [$this, 'enrich_contact_data'], 20, 2);
        add_action('skunkcrm_after_contact_create', [$this, 'background_enrichment'], 10, 2);
    }
    
    public function enrich_contact_data($data, $contact_id) {
        // Real-time enrichment for critical fields
        if (isset($data['email']) && !empty($data['email'])) {
            $domain = substr(strrchr($data['email'], "@"), 1);
            $data['email_domain'] = $domain;
            
            // Check if it's a business email
            $personal_domains = ['gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com'];
            $data['is_business_email'] = !in_array($domain, $personal_domains);
        }
        
        // Auto-format phone numbers
        if (isset($data['phone']) && !empty($data['phone'])) {
            $data['phone'] = $this->format_phone_number($data['phone']);
        }
        
        // Capitalize names
        if (isset($data['name'])) {
            $data['name'] = ucwords(strtolower($data['name']));
        }
        
        return $data;
    }
    
    public function background_enrichment($contact_id, $data) {
        // Schedule background enrichment
        wp_schedule_single_event(time() + 60, 'skunkcrm_enrich_contact', [$contact_id]);
    }
    
    private function format_phone_number($phone) {
        // Remove all non-numeric characters
        $phone = preg_replace('/[^0-9]/', '', $phone);
        
        // Format as (XXX) XXX-XXXX for US numbers
        if (strlen($phone) === 10) {
            return preg_replace('/(\d{3})(\d{3})(\d{4})/', '($1) $2-$3', $phone);
        }
        
        return $phone;
    }
}

// Register the enrichment hook
add_action('skunkcrm_enrich_contact', function($contact_id) {
    // Fetch additional data from external APIs
    $contact = SkunkCRM_Contacts::get_by_id($contact_id);
    
    if ($contact && !empty($contact['email'])) {
        // Example: Enrich with Clearbit, FullContact, etc.
        $enriched_data = fetch_from_clearbit($contact['email']);
        
        if ($enriched_data) {
            SkunkCRM_Contacts::update($contact_id, [
                'company' => $enriched_data['company'] ?? $contact['company'],
                'title' => $enriched_data['title'] ?? null,
                'linkedin' => $enriched_data['linkedin'] ?? null
            ]);
        }
    }
});

Development Guidelines

1. Hook Usage Best Practices

  • Use appropriate priority: Default is 10, use higher numbers to run later
  • Check for plugin existence: Always verify SkunkCRM is active
  • Handle errors gracefully: Don't break core functionality
  • Follow WordPress standards: Use WordPress coding standards

2. Performance Considerations

  • Avoid heavy operations in hooks: Use background processing for intensive tasks
  • Cache external API calls: Implement proper caching mechanisms
  • Use transients: For temporary data storage
  • Database optimization: Use proper indexes and queries

3. Security Best Practices

  • Sanitize all inputs: Use WordPress sanitization functions
  • Validate permissions: Check user capabilities
  • Escape outputs: Prevent XSS attacks
  • Use nonces: For form submissions

4. Testing Your Extensions

// Example test for contact creation hook
class Test_SkunkCRM_Extension extends WP_UnitTestCase {
    
    public function test_contact_creation_hook() {
        // Create a contact
        $contact_data = [
            'name' => 'Test Contact',
            'email' => 'test@example.com',
            'status' => 'prospect'
        ];
        
        $contact_id = SkunkCRM_Contacts::create($contact_data);
        
        // Verify your hook ran
        $this->assertTrue(did_action('skunkcrm_after_contact_create') > 0);
        
        // Verify your modifications
        $contact = SkunkCRM_Contacts::get_by_id($contact_id);
        $this->assertEquals('example.com', $contact['email_domain']);
    }
}

Next Steps

  1. Explore the Hooks & Filters Reference for complete documentation
  2. Check out the REST API Documentation for integration details
  3. Contact our support team for developer assistance and questions

Resources

Was this page helpful?