Formidable to Custom Plugin — Scalability Audit & Migration Decision
Archive record of the analysis and decision that led to the development of the BW Activity Plugin.
Decision made: 2026-03-13 Plugin deployed to production: 2026-03-20 See: BW Activity Plugin — Design & Implementation Plan
Context
Section titled “Context”Through early 2026, the activity tracking system on practice.baseworks.com was built on Formidable Forms. Form 69 served as the core activity log — every practice session was stored as a Formidable entry, and Formidable views handled all display: the dashboard calendar, activity bars, streak tracker, and practice history page.
Two converging pressures triggered a scalability audit in March 2026:
-
PrimerPrint moving online — the PrimerPrint visualization (see Primer Journey Visualizer — PrimerPrint) was being adapted as an interactive web feature. This required a REST API endpoint that could return a user’s complete lesson-visit history in chronological order — a query pattern Formidable’s EAV storage made expensive.
-
Anticipated subscriber growth — as the Primer was being prepared for international marketing, the platform needed to be evaluated against higher user volumes than it had been built for.
The audit was conducted during the initial planning session on 2026-03-13 and concluded that Formidable Form 69 was not a viable long-term foundation for the activity log. The decision to build a custom plugin was made the same day, and development began immediately.
Scalability Audit
Section titled “Scalability Audit”How Formidable stored data
Section titled “How Formidable stored data”Form 69 used Formidable Forms’ standard entry storage: entries in wp_frm_items, field values in wp_frm_item_metas — an EAV (Entity-Attribute-Value) pattern where each field value is stored as a separate row. Each activity session created one row in frm_items and one row per field in frm_item_metas.
Typical entry fields: user_id, timestamp, lesson_id, duration, activity_type ≈ 5+ rows in frm_item_metas per session.
Projected load
Section titled “Projected load”| Subscribers | Daily sessions/user | Entries/day | Entries/year |
|---|---|---|---|
| 100 | 1 | 100 | 36,500 |
| 1,000 | 1 | 1,000 | 365,000 |
| 10,000 | 1 | 10,000 | 3,650,000 |
At 1,000+ subscribers, the frm_item_metas table would reach millions of rows. The Formidable views handling the dashboard (calendar, streak, activity bars) required multi-table JOINs and aggregations across this EAV structure with no domain-specific indexes.
Why EAV was the wrong model here
Section titled “Why EAV was the wrong model here”The EAV pattern is well-suited to form data where fields vary per entry. It is a poor fit for an activity log where:
- The schema is fixed and known in advance
- Queries are predominantly time-range lookups (
WHERE user_id = X AND timestamp > Y) - Aggregates (streak, totals, session counts) are computed frequently on every page load
- PrimerPrint requires fetching a user’s complete lesson-visit history in full chronological order
Verdict: Not viable at scale. A purpose-built table with proper indexing was necessary.
Decision: Custom Plugin
Section titled “Decision: Custom Plugin”The audit led directly to the decision to replace Form 69 with a custom WordPress plugin and a purpose-built database table. Key design criteria at the time of the decision:
- Fixed schema, indexed for time-range and per-user queries
- PHP API for logging activity (callable from Uncanny Automator via custom action)
- Shortcodes to replace Formidable views for all dashboard and history displays
- REST API endpoint for PrimerPrint and future integrations
Proposed schema (as designed in the audit)
Section titled “Proposed schema (as designed in the audit)”The original proposal defined a single table:
CREATE TABLE wp_bw_activity_log ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, user_id BIGINT UNSIGNED NOT NULL, recorded_at DATETIME NOT NULL, lesson_id VARCHAR(100), lesson_label VARCHAR(255), duration_min SMALLINT UNSIGNED, activity_type VARCHAR(50), points SMALLINT, source VARCHAR(50), -- 'presto', 'manual', etc. INDEX idx_user_time (user_id, recorded_at), INDEX idx_lesson (lesson_id));Note: The final implementation added a
clustercolumn and moved activity types into a separatewp_bw_activity_typestable (editable via admin UI). See bw-activity-plugin-plan for the as-built schema.
Migration path (as planned)
Section titled “Migration path (as planned)”- Build and test the custom plugin on pracstage.baseworks.com
- Export Form 69 entries to CSV
- Write a migration script to import CSV into the new table
- Replace Formidable view shortcodes in dashboard and history pages with new plugin shortcodes
- Update Automator recipes to call the new logging action instead of Formidable entry creation
- Verify streak, calendar, and activity bars match previous outputs
- Keep Form 69 in place (deactivated, not deleted) for legacy data access
- Keep Form 71 (timezone settings) — unrelated to the log
What would stay with Formidable (as planned)
Section titled “What would stay with Formidable (as planned)”- Form 70 (Smart Revisit) — to be rebuilt using the new plugin’s data
- Form 68 (Progress bar) — to stay or be replaced
- Form 71 (Timezone settings) — stays permanently; writes directly to an ACF user meta field
Outcome
Section titled “Outcome”The BW Activity Plugin was built across March 13–20, 2026, and deployed to production on 2026-03-20 as v0.7.0. The migration ran successfully: 2,604 entries, 72 users, data back to December 2020. All Formidable activity views were replaced by plugin shortcodes on both English and Japanese pages. Automator Recipes 18036 and 18647 were deactivated and replaced by Recipe 22021. Code Snippets 30–34 (frm-date, dailymax, activity bars, streak, dots) were deactivated. GamiPress was deactivated.
See BW Activity Plugin — Design & Implementation Plan for the full implementation record.
Related
Section titled “Related”- Practice Site Platform Infrastructure — current platform state
- BW Activity Plugin — Design & Implementation Plan — full design and implementation plan
- Primer Journey Visualizer — PrimerPrint — the feature that drove the REST API requirement