Three of Three Questions Correct, Zero of Zero Points: The LearnDash ProQuiz Points Bug
A migrated quiz that grades every answer correctly but reports '0 of 0 points' looks like a frontend bug and is actually a one-column DB problem. Here's the full diagnosis, the fix, and the importer patch so it doesn't happen again.
The symptom
Migrated quizzes graded correctly. A student answered all three questions right, the answers were marked correct, the result screen confirmed `3 of 3 Questions answered correctly` — and then on the next line displayed `0 of 0 point(s)`. The grade lines were both true and contradictory. The quiz worked; the score didn't.
Where LearnDash stores quiz points (two places, not one)
There are two different point fields involved in a LearnDash quiz, and the importer was only filling one of them. Answer-level points live inside the serialized `answer_data` blob on each ProQuiz answer — for our migration these were correctly set to 1 for correct answers and 0 for incorrect. Question-level points live in the top-level `wp_learndash_pro_quiz_question.points` column on each question row. That column was left at `0` for every migrated question — all 213 of them. LearnDash's frontend result total reads the question-level value, not the answer-level value, unless 'answer-point mode' is explicitly active.
Why it wasn't visible in the source data
The Thinkific export JSON has no `points`, `score`, `grade`, `passing`, or `weight` fields anywhere — Thinkific's quiz model is implicitly one-question-equals-one-graded-item. Carrie noted this in a build-time transcript: 'I could just say this quiz requires a passing grade or not. I don't see a way to custom assign points per question.' The source didn't tell us what point value to use because the source didn't model it. The importer should have defaulted to `1` anyway — which is exactly what LearnDash's own quiz builder does when you create a question by hand.
The two-part fix
Existing migrated data: a scoped UPDATE that sets `points = 1` on every migrated question. We targeted by quiz_id rather than blanket-updating the column so we didn't disturb any hand-built quizzes the client adds later. `UPDATE wp_learndash_pro_quiz_question SET points = 1 WHERE quiz_id IN (...migrated quiz IDs)`. Backup first, dry-run-count second, apply third, browser-verify fourth. Importer patch: add `'points' => 1` to the insert array in `learndash-importer.php` so future imports don't repeat the bug.
Important nuance for multi-answer questions
It's tempting to make a question worth two points if it has two correct answers. Don't — at least not for a Thinkific source. Thinkific's data model has no concept of weighted answers; a question is graded as one item, no matter how many of its choices are correct. Mapping it as `one Thinkific question = one LearnDash point` preserves the source's grading logic. Mapping it as `one correct choice = one point` would silently inflate scores on every multi-answer question. We checked: no exported quiz had partial-credit semantics on the source side, so the simple mapping is correct.
Lando-to-staging-to-prod, with proof
We fixed it on a single Lando quiz first (Grade 6 Lesson 3) and verified the result screen changed from `0 of 0 point(s)` to `3 of 3 point(s), (100%)`. Then on staging: backed up the database, ran the same UPDATE scoped to migrated quizzes, browser-tested four quizzes across different grades, archived backups + verification reports + screenshots, cleaned up temporary scripts. Then production with the same playbook. Every step left an artifact we can re-audit later.
What we changed in our QA process
Logical verification (right answer, right grade) and visual verification (right number, right total) are different checks. Our post-launch QA now includes browser-completing at least one quiz per course before sign-off, and a SQL spot-check on the top-level `points` column for any migrated quiz. Both would have caught this pre-launch. The corollary for any importer touching ProQuiz: assume every column on `wp_learndash_pro_quiz_question` needs an explicit value — defaults are not your friend.