Work and Experience
WordPress Engineering Sanitized reference

LearnDash + Keap MU-Plugin

LMS progress driving CRM state through a member roadmap

View the repository
MU layer
integration that cannot be deactivated
Client
Sanitized from production work
Engagement
Must-use WordPress plugin
Stack
WordPressPHPLearnDashMemberium / KeapMU-PluginGamiPress
01

Context

Membership sites that tie learning progress to a CRM usually do it with brittle glue: hooks scattered across functions.php, unguarded direct writes, and no boundary between what the LMS believes and what the CRM believes. It works until it does not, a spoofed AJAX call marks a course complete, a plugin update reorders hooks, and suddenly member access, progress and CRM tags disagree with each other and nobody can say which one is true.

This must-use plugin is the engineered version, sanitized from a production Memberium/Keap + LearnDash + GamiPress stack: LearnDash progress events wired to Keap through Memberium behind capability-checked, nonce-verified handlers, driving a member roadmap and access logic from a single authoritative state. It loads at the MU layer so the integration cannot be deactivated out from under the business logic it protects.

02

The Challenge

  • Progress events are security-sensitive: they gate paid access, so every write path had to be protected against spoofing and replay.
  • Three plugins each hold a partial truth (LMS progress, membership tier, CRM tags), without an explicit boundary they drift apart silently.
  • The integration must survive plugin updates and admin mistakes, hence MU-layer loading rather than a deactivatable plugin.
  • The same progress events feed gamification, awards and points had to stay strictly downstream so a gamification bug could never corrupt access state.
03

Scope and Approach

  1. 01

    Explicit integration boundary

    Separates LMS state from CRM state with a defined sync contract between them: which events flow in which direction, what wins on conflict, and what never crosses. Each system stays internally coherent instead of leaking writes into the other.

  2. 02

    Guarded hook handlers

    Every progress-driven write sits behind capability checks and nonce verification with sanitized inputs, the standard five-step WordPress security pattern applied to every entry point, so progress updates cannot be triggered or spoofed by unauthorized actors.

  3. 03

    Single-source access logic

    Member access and the roadmap render from one authoritative state, so what a member can see always matches verified progress, no drift between LearnDash completion, Memberium tier and Keap tags.

  4. 04

    MU-layer deployment

    Ships as a must-use plugin: loaded before the plugin stack, immune to accidental deactivation, present on every request. The correct layer for integration logic that other systems depend on.

  5. 05

    Sanitization for publication

    Stripped of client identifiers and published as a reference implementation for LMS / membership / CRM integration, production patterns, reviewable in public.

04

Architecture flow

LearnDash progress
course events
MU-plugin hooks
capability + nonce guarded
Access + capability check
is allowed?
allowed
Update roadmap + CRM
denied
Blocked + logged
Keap via Memberium
CRM state
Member roadmap
single source of truth
05

Technical Deep Dive

Why the boundary matters more than the code

The failure mode in LMS-CRM glue is rarely a bug in a handler; it is two systems writing each other's state until neither is authoritative. Defining ownership, LMS owns progress, the roadmap derives from it, CRM receives projections, is what makes every later decision easy and every bug diagnosable.

The five-step guard on every entry point

Capability check, nonce verify, sanitize input, act, escape output. Applied uniformly, including on handlers that "only internal code calls", because in WordPress every hook is a public API the moment another plugin fires it.

Gamification downstream of truth

GamiPress consumes the same progress events but sits strictly downstream of the boundary: awards and points derive from verified progress and can never write back into it. A gamification misfire costs a badge, never a member's paid access, failure domains kept deliberately asymmetric.

Load order as part of the guarantee

MU-plugins load before the regular plugin stack on every request, so the integration observes LearnDash, Memberium and GamiPress from the first hook they fire, no race against activation order, no window where progress events pass by unobserved because a plugin happened to load first.

Denied attempts are evidence

A blocked write is not silently swallowed, it is logged with its context. On an access-gating integration, the denied attempts are the security telemetry: they show who or what tried to move progress without authority, which is exactly the signal worth reviewing.

06

Engineering Decisions

MU-plugin over a regular plugin

Access control that can be switched off in wp-admin is not access control. The MU layer trades a slightly less convenient update path for the guarantee that the integration is always loaded, the right trade for logic that gates paid content.

Derive the roadmap, never store it twice

The member roadmap is computed from progress state rather than persisted as its own copy, one less dataset to drift, and the UI is correct by construction.

Sanitized, not abstracted

The published version strips identifiers but keeps the production shape, real hook wiring, real guard patterns, real boundary decisions. A reference that reads like shipped code teaches more than a tutorial that reads like a slide deck.

07

Results

  • LMS progress and CRM state synchronized through one explicit boundary contract.
  • Capability-checked, nonce-verified handlers on every write path, no spoofable progress updates.
  • Member access and roadmap driven from a single authoritative state, zero drift.
  • Integration loaded at the MU layer, immune to accidental deactivation.
  • A public reference for production-grade LMS / membership / CRM integration.

Have a system that needs
this kind of work?

Tell me what is breaking or what you need built. I will reply with how I would approach it and what I would check first.

Chat on WhatsApp