Transform Learning with a Powerful Enterprise LMS

Learn more
WordPress LMS

Tutor LMS Database Tables: Every Custom Table and What It Stores

A schema reference for Tutor LMS 4.1.3: the 20 core tables for quizzes, orders, coupons, earnings, scheduling and GDPR consent, the Pro add-on tables, foreign keys and the data kept in WordPress core tables.

Learn more about this topic with AI

Gemini opens with the prompt copied - paste it into the chat to start.

Schema diagram of the Tutor LMS custom database tables grouped into quiz, e-commerce, finance, scheduler and GDPR consent tables

Tutor LMS 4.1.3 (the free plugin) creates 20 custom database tables: 15 in its activation routine, two in its 3.8.0 upgrade step and three for the GDPR consent feature added in 4.0.0. The Tutor LMS database tables hold quiz attempts and answers, quiz questions, the native e-commerce data (orders, order items, coupons, carts, customers), instructor earnings and withdrawals, a scheduler and consent records. Courses, topics, lessons, quizzes and enrolments are not in custom tables: they are posts in wp_posts, and lesson and course completion live in user meta and comments.

Tutor LMS Pro adds up to 14 more tables when you switch on specific add-ons, so a Pro site can have 34. This reference lists every table, what it stores, which code creates it and the details that matter for reporting, backups and migrations. It stays at architecture depth; for step-by-step Tutor LMS help, see our sister site Tutor LMS Expert.

Checked against the Tutor LMS 4.1.3 and Tutor LMS Pro 4.1.3 source (classes/Tutor.php, classes/Upgrader.php, GDPR/DB/ and the Pro add-on Database classes), as of October 2026. Table names use the default wp_ prefix; your site uses $wpdb->prefix.

The 20 core Tutor LMS tables at a glance

TableGroupStoresCreated by
wp_tutor_quiz_questionsQuizQuestions, one row each, linked to a tutor_quiz postcreate_database()
wp_tutor_quiz_question_answersQuizAnswer options per question, with is_correctcreate_database()
wp_tutor_quiz_attemptsQuizOne row per attempt: marks, status, resultcreate_database()
wp_tutor_quiz_attempt_answersQuizOne row per question answered in an attemptcreate_database()
wp_tutor_earningsFinancePer-sale split between instructor and admincreate_database()
wp_tutor_withdrawsFinanceInstructor withdrawal requestscreate_database()
wp_tutor_ordersE-commerceNative orders, totals, tax, refunds, payment statuscreate_database()
wp_tutor_ordermetaE-commerceKey-value meta per ordercreate_database()
wp_tutor_order_itemsE-commerceCourses or plans in an order, with pricescreate_database()
wp_tutor_order_itemmetaE-commerceKey-value meta per order itemUpgrader::upgrade_to_3_8_0()
wp_tutor_couponsE-commerceCoupon definitions and limitscreate_database()
wp_tutor_coupon_applicationsE-commerceWhich courses, bundles or categories a coupon applies tocreate_database()
wp_tutor_coupon_usagesE-commerceWho used which couponcreate_database()
wp_tutor_cartsE-commerceOne cart per usercreate_database()
wp_tutor_cart_itemsE-commerceItems in a cartcreate_database()
wp_tutor_customersE-commerceBilling details per customercreate_database()
wp_tutor_schedulerSystemScheduled actions, such as Pro gift coursesUpgrader::upgrade_to_3_8_0()
wp_tutor_legal_consentsGDPRConsent definitions shown on formsGDPR::maybe_install_db()
wp_tutor_legal_consent_logsGDPRChange log for those definitionsGDPR::maybe_install_db()
wp_tutor_user_consentsGDPRPer-user consent recordsGDPR::maybe_install_db()

How Tutor LMS creates and upgrades its tables

Three separate routines create the core tables, and they run at different times. That matters when a table is missing after a restore or a partial migration.

  • Activation. TUTOR\Tutor::tutor_activate() is the plugin's activation hook. It calls Tutor::create_database(), which builds 15 CREATE TABLE statements and passes each to WordPress's dbDelta(). It does not run on a normal plugin update, only on activation, so deactivating and reactivating Tutor LMS is the simplest way to recreate a missing core table.
  • Upgrades. TUTOR\Upgrader hooks admin_init. On 3.8.0 and later, upgrade_to_3_8_0() creates tutor_scheduler and tutor_order_itemmeta if they are missing, adds pre_tax_price to tutor_orders and adds item_type and item_details to tutor_cart_items. upgrade_to_3_8_2() adds indexes to tutor_earnings and normalises process_by to lower case.
  • GDPR. TUTOR\GDPR\GDPR runs maybe_install_db() on init at priority 5. It compares the tutor_gdpr_db_schema_version option with its schema version (1.1.0) and creates the three consent tables when they differ.

Table names are registered as $wpdb properties in the Tutor constructor, for example $wpdb->tutor_quiz_attempts and $wpdb->tutor_orders. Use those properties in your own queries instead of hard-coding the prefix. The constructor also registers aliases for Pro's four subscription tables, even though the free plugin never creates them.

Quiz tables: questions, answers and attempts

Quizzes are tutor_quiz posts, but their questions are not posts. They live in four tables.

  • tutor_quiz_questions: question_id, quiz_id (the quiz post ID), question_title, question_description, answer_explanation, question_type, question_mark, question_settings and question_order.
  • tutor_quiz_question_answers: the options for each question, keyed by belongs_question_id and belongs_question_type, with answer_title, is_correct, image_id, answer_two_gap_match, answer_view_format, answer_settings and answer_order.
  • tutor_quiz_attempts: one row per attempt with course_id, quiz_id, user_id, question counts, total_marks, earned_marks, start and end times, attempt_ip, manual review fields and result. It is indexed on course, quiz, user and result.
  • tutor_quiz_attempt_answers: one row per answered question with quiz_attempt_id, question_id, given_answer, question_mark, achieved_mark, minus_mark and is_correct.

Values worth knowing when you report on these tables:

ColumnValues (constants in Tutor\Models\QuizModel)
attempt_statusattempt_started, attempt_ended, review_required, attempt_timeout
resultpass, fail, pending
is_correct in attempt answers0 incorrect, 1 correct, 3 manually graded (added in 4.1.0)

Two columns hold PHP-serialised data. attempt_info stores a snapshot of the quiz options at the time of the attempt, and given_answer is serialised for multi-answer question types such as multiple choice, ordering, matching and image answering. Simple SQL can count and average marks, but answer-level analysis needs PHP (maybe_unserialize()) or Tutor's own helpers. Note also that is_correct in tutor_quiz_attempt_answers is not binary since 4.1.0, so a report that sums it as a correct-answer count will over-count manually graded rows.

A read-only example that counts passed attempts per quiz for one course, using the registered table alias and a prepared query (not run on a live site; test on staging first):

function aio_tutor_passed_attempts_by_quiz( $course_id ) {
    global $wpdb;
    return $wpdb->get_results(
        $wpdb->prepare(
            "SELECT quiz_id, COUNT(*) AS passed
             FROM {$wpdb->tutor_quiz_attempts}
             WHERE course_id = %d AND result = %s
             GROUP BY quiz_id",
            absint( $course_id ),
            'pass'
        )
    );
}

E-commerce tables: orders, coupons, carts and customers

Since 3.0.0, Tutor LMS has a native checkout, and its data sits in nine tables. Sites that sell through WooCommerce keep their orders in WooCommerce instead; these tables then stay mostly empty apart from earnings.

  • tutor_orders: user_id, order_type (single_order, subscription or renewal), order_status (incomplete, completed, cancelled, trash, pending), payment_status (paid, unpaid, failed, refunded, partially-refunded, pending), the price columns from subtotal_price to net_payment, tax, coupon and discount fields, fees, earnings, refund_amount, payment_method, transaction_id and the raw payment_payloads. Indexed on user, order type, payment status, order status and transaction ID.
  • tutor_order_items and tutor_order_itemmeta: the line items (item_id is a course or plan ID) with regular, sale and discount prices, and their meta.
  • tutor_ordermeta: key-value meta for each order.
  • tutor_coupons: coupon_code (unique), coupon_type (code or automatic), discount_type, discount_amount, applies_to, total and per-user usage limits, purchase requirements and the GMT start and expiry dates.
  • tutor_coupon_applications and tutor_coupon_usages: what a coupon applies to (reference_id) and which users have used it.
  • tutor_carts and tutor_cart_items: the cart per user and its items, including the 3.8.0 item_type and JSON item_details columns.
  • tutor_customers: billing name, email, phone, address, country, state and city per user.

Foreign keys: the detail that breaks partial restores

Unlike most WordPress plugins, Tutor LMS declares real MySQL foreign keys on its e-commerce tables. In 4.1.3 they are:

Child tableConstraintReferencesOn delete
tutor_ordermetafk_tutor_ordermeta_order_idtutor_orders(id)Cascade
tutor_order_itemsfk_tutor_order_item_order_idtutor_orders(id)Cascade
tutor_order_itemmetafk_tutor_itemmetatutor_order_items(id)Cascade
tutor_coupon_applicationsfk_tutor_coupon_application_coupon_codetutor_coupons(coupon_code)Cascade
tutor_coupon_usagesfk_tutor_coupon_usage_coupon_code, fk_tutor_coupon_usage_user_idtutor_coupons(coupon_code), wp_users(ID)Cascade
tutor_cartsfk_tutor_cart_user_idwp_users(ID)Cascade
tutor_cart_itemsfk_tutor_cart_item_cart_id, fk_tutor_cart_item_course_idtutor_carts(id), wp_posts(ID)Cascade

Practical consequences:

  • Import order matters. Restore wp_users, wp_posts, tutor_orders, tutor_order_items, tutor_coupons and tutor_carts before their child tables, or disable foreign key checks for the import session.
  • Deletes cascade at database level. Deleting a user row with SQL removes that user's carts and coupon usage rows without any WordPress hook firing. Deleting a course post the same way removes it from carts.
  • Storage engine. MySQL foreign keys need InnoDB on both the parent and child table. If wp_users or wp_posts is still on MyISAM, as on some very old sites, convert it before activating Tutor LMS or importing these tables.

Finance, scheduler and GDPR tables

tutor_earnings records the revenue split for each sale: user_id (the instructor), course_id, order_id, order_status, the course price totals, instructor_amount and instructor_rate, admin_amount and admin_rate, commission_type, fee deductions and process_by. The class constants for process_by are tutor and woocommerce, so the column tells you which checkout created the row. tutor_withdraws holds instructor withdrawal requests: amount, method_data, status (new requests start as pending) and timestamps.

tutor_scheduler is a generic queue: type, reference_id, scheduled_at_gmt, status (default processing), a payload and the users who scheduled it and are its target. In 4.1.3 its main user is the Pro gift-course feature, which writes rows with type set to gift_course.

The three GDPR tables were added in 4.0.0:

  • tutor_legal_consents: consent definitions with consent_title, display_on, consent_message, a JSON consent_map, version, consent_method, is_active and JSON settings.
  • tutor_legal_consent_logs: legal_consent_id, action and JSON old_data and new_data for each change.
  • tutor_user_consents: one row per consent given, with user_id, user_email, a snapshot of the label and links, version, consent_method, ip_address, user_agent, source and created_at_gmt.

The consent tables use the JSON column type, so the database server must support it. Treat tutor_user_consents as evidence: keep it in backups and do not truncate it during cleanups.

Tables added by Tutor LMS Pro add-ons

Pro tables are created when the matching add-on is switched on, not when Pro is activated. Several are created through hooks named tutor_addon_before_enable_{add-on path} or tutor_addon_after_enable_{add-on path}.

Add-onTablesCreated
Gradebooktutor_gradebooks, tutor_gradebooks_resultsBy core Upgrader::install_gradebook() before the add-on is enabled
Emailtutor_email_queueBy core Upgrader::install_tutor_email_queue() before the add-on is enabled
Subscriptiontutor_subscription_plans, tutor_subscription_plan_items, tutor_subscriptions, tutor_subscriptionmetaBefore the add-on is enabled
H5Ptutor_h5p_quiz_result, tutor_h5p_quiz_statement, tutor_h5p_lesson_statement, tutor_h5p_statementAfter the add-on is enabled
Content Banktutor_cb_content_usageOn admin_init
Notificationstutor_notifications, tutor_notification_preferencesWhen the add-on loads, and on admin_init

The add-on on/off state is stored in the tutor_addons_config option. If you enable add-ons by copying that option from another site instead of using the Add-ons screen, the enable hooks never fire and the tables they create are missing. Toggle the add-on off and on again to create them.

What is not in a custom table

Most "where is X stored" questions about Tutor LMS have an answer outside the tables above:

DataWhere it lives
Courses, topics, lessons, quizzes, assignmentsPosts of type courses, topics, lesson, tutor_quiz, tutor_assignments, linked by post_parent
EnrolmentsPosts of type tutor_enrolled: post_author is the student, post_parent the course, post_status completed, pending or cancel
Lesson completionUser meta _tutor_completed_lesson_id_{lesson_id}, one row per lesson
Course completionComments with comment_type course_completed and comment_agent TutorLMSPlugin
Reviews and Q&AComments of type tutor_course_rating and tutor_q_and_a
Course settings and quiz optionsPost meta _tutor_course_settings and tutor_quiz_option
All plugin settingsThe tutor_option option

This split is why a "completion report" in Tutor LMS joins wp_posts, wp_usermeta and wp_comments rather than reading one table, and why a backup of only the tutor_* tables is not a usable backup. Our Tutor LMS architecture overview covers the post hierarchy in more depth, and the LifterLMS database tables reference is a useful contrast if you are comparing data models.

Backups, migrations and uninstall

  • Back up the whole database. Course structure and progress sit in core WordPress tables, so a Tutor-only export misses most of the learning data.
  • Uninstall is partial. With the delete_on_uninstall setting on, Tutor::erase_tutor_data() drops only six tables: the four quiz tables, tutor_earnings and tutor_withdraws. The e-commerce, scheduler, GDPR and Pro tables stay behind and need manual cleanup if you are removing Tutor LMS for good.
  • Serialised data and domain changes. attempt_info, given_answer and the tutor_option option contain serialised PHP, so use a serialisation-aware search-replace when you change domains.
  • Moving platforms. If you are migrating to or from Tutor LMS, map the custom tables and the post, meta and comment data together. Our LearnDash to Tutor LMS migration guide shows how that mapping works in practice.

How we can help

If you need custom reports on Tutor quiz or order data, a clean migration, or an add-on that adds its own tables the right way, our LMS plugin development team works with the Tutor LMS data model every week. For the wider picture of building on WordPress LMS plugins, start with the WordPress LMS development guide.

Frequently asked questions

How many database tables does Tutor LMS create?

Twenty in Tutor LMS 4.1.3: 15 from Tutor::create_database() on activation, tutor_scheduler and tutor_order_itemmeta from the 3.8.0 upgrade step, and three GDPR consent tables. Tutor LMS Pro can add up to 14 more, depending on which add-ons are enabled.

Where does Tutor LMS store course enrolments?

As posts of type tutor_enrolled in wp_posts. The post_author is the student, post_parent is the course and post_status is completed, pending or cancel. There is no enrolment table.

Where are Tutor LMS quiz results stored?

In tutor_quiz_attempts (one row per attempt, with marks and a result of pass, fail or pending) and tutor_quiz_attempt_answers (one row per answered question). Questions and their options are in tutor_quiz_questions and tutor_quiz_question_answers.

How do I recreate a missing Tutor LMS table?

For the 15 activation tables, deactivate and reactivate Tutor LMS, which re-runs create_database() through dbDelta(). Pro add-on tables are created when the add-on is enabled, so switch the add-on off and on in the Add-ons screen. Take a backup first.

Free 30-minute consultation

Not sure where to start? Talk to an LMS engineer.

Tell us what platform you run and what you need. You’ll get honest, practical advice - even if the answer is that you don’t need us.

Sources & references

  1. Tutor LMS plugin on WordPress.org - WordPress.org
  2. Tutor LMS source repository - Themeum (GitHub)
  3. dbDelta() - WordPress Developer Resources
  4. Creating tables with plugins - WordPress.org
Add as a preferred source on Google

Add All-in-One LMS as a preferred source to see more of our articles in Google’s Top Stories.

Learn more about this topic with AI

Gemini opens with the prompt copied - paste it into the chat to start.

Keep reading

Before you go - get a free LMS audit

Tell us which LMS you use and what is not working. An engineer replies within one business day.

Choose which optional cookies we may use. You can change this at any time from "Cookie preferences" in the footer. Cookie policy · Privacy policy