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:
- Always add database indexes on columns you query frequently, especially
company_idand date columns - Use
db_get_hash_arrayinstead ofdb_get_array+ manual indexing - Cache expensive queries using CS-Cart’s built-in cache registry
- Avoid N+1 queries in hooks — batch your database calls
- 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.