E-Commerce Platform Upgrade (OpenCart 1.5 to 3.0)
A sanitized write-up of the e-commerce platform upgrade I carried out as the sole developer at Fuel Cell Store. Product details and proprietary code are omitted, leaving the approach and engineering decisions.
Context
The online store ran on OpenCart 1.5, two major versions behind the 3.x line it needed to reach, with no supported upgrade path. Custom modules and theme changes had accumulated on top of the core over several years with no documentation, so the behavior of much of the custom functionality was effectively unknown.
With no reliable automated path across that version gap, the project combined reverse engineering, data migration, and the rebuilding of custom functionality in roughly equal measure.
Approach
The gap between the two versions is a set of breaking changes rather than an increment. The template engine moves from native PHP files to Twig, the extension system from vQmod to OCMod, the directory layout is reorganized, and a number of database tables are restructured. A direct in-place upgrade was not viable, so I approached it as a rebuild on the new version paired with a controlled data migration, developed and rehearsed on a staging copy before any production cutover.
- Set up a clean target-version installation on staging
- Audit the legacy store feature by feature, directly from its code
- For each feature, adopt a maintained extension or build a custom one
- Write SQL scripts to clean and remap the legacy data into the new schema
- Rebuild the theme and re-implement admin customizations
- Reconcile the migrated data, then cut over to production
Engineering decisions
1. Reverse engineering features with no documentation
The store carried years of undocumented customization. I read the legacy controllers, models, and template overrides to reconstruct what each feature actually did before deciding how to rebuild it. This ensured that behavior the business depended on was not inadvertently lost.
2. Adopt or build, without editing the core
For each legacy module I made an explicit decision: adopt a maintained extension where a reliable one existed, or build a custom one where it did not. In both cases the changes were applied through the platform's modification system rather than by editing core files, so customizations sit on top of the core and the store remains upgradeable rather than being locked to a single version again.
3. Cross-version data migration in hand-written SQL
The two schemas differ in table layout and relationships, so I wrote SQL scripts to clean, map, and rebuild the data rather than relying on an automated importer. Relational data such as product accessories, SEO URLs, and zone-to-geo-zone mappings had to be remapped table by table to fit the new structure.
4. Reconciling the migration before cutover
The entire migration was built and rehearsed on a staging copy of the live store, and nothing moved to production until the copy reconciled against the source three ways: row counts table by table, summed order totals, and manual spot-checks on sampled records. The catalog is what made the third pass necessary. Products carry multi-dimension variants with their own price, SKU, weight, and stock per combination, and part of the catalog sells through a quote-only path rather than through checkout, so a mismatch that counts and totals both clear would surface later as a wrong price or an unsellable product. Order IDs were carried across unchanged rather than renumbered, so the order references customers and the support team already held stayed valid. The production cutover was completed with no business downtime.
5. Rebuilding the templates by hand
The old templates are native PHP and the new ones are Twig, and the data passed into them changed shape at the same time. A conversion script would have left every file to be corrected by hand anyway, and the number of customized templates sat below the point where writing one would pay for itself, so I rebuilt them individually. Each one meant working out what the customization depended on in the old engine before reimplementing it on the new one. Mobile responsiveness was improved in the process. On the admin side, customizations and third-party module compatibility were re-implemented so the team's daily workflows continued without interruption.
6. Keeping the store up during traffic spikes
The store runs on a cPanel/WHM VPS that I manage directly. When our uptime monitor flagged the site slowing down, I checked server load and the access logs over SSH to find the traffic behind it. I then blocked it in layers: .htaccess rules for bad bots and probe patterns, cPanel's IP Blocker for persistent sources, and ModSecurity to rate limit requests that look normal but arrive too fast. The slowdown did not turn into an outage.
Results
- Upgraded the store from 1.5.6.4 to 3.0.3.8 across 2,000+ products and 70,000+ historical orders, reconciled table by table, with order volume holding steady through the rebuild and the cutover
- Carried order IDs across unchanged rather than renumbering, so the order references customers and the support team already held stayed valid after the cutover
- Rewrote the store's custom features on 3.0 instead of porting the old code, preserving existing behavior such as standard checkout, the quote-only flow, and per-combination variant pricing and stock, while fixing legacy bugs and adding new functionality
- Reconstructed and re-implemented years of undocumented custom functionality by reading the legacy code
- Migrated customizations onto the modification system, so the store no longer depends on edited core files and can be updated more safely