A WordPress LMS handles the parts of a course that are not video. LearnDash, TutorLMS, LifterLMS and LearnPress each hold the enrolment, the lesson order, the quiz and the certificate. Each also gives a lesson somewhere to put a video. The learner watches, the lesson turns green, the progress bar moves, and for the course structure that arrangement works well.
The video half rests on something weaker. A lesson is credited because the browser said the video finished, and a browser can be persuaded to say that. Drag the scrubber to the last second and the player reports the same completion it would report after forty minutes of watching. The lesson row that results is identical, so reading it later tells you nothing about which session produced it.
That gap starts to cost money once a certificate, a compliance record or a refund argument depends on it. What follows is what each of the four plugins gives you for video and where each one stops. Then comes the check a video layer runs on the server, and what it stores about a learner.
TL;DR
The four WordPress LMS plugins record that a lesson finished. None can tell you a learner watched it, because the signal starts in the learner's browser. Skip-proof completion moves the check to the server, which re-reads the threshold from the lesson's own markup and discards beats for videos not embedded in that lesson. In the FastPix plugin the threshold is complete_at, default 90 percent, and per-learner tracking is track_viewer, off by default. Learner rows hold a salted hash, never a name, pruned after 90 days.
Why a green checkmark is not proof a lesson was watched
All four plugins learn that a video finished the same way. The player runs in the learner's browser, reports what happened, and the LMS credits the lesson on that report.
A browser can produce that report without anyone watching. Dragging the scrubber to the final second leaves the player in the completed state, and a replayed request reaches the same end point with no player at all. Nothing in the lesson record separates either one from a learner who watched it through.
None of this is specific to WordPress. How to track and grade video completion works the mechanism through on Moodle, and the boundary sits in the same place wherever the player runs. What changes here is ownership. The LMS plugin owns the lesson record, while the video sits in a block or shortcode the LMS never inspects.
What each of the four LMS plugins gives you for video, and where it stops
All four keep the video as a field or an embed inside the lesson, and all four decide completion in the same place. What differs is how much each does with the player first.
| LMS | Where the video lives | How the lesson is credited | What a video layer still has to supply |
|---|---|---|---|
| LearnDash | A video URL or embed on the lesson or topic, with a video progression option that holds the lesson until the video reports it played | Player progress unlocks mark-complete, then the lesson is credited | A server-side check, per-learner watch records, and delivery a shared link cannot reach |
| TutorLMS | A video field per lesson taking several sources, including self-hosted files and external URLs | The learner marks the lesson complete, with playback position recorded from the player | The same three, plus a threshold the site owner sets rather than the player |
| LifterLMS | A video embed field on the lesson, alongside quizzes and prerequisites | Mark-complete, optionally gated by a quiz or a prerequisite lesson | A watch requirement of any kind, since the gating is on course structure rather than playback |
| LearnPress | The video in the lesson content or a lesson video field | The complete-lesson action, with add-ons extending the rule | A watch requirement, per-learner records and protected delivery |
Versions and add-ons move these boundaries, so treat each vendor's own documentation as the authority for the release you run. In all four, the sentence "this learner finished the lesson" rests on something the learner's browser said.
What skip-proof completion checks that a player event does not
Skip-proof completion takes the decision out of the page. The server receives the progress beats, then re-reads the required threshold from the lesson's own markup rather than trusting the number that arrived with the beat. It also drops any beat for a video not embedded in that lesson, so progress earned on a free preview cannot be spent on a paid one.
That closes two holes. A replayed completion call fails, because the server asks the lesson rather than the message. A threshold edited in the page fails too, because the page is not where the threshold is read from.
Wiring that yourself means a REST endpoint, a nonce scheme, a beat store and a reconciliation job, maintained against four LMS plugins that release on their own schedules. The FastPix WordPress plugin runs the check inside course features, which stay off until it detects LearnDash, TutorLMS, LifterLMS or LearnPress. A site without one sees no change.
Setting the threshold and turning on per-learner tracking
Two attributes on the embed carry the lesson behaviour. complete_at is the percentage watched that counts as complete, and it defaults to 90. track_viewer records per-learner progress and defaults to off, so a site needing only aggregate numbers collects nothing against an individual.
[fastpix id="playback_ID" complete_at="80" track_viewer="true"]Choose the threshold from the shape of the lesson. A recorded session with five minutes of questions at the end will strand learners at ninety. A tight explainer that ends on its final point can sit higher. Two filters cover the rest. fastpix_completion_threshold changes the site-wide default, and fastpix_lesson_post_types adds post types that count as lessons, because the four plugins disagree on that.
Keeping control of what a completion triggers
On completion the plugin calls the LMS's own completion function. The LMS credits the lesson, rather than a row appearing behind its back, so progress bars, drip rules and certificates behave as before.
The fastpix_video_completed action fires first, once per learner per lesson video. That is where your own record goes: an audit row, a webhook to a CRM, a note to the instructor. The fastpix_lms_auto_complete filter turns the automatic credit off.
add_action( 'fastpix_video_completed', function () {
// Runs once when a learner completes a lesson video,
// before the LMS is asked to credit the lesson.
} );
// Stop the plugin crediting lessons. Your own code decides instead.
add_filter( 'fastpix_lms_auto_complete', '__return_false' );Turn the filter off when credit depends on more than playback, such as a video plus a signed acknowledgement. The developer hooks reference lists the rest.
Reading per-course progress without building a report
Once watch data exists, somebody will ask who is behind. The LMS tab in the plugin's Analytics screen answers that with per-course student progress, beside the site-wide and per-video views.
The screen reads a local daily rollup, so no admin page waits on an API call, and exports run as a background job. Every other number here is browser-collected, with the caveats in video analytics in WordPress. Enforced completion is the exception.
What the plugin stores about a learner
Watch tracking creates personal data, and a course business in Europe or California answers for it. Four properties decide how much answering you do.
Per-learner rows hold a salted hash rather than a name, so the table carries no identity on its own, and the rows live only in the WordPress database. Retention prunes them after the per-learner period, which defaults to 90 days. You set it in Settings, Video setup. The WordPress export and erasure tools cover the rows, so a subject request through Tools in wp-admin reaches the watch data.
Consent gates the collection. Progress is recorded only where consent allows it, and the plugin recognises the WP Consent API, Cookiebot, OneTrust and CookieYes. An admin-only name reveal covers the case where a human must act on one learner, and fastpix_reveal_learner_names removes it.
Deciding how strict your completion rule needs to be
Not every course needs an enforced threshold. Match the rule to what completion decides.
| What completion drives | What is enough | Why |
|---|---|---|
| A progress bar and nothing else | The LMS's own mark-complete | Nobody is harmed by a skipped lesson |
| Drip pacing and course order | Mark-complete plus a quiz | The quiz is the real check |
| A certificate shown to an employer | Server-enforced threshold, per-learner records on | The record has to survive a dispute |
| A regulated or audited training record | Server-enforced threshold, per-learner records, retention set to your policy, protected playback | An auditor asks for the record and for how it was produced |
Protected playback sits in the bottom row because an enforced threshold means little if the lesson video is downloadable from the page source. A membership plugin decides who loads the page, not who loads the file. Protecting paid video on WordPress maps which control stops which attack, and how to protect online courses from piracy takes the course-business view.
Turn on course features and run the scrubber test on one lesson
The fastest way to see where your course sits is to try to cheat it. Open one lesson as a test learner, drag the scrubber to the end, and look at what the LMS wrote. If the lesson went green, that is the standard of proof behind every certificate you have issued.
Then run the same test with the check on the server. Install the FastPix plugin, switch on course features once it detects your LMS, add complete_at and track_viewer to that embed, and drag the scrubber again. The free plan covers 10 videos with no card, enough to prove the difference before you move a library.
Frequently Asked Questions (FAQs)
Can a learner skip a video and still complete the lesson?
On a default setup, yes. The player reports progress from the learner's browser, and dragging the scrubber to the final second leaves it in the same state as watching. Video progression settings raise the effort but still read the browser's account of events. The Moodle version is in how to track and grade video completion.
What is skip-proof video completion?
A completion check that runs on the server rather than in the page. The server re-reads the threshold from the lesson's own markup instead of trusting the number that arrived with the beat, and discards beats for videos not embedded in that lesson. A replayed call and an edited page value both fail. See course features.
What percentage of a video should count as complete?
Ninety percent is a reasonable default, and it is what complete_at uses. Set it from the shape of the lesson. A recorded session with several minutes of questions at the end will strand learners at ninety. A tight explainer can sit higher.
Does watch tracking store learner names?
No. Per-learner watch data is stored as a salted hash, kept in the WordPress database, and pruned after the retention period, which defaults to 90 days. Progress is recorded only where consent allows it, and the plugin recognises the WP Consent API, Cookiebot, OneTrust and CookieYes.
Can a learner ask for their watch history to be deleted?
Yes. The WordPress personal-data export and erasure tools cover per-learner watch records, so a request through Tools in wp-admin reaches the watch data. Retention prunes the rows on its own schedule too, and you set that period in Settings, Video setup.
Do course features work on any WordPress LMS?
They turn on when the plugin detects LearnDash, TutorLMS, LifterLMS or LearnPress, and stay off otherwise. A custom lesson post type can be brought in with the fastpix_lesson_post_types filter. Still choosing the video layer? Video APIs for online learning platforms compares the options. How to build a Coursera-like learning platform covers leaving WordPress.
Should course video be public or private on a WordPress LMS?
Keep previews and the free first lesson public so they stay findable, and protect the paid lessons. Page-level membership rules do not protect a file URL in the page markup, so paid video needs access control on the media itself. Protecting paid video on WordPress covers which control stops which attack.










