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
| Table | Group | Stores | Created by |
|---|---|---|---|
wp_tutor_quiz_questions | Quiz | Questions, one row each, linked to a tutor_quiz post | create_database() |
wp_tutor_quiz_question_answers | Quiz | Answer options per question, with is_correct | create_database() |
wp_tutor_quiz_attempts | Quiz | One row per attempt: marks, status, result | create_database() |
wp_tutor_quiz_attempt_answers | Quiz | One row per question answered in an attempt | create_database() |
wp_tutor_earnings | Finance | Per-sale split between instructor and admin | create_database() |
wp_tutor_withdraws | Finance | Instructor withdrawal requests | create_database() |
wp_tutor_orders | E-commerce | Native orders, totals, tax, refunds, payment status | create_database() |
wp_tutor_ordermeta | E-commerce | Key-value meta per order | create_database() |
wp_tutor_order_items | E-commerce | Courses or plans in an order, with prices | create_database() |
wp_tutor_order_itemmeta | E-commerce | Key-value meta per order item | Upgrader::upgrade_to_3_8_0() |
wp_tutor_coupons | E-commerce | Coupon definitions and limits | create_database() |
wp_tutor_coupon_applications | E-commerce | Which courses, bundles or categories a coupon applies to | create_database() |
wp_tutor_coupon_usages | E-commerce | Who used which coupon | create_database() |
wp_tutor_carts | E-commerce | One cart per user | create_database() |
wp_tutor_cart_items | E-commerce | Items in a cart | create_database() |
wp_tutor_customers | E-commerce | Billing details per customer | create_database() |
wp_tutor_scheduler | System | Scheduled actions, such as Pro gift courses | Upgrader::upgrade_to_3_8_0() |
wp_tutor_legal_consents | GDPR | Consent definitions shown on forms | GDPR::maybe_install_db() |
wp_tutor_legal_consent_logs | GDPR | Change log for those definitions | GDPR::maybe_install_db() |
wp_tutor_user_consents | GDPR | Per-user consent records | GDPR::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 callsTutor::create_database(), which builds 15CREATE TABLEstatements and passes each to WordPress'sdbDelta(). 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\Upgraderhooksadmin_init. On 3.8.0 and later,upgrade_to_3_8_0()createstutor_schedulerandtutor_order_itemmetaif they are missing, addspre_tax_pricetotutor_ordersand addsitem_typeanditem_detailstotutor_cart_items.upgrade_to_3_8_2()adds indexes totutor_earningsand normalisesprocess_byto lower case. - GDPR.
TUTOR\GDPR\GDPRrunsmaybe_install_db()oninitat priority 5. It compares thetutor_gdpr_db_schema_versionoption 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_settingsandquestion_order.tutor_quiz_question_answers: the options for each question, keyed bybelongs_question_idandbelongs_question_type, withanswer_title,is_correct,image_id,answer_two_gap_match,answer_view_format,answer_settingsandanswer_order.tutor_quiz_attempts: one row per attempt withcourse_id,quiz_id,user_id, question counts,total_marks,earned_marks, start and end times,attempt_ip, manual review fields andresult. It is indexed on course, quiz, user and result.tutor_quiz_attempt_answers: one row per answered question withquiz_attempt_id,question_id,given_answer,question_mark,achieved_mark,minus_markandis_correct.
Values worth knowing when you report on these tables:
| Column | Values (constants in Tutor\Models\QuizModel) |
|---|---|
attempt_status | attempt_started, attempt_ended, review_required, attempt_timeout |
result | pass, fail, pending |
is_correct in attempt answers | 0 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,subscriptionorrenewal),order_status(incomplete,completed,cancelled,trash,pending),payment_status(paid,unpaid,failed,refunded,partially-refunded,pending), the price columns fromsubtotal_pricetonet_payment, tax, coupon and discount fields,fees,earnings,refund_amount,payment_method,transaction_idand the rawpayment_payloads. Indexed on user, order type, payment status, order status and transaction ID.tutor_order_itemsandtutor_order_itemmeta: the line items (item_idis 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(codeorautomatic),discount_type,discount_amount,applies_to, total and per-user usage limits, purchase requirements and the GMT start and expiry dates.tutor_coupon_applicationsandtutor_coupon_usages: what a coupon applies to (reference_id) and which users have used it.tutor_cartsandtutor_cart_items: the cart per user and its items, including the 3.8.0item_typeand JSONitem_detailscolumns.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 table | Constraint | References | On delete |
|---|---|---|---|
tutor_ordermeta | fk_tutor_ordermeta_order_id | tutor_orders(id) | Cascade |
tutor_order_items | fk_tutor_order_item_order_id | tutor_orders(id) | Cascade |
tutor_order_itemmeta | fk_tutor_itemmeta | tutor_order_items(id) | Cascade |
tutor_coupon_applications | fk_tutor_coupon_application_coupon_code | tutor_coupons(coupon_code) | Cascade |
tutor_coupon_usages | fk_tutor_coupon_usage_coupon_code, fk_tutor_coupon_usage_user_id | tutor_coupons(coupon_code), wp_users(ID) | Cascade |
tutor_carts | fk_tutor_cart_user_id | wp_users(ID) | Cascade |
tutor_cart_items | fk_tutor_cart_item_cart_id, fk_tutor_cart_item_course_id | tutor_carts(id), wp_posts(ID) | Cascade |
Practical consequences:
- Import order matters. Restore
wp_users,wp_posts,tutor_orders,tutor_order_items,tutor_couponsandtutor_cartsbefore 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_usersorwp_postsis 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 withconsent_title,display_on,consent_message, a JSONconsent_map,version,consent_method,is_activeand JSONsettings.tutor_legal_consent_logs:legal_consent_id,actionand JSONold_dataandnew_datafor each change.tutor_user_consents: one row per consent given, withuser_id,user_email, a snapshot of the label and links,version,consent_method,ip_address,user_agent,sourceandcreated_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-on | Tables | Created |
|---|---|---|
| Gradebook | tutor_gradebooks, tutor_gradebooks_results | By core Upgrader::install_gradebook() before the add-on is enabled |
tutor_email_queue | By core Upgrader::install_tutor_email_queue() before the add-on is enabled | |
| Subscription | tutor_subscription_plans, tutor_subscription_plan_items, tutor_subscriptions, tutor_subscriptionmeta | Before the add-on is enabled |
| H5P | tutor_h5p_quiz_result, tutor_h5p_quiz_statement, tutor_h5p_lesson_statement, tutor_h5p_statement | After the add-on is enabled |
| Content Bank | tutor_cb_content_usage | On admin_init |
| Notifications | tutor_notifications, tutor_notification_preferences | When 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:
| Data | Where it lives |
|---|---|
| Courses, topics, lessons, quizzes, assignments | Posts of type courses, topics, lesson, tutor_quiz, tutor_assignments, linked by post_parent |
| Enrolments | Posts of type tutor_enrolled: post_author is the student, post_parent the course, post_status completed, pending or cancel |
| Lesson completion | User meta _tutor_completed_lesson_id_{lesson_id}, one row per lesson |
| Course completion | Comments with comment_type course_completed and comment_agent TutorLMSPlugin |
| Reviews and Q&A | Comments of type tutor_course_rating and tutor_q_and_a |
| Course settings and quiz options | Post meta _tutor_course_settings and tutor_quiz_option |
| All plugin settings | The 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_uninstallsetting on,Tutor::erase_tutor_data()drops only six tables: the four quiz tables,tutor_earningsandtutor_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_answerand thetutor_optionoption 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
- Tutor LMS plugin on WordPress.org - WordPress.org
- Tutor LMS source repository - Themeum (GitHub)
- dbDelta() - WordPress Developer Resources
- Creating tables with plugins - WordPress.org
Learn more about this topic with AI
Gemini opens with the prompt copied - paste it into the chat to start.