Case Study — Web Development

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.

WordPress Custom Plugin Dev PHP 8.2 Elementor Pro Non-Profit 2025–2026

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.

The proposal was accurate on platform direction and content scope, but significantly underestimated the complexity of the database migration, the custom plugin development required, and the volume of original content work that emerged as the project progressed. The gap between the original 153-hour estimate and actual effort is itself a meaningful lesson in scoping non-trivial migrations.

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
The recommendation was a clean platform migration rather than patching Drupal. The codebase had no viable path forward for a small non-profit team.

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.

WordPress 6.xElementor ProPHP 8.2 Contact Form 7Slider RevolutionGTranslate reCAPTCHAGoogle AnalyticsHostup / cPanel

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.

Inductee Directory
Year-grid + A–Z with live search, CPT management, last-name and year sorting
Featured Inductees
3-card rotating home section by category — daily or random rotation
Hero Image Manager
Per-page hero picker via Media Library with full CPT single-page support
Board of Directors
Drag-and-drop ordered cards with Media Library headshots and hover effects
Awards of Distinction
Bar & Hedy, Ambassador, Roll of Honour (Gold/Silver/Bronze) with nomination forms
Banquet Automation
Event CPT, front-end grid, countdown bar with fallback, PDF blurb extraction
Media & Facebook
Video, press, and social cards with filter tabs; Facebook feed section
Announcements & Press
Accordion announcement cards and press release CPT with optional PDF downloads
Auto-Linking
Transient-cached longest-match-first link engine, skips existing anchors
French Corrections
Québec French localization module beyond what machine translation produces

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
A script error during cleanup created duplicate images and crashed the site mid-project, requiring a database restore from the Hostup control panel with support from the Swedish host. The incident prompted tighter backup checkpointing for the remainder of the project.

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
The auto-linking module built into the plugin grew directly out of this work — once all profiles existed as proper CPT entries, it became possible to programmatically detect inductee names in any post content and link them automatically to their profiles.

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.