osCommerce Add-on and Module Development
Overview
osCommerce (Open Source Commerce) is one of the earliest open-source PHP e-commerce platforms, originally released in 2000. Despite its age, osCommerce continues to power thousands of online stores, particularly among merchants who adopted the platform early and have maintained their installations over the years. The platform is built on procedural PHP with direct MySQL queries, using a custom framework with tep_ prefixed utility functions.
Two major versions remain in active use: osCommerce Online Merchant 2.3 (the widely deployed stable release) and osCommerce Online Merchant 2.4 (which introduced a hook system and modernized some internals). The 2.3 line uses direct file modification for customization, while 2.4 provides hooks that reduce the need to edit core files. Both versions share the same module architecture for payment, shipping, and order total extensions.
osCommerce does not use Composer, PSR autoloading, namespaces, or modern PHP patterns. All code follows procedural conventions with global variables, include/require statements, and direct database access through wrapper functions. Understanding this legacy architecture is essential for building add-ons that integrate cleanly.
Module Types
osCommerce extends its functionality through typed module directories. Each module type has a specific location, interface, and purpose.
Payment Modules
Payment modules handle payment processing during checkout. They reside in includes/modules/payment/ with corresponding language files in includes/languages/{language}/modules/payment/. Each payment module is a single PHP class file that implements a standard set of properties and methods.
Required properties: code, title, description, enabled, sort_order. Required methods: selection() (render checkout form), pre_confirmation_check(), confirmation(), process_button(), before_process(), after_process(), install(), remove(), keys().
class my_payment {
var $code, $title, $description, $enabled, $sort_order;
function __construct() {
$this->code = 'my_payment';
$this->title = MODULE_PAYMENT_MY_PAYMENT_TEXT_TITLE;
$this->description = MODULE_PAYMENT_MY_PAYMENT_TEXT_DESCRIPTION;
$this->sort_order = defined('MODULE_PAYMENT_MY_PAYMENT_SORT_ORDER') ? MODULE_PAYMENT_MY_PAYMENT_SORT_ORDER : 0;
$this->enabled = defined('MODULE_PAYMENT_MY_PAYMENT_STATUS') && MODULE_PAYMENT_MY_PAYMENT_STATUS == 'True';
}
function selection() {
return array('id' => $this->code, 'module' => $this->title);
}
function pre_confirmation_check() { return false; }
function confirmation() { return false; }
function process_button() { return false; }
function before_process() { return false; }
function after_process() {
// Process payment after order creation
global $order_id;
// Call payment gateway API, update order status
}
function install() {
tep_db_query("INSERT INTO configuration (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) VALUES ('Enable My Payment', 'MODULE_PAYMENT_MY_PAYMENT_STATUS', 'True', 'Enable payment module?', 6, 0, 'tep_cfg_select_option(array('True', 'False'), ', now())");
tep_db_query("INSERT INTO configuration (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) VALUES ('Sort Order', 'MODULE_PAYMENT_MY_PAYMENT_SORT_ORDER', '0', 'Display sort order.', 6, 1, now())");
}
function remove() {
tep_db_query("DELETE FROM configuration WHERE configuration_key IN ('" . implode("', '", $this->keys()) . "')");
}
function keys() {
return array('MODULE_PAYMENT_MY_PAYMENT_STATUS', 'MODULE_PAYMENT_MY_PAYMENT_SORT_ORDER');
}
}
Shipping Modules
Shipping modules calculate shipping rates during checkout. They reside in includes/modules/shipping/ and implement a quote() method that returns available shipping options with rates. The quote() method receives no parameters -- it reads the order contents from global variables ($order, $shipping_weight, $shipping_num_boxes).
Order Total Modules
Order total modules add line items to the order summary (subtotal, tax, shipping, discount, grand total). They reside in includes/modules/order_total/ and implement a process() method that calculates and stores the module's contribution to the order total. Standard order total modules include ot_subtotal, ot_tax, ot_shipping, ot_total, and custom modules like ot_discount or ot_coupon.
Content Modules
Content modules (osCommerce 2.3.4+ and 2.4) inject content into specific page locations. They reside in includes/modules/content/ organized by page area (e.g., includes/modules/content/index/ for the homepage). Content modules implement an execute() method and use the template system to render output.
Header Tags Modules
Header tags modules add content to the HTML <head> section -- meta tags, canonical URLs, structured data, analytics scripts. They reside in includes/modules/header_tags/ and implement an execute() method that outputs HTML to be inserted in the <head>.
For complete module structure details, configuration key conventions, and version differences, see references/addon-architecture.md.
Add-on System
File-Based Installation
osCommerce add-ons are distributed as file archives (ZIP or TAR.GZ). Installation is manual: extract the files and copy them to the correct locations in the osCommerce directory tree. There is no package manager, dependency resolver, or automated installer.
A typical add-on includes:
- Module PHP file(s) in the appropriate
includes/modules/subdirectory. - Language file(s) in
includes/languages/{language}/modules/. - Optional template modifications (edited core files with change markers).
- Optional SQL queries for database schema changes.
- A README with installation instructions listing every file to copy and every core file to modify.
Apps Marketplace
The osCommerce Apps Marketplace (apps.oscommerce.com) hosts community-contributed add-ons. Add-ons are categorized by type (payment, shipping, admin tools, SEO, reports) and tagged with version compatibility. The marketplace provides download links, version history, support forums, and user ratings.
Publishing to the Apps Marketplace requires an osCommerce community account. Upload the add-on archive, provide a description, installation instructions, and screenshots. There is no formal review process -- community moderation handles quality control through ratings and forum feedback.
Installation Best Practices
- Always back up the store before installing an add-on.
- Verify version compatibility (2.3 vs 2.4) before installation.
- Review all core file modifications before applying them.
- Use a diff tool to merge changes when multiple add-ons modify the same core file.
- Test in a staging environment before deploying to production.
Hook System (osCommerce Online Merchant 2.4+)
osCommerce 2.4 introduced a hook system that reduces the need to directly modify core files. Hooks allow modules to register callbacks for specific events without editing the files that trigger those events.
Site Hooks
Site hooks fire at defined points in the catalog (storefront) and admin page lifecycle. Register a hook by placing a PHP file in the appropriate hooks directory:
includes/hooks/shop/{hookPoint}/my_addon.php
includes/hooks/admin/{hookPoint}/my_addon.php
Each hook file defines a class with methods corresponding to the hook actions. The hook system automatically discovers and loads hook files from these directories.
Action Hooks
Action hooks fire during specific business operations (before/after processing forms, orders, or database operations). Common action hooks include beforeProcess, afterProcess, siteWide, and page-specific hooks.
For complete hook system documentation including registration, catalog vs admin hooks, and migration patterns, see references/hook-system.md.
Development Patterns
tep_ Functions
osCommerce provides a set of utility functions prefixed with tep_ (The Exchange Project, the original project name). These functions wrap common operations:
tep_db_query($query)-- Execute a MySQL query.tep_db_fetch_array($result)-- Fetch the next row from a query result.tep_db_num_rows($result)-- Count rows in a result set.tep_db_insert_id()-- Get the last auto-increment ID.tep_db_input($string)-- Escape a string for safe SQL use.tep_output_string($string)-- HTML-encode a string for display.tep_href_link($page, $params, $connection)-- Generate a URL for an osCommerce page.tep_image($src, $alt, $width, $height)-- Generate an<img>tag.tep_draw_form($name, $action, $params, $method)-- Generate a<form>tag.tep_draw_input_field($name, $value, $params)-- Generate an<input>tag.
Database Access
All database access uses the tep_db_* functions. There is no ORM, query builder, or prepared statement support in the core. Prevent SQL injection by using tep_db_input() for all user-supplied values:
$customer_id = (int)$_GET['cID'];
$query = tep_db_query("SELECT customers_firstname, customers_lastname FROM customers WHERE customers_id = " . $customer_id);
$customer = tep_db_fetch_array($query);
Configuration System
Module settings are stored in the configuration database table. Each setting is a row with configuration_key, configuration_value, and metadata columns. The install() method inserts configuration rows; the remove() method deletes them.
Read configuration values with the constant defined by the configuration key:
if (defined('MODULE_PAYMENT_MY_PAYMENT_API_KEY')) {
$api_key = MODULE_PAYMENT_MY_PAYMENT_API_KEY;
}
osCommerce loads all configuration values as PHP constants at startup, so configuration reads are simple constant references with no database queries at runtime.
Global Variables
osCommerce relies heavily on global variables. The $order object contains the current order during checkout. The $currencies object handles currency formatting. The $languages_id variable identifies the active language. Access these globals within module methods:
function after_process() {
global $order, $order_id, $currencies;
$total = $currencies->format($order->info['total']);
}
Limitations
Understanding osCommerce's limitations is critical for setting development expectations:
- No Composer or PSR autoloading -- All files are loaded via
include/require. There is no dependency management. Introducing third-party libraries requires manual file placement and autoloader bootstrapping. - No namespaces -- All classes exist in the global namespace. Name collisions between add-ons are possible. Use unique, descriptive class names.
- Procedural architecture -- The codebase is primarily procedural PHP, not object-oriented. While modules use classes, the overall architecture relies on global state and function calls.
- Manual file management -- Installation, updates, and removal of add-ons require manual file operations. There is no built-in add-on installer or update mechanism.
- Direct core file editing -- Many customizations in osCommerce 2.3 require editing core files. This creates merge conflicts when updating the core, and multiple add-ons modifying the same file can conflict with each other.
- No REST API -- osCommerce does not include a built-in API. External integrations require custom-built endpoints or third-party API add-ons.
- Limited template system -- Template modifications involve editing PHP files that mix logic and presentation. There is no template engine (no Twig, Smarty, or Blade).
- Security considerations -- The legacy codebase predates modern security practices. Ensure all custom code escapes output, validates input, and uses
tep_db_input()for SQL parameters.
Anti-Patterns
- Modifying core files without markers -- When core file edits are necessary (osCommerce 2.3), always surround changes with comment markers (
// BEGIN MyAddon/// END MyAddon) so they can be identified and merged during updates. - Hardcoding language strings -- Use language files and defined constants for all user-facing text. Hardcoded strings prevent internationalization.
- Ignoring the configuration system -- Store all module settings in the
configurationtable throughinstall()andkeys(). Do not use config files, hardcoded values, or separate database tables for simple settings. - Skipping input validation -- Always cast numeric IDs to
(int), usetep_db_input()for strings in queries, and validate form submissions before processing. - Using deprecated MySQL functions -- osCommerce 2.3 originally used the
mysql_*extension. Updated versions usemysqli_*. Ensure compatibility with the installed PHP version and database wrapper.
Reference Files
- Add-on Architecture -- Module structure for payment/shipping/order_total, configuration keys, install()/remove() methods, language files, file placement, version differences, Apps architecture
- Hook System -- Site hooks, action hooks, hook registration, catalog vs admin hooks, migration from direct file editing to hooks