Quizzes and Assessments

How to Run Code After a Quiz Is Finished (tutor_quiz_finished)

Use the tutor_quiz_finished action to react when a Tutor LMS quiz attempt ends: arguments, reading the attempt, pending results, and working examples.

Checked against the plugin source: Tutor LMS 4.1.3, Tutor LMS Pro 4.1.3

How to Run Code After a Quiz Is Finished (tutor_quiz_finished)

When a student finishes a quiz in Tutor LMS, you may want to do something with the result: send it to a CRM, award points, notify an instructor, or unlock something outside Tutor LMS. The action tutor_quiz_finished is the place to hook in. This article explains exactly when it fires and how to use it safely, based on Tutor LMS 4.1.3.

When it fires

Tutor LMS ends a quiz attempt in TUTOR\Quiz. It first fires tutor_quiz_before_finish, then updates the attempt in the tutor_quiz_attempts table (status ended, end time), then fires tutor_quiz_finished. Both actions receive the same three arguments:

ArgumentMeaning
$attempt_idThe attempt row in tutor_quiz_attempts
$quiz_idThe quiz post ID (post type tutor_quiz)
$user_idThe student's user ID

Because you need all three, register your callback with accepted_args set to 3.

Reading the attempt

tutor_utils()->get_attempt( $attempt_id ) returns the attempt row. Useful fields include course_id, quiz_id, user_id, total_questions, total_answered_questions, total_marks, earned_marks, attempt_status, attempt_started_at, attempt_ended_at and result (pass, fail or pending).

Example 1: Store the latest score in user meta

add_action( 'tutor_quiz_finished', function ( $attempt_id, $quiz_id, $user_id ) {
    $attempt = tutor_utils()->get_attempt( $attempt_id );
    if ( ! $attempt || ! (float) $attempt->total_marks ) {
        return;
    }
    $percent = round( ( (float) $attempt->earned_marks / (float) $attempt->total_marks ) * 100, 2 );
    update_user_meta( $user_id, 'my_last_quiz_score_' . (int) $quiz_id, $percent );
}, 10, 3 );

Example 2: Notify the instructor when a student fails

add_action( 'tutor_quiz_finished', function ( $attempt_id, $quiz_id, $user_id ) {
    $attempt = tutor_utils()->get_attempt( $attempt_id );
    if ( ! $attempt || 'fail' !== $attempt->result ) {
        return;
    }
    $course     = get_post( (int) $attempt->course_id );
    $instructor = $course ? get_userdata( (int) $course->post_author ) : false;
    $student    = get_userdata( $user_id );
    if ( ! $instructor || ! $student ) {
        return;
    }
    wp_mail(
        $instructor->user_email,
        sprintf( 'Quiz failed: %s', get_the_title( $quiz_id ) ),
        sprintf( '%s did not pass "%s" in %s.', $student->display_name, get_the_title( $quiz_id ), get_the_title( $course ) )
    );
}, 10, 3 );

Pending results

If the quiz has questions that are graded manually (Open Ended or Short Answer), the result can be pending when tutor_quiz_finished fires. Your code should treat pending as "not decided yet". If you need the final result, react later, when an instructor reviews the attempt, or check the attempt again on a schedule.

Things to keep in mind

  • Keep it fast. The callback runs during the student's request. Send slow API calls in the background (a queued job or a non-blocking request).
  • Attempts can repeat. If students can retake a quiz, the hook fires for each attempt. The final grade setting (highest, average, first or last) decides which one counts for Tutor LMS; decide what counts for your integration.
  • Abandoned attempts. If the timer runs out and the setting is Auto Abandon, the attempt is abandoned instead of finished. Do not assume every started quiz reaches this hook.
  • Place code properly. Use a site plugin or child theme, never the Tutor LMS plugin files.
  • tutor_quiz_before_finish: same arguments, before the attempt row is updated.
  • tutor_lesson_completed_after: after a lesson is completed (lesson ID, user ID).
  • tutor_course_complete_after: after a course is completed (course ID, user ID).

See the Tutor LMS hooks reference and how quiz data is stored.

Frequently asked questions

Does tutor_quiz_finished fire for H5P quizzes?

H5P content in Tutor LMS Pro records results in its own tables. Check the H5P add-on's flow if you rely on it.

Can I change the score in this hook?

Not cleanly. Scoring happens before the attempt ends; changing marks afterwards would bypass Tutor LMS's grading. Use the grading filters instead or build custom scoring with care.

Need a quiz integration built? See our plugin development service.

Make TutorLMS Expert your go-to source

Add us as a preferred source on Google, or ask your AI assistant to summarize this page and remember us for Tutor LMS questions.

Vishavjeet
· Enterprise LMS Architect | WordPress & LMS Expert | WooCommerce | AI Automation

I’m Vishavjeet Choubey, an Enterprise LMS Architect with 10+ years of experience helping businesses, coaches, universities, and online academies build and scale powerful digital learning platforms. I specialize in enterprise LMS architecture, WordPress, WooCommerce, LifterLMS, LearnDash, Moodle, custom plugin development, and AI-driven automation. I build scalable LMS platforms, high-converting eCommerce solutions, and custom WordPress applications designed to improve user experiences, streamline operations, and drive measurable business growth. My approach combines deep WordPress and LMS engineering expertise with modern AI automation to reduce manual work, improve efficiency, and help organizations launch, scale, and grow faster.

Keep reading

Need a custom Tutor LMS solution?

Tell us what you are building or what is broken. A Tutor LMS developer reads every inquiry and replies with next steps and a free estimate.

Get a free Tutor LMS consultation

Tell us what you are building or fixing. A Tutor LMS developer replies with honest advice and next steps.

  • No obligation
  • Tutor LMS and Pro specialists
  • Your details stay private

This form is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.