Canadian Motorcycle
Hall of Fame Rebuild
Complete platform migration from a stalled Drupal installation to a modern WordPress build with a custom plugin suite — built for a non-profit organization with 20 years of Canadian motorcycling history to preserve.
The project began with a formal proposal dated June 2, 2025, submitted to the Canadian Motorcycle Hall of Fame Board. It defined the scope, timeline, and investment for a comprehensive redesign.
The proposed scope was organized into four areas:
- Strategic Planning & Foundational Design (26 hrs): Wireframes, sitemap redesign, header/footer rebuild, homepage overhaul
- Core Content Rebuild (111 hrs): 144 inductees reformatted, special awards consolidated, media section rebuilt, 18 years of events organized, nominations page, contact page
- Technical Implementation (16 hrs): SEO foundations, mailing list integration, WooCommerce for ticket sales
- QA & Warranty: Mobile testing across devices, 60-day bug warranty post-launch
Optional services in the proposal included temporary migration of the old site to a Swedish host during development, mass email setup via Bravo, and on-site coverage of the annual Gala including photography and interviews.
Ongoing post-launch costs were scoped at $200 CAD/month for up to 4 hours maintenance, with hosting on WPEngine estimated at $50/month and plugin licensing at roughly $50/month.
Before touching any code, a structured audit of the existing site was completed. The picture wasn't great.
- Built on Drupal — under 3% market share and declining, with a shrinking developer ecosystem
- A broken staging version (d945.canadianmotorcyclehalloffame.ca) was publicly indexed on Google, actively confusing visitors and creating duplicate content problems
- Zero structured SEO metadata — no Open Graph tags, no canonical URLs, no schema markup
- Mobile experience stripped graphics with no compensating design elements
- Footer linked to a broken contact page on the staging server
- Social media links throughout pointed to abandoned accounts
- Existing YouTube content of inductees and events was not being surfaced on the site at all
WordPress was selected for its ecosystem depth, Elementor Pro compatibility, and the fact that the two non-technical client contacts needed to manage content post-handoff without calling a developer for routine updates.
Hosting landed on Hostup (cPanel / LiteSpeed / PHP 8.2). All development ran on a staging subdomain — the live Drupal site was never touched until the migration was ready. The Lex Rider Opal theme provided a motorcycle-appropriate dark aesthetic that replaced the original yellow-heavy palette, which had been a long-standing concern.
The core technical deliverable was cmhof-suite — a single consolidated WordPress plugin replacing multiple standalone scripts and mu-plugins. Development moved from early consolidation builds (v2.9.52) through to production-ready releases (v2.9.193+), each iteration driven by new client requirements or bugs surfaced in staging.
The original site had essentially no structured SEO. Open Graph and Twitter Card tags were added site-wide. Custom meta titles and descriptions were written for all major pages. The broken staging domain was flagged immediately for noindex and password protection — it was live on Google and creating active duplicate content damage.
The 2026 banquet is in Trois-Rivières, Québec, making French-language access a genuine requirement. GTranslate handles the main site content, but Slider Revolution renders its hero text as static elements outside the DOM translation layer. The solution loads an alternate French animation stack conditionally when the GTranslate language cookie indicates French is active.
Getting the Drupal content into WordPress was one of the most labour-intensive parts of the project. There was no clean export path — Drupal and WordPress store content in fundamentally different database structures, and the standard migration tools couldn't handle the ACF gallery relationships required by the new site.
SQL Server was installed locally and the full Drupal database was imported. Custom SQL queries were written against the Drupal schema to extract inductee text, category assignments, hero images, and associated gallery image sets — with correct field mapping and character set handling. Getting that export right took many hours of iteration against a schema that wasn't designed to be migrated.
On the import side, pushing 1,500+ images through the WordPress media uploader caused repeated server timeouts. The solution was WP-CLI — scripts were written to handle the imports from the command line, bypassing the HTTP layer entirely. A separate process was developed specifically for importing images into ACF gallery fields, which required its own mapping layer between the Drupal file references and the new WordPress attachment IDs.
When the downloaded image folders were inspected, most photos had never been optimized for web. Many were over 10MB with no consistent dimensions or format. All images were batch-processed in Photoshop — resized, compressed, and saved for web — before being imported. This was unglamorous work but directly responsible for the new site's significantly faster load times.
- Local SQL Server install with custom queries against the Drupal database schema
- Field mapping across Drupal's content structure to WordPress CPT + ACF gallery fields
- WP-CLI scripted import to bypass WordPress HTTP timeouts on 1,500+ images
- Custom mapping layer for ACF gallery field population from Drupal file references
- Photoshop batch processing of the full image library — resize, compress, web export
Getting 200+ inductee profiles off the old Drupal site and into WordPress was not a simple export. The old site had no usable data export, no API, and its content was spread across dynamically rendered per-letter alphabetical pages. The approach was browser-based systematic scraping.
Using Claude in Chrome, a JavaScript crawler was written and executed directly in the browser against the live Drupal site. It walked the full A–Z directory — fetching each letter page, extracting every individual inductee URL, then visiting each profile to pull biography text, header image, gallery images, induction year, and category classification. The same crawler simultaneously pulled matching data from the new WordPress staging site so discrepancies could be flagged inductee by inductee.
Once the data was in WordPress, a custom REST API endpoint was added to the plugin — POST /wp-json/cmhof/v1/memoriam-batch — to handle bulk writes that WordPress's standard REST API couldn't do cleanly. This was used to batch-write the mortality review data (in memoriam notices for 21 deceased inductees) in a single operation using nonce-authenticated browser calls, writing via both update_post_meta and ACF's update_field simultaneously to keep both systems consistent.
A full CSV export of all 206 inductees and award recipients was also generated via browser-side REST API calls — useful as both a project deliverable and a data integrity audit.
- Full A–Z directory crawl across the old Drupal site — no export tool, no API, pure JS scraping
- Inductee-by-inductee comparison between old and new sites: header images, gallery counts, categories
- Custom memoriam-batch REST endpoint for bulk mortality data writes
- Nonce-authenticated batch REST calls executed from the browser admin session
- CSV export of all 206 records as a data integrity deliverable
Beyond development, significant hands-on content work was part of the engagement:
- All Class of 2025 inductees manually added — headshots sourced, biographical descriptions written
- Complete mortality review across the full 200+ inductee roster
- All inductee loop grid pages restyled with dark theme without modifying Elementor templates directly
- WordPress admin accounts created and role-scoped for client contacts François and Vada
- reCAPTCHA configured on all public-facing forms
- Google Analytics and Facebook pixel integrations prepared for go-live activation
- Plugin activation fatal error: A do_action('init') inside register_activation_hook forced Elementor to bootstrap twice — fatal class redeclaration on every activation. One line removed, resolved.
- WP_Query silent filtering: Award CPT records disappearing from admin tabs because meta_key silently drops posts lacking that key. Fixed with named meta_query clauses.
- Elementor CPT hero images: CPT single pages render via .elementor-location-header, not .page-title-bar — required a different CSS strategy and JS-based title injection.
- ACF save failures: update_field() silently failing without registered field pointers. Replaced throughout with standard update_post_meta.
- PDF button logic: Download buttons appearing on cards with no PDF attached. The press_pdf meta field stores in three formats (array, attachment ID, URL string) and validation wasn't catching all three.
Lessons Learned
Scope management matters most when you care about the work. Projects involving subject matter with personal significance are at the highest risk of scope creep. Formal change orders aren't bureaucracy — they're what makes a project economically sustainable.
Plugin architecture pays off at handoff. Consolidating everything into one well-structured plugin rather than accumulating standalone scripts reduced maintenance overhead and made client training straightforward.
Retainer structures belong in the original agreement. Ongoing plugin work — compatibility updates, new features, content support — doesn't fit project billing. Propose the retainer upfront, not after six months of scope creep.