April 15, 2026

How to Fix LearnDash and Vimeo Video Progression (Free Code Included)

I found out the hard way that if your video is hosted by Vimeo it doesn't work with LearnDash. Your students can't complete the lesson if the video is tied to Course progression before the completed sub-steps. Here's a free fix.

Setting up LearnDash was super easy, but…

If you’ve built a course on LearnDash and you’re hosting your videos on Vimeo, you’ve probably run into this: the student watches the entire video, the video ends, and then…nothing happens. The “Mark Complete” button stays grayed out. If you’re an admin, it just moves on, so it’s not something you see when you’re testing your own work at first.

This isn’t a configuration error. It’s a fundamental limitation in how LearnDash handles Vimeo, and I found out after lots of Googling and asking Claude AI, whose response was pretty much “yep, it’s broken” — it took until the next day for me to ask, “Wait, can’t you work with me for a PHP fix??!?!?!”

Here’s what’s happening: LearnDash’s video progression JavaScript looks for a Vimeo iframe element in the page when it first loads. But Vimeo’s standard embed doesn’t inject the iframe until the student actually presses play. By the time that iframe exists in the DOM, LearnDash has already run its setup code and given up looking for it. The event listeners that would normally track video completion were never registered. So as far as LearnDash is concerned, the video was never watched. Changing the settings to autostart didn’t help the issue, either.

LearnDash’s own documentation acknowledges Vimeo limitations. Forum threads about this go back a long way. People have paid for third-party plugins trying to solve it, or switched video hosts entirely. There is no official fix. With Claude AI, I wrote, tested, and pushed this fix on the site with LearnDash, and it has been working smoothly for the client. I had a few text changes and swaps written into the PHP I used on that site; what I’m providing below should be theme-agnostic and has nothing but the LearnDash Vimeo fix.

This particular scenario.

We were building a compliance training platform using LearnDash on WordPress with the Avada theme. The course structure is straightforward: each module had a lesson with a Vimeo video, and after watching the video, students needed to complete a quiz. Linear progression was required — students couldn’t skip ahead.

Switching away from Vimeo wasn’t an option. The client had an existing Vimeo library, privacy controls they depended on, and no interest in re-uploading everything to YouTube or a self-hosted solution. (YouTube has branding and options to click away into YouTube; it’s just not the right model for this at all.)

LearnDash’s built-in video progression had to work with Vimeo, period.

Every setting we tried — auto-completion, video progression modes, different embed methods — failed in the same way. The video would play and end, and LearnDash wouldn’t register it. Students were stuck.

The workflow we needed was simple and completely logical (at least to us!):

Student watches the Vimeo video
Video ends → lesson is automatically marked complete
The Mark Complete button unlocks so the student can confirm and proceed to the quiz
Student takes the quiz

That’s it. LearnDash couldn’t do it natively with Vimeo.

The Solution: Using the Vimeo Player SDK Directly

The fix bypasses LearnDash’s non-working video detection entirely and goes straight to the source. Vimeo provides an official JavaScript Player SDK that fires events — including an ended event — directly from the iframe itself. We use that event to do what LearnDash was supposed to do automatically.

When the Vimeo video ends:

A secure AJAX request fires to WordPress
The server verifies a nonce (security token) tied to the specific lesson and user session
The server confirms the user is actually enrolled in the course
LearnDash’s own internal function learndash_process_mark_complete() is called server-side — the same function LearnDash uses when a student clicks the button manually
The server confirms the completion was actually recorded before responding
The Mark Complete button is unlocked on the front end so the student can proceed

The student experience is seamless. The video ends, the button becomes clickable, they click it, and they go to the quiz.

Working with Claude AI

It took real back-and-forth testing to get right, and several things that seemed like they should work didn’t — for reasons that only became clear through debugging.

We built the first version using Claude as a development partner. The initial approach used PHP hooks and the learndash_quiz_content filter, which looked promising. But it became clear that the problem wasn’t on the PHP side — it was the JavaScript rendering order. LearnDash’s wpProQuiz JavaScript controls the DOM client-side after page load, which meant PHP filters were firing before the elements they needed to act on even existed.

The Vimeo-specific issue had the same root cause. LearnDash’s JS runs at page load; Vimeo’s iframe doesn’t exist at page load. The solution had to intercept at the right moment using the Vimeo SDK’s own event system, not LearnDash’s.

The polling approach for finding the Vimeo iframe — checking every 500ms for up to 10 seconds — came from a real behavior we observed: Vimeo’s click-to-play embeds sometimes don’t inject the iframe until several seconds after the page loads, not just at the moment of play. The poll handles both the immediate case and the delayed case.

I tested while logged in as a student and not an Admin because the problem doesn’t happen with Admins, as they are allowed to move and pass through the entire course without the same rules as the lessons have applied to students. I would have done this anyway, but this was an issue I missed at first because of assembling the lessons and going through them immediately as an Admin…because that’s just how I work.

The Code

This is the fix I used. It’s a single block of PHP that handles everything: loading the Vimeo SDK, attaching the event listener, making the AJAX call, verifying enrollment, and marking the lesson complete on the server.

The code has been independently reviewed for security (for now!), WordPress best practices, and LearnDash integration by a second AI model (Claude Sonnet). That review caught a missing enrollment check and an unchecked return value on the completion function — both fixed in the version below.

Copy the code from the block below. Then follow the installation instructions for whichever method suits your setup.

Copy to Clipboard

Installation

Option 1: WPCode Plugin

This is the safest option if you don’t have a child theme or aren’t comfortable editing theme files directly. WPCode is free and available at wordpress.org/plugins/insert-headers-and-footers/.

Install and activate WPCode – Insert Headers and Footers + Custom Code Snippets
In your WordPress dashboard go to Code Snippets → Add Snippet
Choose PHP Snippet
Give it a name like “LearnDash Vimeo Fix”
Paste the entire code block above into the code editor
Remove the openingSet the Insert Location to "Run Everywhere" or "Site Wide Header"
Click Save Snippet and toggle it to Active

Option 2: Child Theme functions.php

If you’re running a child theme, you can paste the code directly into your child theme’s functions.php.

Go to Appearance → Theme File Editor in WordPress, or access the file via FTP/SFTP
Open functions.php in your child theme
Scroll to the very bottom
Paste the entire code block — including the openingSave the file

What to Know Before You Test

Always test as a non-admin enrolled student. LearnDash intentionally bypasses video progression logic for administrator accounts. If you test while logged in as an admin, the Mark Complete button will already be available and you won’t be able to tell if the fix is working. Create a test student account, enroll it in the course, and test from there.

This works with both embed methods. Whether your Vimeo video is placed in the LearnDash Video Progression field in the lesson settings, or embedded directly in the lesson content, the fix will find it.

Multiple videos on one page are supported. The code attaches the listener to every Vimeo iframe on the page, so if you have more than one video in a lesson, the lesson will complete when any of them finish. If you need “must watch all videos” behavior, the listener logic would need to be modified.

The fix is idempotent. If the video’s ended event fires more than once (it can happen), the client-side guard prevents redundant AJAX requests. Even if a duplicate request did reach the server, marking the lesson complete a second time does nothing harmful.

Failed requests don’t leave students stuck silently. If the AJAX call fails for any reason — a network hiccup, an expired nonce, a server error — the guard resets so the next video completion event can retry. Errors are logged to the browser console for debugging.

Enrollment is verified server-side. The AJAX handler checks that the student actually has access to the lesson before marking it complete. A valid nonce alone isn’t enough — the user must be enrolled in the course.

Quiz prerequisites may not be enforced. Because this fix bypasses LearnDash’s normal client-side completion flow, any server-side prerequisites attached to the Mark Complete button (such as required quizzes at the lesson level) may not be enforced depending on your LearnDash version and configuration. If your course structure uses quiz gates before lesson completion, test this thoroughly with your specific setup.

Technical Specs

Item Detail
Tested with LearnDash 5.x, WordPress 6.x
Theme compatibility Any theme (tested on Avada 7.14.2)
Vimeo SDK Official Vimeo Player JS SDK, loaded from Vimeo’s CDN
Security WordPress nonce scoped per lesson per page load; enrollment verified via sfwd_lms_has_access()
AJAX wp_ajax_ (logged-in users only — unauthenticated users cannot trigger it)
Completion function learndash_process_mark_complete() — LearnDash’s own internal function; return value checked
Iframe detection Polls every 500ms for up to 10 seconds to handle click-to-play embeds
Duplicate prevention Client-side guard prevents redundant AJAX calls on repeat ended events
Error handling Failed requests reset the guard for retry; errors logged to browser console
Dependencies None beyond LearnDash and a Vimeo iframe on the lesson page
Code review Independently reviewed by Claude Sonnet (Anthropic) for security, WP best practices, and LD integration

Need This Done for You?

If you’d rather hand this off than DIY it — or if your LearnDash setup has additional complexities like custom themes, membership integrations, or multi-step quiz flows — this is exactly the kind of work I do at CarrierSignal. I’ve been doing web development since the nineties. 

Built and tested in a real production environment. Developed in collaboration with Claude (Anthropic) through an iterative process of live debugging, page source analysis, and multiple rounds of testing against an actual enrolled student account — not a sandbox. Code independently reviewed by Claude Sonnet for security and quality.

Go to Top