← back to blog
2026-02-05 ecommerce

CS-Cart Multivendor: Building Enterprise E-Commerce Addons

I’ve spent the last 3 years building custom addons for CS-Cart’s Multivendor platform at Shawne Applebee. CS-Cart isn’t as well-known as Shopify or Magento, but for multivendor marketplaces, it’s one of the most powerful platforms out there. Here’s what I’ve learned.

Understanding CS-Cart Architecture

CS-Cart follows an MVC-ish architecture with some unique concepts:

  • Addons — Self-contained modules that extend functionality
  • Hooks — Entry points throughout the codebase where addons can inject logic
  • Schemas — Define how data structures, menus, and settings work
  • Controllers — Handle HTTP requests (backend and API)
  • Templates — Smarty-based frontend rendering

The addon system is where CS-Cart shines. You can modify almost any behavior without touching core files.

Addon File Structure

Every addon follows this structure:

app/addons/my_addon/
├── addon.xml                  # Addon metadata and settings
├── init.php                   # Hook registrations
├── func.php                   # Hook handler functions
├── controllers/
│   ├── backend/
│   │   └── my_controller.php  # Admin panel controller
│   └── frontend/
│       └── my_controller.php  # Storefront controller
├── schemas/
│   └── menu/
│       └── menu.post.php      # Admin menu entries
├── database/
│   └── install.sql            # Database migrations
└── design/
    ├── backend/
    │   └── templates/
    │       └── views/
    └── frontend/
        └── templates/

Creating an Addon

The addon.xml defines your addon:

<?xml version="1.0"?>
<addon scheme="4.0" edition_type="ROOT,ULT:VENDOR">
    <id>vendor_analytics</id>
    <version>1.0.0</version>
    <priority>500</priority>
    <position>100</position>
    <status>active</status>

    <auto_install>MULTIVENDOR</auto_install>

    <name>Vendor Analytics Dashboard</name>
    <description>Advanced analytics for marketplace vendors</description>

    <settings layout="separate" edition_type="ROOT">
        <sections>
            <section id="general">
                <items>
                    <item id="analytics_retention_days">
                        <type>input</type>
                        <default_value>90</default_value>
                    </item>
                    <item id="enable_realtime">
                        <type>checkbox</type>
                        <default_value>Y</default_value>
                    </item>
                </items>
            </section>
        </sections>
    </settings>
</addon>

Working with Hooks

Hooks are the backbone of CS-Cart customization. There are two types: PHP hooks for logic and TPL hooks for templates.

PHP Hook Example

Register hooks in init.php:

<?php

if (!defined('BOOTSTRAP')) { die('Access denied'); }

fn_register_hooks(
    'get_orders_post',
    'place_order',
    'calculate_cart_post',
    'vendor_plan_after_update'
);

Implement them in func.php:

<?php

if (!defined('BOOTSTRAP')) { die('Access denied'); }

function fn_vendor_analytics_get_orders_post(&$params, &$orders)
{
    if (empty($orders)) {
        return;
    }

    foreach ($orders as &$order) {
        $order['analytics'] = db_get_row(
            "SELECT * FROM ?:vendor_analytics WHERE order_id = ?i",
            $order['order_id']
        );
    }
}

function fn_vendor_analytics_place_order(&$order_id, &$action, &$order_status, &$cart)
{
    if (empty($order_id)) {
        return;
    }

    $company_id = fn_get_company_id('orders', 'order_id', $order_id);

    db_query(
        "INSERT INTO ?:vendor_analytics SET " .
        "order_id = ?i, " .
        "company_id = ?i, " .
        "revenue = ?d, " .
        "items_count = ?i, " .
        "created_at = ?s",
        $order_id,
        $company_id,
        $cart['total'],
        count($cart['products']),
        date('Y-m-d H:i:s')
    );
}

Database Migrations

CS-Cart doesn’t have a migration system like Laravel. You write raw SQL:

-- database/install.sql
CREATE TABLE IF NOT EXISTS `cscart_vendor_analytics` (
    `analytics_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
    `order_id` int(11) unsigned NOT NULL,
    `company_id` int(11) unsigned NOT NULL,
    `revenue` decimal(12,2) NOT NULL DEFAULT '0.00',
    `items_count` int(11) NOT NULL DEFAULT '0',
    `conversion_source` varchar(255) DEFAULT NULL,
    `created_at` datetime NOT NULL,
    PRIMARY KEY (`analytics_id`),
    KEY `idx_company_date` (`company_id`, `created_at`),
    KEY `idx_order` (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

For upgrades, create version-specific migration files and handle them in your addon’s install/upgrade logic.

Building Admin Controllers

Controllers in CS-Cart follow a specific pattern:

<?php

if (!defined('BOOTSTRAP')) { die('Access denied'); }

use Tygh\Registry;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if ($mode === 'update') {
        $analytics_data = $_REQUEST['analytics_data'];

        db_query(
            "UPDATE ?:vendor_analytics SET ?u WHERE analytics_id = ?i",
            $analytics_data,
            $analytics_data['analytics_id']
        );

        fn_set_notification('N', __('notice'), __('text_data_updated'));
        return [CONTROLLER_STATUS_OK, 'vendor_analytics.manage'];
    }
}

if ($mode === 'manage') {
    $company_id = Registry::get('runtime.company_id');

    list($analytics, $search) = fn_get_vendor_analytics(
        array_merge($_REQUEST, ['company_id' => $company_id]),
        Registry::get('settings.Appearance.admin_elements_per_page')
    );

    Tygh::$app['view']->assign('analytics', $analytics);
    Tygh::$app['view']->assign('search', $search);

} elseif ($mode === 'dashboard') {
    $company_id = Registry::get('runtime.company_id');

    $stats = fn_get_vendor_dashboard_stats($company_id);
    Tygh::$app['view']->assign('stats', $stats);
}

Performance Considerations

CS-Cart can be slow if you’re not careful. Lessons learned:

  1. Always add database indexes on columns you query frequently, especially company_id and date columns
  2. Use db_get_hash_array instead of db_get_array + manual indexing
  3. Cache expensive queries using CS-Cart’s built-in cache registry
  4. Avoid N+1 queries in hooks — batch your database calls
  5. Use Varnish in front of the storefront for static page caching
// Bad: N+1 query
foreach ($products as &$product) {
    $product['vendor'] = db_get_row(
        "SELECT * FROM ?:companies WHERE company_id = ?i",
        $product['company_id']
    );
}

// Good: Single query, then map
$company_ids = array_column($products, 'company_id');
$vendors = db_get_hash_array(
    "SELECT * FROM ?:companies WHERE company_id IN (?n)",
    'company_id',
    $company_ids
);

foreach ($products as &$product) {
    $product['vendor'] = $vendors[$product['company_id']] ?? null;
}

Testing Addons

CS-Cart doesn’t have built-in testing support, but you can add PHPUnit:

{
    "require-dev": {
        "phpunit/phpunit": "^10.0"
    },
    "autoload": {
        "psr-4": {
            "VendorAnalytics\\": "app/addons/vendor_analytics/src/"
        }
    }
}

I test the business logic in isolation by extracting it into service classes, then write integration tests that hit the actual database.

Wrapping Up

CS-Cart development has a steep learning curve — the documentation is sparse, and the hook system requires reading core source code to understand what data is available. But once you understand the patterns, you can build powerful marketplace features that would take months to build from scratch.

The multivendor addon I built at Shawne Applebee processes thousands of orders daily and has been running in production for over two years without major issues.