Wbcom Designs Learnomy Docs
← Back to product Buy Now

Getting Started

This section covers everything you need to go from a blank WordPress install to a working Learnomy academy.

Introduction to Learnomy

Learnomy is a WordPress LMS plugin that lets you build and sell online courses directly on your own site, without handing control to a third-party platform. It is designed for creators, educators, and businesses who want a professional academy they own completely.

Overview

Learnomy lives entirely inside WordPress. Courses, lessons, quizzes, students, and payments are all stored in your own database. No subscription to an external platform is required. The plugin registers top-level admin menus and a set of front-end pages under your site's URL. A companion mobile/web app connects through a built-in REST API.

Who it is for

Learnomy works for anyone who wants to teach online through their WordPress site:

  • Independent instructors who sell courses and want full control over pricing, branding, and student data.
  • Training teams that need to host internal courses for employees or customers.
  • Membership sites that grant subscribers access to a library of courses.
  • Multi-instructor academies where revenue is split between the platform and individual instructors.
  • B2B / corporate training (with Pro Spaces) that sell seat packs to companies and let a company admin manage their own roster.

The plugin ships two roles out of the box: lrn_student (assigned automatically on registration) and lrn_instructor (admin-approved). Site administrators have full access.

What Free covers

The Free plugin is a fully working LMS. You can create unlimited courses with sections, lessons, and topics. Students enroll, track their progress, and receive signed completion certificates (drag-drop designer included). Quizzes support six question types (true/false, single choice, multi-select with partial credit, short answer, essay, fill-in-the-blank), five exam layouts, question banks, and configurable proctoring. Instructors submit applications and manage their own courses. Payments work through Stripe, PayPal, WooCommerce-as-gateway, or the Test gateway - you set a price on any course and checkout is handled on your site. Memberships, coupons, commissions (with request-based withdrawals), reviews, notifications, moderation/block, GDPR export/erase, PWA (beta), migration from other LMS plugins, push (Expo + optional Firebase), 12 blocks, 10 shortcodes, 6 widgets, the Abilities API (54 actions), and the full REST API are included.

For dedicated Pro features, see Enabling Features and Modules.

What Pro adds

Pro feature. Available in Learnomy Pro.

Learnomy Pro is a companion plugin that installs alongside Free and extends it. It does not replace any Free functionality. A single Pro license unlocks 23 extensions (22 stable; LTI is the one beta). Grouped by job:

B2B / teams

  • Spaces - sell seat packs to companies, enforce seat limits, owner dashboard, sub-groups with scoped managers, bulk invite/CSV, JSON + CSV reports. This is the corporate seat product (not a community forum).

Content standards

  • SCORM, xAPI, QTI, H5P, LTI (beta) - import/play packages, emit learning records, import assessments, embed H5P, act as an LTI 1.3 tool provider. SCORM lessons can use Free's Auto: SCORM / standards package completion gate so Mark Complete stays locked until the package reports complete.

Assessment+

  • Advanced Quizzes - matching, ordering, numeric, math, and calculated questions, plus pools and rubrics.
  • Assignments - text, file upload, and reference links, with instructor grading and assignment reporting in analytics.
  • Gradebook - weighted per-course grade from quizzes, assignments, and SCORM; manual override with reason; learner /account/grades/; CSV export.

Curriculum ops

  • Content Drip - release lessons by fixed date, days after enrollment, or prior lesson.
  • Learning Paths - ordered multi-course sequences with gating and path certificates.
  • Cohorts - time-boxed group enrollments with lifecycle emails and instructor reports.

Money and reporting

  • Membership Pro - proration, pause/resume, dunning, gift codes, MRR/churn/LTV analytics.
  • Stripe Connect - automatic instructor payouts after KYC.
  • Analytics - revenue, engagement, assignment stats, and exports.

Author / brand

  • Frontend Builder - Editor.js course authoring on the front end (stable).
  • AI Authoring - draft curricula and questions (off by default; metered).
  • White Label - rebrand admin, emails, and surfaces.
  • Certificates Pro - Open Badges 3.0, Credly push, tamper-evident hash registry. (The certificate designer and library ship in Free.)

Engage / glue

  • Notes and Bookmarks, User Journey (skills, milestones, learner timeline - not drip), Webhooks Pro (34 events), WP Fusion.

Each Pro module appears on the Modules page with a PRO badge. Activating a Pro module requires Learnomy Pro to be installed and the module toggled on.

Installation and Activation

This page covers installing and activating the Learnomy Free plugin and, optionally, the Learnomy Pro add-on.

Overview

Learnomy is a standard WordPress plugin. You install it the same way as any other plugin -- either through the WordPress admin or by uploading the zip file directly. Once activated, Learnomy creates its database tables and launches the setup wizard automatically.

Requirements:

  • PHP 8.1 or higher
  • WordPress 6.7 or higher
  • MySQL 5.7+ or MariaDB 10.3+ (required for FULLTEXT search)

Installing Learnomy Free

  1. Download the learnomy.zip file from wbcomdesigns.com.

  2. In your WordPress admin, go to Plugins > Add New Plugin.

  3. Click Upload Plugin at the top of the page.

  4. Choose the learnomy.zip file you downloaded, then click Install Now.

  5. After the upload finishes, click Activate Plugin.

    WordPress activates Learnomy, creates the database tables (41 custom tables under the lrn_ prefix), and redirects you to the setup wizard.

    If the setup wizard does not appear automatically, go to LMS Settings > Settings and click Open setup wizard in the General section.

Installing Learnomy Pro

Pro feature. Available in Learnomy Pro.

Learnomy Pro is a separate plugin that extends Free. Both must be active at the same time.

  1. Download learnomy-pro.zip from your Wbcom Designs account.

  2. In your WordPress admin, go to Plugins > Add New Plugin > Upload Plugin.

  3. Upload learnomy-pro.zip and click Activate Plugin.

  4. After activation, go to LMS Settings > Settings > License and enter your Pro license key to enable automatic updates.

  5. Open LMS Settings > Modules to see the full module list, including modules with a PRO badge. Toggle any Pro module on to enable it.

Pro never replaces Free's pages or database tables. It adds to them.

Verifying the installation

After activating, you should see three new top-level items in the WordPress admin sidebar:

  • Learnomy -- courses, students, instructors, and content management.
  • LMS Commerce -- memberships, subscriptions, coupons, and earnings. This menu only appears when at least one commerce module is enabled.
  • LMS Settings -- settings, modules, and system tools.

If you see all three menus (or at least Learnomy and LMS Settings), the plugin is active and working.

Updating Learnomy

Learnomy uses the Easy Digital Downloads (EDD) update system. Updates appear in Plugins > Installed Plugins as a standard WordPress notification. No manual download is needed. Click Update now to apply the update. Database migrations run automatically on the next page load after an update.

Uninstalling

Deactivating Learnomy from Plugins > Installed Plugins stops all plugin functionality but leaves your data intact. To remove all data as well, go to LMS Settings > Settings > Advanced > Data Management and enable the Remove all plugin data when the plugin is deleted option before deleting the plugin.

Tips

  • Run the site on PHP 8.1 or higher. The plugin uses strict types throughout and will not load on older PHP.
  • If you are migrating from another LMS, install Learnomy on a staging site first and run the setup wizard before migrating production data.
  • The setup wizard takes about five minutes. You can skip it and return to it later at any time from LMS Settings > Settings > General > Open setup wizard.

Setup Wizard

The setup wizard runs automatically after you first activate Learnomy. It walks you through five steps in about five minutes and leaves you with a working academy.

Overview

The wizard is a standalone full-screen page -- it does not render the WordPress admin chrome. It saves settings through the REST API as you move between steps, so nothing is lost if you navigate away and come back. You can skip the wizard entirely on Step 1 and run it again later from LMS Settings > Settings > General > Open setup wizard.

Step 1: Welcome

The first step shows what the wizard covers: currency and catalog URL, optional Stripe connection, and an optional demo academy. Click Let's begin to start, or click Skip setup to go directly to the admin dashboard. If you skip, all defaults apply (USD currency, /courses/ catalog URL, Courses + quizzes preset, no payment keys, no demo data).

Step 2: Configure

This step sets two academy-wide defaults, then lets you pick a starting preset.

  1. Course catalog slug -- the URL path where your course catalog will live. The default is courses, giving a URL like https://yoursite.com/courses/. Type a different slug if you want a different URL. Only lowercase letters, numbers, and hyphens are accepted.

    The live preview below the field updates as you type so you can see the full URL before committing.

  2. Currency -- the billing currency for paid courses and memberships. Choose from 13 supported currencies (USD, EUR, GBP, CAD, AUD, INR, BRL, MXN, JPY, SGD, ZAR, NGN, IDR). You can change this later in LMS Settings > Settings > General.

  3. What kind of academy? -- pick the preset that best matches your use case. Learnomy enables only the relevant modules so the admin sidebar stays focused. You can adjust individual modules later on the Modules page.

    Preset What gets enabled
    Simple courses Courses, lessons, and search. No quizzes or payments.
    Courses + quizzes Quizzes, certificates, reviews, notifications, search, SEO. Free academy. Selected by default.
    Train my own team Staff and internal training with nothing put up for sale. Includes quizzes, certificates, and notifications.
    Paid courses Everything in Courses + quizzes, plus payments, coupons, and revenue split for instructors.
    Membership site Recurring subscription access. Includes payments, membership plans, and coupons.
    School or team seats Sell seat bundles to schools and teams. Same free modules as Courses + quizzes; the seat selling itself is powered by Spaces in Learnomy Pro.
    Custom Opens with the default module set. You configure everything from the Modules page.

    Click Continue to move to Step 3.

Step 3: Payment (optional)

This step configures how checkout works. You have three choices:

  • Sandbox demo mode (Recommended) -- fake transactions with no real money. No API keys needed. Useful for building and testing your academy before going live.
  • Connect Stripe -- enter your Stripe test publishable key (pk_test_...) and secret key (sk_test_...) to run real test transactions in Stripe's test environment.
  • Skip for now -- set up payments later from LMS Settings > Settings > Payments.

When you select Connect Stripe, two input fields appear for the publishable key and the secret key. Both must start with pk_test_ and sk_test_ respectively; the wizard validates the format before saving.

Click Continue to move to Step 4.

Step 4: Demo academy (optional)

This step seeds your site with ready-made content so the dashboard has real numbers on day one rather than showing empty states everywhere.

If you choose Load demo academy (recommended), Learnomy creates:

  • 2 sample courses (React Fundamentals at $49 and Mindful Productivity as a Membership course)
  • Sample sections, lessons, and quiz questions in each course
  • 5 demo students enrolled in the courses
  • Sample transactions recorded against those enrollments
  • 1 issued completion certificate
  • 1 active membership plan

The seeding takes about five seconds. Progress items tick off as each piece is created.

If you choose Start empty, the wizard completes with no demo content.

If demo content already exists from a previous wizard run, the choice tiles are replaced by a notice: "Demo content already loaded. Use Settings > Reset demo data to refresh." You can click the Reset demo data button at the bottom of the success screen (Step 5) to replace the current demo with a fresh set.

Click Finish setup to complete the wizard.

Step 5: Success

The wizard lands on the success screen showing four stats: Courses, Students, Revenue, and Certificates. If you loaded demo data these reflect actual counts from the seeded content. If you started empty they all read 0.

From here you can:

  • Click Open Dashboard to go straight to the Learnomy admin dashboard.
  • Click View course catalog to open your front-end catalog in a new tab.
  • Click the Reset demo data text link at the bottom to replace demo content with a fresh set.

The dashboard picks up where the wizard leaves off with a Getting Started checklist. If you loaded the demo academy, expect Create Your First Course to still be pending there - demo courses deliberately do not complete it. See Onboarding checklist.

Resetting demo data

The Reset demo data button is available on Step 5 of the wizard and in LMS Settings > Settings > General. Clicking it deletes the existing demo courses, students, and transactions and creates a fresh set in one operation. A confirmation prompt appears before anything is deleted.

This is useful when you want to show a client or team member a clean demo state, or after experimenting with the demo content.

Tips

  • All wizard settings can be changed after the fact in LMS Settings > Settings.
  • The preset you pick in Step 2 is a starting point. You can add or remove individual modules at any time from LMS Settings > Modules without re-running the wizard.
  • The wizard can be re-run any time by visiting LMS Settings > Settings > General and clicking Open setup wizard, or by navigating directly to wp-admin/admin.php?page=learnomy-setup.
  • Adding &rerun=1 to the wizard URL (for example, admin.php?page=learnomy-setup&rerun=1) starts it in re-run mode, which skips the "Demo content already loaded" lock on Step 4.

Admin Tour

After activating Learnomy, three top-level menus appear in the WordPress admin sidebar. Each menu has a single job: running the academy, managing money, or configuring the system. This page explains what lives in each one.

Overview

The three menus are kept together as a contiguous block in the sidebar, positioned at menu slots 30, 30.1, and 30.2. They stay grouped even when other plugins shift the menu order. Menu labels and icons are filterable, so the white-label module can rename them without breaking any URL.

Learnomy -- run the academy

The top-level Learnomy menu holds everything you need to build and manage courses, handle students, and track content.

Learnomy admin dashboard overview

The submenu items are:

Page What it does
Dashboard Academy-wide stats, trend charts, recent activity, and top courses. Landing page after login.
Courses List of all courses. Create, edit, delete, and set pricing.
Lessons All lessons across every course. Filter by course to find a specific lesson quickly.
Quizzes Quiz list and question bank. Only visible when the Quizzes module is enabled.
Question Bank Reusable question library organized by category. Visible when Quizzes is enabled.
Grading Inbox of submitted quiz attempts that contain essay or manually graded responses. Shows a count badge when there are pending items to grade. Visible when Quizzes is enabled.
Students Student list with enrollment counts and last-active dates. Drillable to a per-student detail page.
Instructors Instructor accounts and the application queue, as two tabs on one screen. Admin-only. Hidden entirely in solo academy mode, where the owner is the only instructor.
Certificates Issued certificates with verify links. Only visible when the Certificates module is enabled.
Categories Course category tree (hierarchical). Also used for Learning Path categories in Pro.
Reviews Course review moderation. Always visible so pending reviews can be cleared even if the public review form is disabled.
Reported Content The moderation queue for reviews, comments, and courses that members have flagged. Shows a count badge when there is an open backlog.
Announcements Site-wide or course-specific announcements sent to students. Visible when the Notifications module is enabled.
Analytics Academy-wide reporting, enrollment funnels, and revenue charts. Pro only.
Gradebook Weighted per-course grades built from quizzes, assignments, and SCORM packages, with a per-student grid and CSV export. Pro only, under the Reports section.
Pro Tools The launcher for every Pro extension page: Assignments, Cohorts, Content Drip, Learning Paths, User Journeys, Milestones, Skills, LTI Platforms, Import QTI, and SCORM Tracking each live one card away. These pages do not have their own sidebar rows, which keeps the menu short; their direct URLs still work and can be bookmarked. Only visible when Learnomy Pro is active.

The menu is grouped under small section labels so related pages sit together. Keeping the row count down is deliberate: WordPress flyout menus do not scroll, so anything past the bottom of the screen would be unreachable. That is why the Pro extension pages collapse into a single Pro Tools row instead of taking a row each.

Section Pages
(top) Dashboard
Content Courses, Lessons, Quizzes, Question Bank, Categories
People Students, Instructors
Engagement Certificates, Reviews, Reported Content, Announcements
Reports Grading, Analytics, Gradebook
Extensions Pro Tools

The Grading submenu shows a number badge (the yellow circle) when there are attempts waiting for manual review. It disappears once all pending submissions are graded. Reported Content shows the same style of badge when the moderation queue has an open backlog.

Some pages are deliberately kept out of the sidebar and are reached from the screen that owns them: the course builder and the course, lesson, quiz and question editors, the certificate designer, the setup wizard, the per-student and per-instructor detail pages, Applications (now the second tab on Instructors), and Grade Responses (reached from Grading). They all still have working, bookmarkable URLs.

LMS Commerce -- money

The LMS Commerce menu only appears when at least one commerce module is enabled (Membership, Commissions, or Coupons), or when Learnomy Pro forces it on via a filter. If none of those modules are active and Pro is not installed, this menu is hidden.

This menu is grouped into four sections:

Section Page What it does
(top) Overview Revenue summary: total earnings, active subscriptions, and recent transactions at a glance.
Sell Memberships Membership plan list. Create and configure recurring billing plans.
Sell Subscriptions Active and expired subscriber list. Drillable to a per-subscription detail page carrying that subscription's payments. See Subscriptions.
Sell Coupons Discount code management. Set fixed or percentage discounts, usage limits, and expiry dates.
Sell Space Plans Pricing plans for learning spaces - what an organization pays for seats. Pro only. The row reads "Space Plans" by default; rename "Space" to "Team" and it becomes "Team Plans". It deliberately does not follow the default label, which would make it the longer "Learning Space Plans".
Money Earnings Instructor earnings ledger. Shows per-instructor revenue splits and pending withdrawal balances.
Money Transactions Full transaction log with provider reference, amount, and status.
Money Refunds The subset of completed membership payments not yet refunded, so you do not have to hunt for them in the full log. Each row has its own refund button. Pro only.
Money Stripe Payouts Manage Stripe Connect payouts to instructors. Pro only.
Teams Learning Spaces Learning spaces and their seat usage. Pro only. This row does follow your Space label, so renaming "Space" to "Team" makes it read "Teams".
Reports Membership Stats Detailed MRR, churn, and LTV charts. Pro only.

Gift Codes (Pro, with Memberships) also lives on this menu, under Sell.

Spaces and Space Plans move menus depending on what your site does. Once at least one space plan is active - that is, once you sell seats - both sit under LMS Commerce as above. A draft plan does not move them. Before then they sit under the main Learnomy menu, so an owner running teams without selling anything never has to open a commerce menu, and the screen that creates the first plan is reachable on a site with no commerce at all. The page slugs never change, so a bookmark resolves either way.

The per-record detail and editor screens - Edit Membership Plan, and the single Subscription, Transaction, Withdrawal and Coupon pages - have no sidebar rows. You reach them by clicking a row in the list above them, and their URLs are bookmarkable.

Enabling or disabling the Membership module from the Modules page immediately shows or hides the Commerce menu on your next page load.

LMS Settings -- config and system

The LMS Settings menu holds configuration and system pages. These are site-owner pages; day-to-day course management lives in the Learnomy menu instead.

LMS Settings navigation

Like the other two menus, this one is grouped under section headers - Configure (Settings, Modules, Import), Logs (Email Log, Payment Log, Outgoing Webhooks) and Add More (Companion Plugins, What's in Pro) - because there are three different reasons to open it: configure the product, read what it did, or get more of it. Import sits under Configure rather than beside the logs; it is a one-off setup action, not a record of anything.

Page What it does
Settings The settings page. Its left-hand navigation is grouped: General (General, URLs & Pages, Labels, Layouts, Registration Fields), Commerce (Payments, Instructors, Commissions, Membership Page), Communication (Emails, Reviews), Content (Course Comments, Certificate Defaults, Quiz Settings, Learning Standards), Advanced (API & Performance, Data Management), and License once Pro is active. A tab only appears when the module behind it is enabled, and Pro extensions register their own tabs into these same groups rather than adding groups of their own.
Modules Toggle features on and off. Required modules are locked; all others are optional.
Import Bring courses in from another LMS or from a previous export.
Email Log History of every notification email sent by Learnomy, with delivery status. Under Logs, alongside Payment Log and Outgoing Webhooks. Visible when the Notifications module is enabled.
Payment Log Log of every webhook Learnomy receives from Stripe or PayPal, with outcome and a replay button. Visible when the Payments module is enabled.
Outgoing Webhooks Configuration for the webhooks Learnomy sends. Point them at your own systems to be notified when LMS events fire.
Companion Plugins One-click install and activate for the plugins Learnomy works with.
What's in Pro A catalog showing all Free and Pro modules side by side with tier badges (FREE / PRO) and status labels (Stable / Beta). Useful for seeing what Pro adds before purchasing.

Payment Log and Outgoing Webhooks sit next to each other on purpose, and their names are the only thing that tells them apart: one is inbound, one is outbound. Payment Log also reads as a pair with Email Log. The delivery log for an individual outgoing webhook has no sidebar row - open it from Outgoing Webhooks.

Tips

  • Bookmarks still work. These menus have been reorganized and relabelled more than once, most recently in 1.9.4 (Payment Webhook Log became Payment Log, Membership Analytics became Membership Stats, Refundable Transactions became Refunds, Learning Space Plans became Space Plans, Applications became a tab on Instructors, and LMS Settings gained its section headers). No page slug - the ?page= part of the URL - has ever changed in any of those moves, so every bookmark you have saved still resolves to the right page. Expect the labels to keep improving; the URLs will not.
  • Your own words show up here too. Courses, Lessons, Quizzes, Categories, Students, Instructors, Spaces and Space Plans are not fixed strings - each row prints whatever you typed in LMS Settings > Labels. Rename "Course" to "Class" and the sidebar says Classes. See Custom Labels.
  • Module gating. If you visit a Commerce page URL when all commerce modules are disabled, Learnomy redirects you to the Modules page with a notice, rather than showing a blank 403 screen.
  • White-label. When the Pro white-label module is active, the word "Learnomy" in all three menu labels can be replaced. The "Commerce" and "Settings" suffixes survive the rename.

Enabling Features and Modules

The Learnomy modules screen

The Modules page lets you turn individual Learnomy features on or off. Disabled modules are completely removed from the admin sidebar, and their REST routes and hooks are not registered -- so the site only loads what you actually use.

Overview

Go to LMS Settings > Modules to reach this page. Modules are organized into nine categories, always in this order: Core, Content, Student experience, Commerce, Community, Integrations, Standards & interop, Reporting, and Operations. A category with nothing in it is not shown. Each module shows a toggle switch, and a card whose toggle is locked always says why - see When a toggle is locked below.

The "Student experience" heading is named after your Student label, so renaming Student to Learner makes it read "Learner experience".

Modules page showing module grid

The page header describes what disabling a module does: "Disabled modules are hidden from the admin menu and don't register their REST routes or hooks. You can enable more modules anytime as your site grows."

Toggling a module on or off

  1. Go to LMS Settings > Modules.

    Modules page header

  2. Find the module you want to change. Each card shows the module name, a description, and a toggle switch in the top-right corner.

    Module grid showing all categories

  3. Click the toggle switch. The card border changes immediately to confirm the new state. A toast notification at the bottom of the screen confirms the save.

  4. Reload the page. Admin sidebar items controlled by that module appear or disappear.

Changes take effect immediately -- no manual save button is needed. Toggling a module off does not delete any data; it only removes the UI and stops registering routes and hooks. Toggling it back on restores everything.

When a toggle is locked

A locked toggle is never silent. The card always prints the reason underneath, and there are three of them:

Note on the card What it means What to do
Required. Cannot be disabled. A Core module the rest of the plugin is built on. Nothing. Courses, Enrollments, Progress and Payments are always on.
Requires Learnomy Pro. Install and activate Pro to enable this module. A Pro module listed so you can see the feature exists, on a site where Pro is not active. Install and activate Learnomy Pro.
Requires the (module name) module. Turn that on first to enable this one. Dependency locking: this module extends another one, and that other one is currently off. Switch the named module on. This card's toggle unlocks on the next page load.

Dependency locking exists so a toggle never lies to you. A module that extends another does nothing at all while its parent is off, so offering a live switch there would store a choice and turn nothing on. Locking the toggle and naming the module it needs is the honest version of that state.

Your choice is remembered. Turning off a parent module does not erase what you had chosen for the modules that depend on it. Learnomy keeps the stored setting and simply stops running the feature, so switching the parent back on restores the dependent module to exactly the state you left it in.

The card tells you the name of the module you need, not a slug, so you can find it in the grid. In the shipped Free and Pro set the case you are most likely to meet is Memberships Pro, which extends Free's Membership module and stays locked until Membership is on.

Module categories and what they contain

Core

Core modules are required and cannot be disabled. Their toggles are locked.

Module What it does
Courses Course catalog, sections, lessons, and topics. The foundation everything else builds on.
Enrollments Enrollment lifecycle, sources (free, purchase, membership, manual), and access gating.
Progress Lesson, quiz, and topic completion tracking.

Core modules category

Content

Module What it does
Quizzes Quiz builder, question bank, six question types, attempts, and grading inbox. Enabling this adds Quizzes, Question Bank, and Grading to the Learnomy menu.
Search MySQL FULLTEXT search across course titles, descriptions, and lesson content.
Advanced Quizzes (Pro) Five more question types, timed questions, random pools, rubric grading, and KaTeX math in the player.
Content Drip (Pro) Schedule when lessons unlock, by days, date, or prerequisite completion.
Learning Paths (Pro) Curated, ordered course sequences with sequential gating and a path-level certificate.
Assignments (Pro) Lesson-attached assignments with text submission and inline instructor grading.
Frontend Course Builder (Pro) Instructors create and manage courses entirely from the front end, with no wp-admin access.
AI Authoring (Pro) AI-assisted course, lesson, and question drafting on top of WordPress core AI Connectors.

Student experience

Module What it does
Certificates Ed25519-signed completion certificates. Includes a Canva-like certificate designer and a public verify URL at /verify/{uuid}/.
Reviews Course star ratings and written reviews from enrolled students.
Certificates Pro (Pro) Open Badges 3.0, Credly issuing, a tamper-evident certificate hash registry, and employer verification.
Notes & Bookmarks (Pro) Searchable, tag-organized lesson notes and bookmarks, with an account hub.

Commerce

Module What it does
Payments Stripe, PayPal, WooCommerce, and Test gateway adapters. Required and always on -- a free-only academy simply never sets a paid price on a course, so its toggle stays locked.
Membership Recurring subscription plans that grant access to a library of courses. Enabling this shows the LMS Commerce menu.
Commissions and Payouts Revenue split for multi-instructor sites. Instructors can request withdrawals of their earned share.
Coupons Discount codes with per-use limits and date windows.
Memberships Pro (Pro) Plan changes with fair credit, pause and resume, payment retries, refunds, gifts, and revenue analytics. Locked until the Free Membership module is on.
Stripe Connect Payouts (Pro) Instructor payouts through Stripe Connect, with KYC pre-flight and self-onboarding.

Enabling any Commerce module (Membership, Commissions, or Coupons) makes the LMS Commerce top-level menu appear in the admin sidebar.

Community

Community modules are contributed by Learnomy Pro via the learnomy_modules_catalog filter. They carry a PRO badge on the card.

Pro feature. Available in Learnomy Pro.

Module What it does
Learning Spaces (Pro) Distributor mini-schools: assign courses, manage members and view reports scoped to each space. Not a forum - see Spaces.
Cohorts (Pro) Group learning with scheduled start and end dates, and cohort-scoped enrollment.

Integrations

Pro feature. Available in Learnomy Pro.

Module What it does
Webhooks Pro (Pro) Unlimited outgoing webhooks with automatic retry and delivery logs.

Learnomy also ships bridges that are not toggles: WP Fusion switches itself on when its companion plugin is active, and Credly badge issuing is part of the Certificates Pro module rather than a module of its own.

Standards and interop

Pro feature. Available in Learnomy Pro.

Grouped together because they tend to be one buying decision rather than five.

Module What it does
SCORM (Pro) Upload SCORM 1.2 / 2004 packages as a lesson type. Completion and score flow into normal progress.
xAPI (Pro) Emit xAPI statements to your Learning Record Store as learners enroll, complete lessons, and answer quizzes.
QTI (Pro) Import IMS QTI 2.x / 3.0 assessments as native questions and quizzes, played and graded by the built-in quiz engine.
H5P (Pro) Attach existing H5P interactive content as a lesson, bridging to the official H5P plugin.
LTI 1.3 (Pro) Let Canvas, Moodle or Blackboard launch Learnomy courses as an LTI 1.3 tool, with grade passback. Marked Beta.

Reporting

Pro feature. Available in Learnomy Pro.

Module What it does
Advanced Analytics (Pro) Revenue trends, course performance, student engagement, quiz statistics, and CSV/PDF exports.
Gradebook (Pro) A weighted per-course grade built from quiz, assignment, and SCORM scores, with instructor overrides and CSV export.
User Journey (Pro) Skill scores, a learning timeline, achievements, and personalized recommendations.

Operations

Module What it does
Notifications Email and in-app notifications for enrollment, completion, grading, and payment events. Enabling this adds Announcements to the Learnomy menu and Email Log to LMS Settings.
SEO JSON-LD Course schema markup and sitemap integration for search engines.
Abilities API Exposes 54 LMS actions across 8 categories through the WordPress Abilities API, so AI assistants and agents like ChatGPT and Claude can discover and operate your academy with full permission checks. See AI Agents and the Abilities API for the full action catalog.
PWA Service worker and web app manifest for an installable PWA experience. Its card still describes itself as Beta even though it no longer carries the Beta pill.
White Label (Pro) Rebrand the LMS with your own name, logo, and colors.

Pro module badges

When Learnomy Pro is installed, Pro module cards appear alongside Free modules in the grid. Each Pro card shows a PRO pill next to the module name. Clicking the PRO pill opens the "What's in Pro" catalog page.

When Pro is not installed, a one-time dismissible notice at the top of the page says Learnomy Pro provides additional modules (analytics, cohorts, learning paths, gradebook, standards, and more). This notice is per-user -- each admin can dismiss it independently. It does not appear again after dismissal.

Beta modules

Some modules are marked BETA with an orange pill. Beta modules are functional but may have rough edges. As of 1.9.4 the only one left is Pro's LTI extension; every Free module, the other standards (SCORM, xAPI, QTI, H5P) and the Frontend Builder now ship as stable. You can enable and use a Beta module; expect occasional updates that change behavior.

Tips

  • Disabling a module does not delete your data. If you disable Quizzes, existing quiz attempts and questions are preserved. Re-enabling the module makes them accessible again.
  • The setup wizard in Step 2 lets you pick a preset that sets a good starting module configuration. You are not locked into that choice -- use the Modules page to fine-tune anytime.
  • If you visit a Commerce page URL (for example, ?page=learnomy-memberships) when no Commerce module is enabled, Learnomy redirects you to the Modules page with a notice instead of showing a blank screen.
  • A module card that is switched off but still owes people something says so, under Switched off, but still owed. Disabling Membership does not stop eleven active subscribers from being billed, and the card keeps telling you that after the fact.

Admin Dashboard

The admin Dashboard is the landing page for Learnomy in wp-admin. It shows academy-wide stats, trend charts, and a summary of recent activity so you can see what is happening at a glance.

Overview

Go to Learnomy > Dashboard to reach this page. The dashboard is built from a registry of widgets. Each widget is an independent section that only renders when it has data to show, so a brand-new site with no students does not show rows of empty cards. Pro and third-party extensions can add their own widgets to the same dashboard through the learnomy_dashboard_widgets filter.

Admin dashboard full page

The page header shows a View Frontend button in the top-right corner. Clicking it opens your public course catalog in a new tab.

Stats row

The stats row is the first section below the header. It shows the four numbers that matter most to a site owner.

Admin dashboard stats

The four stat tiles are:

  • Students -- total number of registered student accounts.
  • Courses -- total published courses.
  • Revenue -- total completed transaction revenue in your configured currency.
  • Certificates -- total certificates issued to students.

Each tile shows the current total. These numbers reflect live counts from the database -- they update on every page load.

Onboarding checklist

A Getting Started checklist appears above the stats row, headed with a welcome greeting and the line "Finish these steps to launch your learning site". It shows your progress as both a count and a bar - for example "2 of 7 complete - 29%" - across seven steps. Completed steps fold into a collapsed "completed steps" group so the ones still needing you stay at the top.

Step Counts as done when Skippable
Complete initial setup You have been through the setup wizard No
Explore your dashboard You have visited the dashboard No
Connect a payment gateway A gateway is configured No
Customize your certificate You have edited the certificate design Yes
Create Your First Course You have created a course of your own (see below) No
Invite instructors A real instructor exists (see below) Yes
Remove Demo Content The demo data has been deleted Yes

Demo content does not count

Two steps deliberately ignore demo data:

  • Create Your First Course needs a course you built. The courses loaded by the setup wizard's demo academy do not complete it.
  • Invite instructors needs a real instructor. The demo academy ships with its own, and those do not count either.

So an owner with the full demo academy installed can see the dashboard report several courses while the checklist still asks them to create their first one. This is intentional, not a bug. Marking onboarding complete for someone who has never opened the course builder would teach them nothing about the product they are evaluating. When demo content is present both steps say so directly, naming how many demo courses you have, and Remove Demo Content is sitting in the same list as the way out.

Do not delete the demo academy just to make the checklist agree with the stats row - it is there for you to explore, and removing it is the last step, not the first.

Skipping and dismissing

Steps marked skippable above carry a Skip button while they are pending. A skipped step shows a "Skipped" pill and an Undo skip button, so nothing is permanent. The three non-skippable steps have no Skip button.

Dismiss, in the top right of the panel beside the collapse arrow, hides the whole guide and replaces it with a Reopen Getting Started Guide button, so dismissing is never final either. The collapse arrow next to it folds the panel away without dismissing it.

Once every step is complete the guide says it will auto-dismiss shortly and does so on its own. You can also turn the guide off from LMS Settings > Settings, in the Getting Started guide card.

Demo data alert

If the site is running demo content loaded by the setup wizard, a yellow alert banner appears near the top of the dashboard reminding you that the data is not real. The banner includes a link to reset or remove the demo content.

The trends section shows two charts in a two-column grid: New Enrollments and Revenue over a selectable time period.

Admin dashboard trends chart

You can switch the period between the last 7 days, 30 days, and 90 days using the selector above the charts. Both charts update together when you change the period. The charts are rendered using ApexCharts and respect the site's light/dark mode.

If there are no enrollments or transactions in the selected period, the chart renders a flat baseline rather than disappearing entirely.

Completion rates and top courses

Below the trend charts is a two-column section that shows completion rates and your top-performing courses.

Admin dashboard top courses

The Top Courses panel ranks courses by enrollment count. Each row shows the course title, enrollment total, and a completion rate percentage. Courses with zero enrollments do not appear. This panel shows up to five courses; visit Learnomy > Courses for the full list.

Recent activity

The recent activity section shows a feed of the latest events: new enrollments, completed lessons, issued certificates, new reviews, and submitted instructor applications.

Admin dashboard recent activity

Each item in the feed shows the event type, the user's name, and a timestamp. Names link to the relevant student or course detail page. The feed shows the most recent 20 events. It does not paginate -- for a full history use the Transactions or Students pages.

Additional widgets

The dashboard includes several more operational widgets that appear when they have data:

  • Recent Transactions -- the last few completed payments with amount and course name. Only appears when at least one completed transaction exists.
  • New Signups -- students who registered in the last 30 days.
  • Top Instructors -- instructors ranked by commission earnings. Only appears when commission data exists.
  • Refund Overview -- refund count and total in the last 30 days. Hidden when there are zero refunds in the period.
  • System Health -- a quick check on database version, PHP version, and whether required pages (catalog, account, membership) are set. A green row means no action needed; a red row links to the relevant settings page to fix the issue.
  • Email Log Summary -- count of notification emails sent in the last seven days, with a link to the full Email Log. Only appears when at least one email was sent in the period.
  • Pending Applications -- instructor applications waiting for admin review, with Approve/Reject buttons inline. Only appears when there are pending applications.
  • Pending Reviews -- course reviews waiting for moderation. Only appears when reviews are pending.

Pro extensions can add their own widgets to this dashboard. For example, Learnomy Pro Analytics adds an MRR (monthly recurring revenue) tile and a churn chart.

Tips

  • The dashboard renders stat counts directly from the database. There is no cache to clear -- what you see reflects the current state.
  • Widgets that have no data simply do not render. A blank-looking dashboard usually means the site is new and has no students or enrollments yet. Install the demo data from the setup wizard to populate it with example content.
  • The View Frontend button in the page header is a quick way to verify that your front-end course catalog is live and loading correctly.

Migrating from Another LMS

The Learnomy LMS importer

Learnomy includes a built-in import tool that brings your existing courses, lessons, and quizzes into Learnomy from another LMS plugin. The import runs on the same site where your old LMS is installed, so it reads your existing content directly and writes it into Learnomy using Learnomy's own data structures.

What the import tool does

The tool reads from one of three source plugins and recreates the content as native Learnomy courses:

  • LearnDash
  • Tutor LMS
  • LifterLMS

A source only appears in the wizard if its plugin is active on the site and it has at least one course to import. If you do not see a source you expected, confirm the source plugin is still active and has content.

The import copies, depending on the source, these item types:

  • Categories
  • Courses
  • Sections (Tutor LMS and LifterLMS)
  • Lessons (and sub-lessons / topics for LearnDash)
  • Quizzes
  • Questions
  • Enrollments (which learners had access)
  • Progress (which lessons learners completed)

Course and lesson categories and tags are brought across automatically together with their courses, lessons, and quizzes. You do not select them separately.

The import is one-directional. It reads from the source and writes into Learnomy. It never modifies, deletes, or writes back to the source plugin. Your existing LMS keeps working exactly as it did before.

Before you start

  1. Back up your database. The import creates a large amount of new content. A backup is your safety net, and it is faster than undoing a run manually if something is not what you expected.

  2. Keep the source plugin installed and active. The import reads live from LearnDash, Tutor LMS, or LifterLMS while it runs. Do not deactivate or delete the source plugin until the import has finished and you have reviewed the result.

  3. Test on staging first if you can. Run the import on a staging copy of the site, review the imported courses, then repeat on production once you are happy with the outcome.

  4. Know what is not migrated. Read the What is and is not migrated section below before you begin so there are no surprises. Certificates, quiz scores, and anything to do with money are intentionally left out.

Running the import wizard

The wizard lives in the WordPress admin under LMS Settings > Import (page title "Import from another LMS"). It is a four-step flow.

Step 1 - Choose a source

  1. In the WordPress admin, go to LMS Settings > Import.

  2. The wizard lists every supported source and shows which ones are available on this site. Pick LearnDash, Tutor LMS, or LifterLMS.

A source that is installed but has no courses, or is not active, is shown as unavailable and cannot be selected.

Step 2 - Choose what to import

After you pick a source, the wizard shows a checklist of the item types that source supports, each with a count of how many items exist.

There are two tiers of content here:

  • Content - categories, courses, sections, lessons, quizzes, and questions. This is the course material itself.
  • People - enrollments and progress. This is the record of which learners had access to each course and which lessons they completed.

To import course material only, select the content items and leave Enrollments and Progress unchecked. To bring your learners across as well, also check Enrollments (grants each learner access to the imported course) and Progress (marks the lessons they had completed).

Select at least one item type. The wizard imports parents before children automatically (categories, then courses, then sections, then lessons, and so on), so you do not need to worry about ordering.

When you are ready, click Preview import.

Step 3 - Review the dry run

The preview is a dry run. It counts exactly what will be imported and lists any warnings, and it writes nothing to your database.

The review screen shows three things:

  • A per-item count of everything that will be created.
  • Question-type warnings, if any. When a source question type has no exact Learnomy equivalent, the dry run tells you how it will be handled. See the question-type fallback map below.
  • A "Not migrated" list, stating plainly what the import deliberately leaves out.

Nothing has been written yet at this point. If the counts or warnings are not what you expected, click Back and adjust your selection.

Step 4 - Start the import

Click Start import to begin the live run.

The import runs in resumable batches in the background (50 items per batch) using Action Scheduler, so a large library does not time out a single page request. The wizard polls progress and shows a live percentage and per-item counts (created, updated, skipped) as each batch completes.

The import is also idempotent. Each source item is remembered once it has been imported, so if a run is interrupted and resumes, or if you run the same import again, already-imported items are skipped rather than duplicated.

When Action Scheduler is not available on the site, the whole import runs inline in one request instead and returns once it is complete.

What is and is not migrated

Migrated

  • Course structure: categories, courses, sections, lessons, sub-lessons, quizzes, and questions.
  • Course and lesson categories and tags (brought across with their content).
  • Optionally, enrollments and lesson-completion progress.
  • Completed enrollments are marked complete in Learnomy, so a learner who had finished a course keeps that standing.

Not migrated (by design)

These are stated up front in the dry run so there are no surprises:

  • Certificates are not migrated. No source provides a reliable per-course certificate flow to carry over. After importing, enable certificates per course with a Learnomy template.
  • Quiz attempt scores are not reconstructed. Learners who passed are marked complete, but per-question scores from old attempts are not carried over.
  • Payments, pricing, coupons, and orders are never touched. Imported courses come in as free. Your money stays in your existing system. The import deliberately sets every migrated course to free access so that importing content can never affect billing.

The question-type fallback map

Learnomy and the source plugins do not support exactly the same set of question types. When the import meets a source question type that has no exact Learnomy match, it downgrades it to the closest Learnomy type, or skips it when there is no sensible equivalent. The dry run lists every downgrade and skip before you commit.

The default mapping is:

Source question type Learnomy result
Image matching Matching
Image answering Short answer
Picture choice Multiple choice (single answer)
Picture reorder Ordering
Upload Essay
Code Essay
H5P Skipped (no equivalent)
Assessment answer Skipped (no equivalent)
Scale Skipped (no equivalent)

Question types that map cleanly (for example true/false, single choice, multiple choice, short answer, essay, fill in the blank) are imported as their direct Learnomy equivalent and do not appear as warnings.

A skipped question is left out of the imported quiz and reported in the run warnings, so you can see exactly which questions need to be rebuilt by hand. Developers can change the default mapping with the learnomy_migration_question_type_fallbacks filter, and a per-run override can be passed from the command line.

The WP-CLI alternative

Everything the wizard does is also available from the command line through wp learnomy migrate. This is useful for large libraries, scripted migrations, or running an import over SSH. The source slugs are learndash, tutor, and lifterlms.

Preview an import (dry run, no writes):

wp learnomy migrate preview learndash

Limit which item types are previewed:

wp learnomy migrate preview learndash --entities=course,lesson,quiz

Run an import:

wp learnomy migrate run learndash

Run a subset, or preview from the run command:

wp learnomy migrate run tutor --entities=course,section,lesson
wp learnomy migrate run tutor --dry-run

Run a large import in the background (batched and resumable through Action Scheduler), returning a run id to poll:

wp learnomy migrate run learndash --async

Check the progress of a queued run:

wp learnomy migrate status <run-id>

List recent runs:

wp learnomy migrate runs

Undo a run (delete exactly what that run created, including child rows and tag links):

wp learnomy migrate undo <run-id>

You can also pass a JSON selection and mapping file with --map=<file>, which accepts the same entity selection and question_type_fallbacks overrides the wizard uses internally.

Every import prints its run id when it finishes, so you can undo it later if needed.

Troubleshooting

A source does not appear in the wizard. The source is only listed as available when its plugin is active and it has at least one course. Confirm LearnDash, Tutor LMS, or LifterLMS is still active and has content.

An import seems stuck partway through. Background imports run in batches through Action Scheduler. If batches are not advancing, your site's scheduled tasks may not be running. Confirm Action Scheduler is processing (it is shared with WooCommerce and other plugins) or re-run the import on the command line without --async so it runs inline in one pass.

Re-running the same import. Re-running is safe. Each source item is remembered once imported, so a second run skips items that already came across rather than creating duplicates. The "skipped" counts in the progress view reflect this.

You want to start over. Use the undo action to delete everything a run created, then run the import again with a different selection. In the wizard, undo is available from the recent-runs list. On the command line, use wp learnomy migrate undo <run-id>. Undo removes the courses, lessons, quizzes, and questions that the run created, along with their child rows and tag links.

Some questions did not come across. Check the run warnings. Question types with no Learnomy equivalent are skipped and listed by name, so you know which ones to rebuild manually. See the question-type fallback map above.

Imported courses show as free. This is intentional. The import never touches pricing or payments, so every migrated course comes in as free. Set up pricing for the imported courses afterwards in Learnomy.

Roles and Capabilities

Learnomy adds two roles on top of WordPress -- Student and Instructor -- and gates every action through a single permission check. New sign-ups become students automatically; instructors are approved by an admin. WordPress administrators always have full access.

What you can do

  • Let anyone who registers through Learnomy start as a Student with the right to enroll, learn, take quizzes, and leave reviews.
  • Promote a member to Instructor by approving their application, which adds course authoring, quiz management, grading, and their own students and earnings.
  • Keep full control with the WordPress Administrator, who has every Learnomy capability plus the site-wide settings.
  • Rely on one permission gate for every action, so access is decided the same way across the frontend, the admin, and the API.

Roles and Capabilities first view

How to use it

Students -- automatic on registration

When someone registers through Learnomy they are assigned the Student (lrn_student) role immediately. They can browse the catalog, enroll, take lessons and quizzes, and review courses without any further setup.

Instructors -- admin-approved

An instructor is not self-serve. A member applies to become an instructor, which creates an application for review. An admin approves it from Learnomy > Applications, and on approval the member gains the Instructor (lrn_instructor) role in addition to their student role. From then on they can create and manage their own courses, manage quizzes, grade submissions, and see their own students and earnings.

Administrators

Any WordPress user with the Administrator role has every Learnomy capability, including the academy-wide settings, categories, certificate management, instructor approvals, and analytics. You do not assign Learnomy roles to admins -- their access is total by default.

Settings and options

The three roles and what each can do:

Role Slug How it is assigned Can
Student lrn_student Automatically, on Learnomy registration Enroll in courses, learn (view lesson content), take quizzes, leave reviews, view courses
Instructor lrn_instructor Admin approves an instructor application Everything a student can, plus: create courses, manage their own courses, manage quizzes, grade submissions, view their own students, view their own earnings, and upload media
Administrator WordPress administrator WordPress core role Everything above, plus: manage settings, manage categories, manage users, manage certificates, approve instructors, and view analytics

Notes:

  • Instructor access is scoped to the instructor's own content -- for example, the grading inbox and earnings show only their own courses' data, not the whole site's.
  • WordPress subscribers are also granted the student capabilities, so an existing subscriber can enroll and learn without a separate Learnomy account.
  • Both Learnomy roles carry WordPress's baseline read capability, so dashboard, profile, and application flows behave like any other logged-in WordPress user.

For developers

  • Roles + capabilities: registered in Learnomy\Permissions\Capabilities. Student caps: learnomy_enroll, learnomy_learn, learnomy_take_quiz, learnomy_leave_review, learnomy_view_courses. Instructor adds: learnomy_create_course, learnomy_manage_own_courses, learnomy_view_own_students, learnomy_view_own_earnings, learnomy_manage_quizzes, learnomy_grade, upload_files. Admin adds: learnomy_manage_settings, learnomy_manage_categories, learnomy_manage_users, learnomy_manage_certificates, learnomy_approve_instructor, learnomy_view_analytics.
  • The single gate: every access decision goes through Learnomy\Permissions\Permission_Engine::can( $user_id, $action, $context_id ). It short-circuits to allow any user with manage_options (administrators), otherwise maps the action to the matching WordPress capability. REST controllers call it in their permission_callback; nothing checks capabilities directly.
  • Role maintenance: Capabilities::register() is idempotent and self-heals -- it re-applies any capability missing from an existing role on upgrade, so a role created on an older version still gains new caps.

Courses and Content

Everything related to building, organizing, and delivering courses in Learnomy.

Course Catalog

The Learnomy course catalog

The course catalog is the public storefront for your academy. Students browse, filter, and enroll from here. It lives at /courses/ and is created automatically when Learnomy is activated.

Overview

The catalog page shows all published, non-private courses. Visitors can search, narrow the list by category and -- in the filter sidebar layout -- by price, level, rating, duration, instructor and tags, sort the results, and click into any course to see its detail page. Every choice is written into the page URL, so any filtered view can be linked to or bookmarked.

Go to Learnomy → Courses to manage what appears here.

The catalog page

Course catalog

The header displays the catalog title, a search input, and the total number of published courses.

Catalog header

Two catalog layouts

The catalog has two layouts, and the one you pick decides how much filtering students get. Choose it at LMS Settings → Settings → Layouts, in the row for the course catalog:

Layout What students get
Grid (default) A filter bar above the course grid: search, category dropdown, sort.
Filter sidebar A filter rail down the left side: search, category list, sort, and the full set of filters described below.

Switching layouts never changes the catalog URL, and no course settings change. If you want students to be able to narrow the catalog by price, level, rating, duration or instructor, switch this setting to Filter sidebar -- those filters do not appear in the grid layout.

Filter bar (grid layout)

The filter bar sits above the course grid. It provides:

  • Search -- keyword search across course title, description, and short description.
  • Category -- filter to a single category by selecting it from the dropdown.
  • Sort -- order results by: Newest First, Most Popular, Highest Rated, Price: Low to High, Price: High to Low.

Filter bar

Filters are composable: you can search within a category and sort by rating at the same time.

Filter sidebar

In the sidebar layout the same search, category and sort controls move into a rail on the left, and a set of filters sits underneath them. Learnomy ships these out of the box:

Filter Options Selection
Price Free, Paid, Membership, Paid or membership, External / closed -- whichever of these your published courses actually use Multiple
Level Beginner, Intermediate, Advanced Multiple
Rating 4 stars & up, 3 stars & up, 2 stars & up One at a time
Duration Under 1 hour, 1 to 3 hours, 3+ hours -- measured from the course's total video length Multiple
Instructor The instructors with published courses, most prolific first, up to 50 of them Multiple
Tags Every course tag that is on at least one published course Multiple

The Instructor filter is titled with whatever word your site uses for instructors, and the Tags filter follows the course tag vocabulary, so both read naturally on a site that has renamed them.

Every option carries a live count of how many published courses match it, so a student can see there are 12 beginner courses before clicking. Options with no published courses behind them are not offered, and a filter with nothing to show does not render at all -- a brand new site with three free courses sees a much shorter rail than a mature catalog, which is intended.

Selecting a filter reloads the results and writes the choice into the page URL, so a filtered view can be bookmarked, shared or linked to from a campaign. Choices stack: a student can search "python", stay inside a category, tick Beginner and Under 1 hour, and sort by rating, all at once. Changing any filter returns to the first page of results.

The results area above the grid reports Showing 1-12 of 34 so the effect of each choice is visible, and the count in the page header updates too.

On phones and narrow screens the rail collapses behind a Filters button and expands on tap, so the course grid still gets the full width.

Adding your own filters

The filter list is a registry, not a fixed set. Two ways to extend it without touching a template:

  • Add a course tag vocabulary. Any tag vocabulary registered through the learnomy_course_taxonomies filter -- "Industry", "Language", "Skill" -- automatically gets its own catalog filter, populated from the terms actually in use.
  • Register a filter directly. Developers can call \Learnomy\Catalog\Facets::register() on init to add a filter with its own options and query rules, or deregister() to remove one of the built-in filters from a site. Filters registered this way get the same sidebar UI, counts and URL handling as the built-in ones.

Search autocomplete

As a visitor types in the search field, a dropdown appears below it listing courses that match so far. Clicking a suggestion goes straight to that course's page -- there is no need to press Enter and wait for the full catalog to reload. The list updates as the visitor keeps typing (with a short debounce so it does not fire on every keystroke), and shows a "No matching courses" message when nothing matches.

The dropdown is keyboard-accessible: arrow up/down moves between suggestions and Enter opens the highlighted one, so keyboard-only visitors get the same shortcut as a mouse user.

Course grid

Published courses appear as cards in a responsive grid.

Course grid

Each card shows:

  • Course thumbnail (gradient with a title initial if no image is set)
  • Category badge
  • Course title
  • Instructor name
  • Star rating and review count
  • Student count (hidden when zero)
  • Content type badge (Video, Text, PDF, etc.) -- shows the format most of the course's lessons are delivered in. If the course is mostly delivered through a standards format such as SCORM, xAPI, LTI, QTI, or H5P, the badge is hidden rather than naming the technical format; students see a normal course card either way. See Lesson Content Types for how these labels are decided.
  • Price or "Free" label
  • An action: Enroll on a free course, Buy on a paid or on-sale one (new in 1.9.1)

Course card

Clicking the card title, thumbnail or action takes the visitor to the single course page, where they enrol or buy.

The action appears only where there is something a visitor can do. Invite only, External and Members only cards keep their explanatory label and get no action, because sending someone to a course they cannot buy is a dead end. A card for a course the visitor is already enrolled on shows Start, Continue or Review instead, as it always has -- 1.9.1 closed the gap where the course you owned invited you in and the course you might buy did not.

Private courses

A course marked Private (hide from catalog) in its Pricing settings does not appear in the catalog grid. It is only visible to the enrolled student and site admins. Instructors can still see their own private courses when logged in.

Category pages

Clicking a category badge on a card, or navigating to /courses/category/your-slug/, shows a filtered view of that category.

Category header

The category page uses the same card grid and supports the same sort options, scoped to that category.

Category grid

The full category listing at /courses/ shows all top-level categories. Courses inside a sub-category also count toward the parent's course count.

Single course page -- not enrolled

When a visitor has not yet enrolled, the course page shows the marketing view.

Single course, not enrolled

Hero section

The hero displays the course title, short description, star rating, student count, and instructor name. If a thumbnail is set it appears here; otherwise a gradient fills the space.

Course hero

Pricing and access box

The access box shows the enrollment call to action.

Enroll box

What appears depends on the course's access type:

Access type What the student sees
Free "Enroll Free" button
One-time payment Price and "Buy Now" button
One-time payment (time-limited) Price, access duration, and "Buy Now" button
Recurring subscription Recurring amount, billing interval, and "Subscribe" button
Members only "Join to Enroll" and a link to the membership pricing page
One-time payment or Members Both the buy price and a members-only path

When a valid sale price and date range are configured the box shows the sale price crossed against the regular price.

Curriculum preview

The curriculum preview lists sections and lessons so visitors can see the course structure before enrolling.

Curriculum preview

Lessons marked as preview lessons include a "Preview" badge. Clicking a preview lesson opens the lesson player without requiring enrollment.

Instructor

The instructor card shows the instructor's avatar, name, bio, and a link to their public profile page.

Instructor card

Reviews

If reviews are enabled at both the site level (LMS Settings → Settings → Reviews) and on the individual course, the reviews section shows the aggregate star rating, a rating breakdown bar chart, and individual student reviews.

Reviews

When there are no approved reviews yet, a single "No reviews yet" message appears.

Single course page -- enrolled view

After enrollment, the course page changes to the learning view.

Enrolled course page

Progress bar

A progress bar at the top shows how far through the course the student is, as a percentage of completed lessons.

Progress indicator

Curriculum with resume

The curriculum now shows completion status for each lesson. Completed lessons carry a checkmark. The student can click any unlocked lesson to open it in the lesson player.

Enrolled curriculum

If the course requires sequential lesson completion, locked lessons show a lock icon and are not clickable until the previous lesson is complete.

Final quiz

When the course has a final quiz and the student has completed the required lessons, the final quiz card appears at the bottom of the curriculum.

Final quiz card

Tips

  • The catalog URL is /courses/ by default. You can change the base slug in LMS Settings → General.
  • The price, level, rating, duration, instructor and tag filters live in the Filter sidebar layout only. If students say they cannot narrow the catalog, check LMS Settings → Settings → Layouts first -- the default is the grid.
  • Price and the tag-based filters cache their counts and refresh whenever a course, tag or category changes, so a newly published course shows up in the counts without any manual clearing.
  • The Level, Rating, Duration and Instructor filters are not cached: their counts are recalculated on every catalog page load, and Rating runs one query per band. Their numbers are therefore always current, but on a large catalog under heavy traffic this is the part of the page worth watching. Caching them is a plugin-side change, not something you configure.
  • The catalog only shows published courses. Draft, pending, and trashed courses never appear.
  • The Featured flag in a course's Visibility settings affects the default listing order internally but is not a named sort option in the catalog UI. The available sort options in the dropdown are Newest First, Most Popular, Highest Rated, Price: Low to High, and Price: High to Low.
  • The catalog query is filterable via the learnomy_catalog_where_clauses hook for custom integrations.

Creating a Course

The Learnomy course builder

This page walks through creating a course from scratch, configuring its settings, and building its curriculum using the Course Builder.

Overview

Courses live under Learnomy → Courses. You create one course form, then open the Course Builder to arrange your lessons and quizzes, with or without sections. The course form handles metadata, pricing, and settings; the builder handles curriculum structure.

The courses list

Go to Learnomy → Courses to see all courses.

Courses list

The list header shows filters for status and category, plus a search input.

Courses list header

The filter bar lets you narrow by status (All, Published, Draft, Pending, Trashed) and by category.

Courses list filters

The table shows each course's title, category, status, price, student count, and rating.

Courses list table

Row actions: Edit opens the course form. View opens the public course page. Duplicate makes a copy of the course, including its full curriculum (sections and lessons); the copy is created as a draft so you can rename and change it before publishing. Publish or Draft flips the status. Trash moves the course to trash. Trashed courses show Restore and Delete Permanently instead.

Creating a new course

  1. Go to Learnomy → Courses and click Add New Course in the page header.

  2. Fill in the required fields:

    • Title -- the course name shown on the catalog card and the course page.
    • Slug -- the URL-friendly identifier. Auto-generated from the title; you can change it.
    • Category -- assign the course to one category.
    • Thumbnail -- the image shown on the card and in the hero. Recommended size: 1280 x 720 px.
  3. Add a Short Description (one or two sentences shown on the catalog card) and a longer Description (shown in the About section of the course page). Both use the WordPress block editor.

  4. Configure the settings sections described below.

  5. Click Save to create the draft. Click Publish when it is ready for students.

Course settings

The course form is divided into settings sections. Click any section header to expand or collapse it.

Pricing and Access

This section controls how students get access to the course.

Choose a Pricing model from the radio group:

Model Description
Free Anyone signed in can enroll. No payment required.
One-time payment (lifetime access) Student pays once and keeps access permanently.
One-time payment (time-limited access) Student pays once and keeps access for the number of days you set. A renew option appears when access expires.
Recurring subscription Student pays every month or year. Requires Stripe to be connected -- Stripe creates the price automatically.
Members only Student needs an active membership plan that includes this course. Only visible when the Membership module is enabled.
One-time payment OR Members Student can either buy outright or enroll through an active membership. Only visible when both Payments and Membership are enabled.

When you choose a paid model, fields for Regular price and Sale price appear. You can also set a Sale starts and Sale ends date range; outside that range the regular price applies.

For time-limited access, set the Access duration (days).

For recurring subscription, set the Recurring amount and Billing interval (Monthly or Yearly).

Visibility is a dropdown with three options:

Option Description
Public Listed in the catalog; anyone can enroll.
Unlisted Hidden from the catalog, but anyone with the direct link can enroll. Use this to share a course by link without listing it publicly.
Private Hidden from the catalog; only enrolled students and admins can open it.

Pricing model lock: once at least one student has paid for a course, the pricing model is locked to protect existing customers. To relaunch the course with different pricing, copy it with the Duplicate row action on the courses list and price the copy.

Gateway warnings: if you select a paid model but no real payment gateway is configured, the form shows a warning banner with a link to LMS Settings → Payments.

Completion and Certificate

Define what counts as completing this course.

Completion Requirement options:

Option Description
All Lessons Completed Student must mark every lesson complete.
Lessons + Final Quiz Student must complete all lessons AND pass the final quiz.
Percentage of Content Student must complete at least the set percentage of lessons (slider, 1-100%).
Manual Completion Admin or instructor marks the course complete manually.

Per-Lesson Rule Enforcement controls how strictly individual lesson completion rules (minimum watch time, video percentage, scroll-to-end, quiz pass) are applied in this course. Options: Inherit from site setting, Strict (rules must be met before Mark Complete activates), Advisory (rules shown but optional), Off (no rules). Individual lessons can still override this per lesson.

Award a certificate on completion -- tick to enable. When ticked, a Certificate Template picker appears. If no templates exist yet, a shortcut link takes you to the certificate designer. See the Certificates documentation for how to build templates.

Completion Message -- text displayed on the completion screen. Supports basic formatting.

Allow students to retake the course -- tick to let students restart the course after completing it. Optionally tick Reset progress when they retake to clear all lesson and quiz progress on retake.

Progression and Flow

Controls how students move through content.

Require lessons to be completed in order -- when ticked, students must finish each lesson before the next one unlocks. The lesson player shows a lock icon on future lessons.

Minimum Time per Lesson -- sets a site-wide floor in minutes. The Mark Complete button stays disabled until this time elapses. Set to 0 to disable. Individual lessons can set their own minimum time in their lesson settings, which overrides this course-level value.

Engagement

Configure the community features available inside this course.

Setting Default Description
Enable comments on lessons On Allows students to comment on individual lessons.
Enable course reviews On Students can leave a star rating and review after enrolling. Both this flag and the global Reviews setting must be on for reviews to show.
Course announcements On Instructors can send announcements to enrolled students.
Welcome message -- A message shown to the student immediately after enrollment.

Visibility and Media

Featured in catalog -- marks the course as featured. Featured courses appear on the homepage featured row and in any "featured" block instance. Note that the catalog sort dropdown has no "Featured" option; the flag drives the featured row and blocks, not a sort order.

Intro Video URL -- a YouTube, Vimeo, or direct MP4 URL that plays on the sales page above the fold.

The course's public/unlisted/private setting lives in the Pricing and Access section (see above), not here.

Building the curriculum

After saving the course, click Manage Curriculum in the course form sidebar (also reachable as the Curriculum button in the course header toolbar).

Course builder

Builder header

The header shows the course name, a breadcrumb back to the course form, and a Preview link that opens the public course page in a new tab.

Course builder header

Adding sections (optional)

Sections are optional. They group lessons and quizzes into chapters, which is useful for longer courses. A short course can hold lessons and quizzes directly with no sections at all.

Click Add Section to create a section (chapter).

Builder section

Give each section a title. Drag sections to reorder them.

Lessons and quizzes without a section

When a course has lessons or quizzes that do not belong to any section, the builder groups them at the top under a heading called Ungrouped. These items display directly under the course for students, in their saved order, with no chapter heading.

This is how flat courses work. It is also what you see after importing a course whose source had no chapters. You can mix the two layouts: keep some content in the Ungrouped group and organize the rest into sections.

Adding lessons

Inside each section, click Add Lesson to create a new lesson or Attach Existing to attach a lesson that already exists in the system.

Section body

Lessons can be reordered within a section by dragging. They can also be moved between sections.

Adding a quiz to a section

Click Add Quiz inside a section to attach an existing quiz as the section's assessment.

Empty state

If the course has no content yet, the builder shows an empty state. It explains that you can add a section to group lessons into chapters, or add lessons directly, because sections are optional.

Tips

  • A course must have status Published to appear in the catalog. You can save as Draft and preview it before publishing.
  • Once any paying student has enrolled, the pricing model cannot be changed. Plan your pricing before your first promotion.
  • The course builder saves changes immediately via the REST API; there is no separate Save button in the builder.
  • The course form's Instructors card lets a site administrator reassign the lead author (the primary instructor) and add co-instructors. The card shows only when Allow multiple instructors per course is enabled. Co-instructors can see analytics and student progress, but only the primary instructor receives commission. See Managing Instructors for the full reassignment steps.
  • Tags are separate from categories. You can add course tags in Learnomy → Categories and assign them from the course form.

Sections and Lessons

Sections are optional chapters that divide a course into logical groups. Lessons are the individual learning units. This page covers how to create and configure both.

Overview

The curriculum lives in the Course Builder, opened with the Manage Curriculum button in the course form (or the Curriculum button in the course header toolbar). Sections and lessons are also accessible directly from Learnomy → Lessons, which lists every lesson across all courses.

Are sections required?

No. Sections are optional. A lesson or quiz can sit directly under a course with no section assigned. Use sections when you want to group lessons into chapters; skip them for short, flat courses. A lesson with no section assignment still appears in the curriculum and on the Lessons list, just without a section grouping.

See Creating a Course for where the curriculum fits into the overall course setup.

Sections

Sections exist only inside the Course Builder. They group lessons and optionally hold a section-level quiz.

Creating a section

  1. Open the Course Builder for a course: go to Learnomy → Courses, click Edit on the row to open the course form, then click Manage Curriculum (or use the Curriculum button in the course header toolbar).
  2. Click Add Section.
  3. Type the section title in the inline field and press Enter or click away to save.

Sections can be reordered by dragging the drag handle on the left side of the section row. Each section shows a count of its lessons.

To delete a section, use the section's row action menu. Deleting a section removes the section record but does not delete the lessons inside it. Those lessons become unattached and will appear on the Lessons list without a section assignment.

Lessons list

Go to Learnomy → Lessons to see all lessons across every course.

Lessons list

Lessons list header

Lessons list table

The table shows the lesson title, the course it belongs to, its section, content type, status, and sort order. Row actions: Edit opens the lesson editor. View opens the lesson in the lesson player. Trash moves the lesson to trash.

Lesson editor

Click Add New Lesson from the page header, or click Edit on an existing lesson, to open the lesson editor.

Lesson editor

The editor is divided into several panels.

Header and title

The top of the editor shows the lesson title field and the Publish button group.

Lesson editor header

Give each lesson a clear, specific title. The slug auto-generates from the title and determines the lesson URL.

Course and section assignment

Before adding content, assign the lesson to a course using the dropdown. The section dropdown is optional: leave it unset to place the lesson directly under the course with no section. If you are creating the lesson from inside the Course Builder context, these fields are pre-filled.

Content type and content

The Content Type dropdown sets what kind of content the lesson delivers. Choose one:

Type Description
Text A WYSIWYG body editor (default).
Video An external or uploaded video. Paste the URL into the Video URL field.
Audio An audio lesson. Paste the audio file URL.
PDF An inline or downloadable PDF. Paste the PDF URL.
Embed A third-party embed such as a slide deck or SoundCloud track. Paste the embed URL.
Live A scheduled live session (Zoom, Google Meet, Teams). Paste the meeting URL into the Join link field, then set when the session starts and how long it runs.
Download A standalone downloadable file (ZIP, ebook, etc.). Paste the download URL.
Protected video (MediaShield) A video delivered through the MediaShield plugin so the source URL is never exposed in the page. You pick a MediaShield-protected video rather than pasting a URL. This option appears only when the MediaShield plugin is active.

The content editor adjusts to the selected type. For Text lessons, a full block editor appears. For Video, Audio, PDF, and Embed, a URL field appears.

For Live, three fields appear instead:

Field What it does
Join link The Zoom, Google Meet, or Teams meeting URL.
Starts at The date and time the session takes place, in the site's timezone. Leave blank for an always-open room.
Runs for (minutes) How long the Join button stays available after the start. Leave blank and the session runs for 60 minutes.

Before the session starts, students see a countdown with the Join button disabled. From the start until the end of the window, they see an enabled Join now button that opens the link in a new tab. After that, they see "This session has ended".

Lesson content panel

Third-party shortcodes in lesson content

You can type a shortcode from another plugin (a contact form, a WP Fusion tag block, a gallery, or any shortcode registered on your site) directly into the Text lesson body or the course description. It stays visible as plain shortcode text while you are editing -- exactly like a normal WordPress post -- and only expands into its rendered output on the front end when a student views the lesson or course page.

Lesson settings

The settings area below the content editor contains the completion, scheduling, and behavior controls.

Lesson settings

Completion rules

Set how students complete this lesson.

Completion mode options:

Mode Description
Manual Student clicks "Mark Complete" to complete the lesson.
Minimum time Mark Complete activates only after the student has spent the set time on the lesson.
Video percentage For video lessons: Mark Complete activates after the student has watched the set percentage of the video.
Scroll to end Mark Complete activates only after the student has scrolled to the bottom of the lesson content.
Quiz pass Mark Complete activates after the student passes the lesson quiz.
Auto: SCORM / standards package Mark Complete stays locked until a Pro standards module (SCORM, and later cmi5) reports the package complete for this learner. Strict gate - advisory/off course settings do not relax it. Requires Learnomy Pro with SCORM (or another answering standard) enabled on the lesson.

Lock next lesson until complete -- when ticked, the next lesson in the course order is locked until this lesson is marked complete (regardless of the course-level sequential setting).

Per-Lesson Rule Enforcement -- overrides the course-level enforcement setting for this specific lesson. Options: Inherit from course, Strict, Advisory, Off.

Schedule (available date)

Available date -- if set, the lesson is not accessible to students until this date and time (in the site's timezone). Leave blank for immediate availability.

This is the Free availability gate. For day-count scheduling relative to enrollment date, see Content Drip (Pro).

This is not a live session's start time. A live session's start goes in its own Starts at field in the content panel. Available date only decides when the lesson unlocks, so setting it on a live lesson hides the countdown from your students -- they cannot open the lesson at all until that date. Normally leave it blank on a live lesson: students can then open the lesson early and watch the countdown, and they still cannot join before the session begins.

Duration

Duration -- how long this lesson takes, entered as minutes and seconds. Students see it beside the lesson in the course curriculum, and it adds up to the total shown in the course header.

Leave it at zero and no time is shown for that row. Nothing displays "0 min", because a zero is a gap in your data rather than a claim about the lesson, but a curriculum where only some rows carry a time looks unfinished. Fill it in for every lesson or none.

Duration is not filled in for you. Learnomy does not read the length out of a video file or a YouTube URL, so a video lesson starts at zero like any other.

Live sessions do not use this field. A live session's length is its Runs for value in the content panel, and the Duration field is hidden while the content type is Live so there is only ever one place to answer the question. The curriculum shows the "Runs for" value for those rows.

Preview lesson

Mark as preview lesson -- tick to let visitors watch or read this lesson without enrolling. Preview lessons show a "Preview" badge in the curriculum list on the course page. This is useful for showing potential students a sample of the course content.

Every other lesson shows a padlock to a visitor who has not enrolled, and its title is not clickable. The curriculum still lists every lesson with its type and duration, so a visitor can see exactly what the course covers before buying -- they just cannot open it. Preview lessons are the rows they can open, which is what makes the badge worth having.

Students who do have access see no padlocks at all. That includes people who never bought the course directly: a member whose plan covers it, a student added through a learning path or a space, and anyone you enrolled by hand. Access is what decides the padlock, not whether money changed hands.

Resources and attachments

The Attachments panel lets you add downloadable files to a lesson. Students see these in the Resources section of the lesson player.

Lesson attachments

To add an attachment:

  1. Click Add Attachment in the Attachments panel.
  2. Enter a title for the file and paste its URL, or upload a file via the media uploader.
  3. Click Save Attachment.

You can add multiple attachments per lesson. Drag to reorder. Click the trash icon to remove one.

Attachments support any file type. The lesson player renders each one as a labeled download link.

Per-lesson access requirements (developer feature)

Beyond enrollment, the lesson player can require an extra condition before it shows the lesson content. For example, an enrolled member might need to fill in a profile field (such as a Bar Number) before a webinar lesson unlocks. When a requirement is not met, the player shows an access gate with a custom title, message, and call-to-action button instead of the lesson.

This is a developer extension point. There is no admin or settings screen for it in Learnomy. A requirement is added in code through a WordPress filter. If your site has no integration that uses these filters, lessons behave as before: enrollment alone grants access.

How the gate behaves

  • The requirement is checked only for a viewer who would otherwise have access (an enrolled student or a viewer of a preview lesson).
  • Administrators and the course's instructor are never blocked by a requirement, so a site manager cannot lock themselves out of their own content.
  • When a requirement returns a reason, the player denies access and shows the gate. The reason carries the gate copy: title, message, cta_label, and cta_url.
  • The gate applies to both lesson views: the focused lesson player and a lesson embedded elsewhere through the lesson templates. Both read the same access data, so a custom prompt shows wherever the lesson renders, with no need to hide the player with CSS.

Filters

Filter Purpose
learnomy_lesson_access_requirements Add a requirement for one lesson. Receives ( array $requirement, $lesson, $course, int $user_id ). Return an empty array for no requirement, or a reason map (key, title, message, cta_label, cta_url) to deny access.
learnomy_course_access_requirements Add a requirement that gates every lesson in a course at once. Receives ( array $requirement, $course, int $user_id ). Checked only when the per-lesson filter returned nothing. Same reason shape.
learnomy_lesson_access_gate Filter the gate copy shown when access is denied. Receives ( array $gate, $lesson, $course, int $user_id ), where $gate has title, message, cta_label, and cta_url. Any requirement reason pre-fills these values; the defaults are the standard "Enrollment Required" copy.

A typical integration uses learnomy_lesson_access_requirements (or the course-level variant) to decide whether to gate, and lets the reason flow through to the gate copy automatically.

Tips

  • Sections are display containers only. The course's completion calculation is based on lessons, not sections.
  • A lesson belongs to one course at a time, and to at most one section. Sections are optional, so a lesson can sit directly under the course with no section. If you need the same content in two courses, create separate lessons.
  • The lesson slug must be unique within a course. Learnomy appends a numeric suffix automatically if a duplicate slug is detected.
  • For Video and Audio lessons, Learnomy uses the Plyr player when possible. Plyr tracks watch percentage, which the video-percentage completion mode reads.
  • Setting a lesson to Draft status removes it from the course curriculum for students but keeps it visible in the builder and admin list.

Categories and Tags

Learnomy course categories

Categories group courses by subject area. Tags are flat labels you can apply across courses, lessons, quizzes, and questions. Both help students browse and discover content.

Overview

Both are managed from Learnomy → Categories. The page has tabs: one for Categories and one tab per tag vocabulary (Course Tags, Lesson Tags, Quiz Tags, and so on).

Categories

The category tree

The Categories tab shows all course categories in a hierarchical tree.

Categories page

Categories header

The tree view shows parent categories and their children indented beneath them. Each row shows the category name, slug, and course count.

Category tree

Row actions: Edit opens the inline form pre-filled with the category's data. Delete removes the category. If the category still has courses, deleting it does not lose them: like WordPress moving posts to the default category, Learnomy moves those courses to Uncategorized and then removes the category. The confirmation explains that the courses will move, and the success message reports how many were moved. The courses themselves stay published. Deleting an empty category removes it with a plain confirmation.

Adding a category

The Add New Category form sits on the left side of the Categories tab.

Add category form

  1. Go to Learnomy → Categories.
  2. Fill in the Name field. This is the display name shown in the catalog and on course cards.
  3. The Slug auto-generates from the name. Change it if you want a different URL fragment (for example, /courses/category/web-dev/).
  4. Optionally choose a Parent Category to make this a sub-category.
  5. Optionally add a Description (shown on the category archive page header).
  6. Click Add Category.

Editing a category

  1. Click Edit on any category row.
  2. The add form on the left side of the page fills with the category's current values.
  3. Make your changes and click Update Category.

Assigning a category to a course

When creating or editing a course, select the category from the Category dropdown in the course form. A course can belong to one category at a time.

Category archive pages

Each category has a public archive page at /courses/category/your-slug/. The catalog filter bar also lets students filter by category. The category archive shows the same course card grid and supports the same sort options as the main catalog.

Tags

Tags are flat (no hierarchy) and shared across multiple content types. The available tag vocabularies are: Course Tags, Lesson Tags, Quiz Tags, and Question Tags.

Switching between tag vocabularies

Click the tab for the vocabulary you want to manage (for example, "Course Tags").

Adding a tag

  1. Click the tab for the tag type (for example, Course Tags).
  2. In the Add New Tag form, enter the Name and optionally edit the Slug.
  3. Click Add Tag.

Assigning tags

  • Course tags -- assign from the course form. A multi-select field in the course form shows all course tags.
  • Lesson tags -- assign from the lesson editor.
  • Quiz tags -- assign from the quiz editor.
  • Question tags -- assign from the question editor.

Tags do not affect the catalog filter in Free. They are available for custom filtering via the learnomy_catalog_where_clauses hook.

Tips

  • Category names are de-duplicated in the catalog filter dropdown. If two categories have the same name (for example after an import), only the oldest one appears in the dropdown. Use the admin tree view to find and clean up duplicates.
  • Changing a category's slug changes its archive URL. Any external links pointing to the old URL will break. Use a redirect plugin if you rename a category after publishing.
  • Category course counts are computed live on every read from the database, so they are always accurate. There is no stale counter to repair. If a count looks wrong after a bulk import, reload the page.
  • The tag system uses the lrn_tags and lrn_tag_map tables with a type column to separate vocabularies. This means a Course Tag and a Lesson Tag with the same name are separate records.

Content Drip (Pro)

Pro feature. Available in Learnomy Pro.

Content Drip lets you schedule when individual lessons become available to students. Instead of releasing the entire course at once, you control the unlock timing lesson by lesson.

Overview

Content Drip is configured from the Pro Tools hub in the Learnomy admin. Select a course, then set a drip rule on each lesson you want to delay. Lessons without a rule are available immediately after enrollment.

Go to Learnomy → Pro Tools, then click the Content Drip card to get started.

Setting up a drip schedule

Content Drip page

Step 1 -- select a course

The Content Drip page opens with a course selector at the top.

Drip schedule header

  1. Open the Course dropdown.
  2. Select the course you want to schedule.
  3. Click View schedule.

Step 2 -- review the lesson schedule

The lesson schedule table appears once you select a course.

Drip schedule table

The table has four columns:

  • Lesson -- the lesson title.
  • Drip Type -- the unlock rule in effect, or "Immediate" if no rule is set.
  • Value -- the specific value for the rule (number of days, a fixed date, or a prerequisite lesson name).
  • Status -- "Available" (no rule) or "Dripped" (a rule is active).

Step 3 -- set drip rules

Drip rules are created and updated via the REST API: POST /learnomy-pro/v1/courses/{id}/drip-rules.

The three rule types are:

Drip Type Value When it unlocks
Days after enrollment A whole number (for example, 7) N days after the student enrolled in the course
Fixed date A date (for example, 2026-09-01) On or after the specified date, for all students regardless of when they enrolled
After completing lesson Another lesson in the same course As soon as the student marks the prerequisite lesson complete

Each lesson can have at most one drip rule. Setting a new rule on a lesson replaces the previous one.

To remove a drip rule from a lesson, send DELETE /learnomy-pro/v1/drip-rules/{rule_id}.

The locked-lesson experience for students

When a student opens a course and a lesson is not yet unlocked by its drip rule, that lesson is inaccessible. In the curriculum list on the course page the lesson row shows a lock indicator. Clicking the lesson title does not open the player.

If the rule type is Days after enrollment or Fixed date, the lesson player shows the date when the lesson will unlock so the student knows when to return.

If the rule type is After completing lesson, the player tells the student which lesson they need to complete first.

Lessons with no drip rule, or whose rule conditions are already satisfied, open normally.

How scheduling works

Whether a lesson is locked or unlocked for a given student is evaluated live against your drip rules every time they open the course, so it is always current.

At the moment a student enrolls, Learnomy also works out when each dripped lesson in that course will unlock for them, so it can fire the normal "lesson unlocked" notification at the right time. On a large enrollment -- a bulk CSV import or a cohort with hundreds of members enrolling into a course with many dripped lessons -- computing and writing that schedule for everyone at once could slow down the enrollment request itself. To avoid that, this step runs in the background via Action Scheduler immediately after enrollment, instead of inline as part of it, and writes the schedule in a single batch. Enrollment completes at normal speed regardless of how many lessons are dripped or how many students are enrolling at once.

Notes

  • Content Drip rules apply to enrolled students only. Non-enrolled visitors and admins can always access all lessons regardless of drip rules.
  • The prerequisite rule type checks whether the prerequisite item (lesson or quiz) has a completed progress record. Graded attempts count as completed for quizzes.
  • The "Days after enrollment" calculation is based on the student's enrollment timestamp in UTC. A student who enrolled at 11 PM will have their Day 1 unlock happen at the same clock time the following day.
  • Drip rules survive course updates. If you change a lesson's title, its existing drip rule is unaffected.
  • There is no bulk-assign tool in the current admin view. To schedule many lessons at once, use the REST API with a scripted loop.

Incoming Enrollment Webhook

The incoming enrollment webhook lets an outside system grant or revoke access to a Learnomy course after a sale happens somewhere else. You sell a course on your own checkout, Gumroad, a CRM, or any other platform, and that platform tells Learnomy to enroll the buyer by POSTing a signed request to one endpoint.

This is built for courses set to the Access granted externally (closed) pricing model. Closed courses are not sold on your Learnomy site, so this webhook is how buyers actually get in.

What you can do

  • Enroll a buyer in a closed course as soon as they pay on an external platform, with no manual work.
  • Revoke access automatically when the external platform reports a refund or cancellation.
  • Create the buyer's account on the fly if they do not already have one, so a first-time customer is never turned away.
  • Record an external order against the enrollment (amount, currency, provider) so you have a receipt for support and refund reconciliation.
  • Send a signed test enrollment from the admin screen to confirm the whole chain works before you connect your store.
  • Connect through Zapier or Make with a no-code "New sale -> POST" recipe, or POST directly from your own backend.

Incoming enrollment recipe panel

How to use it

Step 1 -- set the course to closed pricing

The webhook only manages courses whose access is granted externally. Open the course editor, go to the pricing settings, and choose Access granted externally (closed). Any course that is free, one-time, or membership-priced is rejected by the endpoint, so this step comes first.

Step 2 -- open the Webhooks screen

Go to LMS Settings -> Webhooks (?page=learnomy-webhooks). The Incoming enrollment (external / closed courses) panel is at the top of the page.

Step 3 -- copy the endpoint URL

The Endpoint URL field holds the address your store will POST to:

POST /wp-json/learnomy/v1/webhooks/enrollment

Use the copy button next to the field. Your external platform sends a JSON body to this URL to grant or revoke access.

Step 4 -- set and copy the signing secret

The signature is mandatory. If no signing secret is set, the endpoint refuses every request, so this is not optional.

  1. In the Signing secret field, click Regenerate to create a secret (Learnomy stores it as the learnomy_incoming_webhook_secret option).
  2. Use the show/hide and copy buttons to reveal and copy it.
  3. Paste the secret into the external system that will sign the requests.

Regenerating the secret breaks any connection still using the old value until you update the secret there too.

Step 5 -- sign each request

Your external system builds a JSON body and signs it:

  • Body (minimum): { "user_email": "buyer@example.com", "course_id": 123 }
  • Signature: HMAC-SHA256 of the exact JSON body, keyed with your signing secret.
  • Header: send the signature as X-Learnomy-Signature.

To grant access, send "action": "enroll" (this is the default if action is omitted). To pull access on a refund, send "action": "revoke".

Step 6 -- send a test enrollment

Before wiring your store, confirm the chain works from inside wp-admin:

  1. In the Send a test enrollment row, pick one of your closed courses from the dropdown.
  2. Enter a buyer email (your own email is pre-filled).
  3. Click Send test. Learnomy signs a real request with your secret and runs it through the live endpoint, then shows a pass or fail result.

If no course is set to closed pricing yet, this row tells you to set one first.

Step 7 -- connect your store

Wire the trigger in Zapier, Make, or your own backend, following the recipe shown in the Connect your store (Zapier / Make) row:

  1. Trigger: "New sale" in your store (Gumroad, Stripe, WooCommerce, and so on).
  2. Action: "Webhooks - POST" to the endpoint URL, JSON body { user_email, course_id }, with the X-Learnomy-Signature header set to the HMAC-SHA256 of the body.
  3. Refund recipe: trigger on "Refund" and send the same POST with "action": "revoke".

Settings & options

The panel lives on the Webhooks admin screen. The fields it manages:

Field What it is
Endpoint URL Read-only. POST /wp-json/learnomy/v1/webhooks/enrollment.
Signing secret Stored in the learnomy_incoming_webhook_secret option. Shown, copied, and regenerated from the panel. Never returned by any read endpoint.
Send a test enrollment Course selector (your closed courses) plus a buyer email, fires a real signed test.

The JSON body accepted by the endpoint:

Field Required Notes
user_email Yes The buyer. Provisioned as a new account if it does not exist.
course_id Yes Must be a course set to closed pricing, or the request is rejected with 409.
action No enroll (default) or revoke.
external_ref No Your external order or reference id. Used for idempotency and reconciliation. A repeated external_ref will not double-enroll or double-charge.
amount No Order amount recorded on the transaction. Defaults to 0.
currency No Three-letter currency code. Defaults to USD.
full_name No Used when provisioning a new buyer account.
provider No Name of the external system that sold the course, recorded on the transaction. Defaults to external.

Behavior worth knowing:

  • The signature is always required. With no secret configured the endpoint returns 401; a missing signature returns 401; a signature that does not match returns 403.
  • Only closed courses are managed here. A free, priced, or membership course returns 409.
  • Enroll records an external transaction (marked completed) and enrolls the buyer with source = webhook.
  • Revoke pulls the seat. Revoking a buyer who does not exist, or a seat that is already gone, is treated as a success so retries are safe.

For developers

REST routes (all under the learnomy/v1 namespace):

  • POST /webhooks/enrollment -- the public incoming trigger. Permission is __return_true because a mandatory HMAC signature (X-Learnomy-Signature) is verified inside the handler.
  • POST /admin/webhooks/incoming/regenerate-secret -- rotates the signing secret (admin only). Returns the new secret once.
  • POST /admin/webhooks/incoming/test -- fires a real signed test enrollment against a chosen course_id and user_email (admin only).

Hooks:

  • learnomy_incoming_webhook_payload (filter) -- normalize a third-party payload shape into Learnomy's { user_email, course_id, action, external_ref, amount, currency, full_name, provider } params. Use this to adapt a provider that posts a different body (for example Gumroad, Teachable, or Podia). Passed the payload array and the WP_REST_Request.
  • learnomy_external_enrollment_requested (action) -- fires after a successful external enroll, with $user_id, $course_id, and the WP_REST_Request. Intended for add-ons and xAPI notifications; the enrollment itself is already done.

Options:

  • learnomy_incoming_webhook_secret -- the shared HMAC-SHA256 signing secret.

New buyers are resolved or created through Provisioning_Service::ensure_user_by_email(), and the external order is written to lrn_transactions (idempotent on external_ref via Transaction::find_by_provider_id()).

Bulk Enroll from a List

Bulk enroll lets you add many students to a course at once by pasting a list of email addresses or uploading a CSV file. It runs from the course detail screen in wp-admin, creates accounts for people who do not have one yet, and skips anyone who is already enrolled.

What you can do

  • Enroll a whole list of students in one course in a single action instead of adding them one by one.
  • Paste email addresses straight into a box, one per line or separated by commas.
  • Upload a CSV (or plain text) file of email addresses instead of pasting.
  • Automatically create an account for any email that does not have one yet, and send that person a set-password invite so they can log in.
  • Skip anyone who is already enrolled in the course, so re-running the same list never double-enrolls or double-charges.
  • See a per-run summary that tells you how many were enrolled, how many new accounts were invited, how many were skipped, and how many failed.
  • Grant access to a course set to Access granted externally (closed) without a payment step, since these courses are not sold on your site.

Bulk enroll section on the course detail screen

How to use it

Step 1 -- open the course detail screen

Go to Learnomy -> Courses, then open the course you want to enroll people into. This opens the course detail screen (?page=learnomy-course-detail&course_id=<course id>).

Step 2 -- find the Enroll a student card

Scroll to the Enroll a student card. Below the single-student email box there is a collapsible row labelled Bulk enrol from a list (CSV / emails). Click it to expand.

Step 3 -- provide the list of emails

You have two ways to supply the list, and you can use either one:

  • Paste the addresses into the text box, one email per line or comma-separated. Whitespace, commas, and semicolons between addresses all work as separators.
  • Upload a file by clicking Choose CSV and picking a .csv or plain text file. Its contents are read into the same text box, so you can review or edit the list before submitting.

Invalid entries are ignored, and duplicate addresses are collapsed to one (matching is case-insensitive), so a messy list is fine.

Step 4 -- enrol everyone

Click Enrol everyone. Learnomy processes the list and, for each address:

  • If the person already has an account and is already enrolled, they are skipped.
  • If the person already has an account but is not enrolled, they are enrolled (source: manual).
  • If the email has no account yet, Learnomy creates one and sends a set-password invite, then enrolls the new account.

Step 5 -- read the result summary

When the run finishes, a summary line appears under the button, for example: how many were enrolled, how many new accounts were invited, how many were already in, and how many failed.

If your list was longer than the per-request cap (500 by default), the summary tells you how many are left to do and asks you to submit again to continue. The remaining addresses are never silently dropped, so keep clicking Enrol everyone until nothing is left.

Settings & options

The bulk enroll controls live inside the Enroll a student card on the course detail screen. The fields are:

Control What it does
Bulk enrol from a list (CSV / emails) The collapsible section that holds the whole feature.
Choose CSV File picker. Accepts .csv, text/csv, and text/plain. The file is read into the text box.
Email text box Paste target. One email per line or comma / semicolon / whitespace separated.
Enrol everyone Submits the list to the bulk enroll endpoint.

Behavior worth knowing:

  • Source is manual. Every seat created this way is recorded with source = manual, which bypasses payment. Use it for teams, cohorts, comped access, or closed courses.
  • Already-enrolled students are skipped, so re-submitting the same list is safe.
  • New emails are provisioned with an account plus a set-password invite, the same way the incoming enrollment webhook provisions first-time buyers.
  • Per-request cap. At most 500 addresses are processed per submission. Anything beyond that is reported as "left to do" so you can submit the rest, rather than being truncated without notice. The cap is adjustable with the learnomy_bulk_enroll_max filter.

For developers

REST route (under the learnomy/v1 namespace, admin only):

  • POST /admin/courses/{id}/bulk-enroll -- enrolls a list of emails into course {id}. Permission callback is is_admin. Body param emails is a required array of strings (each string may itself contain several addresses; the handler splits on whitespace, commas, and semicolons, sanitizes, and dedupes). Returns a summary object (enrolled, created, skipped, failed), a per-email rows array, and a remaining count for anything past the cap.

Filter:

  • learnomy_bulk_enroll_max -- the maximum number of addresses processed per request. Defaults to 500. Raising it increases the risk of a request timing out on a large paste, which is why the default is capped and the remainder is reported instead of dropped.

Under the hood the handler resolves or creates each account through Provisioning_Service::ensure_user_by_email() (the same path the closed-course incoming webhook uses) and enrolls through Enrollment_Service::enroll( $user_id, $course_id, 'manual' ). Already-active seats are detected with Enrollment::find_active().

Closed Course Pricing

Closed pricing is for courses you do not sell on your Learnomy site. The course is priced and paid for somewhere else -- your own checkout, Gumroad, a CRM, a sales team -- and you grant access to Learnomy after the sale. There is no Buy button and no on-site checkout.

What you can do

  • Run a course that is never purchasable on your Learnomy site, with every price tag and Buy button removed from the course page and the catalog.
  • Grant access by hand from the course's Students screen, one email at a time or by CSV upload.
  • Grant and revoke access automatically from an outside platform through the incoming enrollment webhook.
  • Show an optional "Request Access" style button on the course page that links to wherever the course is actually sold.
  • Show a plain "Enrolment is managed externally" note instead of a button when you would rather not link out.
  • Use closed pricing without turning on the payments or membership modules -- it is always available.

Closed pricing model first view

How to use it

Step 1 -- open the course pricing settings

Edit the course and open the Pricing & Access section of the course editor. You will see the Pricing model radio group.

Step 2 -- choose the closed model

Select Access granted externally (no on-site checkout). This is the option LearnDash users know as "Closed." Unlike the paid and members-only models, it is always listed -- it does not require the payments or membership module to be enabled, because the whole point is that no on-site sale happens.

When you pick it, the regular price, sale price, and access-duration fields disappear, because they do not apply to a course that is not sold here.

Two optional fields appear under the closed model:

  • Custom button label -- the text on the button shown on the course page, for example Request Access or Buy on Gumroad. If left blank it falls back to "Enrol externally."
  • Custom button URL -- where that button links, for example your external checkout or a contact form.

If you set a URL, the course page shows the button linking out to it (opening in a new tab). If you leave both blank, the course page shows a plain "Enrolment is managed externally" note instead.

Step 4 -- read the access summary and save

The editor shows a one-line, plain-English summary of what a visitor will experience, composed from the visibility and pricing model you chose. For a closed course it explains that students cannot buy it here and that you grant access by email, CSV upload, or a connected external system. Confirm it reads the way you expect, then save.

Step 5 -- get students in

A closed course has no self-serve enrollment, so you decide who gets in. There are two ways:

  1. Manually or by CSV -- open the course's Students screen and add students by email or upload a CSV list.
  2. From an external system -- connect your checkout, Gumroad, a CRM, or Zapier/Make to the incoming enrollment webhook on LMS Settings -> Webhooks, so buyers are enrolled automatically after they pay elsewhere. See Incoming Enrollment Webhook.

Until at least one student is enrolled, the course detail screen shows a reminder banner that the course is set to external access but has no students yet, with a pointer to both paths.

Settings & options

The Pricing & Access section of the course editor:

Field What it is
Pricing model: Access granted externally (no on-site checkout) The closed model. Always available, no module required. Stored in both pricing_model and access_type as closed (varchar reuse, no schema change).
Custom button label Optional. Stored in the course settings_json as closed_cta_label. Falls back to "Enrol externally" when a URL is set but no label.
Custom button URL Optional. Stored as closed_cta_url. When set, the course page renders the CTA button; when blank, a plain "Enrolment is managed externally" note is shown.
Access summary Read-only live sentence describing what a student experiences, built from visibility + pricing model.

What a closed course does everywhere else:

  • Course page -- the price area shows "Sold externally" instead of a price. The Buy / Enroll button is replaced by your external button or the managed-externally note.
  • Catalog -- the course card and price facet suppress the price, so a closed course never shows a price it cannot honor.
  • Checkout -- the on-site checkout (and the test checkout) refuse a closed course, so nobody can reach a pay flow for it.

For developers

Model (Learnomy\Models\Course):

  • Course::is_closed( $course ) -- true when the resolved pricing_model is closed. This is the single branch every buy/price surface checks.
  • Course::PRICING_MODELS / Course::ACCESS_TYPES -- the canonical option lists, both include closed. Use Course::model_for_access() / Course::access_for_model() to convert between the two vocabularies; never hardcode the map.
  • Course::get_setting( $course_id, 'closed_cta_url', '' ) / ... 'closed_cta_label', '' ) -- read the custom CTA fields from settings_json.
  • Course::access_summary( $course ) -- the plain-English summary string.

Filters:

  • learnomy_closed_cta -- override the entire closed-course CTA HTML on the course page (the external button or the managed-externally note). Passed the default HTML, the course row, and the current user id.
  • learnomy_bulk_enroll_max -- cap on how many rows a single CSV / paste bulk enroll accepts.

REST:

  • POST /learnomy/v1/admin/courses/{id}/bulk-enroll -- admin bulk enroll for closed courses (manual source, deduped, provisions new accounts, capped by learnomy_bulk_enroll_max).
  • POST /learnomy/v1/webhooks/enrollment -- the public incoming enrollment trigger for closed courses (mandatory X-Learnomy-Signature). See Incoming Enrollment Webhook for the full contract.

Not to be confused with Invite only

Closed and Invite only share the same access rule -- nobody enrols themselves and nothing is sold on-site. Use Closed when the course is still bought somewhere else and you want to link there. Use Invite only when there is no sale at all and you add the students yourself, as in internal training.

Embedding Video and External Content

Learnomy turns a pasted link into a clean, responsive player inside your lesson. Use the Embed lesson content type, or the embed tool in the lesson body, and paste a URL -- Learnomy renders the provider's player. When a link cannot be embedded, you get a tidy link-preview card instead of a broken frame.

What you can do

  • Embed video and rich media from the sites WordPress already knows -- YouTube, Vimeo, and the other built-in oEmbed providers -- by pasting the page URL.
  • Embed video from the Fediverse (PeerTube) from any instance, with no per-site setup -- paste a /w/, /videos/watch/, or /videos/embed/ link and it plays inline.
  • Fall back gracefully: a link Learnomy cannot embed becomes a clean card with the site name and an Open button, never a broken iframe.
  • Keep every embed looking the same across the lesson editor, the front-end builder, and the student player -- one renderer handles all three.
  • Add an embed either as a whole lesson (the Embed content type) or inside a text lesson's body (the embed tool).

Embedding video and external content first view

How to use it

Step 1 -- add or edit a lesson

Open a course, add or edit a lesson, and find the Content type dropdown in the lesson editor.

Step 2 -- choose how to embed

  • For a whole-lesson embed, set Content type to Embed and paste the URL in the media URL field.
  • To place an embed inside a text lesson, keep the content type on Text and use the embed tool in the lesson body, then paste the URL.

Paste the page URL (not an iframe snippet). For a PeerTube video, any of these forms work:

  • https://your-instance.example/w/aBcD1234...
  • https://your-instance.example/videos/watch/aBcD1234...
  • https://your-instance.example/videos/embed/aBcD1234...

Step 4 -- save and check the player

Save the lesson and open it in the student player. The video appears in a responsive 16:9 frame. If you see a link card instead of a player, the source does not support embedding (see below).

Settings and options

  • Aspect ratio. Embeds render in a responsive 16:9 frame by default; 4:3 and 1:1 are also supported by the renderer.
  • Fediverse embedding is on by default. Safe hosts only -- any URL that resolves to a private or internal network address is never fetched and falls back to a link card. Nothing to configure per instance.
  • Link-card fallback. Any URL that is not a known provider, not a recognised Fediverse platform, and does not advertise oEmbed shows a link-preview card with the host name and an Open button.

Learnomy resolves an embed in this order:

  1. WordPress core oEmbed providers (YouTube, Vimeo, and the rest of the built-in list).
  2. Known Fediverse platforms (PeerTube) by URL shape -- no network request, works for every instance.
  3. Safe oEmbed discovery for any other source that advertises oEmbed, after a safety check that blocks private and internal hosts.
  4. A link-preview card, if none of the above produced a player.

So a card means the source did not offer an embeddable player -- or the host failed the safety check.

For developers

The one renderer is \Learnomy\Content\Embed::render( $url, $opts ). Provider HTML is sanitised through the content render floor, and results are cached per URL. Extension points:

  • learnomy_embed_platform_adapters -- register a callable( $url, $host, $path ): ?string that turns a recognised URL shape into sandboxed iframe HTML, so a whole platform works with no per-domain list (PeerTube ships built in).
  • learnomy_embed_fediverse_enabled -- filter (default from the learnomy_embed_fediverse option, on) to turn safe oEmbed discovery on or off.
  • learnomy_embed_blocked_hosts -- a host deny-list for moderation. Not an allow-list.
  • learnomy_embed_oembed_args -- filter the args passed to wp_oembed_get for the core-provider pass.
  • learnomy_embed_html -- filter the final wrapped embed markup.

Safe oEmbed discovery is gated by \Learnomy\Security\Webhook_URL_Validator::is_safe(), the same guard used for outgoing webhook URLs, so an author-supplied link that resolves to a private or reserved address is never fetched server-side.

Invite-Only Courses

Invite only is for a course where you add the students yourself. Nothing is shown for sale, there is no checkout, and nobody can enrol themselves. You put people in, one at a time or by CSV, and that is the only way in.

It is the model for internal training: a company teaching its own staff, a school running a class, an academy onboarding a client's team. Nobody in those situations is buying anything, and every price tag, Buy button and coupon field on the page is noise at best and a wrong promise at worst.

What you can do

  • Run a course that is never purchasable and never self-enrollable, with every commerce affordance removed from the course page, the catalog card and the mobile buy bar.
  • Add students directly under Course → Students, individually or by CSV.
  • Keep the course listed or unlisted independently, using the course's own Visibility control.

How to use it

Step 1 -- pick the model

In the course editor, open Pricing & Access and choose Invite only (you add the students).

No price fields appear, and no URL fields appear either. That is deliberate: there is nothing to price and nowhere to link to.

Step 2 -- add your students

Go to Course → Students and add people by email, one at a time or by uploading a CSV. They are enrolled with source=manual and no transaction is created, because no money changed hands.

Step 3 -- tell them it exists

An invite-only course does not advertise itself. Send your students the course link, or point them at their dashboard, where the course appears once they are enrolled.

What a student sees

Viewer What the course page shows
Enrolled student The normal course, with Start / Continue
Signed-in, not enrolled Price reads "Invite only", and a note reads "Enrolment is by invitation." No Buy, no Enrol, no coupon field, no wishlist, and no "Lifetime access" line
Logged out The same, subject to the course's Visibility setting

The wishlist and the "Lifetime access" line are hidden on purpose. A wishlist means "I mean to take this later", which presumes the viewer could enrol at some point - on an invite-only course that action is never theirs to take. "Lifetime access" is a promise about what a purchase buys; here access lasts exactly as long as you leave it in place.

Invite only vs Closed

They are the same access rule - nobody may enrol themselves, nothing may be sold on-site, and the enrolment API answers 403 for both. They differ only in where the student comes from, and therefore in what the page should say.

Closed Invite only
Where the sale happens Somewhere else - your own checkout, Gumroad, a CRM Nowhere; there is no sale
Optional external link Yes - a button pointing at where it is sold No; there is nothing to point at
Catalog card reads External Invite only
Typical use Selling off-site, then granting access Internal training, a class, a client team

Pick Closed when someone can still buy the course, just not here. Pick Invite only when nobody buys it at all.

If you switch a course from Closed to Invite only, any external URL you set earlier stops being used - the course page and the mobile bar both ignore it, so a stale link cannot send your students off-site.

Settings and options

  • Visibility is separate. Invite only controls how someone gets in; the course's Visibility control (Public / Unlisted / Private) controls who can see the page. An invite-only course can still be publicly listed if you want people to know the course exists and ask to join.
  • No checkout, at any layer. The on-site checkout refuses an invite-only course, so a hand-built or bookmarked checkout URL cannot be used to buy one.
  • Bulk enrolment still works, and so does the external-enrolment webhook if you would rather add students from another system.

For developers

  • Course::is_closed( $course ) is true for both closed and invite_only. It answers the access question - may a visitor enrol themselves, may this be sold, does the webhook manage it. Always ask it through MODEL_ACCESS_MAP / access_for_model(); comparing pricing_model to the literal 'closed' returns the wrong answer for invite-only.
  • Course::is_invite_only( $course ) is the wording question, and the only place the two models differ.
  • The stored access type is closed for both, so anything already gating on access_type needs no change.

Pricing and Access Models

Every Learnomy course carries two linked settings: an access type (the coarse "how is this course reached" value) and a pricing model (the richer "how a student pays" value). Together they decide whether a course shows a price, a Buy button, a membership path, or nothing at all.

What you can do

  • Offer a course for free, so any signed-in student can enrol with one click and no payment.
  • Sell a course for a one-time price, with an optional timed sale price.
  • Sell time-limited access, where a purchase grants entry for a fixed number of days and then expires.
  • Sell a course as a recurring subscription, billed per month or per year.
  • Gate a course behind a membership plan, so only members whose plan includes it can enrol.
  • Offer both paths at once: let students either buy the course outright or reach it through their membership.
  • Run a closed course that is never sold on your site, granting access from an outside system instead. See Closed Course Pricing.
  • Run an invite-only course that is never sold at all, adding the students yourself -- the model for internal training. See Invite-Only Courses.

Pricing and Access Models first view

How to use it

Step 1 -- open the Pricing & Access section

Edit the course and open the Pricing & Access section of the course editor. You will see the Pricing model control and the price fields.

Step 2 -- pick a pricing model

Learnomy supports eight pricing models. Each maps to one of five access types under the hood, so the catalog, checkout, and membership surfaces all agree:

Pricing model What the student does Access type stored
Free Enrols for free, no payment free
One-time Buys once, keeps lifetime access paid
Fixed-term Buys once, access expires after a set number of days paid
Recurring Subscribes per month or per year to keep access paid
Members only Enrols only if their membership plan includes the course membership
One-time or members Buys the course outright OR reaches it through membership paid_or_membership
Closed Cannot buy here; you grant access externally closed
Invite only Cannot buy here; you add the students yourself closed

There is no Stripe Price ID or PayPal Plan ID to create or paste anywhere on a course. This is the most common thing owners come looking for, because several other LMS plugins work that way. Learnomy does not: you enter a price here, and the gateway's product is created from it when the first purchase is made. That applies to the Recurring model too, where the expectation is strongest.

If you are following an older guide or a screenshot showing a Payment Provider Mapping accordion with an ID field, it was removed on purpose - a hand-typed ID is a typo that only surfaces at checkout. Setting the price is the whole job. See Payment Gateways.

Step 3 -- set the price fields

The visible price fields depend on the model you chose:

  • One-time and fixed-term courses use the regular Price, with an optional Sale price inside a Sale starts / Sale ends window.
  • Fixed-term courses also read an access-duration value (in days); when set, each enrollment is stamped with an expiry date that many days out.
  • Recurring courses use a recurring amount plus an interval of month or year.
  • Free, members only, closed, and invite only courses show no price -- switching a course to free also zeroes any lingering price and clears the sale price automatically.
  • Invite only shows no URL fields either. Closed courses can link out to wherever the course is sold; an invite-only course is not sold anywhere, so there is nothing to link to.

Step 4 -- save

On save, Learnomy reconciles the two columns so they never drift: the model you picked sets the matching access type, and a free course can never keep a stale price.

Settings and options

  • Sales. A sale price only counts as active when it is positive, below the regular price, and the current time falls inside the sale window. Every surface (single course page, catalog card, price facet) reads the same rule, so they cannot show different prices.
  • Fixed-term expiry. Access expiry is computed from the course's access-duration days at enrollment time. Only fixed-term courses expire; every other model grants lifetime access by default.
  • Pricing lock after purchase. Once at least one student holds a non-pending paid or subscription enrollment, the course form refuses to rewrite the pricing model. Existing paying customers must not be moved into a half-state; clone the course to relaunch it with new pricing. Free enrollments do not lock the model.
  • Access-type filter. The catalog can be filtered by access type, so students can narrow to free or paid courses.

For developers

Model (Learnomy\Models\Course) is the single source of truth for the pricing vocabulary -- never hardcode the maps:

  • Course::ACCESS_TYPES / Course::PRICING_MODELS -- the two canonical option lists.
  • Course::MODEL_ACCESS_MAP / Course::ACCESS_PRIMARY_MODEL -- the maps between the two dimensions.
  • Course::model_for_access( $access_type ) / Course::access_for_model( $model ) -- convert between them.
  • Course::pricing_model( $course ) -- the resolved model for a course row, guarding against a stale pricing_model='free' column.
  • Course::is_free() / is_one_time() / is_fixed_term() / is_recurring() / is_members_only() / has_member_path() / requires_purchase() / is_closed() -- predicate helpers that take a stdClass course row.
  • Course::is_closed( $course ) answers the ACCESS question -- may a visitor enrol themselves, may this be sold, does the external-enrolment webhook manage it. It is true for both closed and invite_only, because they are the same access rule. Ask it through MODEL_ACCESS_MAP, never by comparing pricing_model to the string 'closed': that comparison answers "no" for an invite-only course and is how an "Enroll Now" button once appeared under the words "Invite only".
  • Course::is_invite_only( $course ) answers the WORDING question, which is the only place the two models differ. A closed course is provisioned somewhere else, so "External" and "Enrol externally" describe it; an invite-only course is provisioned right here by the owner, and those words describe nothing.
  • Course::is_on_sale( $course ) / Course::effective_price( $course ) -- sale state and the price the buyer actually pays.
  • Course::compute_access_expiry( $course, $from = null ) -- the expiry DateTimeImmutable for a fixed-term course (null for every other model).
  • Course::recurring_amount( $course ) / Course::recurring_interval( $course ) -- per-period price and month / year interval for recurring courses.
  • Course::lock_pricing_model_if_purchased( $course_id ) -- true when a paid/subscription enrollment locks the model.

REST: the courses controller accepts access_type (enum including paid_or_membership) and the pricing fields on create/update.

Course Completion Rules

A course's completion rule decides when a student counts as finished -- the moment their enrollment flips to completed, progress hits 100%, and any certificate is awarded. You choose one rule per course from four options.

What you can do

  • Complete a course when the student finishes every lesson.
  • Complete a course only when every lesson is done AND the student passes the course's quiz.
  • Complete a course when the student finishes a set percentage of the content, so a long course can finish before the very last lesson.
  • Keep completion manual, so an admin or instructor marks each student complete by hand and the course never auto-completes.

Course Completion Rules first view

How to use it

Step 1 -- open the Completion & Certificate section

Edit the course and open the Completion & Certificate section of the course editor.

Step 2 -- choose a Completion Requirement

The Completion Requirement dropdown offers four rules:

  1. All Lessons Completed -- the enrollment completes when every lesson in the course is marked complete (progress reaches 100%).
  2. Lessons + Final Quiz -- every lesson must be complete AND the student must have at least one passing attempt on a quiz scoped to the course (or one of its lessons). Practice and survey quizzes do not gate completion.
  3. Percentage of Content -- the enrollment completes when the student's progress reaches the percentage you set (see step 3). Useful when the last few lessons are optional.
  4. Manual Completion -- the course never auto-completes. Only an admin or instructor marks a student complete.

Step 3 -- set the percentage (percentage rule only)

When you pick Percentage of Content, a Completion Percentage slider appears. Set the minimum share of lessons a student must finish (1-100). At that threshold the course completes; it does not wait for the last lesson.

Step 4 -- save

Save the course. From then on, whenever a student finishes a lesson or submits a quiz, Learnomy re-checks the rule and completes the enrollment the moment it is satisfied. Completion fires whether the last action was a lesson finish or a quiz pass -- both paths converge on the same check.

Settings and options

  • Where completion is re-checked. Completion is evaluated on two events: a lesson being marked complete, and a quiz being submitted. Either can be the one that finishes the course, so a lessons_and_quiz course that had all lessons done completes the instant the quiz is passed.
  • Manual never auto-completes. With the manual rule, the auto-completion path is skipped entirely; nobody is completed until an admin or instructor does it.
  • Certificates ride on completion. If the course awards a certificate, it is issued when the completion rule is met -- so the rule you pick also controls when the certificate lands.
  • Per-lesson enforcement is separate. The Completion Requirement decides when the whole course finishes. How strictly each individual lesson's watch-time / video-percent / scroll / quiz-pass rules are enforced is a separate control (Per-Lesson Rule Enforcement: inherit / strict / advisory / off).

For developers

Service (Learnomy\Services\Progress_Service):

  • mark_lesson_complete( $user_id, $lesson_id, $bypass_rules, $time_spent_seconds, $client_signals ) -- marks a lesson complete, rolls up course progress, and completes the enrollment when the rule is satisfied.
  • maybe_complete_course( $user_id, $course_id ) -- idempotent re-check called from both the lesson-complete and quiz-submit paths; returns true when the call resulted in completion.
  • calculate_course_progress( $user_id, $course_id ) -- the 0-100 progress figure the rules read.

The lrn_courses.completion_type column holds one of all_lessons, lessons_and_quiz, percentage, manual; completion_value holds the percentage threshold (1-100) for the percentage rule.

Hooks: learnomy_lesson_completed (fires per lesson), learnomy_course_completed (fires when an enrollment completes). REST: POST /learnomy/v1/progress/sync posts { item_type: 'lesson', item_id, status: 'completed' } and runs the same completion logic.

Course Prerequisites

Prerequisites let you require a student to finish one or more other courses before they can enrol in this one. It is how you enforce a learning order instead of letting students jump straight to the advanced material.

What you can do

  • Require one or several other courses to be completed before a student can enrol in this course.
  • Block enrollment for any student who has not finished all of the required courses.
  • Show the required courses on the course page as links, so a student can discover and take them first.
  • Leave prerequisites empty to keep a course open to everyone.

Course Prerequisites first view

How to use it

Step 1 -- open the Prerequisites section

Edit the course and open the Prerequisites, Difficulty & Target section of the course editor. The prerequisite picker appears when you are editing an existing course and there is at least one other course to choose from.

Step 2 -- pick the required courses

Use the search box to filter the course list, then tick each course a student must complete first. You can select as many as you need. The course you are editing is never shown in its own list.

Step 3 -- save

Save the course. Prerequisites are stored as a list of course IDs on the course.

Step 4 -- what the student sees

On the course page, the required courses render under a Prerequisites heading as links, so a student can go take them. When a student tries to enrol, Learnomy checks that they hold a completed enrollment for every required course. If any is missing or only in progress, enrollment is refused with a message to complete the prerequisites first.

Settings and options

  • Completed, not just enrolled. A prerequisite is satisfied only by a completed enrollment. An active (in-progress) enrollment in a required course does not unlock the target course.
  • All required courses must pass. Every course in the list must be completed; there is no "any one of" mode.
  • Enforced everywhere. The prerequisite check runs at the service layer, so it applies to REST, admin, and CLI enrollment paths alike, not just the front-end Enrol button.
  • Empty means open. A course with no prerequisites is open to everyone (subject to its pricing and access model).

For developers

Service (Learnomy\Services\Course_Service):

  • check_prerequisites( $user_id, $course_id ) -- returns true when the user holds a completed enrollment for every prerequisite course, or the course has none. Called from Enrollment_Service::enroll() before any enrollment row is created; a failed check makes enroll() return 0.

Storage: lrn_courses.prerequisites holds a JSON array of course IDs. The courses REST controller accepts a prerequisites array on create/update and stores it as JSON.

REST: POST to the enrollments endpoint returns a validation error ("You must complete the required prerequisite courses first.") when prerequisites are unmet.

Lesson Content Types

Each lesson has a content type that decides how it is delivered -- a text body, an audio player, a PDF, a downloadable file, or a scheduled live session. This page covers the non-video types. Video and embedded content have their own page: see Embedding Video and External Content.

What you can do

  • Deliver a written lesson with the Text type (the default).
  • Play audio-only lessons in an inline Audio player.
  • Show a PDF inline in the lesson, with an "Open in a new tab" fallback link.
  • Offer a Download lesson -- a single button that downloads a file (zip, ebook, worksheet).
  • Run a scheduled Live session that links out to Zoom, Meet, or Teams, with before / during / after states.
  • Rely on a graceful empty state: when a media type has no URL set, the student sees a clear "not available, contact your instructor" message instead of a broken player.

Lesson Content Types first view

How to use it

Step 1 -- add or edit a lesson

Open a course, add or edit a lesson, and find the Content type dropdown in the lesson editor.

Step 2 -- choose the content type

Pick one of the non-video types:

  • Audio -- an audio-only lesson.
  • PDF -- a document shown inline.
  • Download -- a single downloadable asset.
  • Live -- a scheduled live session.

(#embedding-video-and-content-lrn).)

Step 3 -- set the media URL

Audio, PDF, and Download all read the lesson's media URL field -- paste the file or document URL there. A Live lesson does not use this field: it has its own fields, covered in Step 4.

Step 4 -- for a Live lesson, fill the three live fields

A live lesson has three dedicated fields, both in the lesson editor (Learnomy → Lessons → edit a lesson) and in the Pro front-end course builder:

  • Join link -- the Zoom, Google Meet, or Teams meeting URL.
  • Starts at -- the date and time the session takes place, in the site's timezone. Leave it blank for an always-open room.
  • Runs for (minutes) -- how long the Join now button stays available after the start. Leave it blank and the session runs for 60 minutes.

Do not put the session start in the Schedule card's Available date. That field is the drip schedule -- it decides when the lesson unlocks, not when the session happens. Setting it locks the lesson, so students cannot open it and see the countdown at all. Leave it blank on a live lesson.

Step 5 -- save and check the player

Save the lesson and open it in the student player to confirm the right player renders.

Settings and options

  • One shared media field. Audio, PDF, and Download all reuse the lesson's single media-URL column. That is why you only ever fill one URL field for those types.

  • Live sessions have their own fields. A live lesson is authored with Join link, Starts at, and Runs for (minutes) instead of the generic media-URL field. All three are available in wp-admin, in the Pro front-end course builder, and over the REST API.

  • Empty-state handling. If a media type is selected but no URL is set, the lesson renders a labelled empty state ("Audio not available", "PDF not available", and so on) rather than a broken element.

  • Access gating is the same for every type. Content type only changes how the lesson renders. Whether a student can open the lesson at all is decided by course enrollment, the course access model, and any content drip -- not by the content type. A preview lesson stays previewable and a gated lesson stays gated across all types.

  • Live session window. Runs for decides how long the Join now button stays available, counting from Starts at. Leave it blank and the session runs for 60 minutes. That window drives the before / during / after states below.

  • What the student sees in a live lesson. Three states:

    • Before the start -- a countdown ("Starts in 120 minutes") with the Join now button disabled.
    • From the start until the end of the window -- an enabled Join now button that opens the join link in a new tab.
    • After the window -- "This session has ended".

    With no Starts at set, the lesson is treated as live now, so the Join button is enabled straight away.

  • Available date is not the session start. The Schedule card's Available date is the drip schedule -- when the lesson unlocks. It is a separate, unrelated field, and using it for a live start locks the lesson so students never see the countdown. The lesson editor says as much in the field's own help text. On a live lesson, normally leave it blank.

Standards content types (admin only)

If a compatible Pro or third-party extension is active, a lesson can also be delivered through a standards format: SCORM, xAPI, LTI, QTI, or H5P. These are registered the same way as any other content type, so they appear in the admin Content Type dropdown and in the Lessons list with their correct technical name.

Students never see the technical name. On learner-facing surfaces -- the course card's content type badge, and anywhere else a format is announced to a student -- a standards type resolves to a plain label or no badge at all, so a student sees "a lesson," not "a SCORM lesson." Only wp-admin shows the accurate classification.

For developers

Model (Learnomy\Models\Lesson):

  • Lesson::CONTENT_TYPES -- the canonical list: text, video, audio, pdf, embed, live, download, mediashield.
  • Lesson::content_types() -- the filterable, selectable list read by the wp-admin editor, the Pro front-end builder, and REST validation.
  • Lesson::content_type_label( $type ) / Lesson::content_type_icon( $type ) -- the human label and Lucide icon for a type. This is the admin-accurate label: text, video, audio, pdf, embed, live, download, mediashield, and the standards types scorm, xapi, lti, qti, h5p all resolve to their correct name here (admin lists, the lesson editor dropdown).
  • Lesson::learner_content_type_label( $type ) -- the learner-facing label. Identical to content_type_label() for the everyday formats, but returns an empty string for any type in Lesson::TECHNICAL_CONTENT_TYPES (scorm, xapi, lti, qti, h5p), so a customer surface never announces the delivery technology. Course::attach_content_type_summary() (the batch helper that decorates catalog cards with their dominant content type) reads through this method, not content_type_label().

The lrn_lessons.content_type column stores the type; video_url is the generic media-URL column reused across audio / pdf / download / embed / live (its name predates the multi-type scope) -- for a live lesson it holds the Join link.

A live session's schedule has its own columns: lrn_lessons.scheduled_start_at (when the session happens) and lrn_lessons.live_duration_minutes (how long the Join button stays up; blank means the default). These are distinct from available_date, which is the drip column -- when the lesson unlocks.

Filters and actions: learnomy_lesson_content_types (register a new selectable type), learnomy_lesson_content_type_label (name it across surfaces), learnomy_technical_content_types (filter the list of types treated as internal/technical -- defaults to Lesson::TECHNICAL_CONTENT_TYPES; remove a type from this list to show its label to learners too), learnomy_lesson_render_content (render a Pro-registered type's player -- fires only for types the built-in chain does not handle), learnomy_live_session_duration (change the 60-minute default used when Runs for is blank), and learnomy_lesson_live_post_actions (inject a recording or contact link after a live session ends).

Course Reviews

Reviews let enrolled students rate a course from 1 to 5 stars and leave a short written review. Approved reviews feed the course's average rating and review count, which show on the course page and catalog cards.

What you can do

  • Let enrolled students leave a star rating (1-5) with a title and a written review.
  • Require a student to have completed at least one lesson before they can review, so reviews come from people who actually started the course.
  • Allow only one review per student per course.
  • Moderate reviews: approve, reject, or mark as spam from the admin Reviews screen, or auto-approve them site-wide.
  • Let an instructor reply to a review.
  • Keep the course's average rating and review count accurate automatically, recomputed whenever a review's status changes.

Course Reviews first view

How to use it

Step 1 -- turn reviews on

Reviews show on a course page only when both gates pass: the site-wide Enable Reviews setting is on, and the course's own reviews toggle is not switched off. This lets you disable reviews everywhere at once while each course keeps its own preference for later.

Step 2 -- a student leaves a review

A signed-in student can submit a review only when all of these are true:

  1. They are enrolled in the course.
  2. They have completed at least one lesson in it.
  3. They have not already reviewed it.

They pick a star rating (1-5), add a title, and write the review. If any condition fails, the form is not offered and the submission is refused with a clear message.

Step 3 -- the review is moderated

New reviews are held as pending by default, so nothing publishes without your say-so. Approve, reject, or mark reviews as spam from the admin Reviews screen. Turn on Auto-approve reviews in settings to publish new reviews immediately instead.

Step 4 -- ratings update automatically

Only approved reviews count toward the average. Whenever a review is submitted, approved, rejected, marked spam, or deleted, Learnomy recomputes the course's average rating and review count, so the catalog and course page stay in sync without a cron pass.

Settings and options

  • Two-gate visibility. Site-wide Enable Reviews plus a per-course toggle. Both must allow it for the reviews surface to render.
  • Auto-approve. Off by default -- fresh sites moderate reviews until you opt into auto-publish. When on, new reviews are approved on submission.
  • One review per student. Enforced per (student, course); a student cannot stack multiple reviews on one course.
  • Rating is clamped. The star rating is always stored within 1-5.
  • Instructor reply. An instructor can attach a single reply to a review.
  • Denormalized totals. Each course stores its own avg_rating and review_count; these are the numbers shown on cards and the course page, and only approved reviews move them.

For developers

Service (Learnomy\Services\Review_Service):

  • submit( $user_id, $course_id, $rating, $title, $content ) -- enforces enrolled + one-lesson-done + not-already-reviewed, clamps the rating to 1-5, sets status to approved or pending from the auto-approve setting, and recomputes the rating.
  • approve() / reject() / spam() -- status flips from the moderation screen; each recomputes the course rating and fires its hook.
  • reply( $review_id, $reply ) -- store an instructor reply.
  • recalculate_rating( $course_id ) -- recompute avg_rating and review_count from approved rows.
  • Review_Service::auto_approve_enabled() -- reads the auto_approve_reviews setting (default off).

Model gate: Course::reviews_enabled_for( $course_id ) -- the two-gate render check (site setting + per-course flag).

Hooks: learnomy_review_submitted, learnomy_review_approved, learnomy_review_rejected, learnomy_review_marked_spam.

REST (reviews controller): GET /learnomy/v1/courses/{course_id}/reviews (public list), POST /learnomy/v1/courses/{course_id}/reviews (submit, eligibility-gated), PATCH /learnomy/v1/reviews/{id}, plus admin status and bulk endpoints under /admin/reviews.

Course Access and Enrollment

Enrollment is the record that a student has access to a course. Learnomy grants that access based on the course's access type (free, paid, membership, or both) and keeps one enrollment row per student, per course, per source, so a student who reaches a course through more than one path never loses access when one path ends.

What you can do

  • Let a signed-in student enrol in a free course with one click and no payment.
  • Route paid and members-only courses to checkout or the membership page instead of a plain Enroll button, so nobody reaches a course without the access they paid for.
  • Grant access by hand from the admin, either one student at a time or in bulk from a list of emails.
  • Remove a student's access to a single course, or clear all of a student's enrollments at once.
  • Let students leave a free course they joined themselves, while paid and membership access stays with their account.
  • Require students to finish prerequisite courses before they can enrol.
  • Keep membership access and a one-time purchase side by side on the same course, because each source is its own enrollment row.
  • Have fixed-term (time-limited) enrollments expire automatically once their access window ends.

Course access and enrollment first view

How to use it

Step 1 -- set how the course is reached

The Enroll button a student sees is driven by the course's access type. Edit the course, open the Pricing & Access section, and pick a pricing model. Free courses show an instant Enroll button; paid and members-only courses show a Buy or membership CTA instead. See Pricing and Access Models for the full model list.

Step 2 -- let students enrol themselves

On the single course page (/courses/<slug>/) a student who is not yet enrolled sees the enroll card in the sidebar:

  • Free course -- an Enroll Now button that enrols them instantly and drops them into the course.
  • Paid course -- a Buy button that routes to checkout; the enrollment is created only after payment completes.
  • Members-only course -- a link to the membership page; the enrollment is created from their active membership.

You can also drop an enroll button anywhere on the site with the shortcode [learnomy_enrollment_button course_id="123" text="Enroll Now"]. It renders the right CTA for the course and shows an "Already enrolled" state when the current user already has access.

Step 3 -- grant access by hand (admin)

When you need to enrol someone yourself, open the course's overview page in wp-admin and use the Enroll a student control. Enter the student's email or username (there is no dropdown of every user, so it stays fast on large sites). This creates a manual-source enrollment. You can also enrol a student into a course from that student's detail page.

Step 4 -- enrol a list in bulk

To onboard a group, use the course's bulk-enroll control and paste or upload a list of emails. Unknown emails are provisioned as new accounts with a set-password invite. The list is processed in chunks and capped per request so a large paste never times out. See Bulk Enroll from a List.

Step 5 -- remove access

To revoke access, remove the student's enrollment from the Students screen. You can remove a single course enrollment, or clear every enrollment a student holds in one action.

The student disappears from the course roster immediately, the student count drops, and they lose access to the lessons. They are no longer counted in the course's average progress either.

The enrollment record itself is kept, marked cancelled. That record is what their quiz attempts, progress and certificates hang from, and on a paid course it is part of the history of the sale, so deleting it outright would erase things you may need later - a refund enquiry, a certificate someone asks you to confirm, or your own revenue records. Removing access stops the enrollment counting; it does not shred it.

If the same person enrolls again later, they get a new enrollment and start fresh. The old cancelled record stays where it is.

Step 6 -- let students leave on their own

Students do not need you for a free course they joined themselves. The course page carries a Leave this course link that cancels their own enrollment, so routine "please take me off this" requests never reach your inbox.

Paid, membership and admin-granted enrollments are deliberately not self-removable. Leaving a course someone paid for would forfeit their access without refunding anything, and membership access ends with the membership, not with a button on a course page. The rule lives in one place on the server, so the link, the REST route and the mobile app all agree: the link is not rendered, and a request to remove one of those rows is refused with a 403. Those removals stay with you, on the Students screen, where you can pair them with a refund.

Full student-facing walkthrough: Leaving a course.

Settings & options

  • Access type decides the enroll path per course (free / paid / membership / paid_or_membership). The public enroll action never trusts a source sent by the browser; the server derives it from the course's access type plus the student's verified state (a completed transaction or an active membership).
  • Prerequisites -- when a course requires prerequisite courses, enrollment is refused until the student has completed them. Configure these in the course editor; see Course Prerequisites.
  • Enrollment sources -- each enrollment carries a source: free, purchase, membership, subscription, manual, or webhook. The table is unique on (user_id, course_id, source), so a student can hold both a membership enrollment and a purchase enrollment on the same course without one overwriting the other.
  • Enrollment status -- active, completed, cancelled, expired, or pending. Fixed-term enrollments are moved to expired automatically when their access window closes.
  • External enrollment -- courses set to the closed model are enrolled from an outside system through the incoming webhook. See Incoming Enrollment Webhook.

For developers

Service (Learnomy\Services\Enrollment_Service):

  • enroll( int $user_id, int $course_id, string $source = 'free', array $options = [] ) -- the single write path; checks prerequisites, dedupes on source, returns the enrollment id or a WP_Error. Adapters call this directly with their own verified source.
  • unenroll( int $enrollment_id ) -- revokes one enrollment. This is a soft cancel: the row is set to cancelled and kept, so the attempts, progress, certificates and transaction that reference it stay intact.
  • guard_self_unenroll( object $enrollment ) -- the single home of the "who may leave" rule. Returns a WP_Error (403) for any enrollment whose source is not free. The course page button, the REST route and the learnomy/unenroll ability all consult it, so they cannot drift apart. Callers who can manage the course are trusted and skip it.
  • Enrollment::current_statuses() -- the single definition of "counts as enrolled" (active, completed). The roster, the count and the average progress all read it, so they cannot disagree about who is on a course. Filter it with learnomy_enrollment_current_statuses if your site adds a lifecycle status of its own (paused, suspended) and needs to say whether it still counts.
  • can_access( int $user_id, int $course_id ) -- delegates to Permission_Engine::can( $user_id, 'access_course', $course_id ).
  • retake( int $user_id, int $course_id ) -- reopens a completed course when the per-course allow_retake setting is on.

Permissions and capabilities:

  • Access decisions run through Permission_Engine::can( $user_id, 'access_course', $course_id ) -- never a direct capability check.
  • The learnomy_can_access_course filter (result, user_id, course_id) lets add-ons override the access decision.
  • Self-enrollment requires the learnomy_enroll capability.

Hooks fired:

  • learnomy_student_enrolled -- ( enrollment_id, user_id, course_id, source ). Fires on every enrollment; notifications, lesson-unlock scheduling, and the outgoing webhook listen here.
  • learnomy_enrollment_expired -- ( enrollment_id, user_id, course_id ). Fired by cron when a fixed-term enrollment lapses.
  • learnomy_course_completed -- ( enrollment_id, user_id, course_id ).
  • learnomy_course_retaken -- fired when a completed course is reopened.

REST (learnomy/v1):

  • POST /courses/{course_id}/enroll -- self-enroll for the logged-in user. source and plan_id are deliberately not accepted; the server derives the source. Paid or members-only courses without proof return 402 with a checkout_url or membership_url.
  • DELETE /enrollments/{id} -- unenroll a single row. A student calling this on their own row passes the self-unenroll guard only when the row's source is free; anything else returns 403. Anyone who can manage the course (its instructor, or an administrator) bypasses the guard and may remove any row.
  • DELETE /students/{user_id}/enrollments -- remove every enrollment for a user (cap manage_settings).
  • POST /admin/courses/{id}/enroll -- admin enrol by email/username (manual source).
  • POST /admin/courses/{id}/bulk-enroll -- admin CSV/paste bulk enroll, capped by learnomy_bulk_enroll_max.
  • POST /admin/students/{id}/enroll -- admin enrol a student into a course from the student detail page.

Course Difficulty and Target Audience

Set a difficulty level and describe who a course is for so students can tell at a glance whether it fits them. The difficulty shows as a badge on the course page and the target audience shows as a short "Who This Course Is For" section.

What you can do

  • Tag each course with one of four difficulty levels: Beginner, Intermediate, Advanced, or Expert.
  • Leave difficulty unset so no badge appears when a level does not apply.
  • Write a short target audience note in plain language, for example "Intermediate developers looking to master React hooks."
  • Show the difficulty as an outline badge on the course page header, next to rating and duration.
  • Show the target audience as a "Who This Course Is For" block in the course body.
  • Set both fields from the course editor, or through the REST API when importing or syncing courses.

Course difficulty and target audience first view

How to use it

Step 1 -- open the course settings

Edit the course and open the Prerequisites, Difficulty & Target section of the course editor. It carries a target icon and a short line: "Describe who this course is for, what they should know going in, and what they will leave with."

Step 2 -- choose a difficulty

Use the Difficulty dropdown and pick one of Beginner, Intermediate, Advanced, or Expert. Leave it on Select if you do not want a difficulty badge on the course page.

Step 3 -- describe the target audience

In the Target Audience box, write one or two sentences about who the course is for. This is free text, so keep it short and specific about the reader's role or starting point.

Step 4 -- save and check the course page

Save the course, then open the public course page. The difficulty appears as an outline badge in the course header, and the target audience appears under a "Who This Course Is For" heading in the body.

Settings & options

  • Difficulty (difficulty) -- stored on the course record. Accepted values are beginner, intermediate, advanced, and expert; an empty value means no badge.
  • Target Audience (target_audience) -- stored in the course settings. Plain text, rendered as the "Who This Course Is For" section.

Both fields sit in the same editor section as Language, Estimated Duration, What you will learn, and Prerequisites, but each is independent and optional.

For developers

  • REST route: POST/PUT /learnomy/v1/courses and /learnomy/v1/courses/{id} accept difficulty (string, sanitized with sanitize_text_field) and target_audience (text setting).
  • The difficulty label shown on the course page is resolved in Single_Course_View_Data, which maps the stored difficulty value to its display label (Beginner, Intermediate, Advanced, Expert).

Course SEO and Sharing

Every course page already produces a sensible search snippet and a rich link preview with nothing configured: the title comes from the course title, the description from the start of the course description, and the share image from the course thumbnail.

When you want to write those yourself, each course has a Search & Sharing section with five fields. Leave any of them empty and that value keeps deriving exactly as it did before, so turning this on for one course changes nothing about the rest.

Where to find it

Open a course for editing and expand Search & Sharing. It is on all three editors -- the wp-admin course editor, the front-end course builder (Pro), and the REST API -- so whichever way you author, the same five fields are there.

The fields

Field What it controls When left empty
Search title The <title> of the course page and the og:title in a shared link The course title
Search description The meta description and og:description The first part of the course description
Share image URL The image a shared link shows (og:image) The course thumbnail, then the site logo
Share image (media ID) The same, picked from the media library Used only when no Share image URL is set
Search visibility Whether search engines may index the course Visible in search

A couple of details worth knowing:

  • Search title also changes the page title, not just the social preview. If you set one, you are naming the page as search will show it.
  • Search description is not truncated. Derived descriptions are capped at 155 characters so machines get a clean snippet, but a description you typed is printed in full -- if you wrote 200 characters, you meant them.
  • Share image URL wins over the media ID. This mirrors how the course thumbnail already resolves, so a course imported with an external image URL keeps working without an attachment in the library.

Search visibility

Search visibility is a fixed list, not a free-text robots field:

  • Visible in search (default) -- no robots directive is printed.
  • Hide from search -- prints noindex.
  • Hide from search and do not follow its links -- prints noindex,nofollow.

Free text is deliberately not offered. A typo such as no-index is silently ignored by crawlers, so you would believe a course was hidden while it stayed indexed.

Hiding a course from search is not access control. A noindex course is still public to anyone with the link. To actually restrict who can open it, use a pricing model that gates access -- see Pricing and Access Models and Invite-Only Courses -- or turn on Private site in General Settings.

If you also run Yoast SEO or Rank Math

These fields are still the ones that apply. Courses live in Learnomy's own tables rather than as WordPress posts, so Yoast and Rank Math cannot see a course at all and offer no snippet editor for one. Learnomy keeps ownership of the head tags on its own pages for exactly that reason. See SEO Plugin Handoff for what each plugin owns where.

For developers

The five values are columns on the course row -- seo_title, meta_description, og_image_url, og_image_id and meta_robots -- and are readable and writable through the course REST endpoints alongside every other course field.

  • learnomy_meta_description_max_length -- filter (int, default 155) for the cap applied to derived descriptions. An authored Search description is never capped, regardless of this value.
  • learnomy_seo_yield_to_plugin -- filter (bool) controlling whether Learnomy suppresses its canonical and Open Graph tags in favour of another SEO plugin. See SEO Plugin Handoff.

Quizzes and Grading

Learnomy quizzes test student knowledge, gate course progress, and generate scores that feed certificates and analytics. Questions are stored in a central bank and added to quizzes as slots so one question can appear in many quizzes.

Building a Quiz

Quizzes let you test what students have learned. You can attach a quiz to a specific lesson inside a course, place it at the end of a course section, or create a standalone quiz that lives at its own URL.

Overview

Quizzes live under Learnomy in your WordPress admin sidebar. Go to Learnomy > Quizzes to see all your quizzes and Learnomy > Question Bank to manage the questions that populate them.

A quiz is a container. Questions are stored separately in the Question Bank and added to a quiz through "slots". This means one question can appear in multiple quizzes, and editing a question updates it everywhere.

Quizzes list

Creating a quiz

  1. Go to Learnomy > Quizzes.

    Quizzes list header

  2. Click Add New Quiz in the top-right corner. The quiz editor opens.

  3. Enter a Quiz Title. The URL slug is generated from the title automatically. You can override it in the Quiz URL slug field -- use lowercase letters, numbers, and hyphens only.

  4. Write an optional Description using the rich text editor. Students see this on the start screen before they begin.

  5. Choose a Mode:

    • Graded -- scores the quiz and counts toward course completion. This is the default.
    • Practice -- students can take it freely without it affecting their progress.
    • Survey -- collects answers without grading them.
  6. Click Create Quiz to save. The quiz is created in Draft status, hidden from students until you publish it.

    Quiz editor

Attaching a quiz to a course

A quiz can be standalone (accessible at /quiz/<slug>/) or attached to a course. To attach it:

  1. In the quiz editor, open the Basic section.
  2. Select a Course from the dropdown. The Section dropdown becomes active.
  3. Select the section the quiz belongs to.
  4. Optionally select a Lesson to tie the quiz to a specific lesson.

If you leave Course empty, the quiz is standalone and appears at /quiz/<slug>/.

Where the quiz appears

The Lesson field decides where students see the quiz, so pick it deliberately:

Lesson field Where the quiz appears
A specific lesson Indented directly under that lesson in the course outline and in the player sidebar, and as a "Take quiz" prompt at the end of that lesson.
End of section After every lesson in its section, in both the course outline and the player sidebar.
No course selected Standalone, at its own URL. Not listed in any course outline.

A quiz attached to a course but to no section appears under Final assessment, after the last section. That is the right place for a final exam.

If you later set the bound lesson back to draft, or move that lesson to another section, the quiz falls back to the end of its section rather than disappearing. It stays visible so you can re-point it.

Adding questions via slots

The Questions card is available right away -- you do not need to save the quiz first. It sits below the settings, whether you are creating a new quiz or editing an existing one.

Quiz editor slots

  1. Enter the Quiz Title at the top of the editor. If you have not saved the quiz yet, the title is the only thing required before you can start adding questions.
  2. Click Add questions from bank. A picker modal opens, showing your existing questions. Use the search field or type filter to find what you need.
  3. Check one or more questions and click Add selected.
    • If the quiz has not been saved yet, Learnomy saves it automatically at this point (using the title and whatever settings you have filled in so far), then attaches your selected questions in the same step. There is no separate save-and-reopen round trip.
    • If the quiz is already saved, the selected questions are attached immediately.
  4. The questions appear as a numbered slot list. Drag the handle on the left of any row to reorder them.
  5. Each slot has a Marks field. Click the field in the row to change the point value for that question in this quiz (this does not change the question itself).
  6. To remove a question from the quiz, click the trash icon on its row.

To create a new question directly from the quiz editor, click Create new question. It opens the question editor in a new tab. When you return and refresh the picker, the new question is available.

Quiz settings

Open the Settings card to configure all quiz behaviour. Settings are grouped into collapsible sections.

Quiz editor settings

Grading section

Setting What it does
Passing score Slider from 0 to 100%. Default is 70%. Students who score at or above this threshold pass the quiz.
Time limit Enter minutes and seconds. Leave at 0 for no time limit. The timer is shown to the student during the quiz.
Attempt limit How many times a student can take the quiz. 0 means unlimited. Default is 1.
Grading method Which attempt counts when a student has multiple: Highest, First, Latest, or Average.

Behaviour section

Setting What it does
Shuffle questions Randomises question order on each attempt.
Shuffle answers Randomises the order of answer choices.
Navigation Free lets students jump between questions. Sequential shows one question at a time and does not allow going back.
Feedback timing Controls when students see correct answers and per-question feedback. See the table below.

Feedback timing options:

Option Student experience
After submit Full feedback after the whole quiz is submitted.
After each question Feedback shown immediately after each question is answered.
Immediate Feedback shown the moment an answer is chosen.
After attempt finalizes Feedback held until the attempt is fully graded (relevant for quizzes with essay questions).
Only after passing Students who pass see full feedback; those who fail see score only.
Never Answers stay hidden.

Display template

Choose how the quiz looks to students. Two templates are free; three require Learnomy Pro.

Template Description Availability
Focused One question at a time. Good for high-stakes quizzes. Free
Classic All questions on one scrollable page. Free
Sectioned Questions grouped into named sections. Pro
Conversational Chat-style one-question flow. Pro
Review board Full-page grid with a jump-to-question map. Pro

Access and Schedule section

Setting What it does
Access code Students must enter this code before starting. Leave blank for no gate.
Opens at Students see the quiz but cannot start until this date and time.
Closes at No new attempts are accepted after this date and time.
Status Draft hides from students. Published makes it visible. Archived keeps it accessible to enrolled students but removes it from search.

Attempts and Cooldown section

Setting What it does
Cooldown between attempts Minutes a student must wait after submitting before they can start again.
Max failed attempts Students who fail this many times are locked out until an instructor resets them. 0 means no lockout.

These settings apply to everyone taking the quiz. To give one student an extra attempt without changing the cap for the rest of the cohort, use Grant 1 attempt on their profile - see Quiz attempt limits. A quiz with no cap does not appear there, because there is nothing to grant.

Tips

  • The Questions card shows the total mark value across all slots. Review this before publishing so the passing score percentage translates to a sensible point threshold.
  • A new quiz defaults to Draft status. Change the Status field to Published when you are ready for students to see it.
  • Changing marks on a slot does not change the question -- it sets the value of that question in this quiz only. The same question can have different marks in different quizzes.
  • If a quiz has essay questions, set feedback timing to After attempt finalizes so students do not receive partial results while grading is still pending.

Question Types

Learnomy supports six question types out of the box. All six are graded automatically. Five advanced types -- matching, ordering, numeric, math expression, and calculated -- are available with Learnomy Pro and require the Pro advanced-quizzes module. Pro also renders LaTeX math in any question (see Math and formulas).

Overview

Question types are set per question in the Question Bank or the question editor. Changing the type on an existing question updates how the answer editor is displayed and which grading logic is applied. Historical attempt responses record the question version they answered, so old attempts are not affected by later edits.

Multiple choice (single correct answer)

The student picks one answer from a list of options. Only one answer can be marked correct. This is the default type when you create a new question.

Setting up answers:

  1. Write each answer choice in a separate answer row.
  2. Check the radio button next to the one correct answer.
  3. Optionally add per-answer feedback that is shown to the student when feedback is enabled on the quiz.

The question is graded as full marks if the correct option is selected, zero otherwise.

Multiple choice (multiple correct answers)

The student selects all correct answers from a list. More than one answer can be marked correct. The question uses partial credit: each answer carries a fraction value that represents its portion of the total marks for the question.

Setting up answers:

  1. Write each answer choice.
  2. For correct answers, set the fraction to a value between 0 and 1. Fractions across all correct answers should add up to 1.0 for full marks.
  3. For incorrect answers, you can leave the fraction at 0 or set a negative fraction to penalise incorrect selections.

True / False

A two-option question where the student picks either True or False. Mark one of the two options as correct.

This type is graded identically to a single-answer multiple choice question.

Short answer

The student types a short free-text response. The grader compares the response against one or more accepted answer strings using a case-insensitive exact match.

Setting up answers:

  1. Add one row per accepted answer. All variations that should be counted as correct need their own row.
  2. You can mark multiple rows as correct. The student's response is checked against all of them.

For a question where you want the answer to be graded by a human instead of automatically matched, use the Essay type.

Fill in the blank

The student fills in missing words in a sentence or paragraph. Blanks are defined by tokens in the question text.

Setting up the question text: Use tokens like {{blank1}}, {{blank2}} at each position where a blank should appear. For example:

The capital of France is {{blank1}} and its currency is {{blank2}}.

Setting up answers: Add one answer row per blank. Each row corresponds to the matching {{blank_N}} token in order.

The grader checks each blank independently using case-insensitive exact matching. Each correctly filled blank earns its fraction of the question's marks.

Essay

The student writes a longer free-text response. Essay questions are not graded automatically -- they go to the Grading inbox in the admin for a human reviewer to score and provide feedback.

A quiz attempt containing one or more essay questions stays in Submitted status until every essay response has been manually graded. Once the last response is graded, the attempt is finalized automatically.

Set quiz Feedback timing to After attempt finalizes when your quiz contains essay questions, so students do not receive a partial result while grading is still in progress.


Advanced question types (Pro)

Pro feature. Available in Learnomy Pro with the advanced-quizzes module active.

Matching

The student pairs items from two columns: a set of prompts and a set of responses. Each prompt has one correct response.

Setting up answers:

  1. Each answer row represents one pair. Write the prompt in the main content field and the matching response in the pair field.
  2. The quiz player displays prompts and responses shuffled so the student cannot rely on position.
  3. Each correctly matched pair earns its fraction of the question's marks.

Ordering

The student drags items into the correct sequence.

Setting up answers:

  1. Add one answer row per item in the sequence.
  2. Set the sort order on each row to indicate the correct position (1 = first, 2 = second, and so on).
  3. The quiz player presents the items in a shuffled order. The student drags them into the correct sequence.
  4. Scoring awards partial credit per correctly placed item based on the fraction assigned to each row.

Numeric

The student types a number. The grader compares it to your correct value, optionally within a tolerance, so close-enough answers still count.

Numeric question answer fields: Correct value and Accepted tolerance

Setting up the answer:

  1. Enter the Correct value (for example, 0.333).
  2. Set an Accepted tolerance if you want a range. Leave it blank for an exact match. A tolerance of 0.01 accepts any answer within 0.01 of the value, so 9.81 would accept 9.80 through 9.82.

The student sees a number field in the quiz. Grading is all-or-nothing: full marks when the answer is within tolerance, zero otherwise. Use this for calculations, physics constants, unit conversions, and any question with a single numeric answer where small rounding differences should be forgiven.

Math expression

The student types an algebraic expression. Unlike short answer, the grader does not look for matching text -- it checks whether the student's expression is mathematically equivalent to yours, so (x+1)^2 is accepted for x^2+2*x+1 even though the two strings differ.

Math expression answer fields: correct expression and an optional test range

Setting up the answer:

  1. Enter the correct expression. Use ^ for powers, * for multiply, parentheses for grouping, and functions such as sqrt, sin, cos, tan, ln, log, abs, min, max. Constants pi and e are recognised.
  2. Optionally set a test range. Answers are graded by evaluating both expressions at several random points within this range and confirming they agree everywhere; the default range is -7 to 7.

In the quiz the student sees a text field and types their expression. It renders nicely when you write the question stem with math delimiters.

A math-expression question in the quiz with a rendered formula and an answer field

The check is numeric, not a full computer-algebra system, so it reliably recognises equivalent algebra and trigonometric identities (for example sin(x)^2 + cos(x)^2 equals 1). For safety, expressions are evaluated in a sandbox that only understands the operators and functions above -- a quiz answer can never run code.

Calculated

Each student gets the same question with different numbers. You write the question text with {name} placeholders and define a set of variables with ranges; every attempt fills the placeholders with its own random values, and the student is graded against a formula evaluated on those exact values. This makes it easy to set one problem that everyone answers genuinely rather than copying a single answer.

Calculated question setup: an answer formula, a tolerance, and a variable repeater with Name, Min, Max and Decimals

Setting up:

  1. Write the question text with placeholders, e.g. A box holds {a} rows of {b} items. How many items in total?
  2. Enter the answer formula in terms of those variables, e.g. a*b. It uses the same operators and functions as the Math expression type.
  3. Add each variable with a name, a min and max, and how many decimal places to round to. Use Add variable for more, and the Ɨ button to remove one.
  4. Optionally set a tolerance if the answer is not a whole number.

When a student starts the quiz, the placeholders are replaced with concrete numbers (for example "A box holds 8 rows of 5 items.") and they type the result. The values are generated per attempt and the same values are used to grade, so each student is marked on exactly the numbers they saw, and the answer cannot be shared.

Matching, Ordering, Numeric, Math expression, and Calculated all require the advanced-quizzes extension to be active. On a Free installation these types are not available in the type selector.


Math and formulas

Pro feature. Rendered by the advanced-quizzes module.

You can write mathematical notation anywhere in a question -- the stem, the answer options, and the explanation -- using LaTeX delimiters. The quiz player renders it as typeset math.

A quiz question with a rendered LaTeX formula in the stem

Delimiters:

  • $$ ... $$ -- a display formula on its own centered line. Example: $$y = v_0 t - \tfrac{1}{2} g t^2$$
  • \( ... \) -- inline math that flows with the surrounding text. Example: What does \(v_0\) represent?
  • \[ ... \] -- an alternative display formula.

Single $ is intentionally not a math delimiter, so prices like "$5" are never mistaken for a formula. Write your math with $$ or \( instead.

This works for every question type, so you can, for example, render a formula in the stem and have students answer with the Numeric type. On a Free installation the delimiters are shown as plain text.


Tips

  • Hints are supported on all question types. Write a hint in the Hint field. Students can reveal it during a quiz at a small marks cost configured on the quiz settings.
  • Explanations work on all types. The explanation is shown to students after they answer (depending on the quiz feedback timing). Use it to teach rather than just confirm whether they were right or wrong.
  • Difficulty levels (Beginner through Expert) are metadata for filtering in the Question Bank. They do not affect scoring.
  • The question version counter increments each time you update an existing question. Past attempts record which version they were answered against, so your historical data is preserved even if you later revise a question.

Question Bank

The Learnomy question bank

The Question Bank is a central library of all your questions. Questions are stored independently of any quiz so you can reuse them across multiple quizzes, edit them in one place, and keep your question sets organized with categories and tags.

Overview

Go to Learnomy > Question Bank to see every question on your site. The Times Used column shows how many quizzes each question appears in.

Question bank

The question list

The list shows one row per question. Each row displays the question text (trimmed), its type, category, difficulty, status, how many quizzes use it, and when it was created.

Question bank table

Row actions:

  • Edit -- opens the question editor.
  • Duplicate -- creates a copy of the question with all its answers. Useful for creating variations.
  • Delete -- permanently removes the question. It is removed from any quiz slot it was in.

You can select multiple rows with the checkboxes and use the Bulk Actions bar at the bottom to delete them all at once.

Filtering questions

The filter bar above the table lets you narrow down the list before you scroll or search.

Question bank filters

Filter What it does
Category Show only questions in a specific question category.
Type Filter by question type (multiple choice, essay, etc.).
Difficulty Show questions at a specific difficulty level (Beginner through Expert).
Status Filter by Draft, Published, or Archived status.
Author Show questions created by a specific instructor or admin.
Tag Filter by a question tag.
Search Keyword search against the question text.

Select your filters and click Filter. Click Clear to reset to all questions.

Creating a new question

Click Add Question in the top-right corner to open the question editor.

Question editor

Question Details section

Field Notes
Question Text The prompt shown to the student. Required. For fill-in-the-blank questions, place {{blank1}}, {{blank2}}, etc. where the blanks should appear.
Passage / Context (optional) A rich passage, code block, or image shown above the answer choices. Supports formatting. Use this field for any formatted passage or code sample.
Additional Context (plain text) Optional plain-text context shown with the question. No formatting - for a formatted passage or code block, use Passage / Context above instead.
Hint A nudge the student can reveal during the quiz at a small marks cost.
Type The question type. Changing this updates the Answers section below.
Category Assign the question to a question category for filtering.
Difficulty Beginner, Easy, Medium, Hard, or Expert. Used for filtering only; does not affect scoring.
Status Draft hides the question from the quiz picker. Published makes it available. Archived keeps it in the bank but removes it from the Add Questions picker.
Explanation Shown to the student after they answer (depending on quiz feedback timing).
Add to quizzes A multi-select list of existing quizzes. Hold Ctrl or Cmd to select more than one. Assigning the question here adds it as a slot to the selected quizzes.

Question editor header

Answers section

The answers area changes dynamically based on the Type you selected. See Question Types for details on how to fill in each type.

Question editor answers

Click Add answer to add another answer row. Drag the handle on any row to reorder answers.

Each answer row has:

  • Content -- the answer text.
  • Correct -- checkbox or radio button to mark it correct.
  • Fraction -- partial-credit weight (used in multiple-correct and advanced types).
  • Feedback -- optional per-answer feedback shown to the student (depending on quiz feedback timing).

After saving a question, a sidebar appears with a Tags card and a Details card.

The Details card shows the question ID, how many quizzes it is currently assigned to, its version number, author, and last-updated date. Every time you save changes to a question, the version number increments. Past attempts record the version they were answered against.

Settings section

Question editor settings

The Settings section on an existing question groups the metadata fields (category, difficulty, status) in one place.

Question categories

Question categories are a separate hierarchy from course categories. They are used only to organize the Question Bank.

Question categories are selected from a dropdown in the question editor. The category list is populated when you first create questions. A question can belong to one category. There is no standalone admin tab for managing question categories in the current release.

Tags

Tags can be applied to both questions and quizzes. They appear as checkboxes in the sidebar of the question and quiz editors. Admins can create new tags inline using the quick-add field in the tag card.

Tags for questions and quizzes are managed under Learnomy > Categories, on the respective tag vocabulary tab (for example, Question Tags or Quiz Tags).

Importing and exporting questions

The Question Bank includes CSV import and export buttons in the page header.

Export: Click Export CSV to download all questions currently visible in the list (respecting any active filters). The file can be opened in a spreadsheet.

Import: Click Import CSV and upload a file. The expected columns are:

title, type, difficulty, category_slug, status, hint, body_text, answers_json

The answers_json column contains a JSON array of answer objects. Use the export format as a template.

Tips

  • Questions with Draft status do not appear in the Add Questions picker inside the quiz editor. Set status to Published when a question is ready to use.
  • The Times Used count is a fast way to see which questions are used widely. Before editing a commonly-used question, consider whether the change might affect existing quiz attempts.
  • A question marked Archived is excluded from the picker but stays in existing quiz slots. Use this to retire old questions without removing them from quizzes that already have them.

Sectioned exam layout

The Sectioned layout splits a quiz into ordered parts (sections) and shows the student one section at a time, in the style of an SAT, GRE, or mock certification exam. Questions are grouped into sections of a fixed size you choose (ten by default), and the student completes each section before moving on.

Pro feature. Available in Learnomy Pro. The Sectioned display template is a Pro layout on the quiz editor.

What you can do

  • Present a long exam as a series of shorter, self-contained sections instead of one endless list.
  • Show a section intro card between parts so the student knows how many questions each section holds before they start it.
  • Run the exam in Linear order (finish each section before the next, no going back) for high-stakes proctored-style tests.
  • Run the exam in Free order (the candidate picks any section first and switches between them until they submit) for mock exams where students want to attempt their strong sections first.
  • Track two levels of progress at once: a per-section question counter and a segmented bar showing which sections are done, active, or locked.
  • Keep a single quiz timer across the whole exam, with the last minute highlighted and auto-submit when time runs out.
  • Give students a per-section score breakdown on the results screen, with the question review grouped section by section.

Sectioned exam first view

How to use it

  1. Open the quiz in Learnomy > Quizzes and edit it (or add a new one).
  2. In the quiz settings, find Display template and choose Sectioned. This template is a Pro layout, so Learnomy Pro must be active for it to be selectable.
  3. Set Section navigation to either Linear - complete each section in order or Free - candidate picks section order. Linear is the default.
  4. Set Questions per section if you want sections larger or smaller than the default of ten (any value from 1 to 100).
  5. Add your questions as normal. Learnomy groups them into sections of that size in the order they appear on the quiz. With the default of ten, the first ten questions become Section 1, the next ten become Section 2, and so on.
  6. Set a Time limit if you want the exam timed. The timer runs across the whole exam, not per section.
  7. Save and publish the quiz.

What the student sees at exam time:

  1. A start screen with the usual rules summary, plus one extra rule that reads "Multi-section exam - complete each section in order." The student clicks Start Quiz to create the attempt.
  2. Linear mode: a section intro card ("Section 1 of 3") showing how many questions are in that section. The student clicks Begin Section to start answering. Free mode: a "Choose a section" picker listing every section with its answered / total count. The student clicks a section card to enter it.
  3. Inside a section, questions appear one at a time. The top bar shows the section name, a "Q2 of 10" counter, a segmented section-progress bar, an overall "X of N answered" count, and the timer.
  4. The student moves through the section with Previous and Next. On the last question of the section, the Next button becomes End Section.
  5. Linear mode: ending a section asks for confirmation ("Are you sure you want to end this section? You cannot return to it."), locks that section, and moves to the next section intro. In Free mode, finishing a section returns the student to the picker so they can switch to another section; a Sections button also lets them jump back to the picker at any time.
  6. After the last section (Linear) or from the Submit exam button on the picker (Free), the attempt is submitted.
  7. The results screen shows the pass/fail score, the passing threshold, and a per-section question review.

Settings and options

These are set on the quiz editor (Learnomy > Quizzes > edit a quiz) and stored on the quiz.

Setting Field name Values Notes
Display template display_template sectioned Pro layout. Selecting it turns the quiz into a sectioned exam.
Section navigation section_navigation linear (default), free Applies to the Sectioned template only. linear locks each section after it ends; free lets the candidate choose the order and switch between sections until submit.
Questions per section questions_per_section number 1-100 (default 10) How many questions each auto-built section holds. Applies to the Sectioned template only.
Time limit (quiz time limit) minutes One timer for the whole exam. When under 60 seconds remain the timer turns red; at zero the attempt auto-submits.
Passing score passing_score percentage Same passing rule as every other layout; scored across all sections.

Section size defaults to ten but is set by Questions per section on the quiz editor. How many sections an exam has is then a function of that size and how many questions the quiz contains.

The Section navigation help text on the quiz editor reads: "Applies to the Sectioned display template. Free lets the candidate choose which section to attempt first and switch between them until submit."

For developers

  • REST: the quiz create and update endpoints (learnomy/v1/quizzes) accept display_template (enum includes sectioned), section_navigation (enum linear | free), and questions_per_section (integer, clamped to 1-100). All are returned on the quiz resource. Send section_navigation and questions_per_section only for sectioned quizzes; other values are ignored.
  • Database: section_navigation is a column on lrn_quizzes (varchar(20), default linear), added in migration 3_14_0. questions_per_section is a column on the same table (smallint unsigned, default 10), added in migration 3_45_0 - before that release the client hardcoded ten.
  • Template override: the layout renders from templates/quiz/sectioned.php. Copy it to theme/learnomy/quiz/sectioned.php in your theme to override it through the Template Loader.
  • Frontend store: the player extends the shared learnomy/quiz Interactivity store in assets/js/quiz-view-sectioned.js. It reads section_navigation from the start payload into state.sectionNavigation; in free mode the player opens on the section picker instead of the Section 1 intro. Section size comes from the start payload into state.questionsPerSection (default 10).

Conversational quiz layout

The Conversational layout shows one question at a time on a full, uncluttered screen and moves forward on its own the moment a student picks an answer. It is the Typeform-style layout, built for short, casual knowledge checks where you want the quiz to feel light and quick rather than like a formal exam.

Pro feature. Available in Learnomy Pro. The Conversational display template is a Pro layout on the quiz editor.

What you can do

  • Present a quiz as a series of single, full-screen question cards instead of one long scrolling page.
  • Auto-advance to the next question the instant a student picks a single-answer multiple-choice or true/false answer, so there is no separate "Next" click.
  • Let students answer with the keyboard: number keys pick the matching answer option (1 for the first option, 2 for the second, and so on).
  • Keep multi-select multiple-choice questions on the layout with a manual Continue button, since the student is still choosing until they say they are done.
  • Keep text-style questions (short answer, fill in the blank, essay) on the same layout with a manual Continue button, since those cannot auto-advance.
  • Show a thin progress bar across the top of the screen so students always know how far through they are.
  • Run the quiz timed, with a minimal top-right timer that turns red in the last minute and auto-submits when time runs out.
  • Offer an optional per-question hint that the student can expand with Show hint.
  • Give students a full results screen with the score, pass/fail status, an optional per-category breakdown, and a question-by-question review.

Conversational quiz first view

How to use it

  1. Open the quiz in Learnomy > Quizzes and edit it (or add a new one).
  2. In the quiz settings, find Display template and choose Conversational. This template is a Pro layout, so Learnomy Pro must be active for it to be selectable.
  3. Add your questions as normal. Single-answer multiple-choice and true/false questions auto-advance; multi-select multiple-choice, short answer, fill in the blank, and essay questions get a Continue button instead.
  4. Set a Time limit if you want the quiz timed. One timer runs across the whole quiz.
  5. Set the Passing score and any attempt limit as you would for any other layout.
  6. Save and publish the quiz.

What the student sees at quiz time:

  1. A full-screen start card with the quiz title, the description, and a short rules summary (question count, time limit if set, passing percentage, and attempts if limited). The student clicks Start Quiz to create the attempt. If the quiz belongs to a course they have not enrolled in, they see an enroll prompt instead of the start button.
  2. One question fills the screen at a time. The top of the screen shows a thin progress bar and, when the quiz is timed, a minimal timer in the top-right corner.
  3. For single-answer multiple-choice and true/false questions, the student clicks one of the pill-shaped answer buttons (or presses the matching number key). The card fades and the quiz advances to the next question on its own after a short pause.
  4. For multi-select multiple-choice questions, the student ticks as many options as apply and then clicks Continue to move on -- these do not auto-advance, since a single tick is not a "done" signal.
  5. For short answer, fill in the blank, and essay questions, the student types their answer and clicks Continue to move on. These do not auto-advance.
  6. A Show hint control appears under the question when the question has a hint.
  7. Selecting an answer (or pressing Continue) on the last question submits the quiz.
  8. The results screen shows the pass/fail icon, the score, the passing status, an optional score-by-category breakdown, and a per-question review. Depending on your quiz settings the student can move on to the next item, go back to the course, or try again if attempts remain.

Settings and options

These are set on the quiz editor (Learnomy > Quizzes > edit a quiz) and stored on the quiz.

Setting Field name Values Notes
Display template display_template conversational Pro layout. Selecting it turns the quiz into a conversational, one-at-a-time quiz.
Time limit (quiz time limit) minutes One timer for the whole quiz. When under 60 seconds remain the timer turns red; at zero the attempt auto-submits.
Passing score passing_score percentage Same passing rule as every other layout.
Attempt limit attempt_limit number When attempts remain, the results screen shows Try Again; otherwise it shows an "all attempts used" message.

The Conversational option in the Display template picker is described as "One at a time, keyboard-friendly. Best for casual knowledge checks."

Auto-advance behaviour is built into the layout, not a setting: single-answer multiple-choice and true/false answers advance automatically after a short delay, while multi-select multiple-choice, short answer, fill in the blank, and essay questions always require a manual Continue click.

For developers

  • REST: the quiz create and update endpoints (learnomy/v1/quizzes) accept display_template (enum includes conversational) and return it on the quiz resource.
  • Database: display_template is a column on lrn_quizzes (varchar(30), default focused), added in migration 1_1_0.
  • Template override: the layout renders from templates/quiz/conversational.php. Copy it to theme/learnomy/quiz/conversational.php in your theme to override it through the Template Loader.
  • Frontend store: the player extends the shared learnomy/quiz Interactivity store in assets/js/quiz-view-conversational.js. Auto-advance is driven by actions.selectAndAdvance after state.autoAdvanceDelay (500ms), but it bails without advancing when state.isCurrentMultiSelect is true; multi-select questions advance through actions.continueFromMultiSelect, and text and essay types (state.isTextInputQuestion) through actions.continueFromText, both via the Continue button. The callbacks.initKeyboard handler binds the number-key shortcuts, which map key n to the nth answer option.

Review board exam layout

The Review board layout shows a long exam as a split panel: a scrollable question navigator on the left and the active question at full detail on the right. The student can jump to any question in one click, flag questions to come back to, and filter the list down to what is still unanswered, which suits long certification and entrance exams.

Pro feature. Available in Learnomy Pro. The Review board display template is a Pro layout on the quiz editor.

What you can do

  • Present a long exam (40, 100, 150+ questions) as a navigable board instead of one endless scroll or a strict one-at-a-time run.
  • Let candidates jump straight to any question from the left-hand list, in any order, at any time.
  • Show each question's status at a glance in the navigator: unanswered, answered, current, or flagged.
  • Filter the navigator to All, Unanswered, or Flagged so a candidate can find the questions they still owe an answer to near the end of a long paper.
  • Let candidates flag any question for review and come back to it before submitting; flags survive a mid-exam page refresh.
  • Group the navigator by subject when the quiz groups questions by category, so a candidate can collapse and expand each subject and see a per-subject answered count.
  • Keep a single exam timer with the last minute highlighted and auto-submit when time runs out.
  • Give a per-category score breakdown on the results screen, plus a full per-question review, when the quiz groups by category.
  • Work on mobile: the left navigator becomes a bottom sheet that a candidate pulls up to switch questions.

Review board first view

How to use it

  1. Open the quiz in Learnomy > Quizzes and edit it (or add a new one).
  2. In the quiz settings, find Display template and choose Review board. This template is a Pro layout, so Learnomy Pro must be active for it to be selectable.
  3. Add your questions as normal. Every question the quiz contains appears in the left navigator in quiz order.
  4. Optional: turn on Group questions by category to make the navigator show a collapsible group per subject with a per-category answered count, and to add a per-category score to the results screen. This works best when your questions belong to question categories.
  5. Set a Time limit if you want the exam timed. The timer runs across the whole exam.
  6. Save and publish the quiz.

What the student sees at exam time:

  1. A start screen with the usual rules summary, plus one extra rule that reads "Review board - jump to any question at any time." The student clicks Start Quiz to create the attempt.
  2. A two-panel player. The left panel is the question navigator; the right panel shows the active question with its answer inputs, an optional hint, and a flag button.
  3. The top bar shows an "X of N answered" progress count, the timer (when the quiz is timed), and the Submit button.
  4. The student answers the current question, then either clicks another question in the navigator to jump to it, or uses Previous and Next to move one at a time.
  5. To flag a question for later, the student clicks the flag button in the question header. Flagged questions show a flag icon in the navigator and appear under the Flagged filter.
  6. The student narrows the navigator with the All, Unanswered, and Flagged filter pills, for example to clear the last few unanswered questions before submitting.
  7. When the quiz groups by category and has more than one category, the navigator is split into collapsible subject groups, each with an answered / total count. The student clicks a subject header to collapse or expand it.
  8. On mobile, the navigator collapses into a bottom sheet. The student taps the handle (which shows the answered count and any flag count) to pull the list up, picks a question, and the sheet closes.
  9. The student clicks Submit when done, or the exam auto-submits if the timer reaches zero.
  10. The results screen shows the pass/fail score, the passing threshold, a per-category score breakdown (when grouped by category), and a full per-question review.

Settings and options

These are set on the quiz editor (Learnomy > Quizzes > edit a quiz) and stored on the quiz.

Setting Field name Values Notes
Display template display_template review-board Pro layout. Selecting it turns the quiz into a review-board exam.
Group questions by category group_by_category 0 (off, default), 1 (on) When on and the quiz has more than one category, the navigator renders as collapsible per-subject groups with a per-category answered count, and the results screen adds a per-category score breakdown.
Time limit (quiz time limit) minutes One timer for the whole exam. When under 60 seconds remain the timer turns red; at zero the attempt auto-submits.
Passing score passing_score percentage Same passing rule as every other layout; scored across all questions.

The Group questions by category help text on the quiz editor reads: "Show questions under their category heading, and a per-category score on the results screen."

The navigator filters (All / Unanswered / Flagged) and per-question flagging are always available on the Review board layout. There is nothing to configure for them.

For developers

  • REST: the quiz create and update endpoints (learnomy/v1/quizzes) accept display_template (enum includes review-board) and group_by_category (boolean). Both are returned on the quiz resource.
  • Database: group_by_category is a column on lrn_quizzes (tinyint(1), default 0), added in migration 3_11_0. display_template is stored on the same table.
  • Template override: the layout renders from templates/quiz/review-board.php. Copy it to theme/learnomy/quiz/review-board.php in your theme to override it through the Template Loader.
  • Frontend store: the player extends the shared learnomy/quiz Interactivity store in assets/js/quiz-view-review-board.js. It adds the navigator statuses (state.questionStatuses), the filtered list (state.filteredQuestionList), the grouped-by-category navigator (state.groupedQuestionList, gated by state.showGroupedList), and the mobile bottom sheet. Flags are held on the shared base state.flagged array so they hydrate on resume and persist through persistRunnerState.

Taking a Quiz

The Learnomy quiz player

This page describes the student experience: what students see before, during, and after a quiz. Use it to understand the player flow and to set student expectations in your course.

Overview

Students access quizzes through a course lesson, from the course outline, or directly at a standalone quiz URL. The player is the same in all cases. The quiz template (Focused, Classic, or a Pro template) is chosen by the instructor per quiz.

Before the quiz: gates

If the instructor configured any access restrictions, the student sees a blocking screen before the quiz player loads.

Gate What the student sees
Scheduled "This quiz opens on [date]." Students can see the start screen but cannot start until the open date.
Closed "This quiz closed on [date]." No new attempts are accepted.
Cooldown "Another attempt available in [countdown timer]." The page reloads automatically when the cooldown ends.
Locked out "You've used all [N] attempts." The student must contact the instructor to be reset.
Access code A code entry form. The student must enter the correct code to proceed.

The start screen

When no gate is blocking, the student lands on the start screen.

Quiz player start screen

The start screen shows:

  • The quiz title and description (if the instructor added one).
  • A rules summary card listing the number of questions, time limit (if set), passing score, and attempt limit (if set).

Quiz player rules

If the student is not yet enrolled in the course the quiz belongs to, the start screen shows an enrollment prompt instead of the Start Quiz button.

Click Start Quiz to begin. The attempt is created at this point.

Answering questions

Once the quiz starts, the question player appears. The layout depends on the display template the instructor selected.

Focused template (default): One question at a time. The top bar shows a progress ring, a question counter (e.g. "Question 2 of 8"), and a countdown timer if a time limit is set.

Quiz player

Classic template: All questions on a single scrollable page.

Pro templates (Sectioned, Conversational, Review Board): Available with Learnomy Pro. See Building a Quiz for descriptions.

Answering each question type

Question type How to answer
Multiple choice (single) Click one option.
Multiple choice (multiple) Click all options that apply.
True / False Click True or False.
Short answer Type a response in the text field.
Fill in the blank Type the missing word(s) in each blank field.
Essay Type a longer response in the text area.
Ordering (Pro) Drag the items into the correct sequence.
Matching (Pro) Click a term on the left, then click its match on the right. Click "Clear matches" to redo.

Hints

If the instructor added a hint to a question, a "Show hint" link appears below the question. Clicking it reveals the hint. Hints may carry a small marks cost depending on quiz configuration.

In Free navigation mode, Previous and Next buttons let students move between questions freely. In Sequential mode, only the Next button is shown and students cannot go back.

Tab switching detection

Learnomy detects when the student switches away from the quiz tab. A warning banner appears when this happens: "Tab switching detected. This activity is recorded." Tab switch counts are stored with the attempt for the instructor to review.

Timer

If a time limit is set, the remaining time appears in the top bar. When fewer than 60 seconds remain, the timer turns red. When time runs out, the attempt is submitted automatically with whatever answers have been saved.

Reloading or leaving mid-quiz

While an attempt is under way, the browser asks the student to confirm before it reloads the page, closes the tab, or navigates away. Answers are saved as they are given, so a reload resumes the attempt with them intact - but a timed quiz keeps counting down while the page reloads, so the confirmation is there to stop a stray refresh costing exam time.

It is armed only between starting and submitting:

Moment Does it ask?
Start screen, or a gate screen (scheduled, closed, cooldown, locked out, access code) No. No attempt exists yet.
Any question, in any layout Yes.
Submitting the quiz, including the automatic submit when time runs out or the tab-switch limit is reached No. Submission is already under way, so leaving costs nothing.
Results screen No.
After clicking Retake, before starting the new attempt No. It arms again once the new attempt begins.

If a submission fails and the student is returned to their answers to try again, the confirmation comes back with them.

The wording of the prompt comes from the browser, not from Learnomy, so it differs between Chrome, Firefox and Safari and cannot be customised or styled.

Immediate feedback

If the instructor set Feedback timing to After each question or Immediate, a feedback panel appears after each answer showing whether it was correct and the explanation (if the instructor wrote one). In other feedback modes, this panel does not appear during the quiz.

Submitting

When the student reaches the last question, the Next button changes to Submit Quiz. Clicking it opens a confirmation step, then submits the attempt.

Once submitted, the attempt status changes from In Progress to Submitted. For quizzes with only auto-graded questions, the status immediately advances to Graded and the results screen is shown. For quizzes with essay or manual questions, the attempt stays Submitted until an instructor grades those responses.

Results screen

After submission (and finalization for auto-graded quizzes), the results screen shows:

  • Pass or fail with the score percentage.
  • The passing threshold.
  • Per-question feedback (if the instructor enabled it in the feedback timing settings).
  • A button to retake the quiz if the student has remaining attempts.
  • A link to go back to the course.

For essay quizzes, the results screen shows a "Pending review" message until the instructor finishes manual grading.

Tips for instructors

  • Students who are locked out see a message to contact their instructor. You can reset an attempt limit manually from the student's profile.
  • If a student's attempt is stuck in "Submitted" and does not show results, check the Grading inbox. There may be an ungraded essay response holding it.
  • The cooldown countdown timer on the gate screen reloads the page automatically. Students do not need to refresh manually.
  • A student who reports "the browser asked me if I really wanted to leave" is seeing the mid-quiz confirmation working as intended. It cannot be turned off per quiz, and their answers are safe either way.

Focused

The Focused layout shows one question at a time on a clean screen, with a start screen, previous/next navigation, and a results screen. It is the default quiz layout in Learnomy Free and works for quizzes of any length.

What you can do

  • Present a quiz as one question at a time instead of a long scrolling page, so students focus on a single question.
  • Give students a start screen with the quiz rules (question count, time limit, passing percentage, and attempts when limited) before the attempt begins.
  • Move through questions with Previous and Next, and submit from the last question.
  • Show a progress ring with the answered count, and a question map (opened from the ring) to jump straight to any question.
  • Flag a question for review and come back to it before submitting.
  • See a pre-submit review that lists anything still unanswered or flagged before the final submit.
  • Run the quiz timed, with a top-bar timer that turns urgent in the last minute and auto-submits at zero.
  • End on a results screen with the score, pass/fail status, an optional per-category breakdown, and a question-by-question review.

Focused first view

How to use it

  1. Open the quiz in Learnomy > Quizzes and edit it (or add a new one).
  2. In the quiz settings, find Display template. Focused is selected by default, so a new quiz already uses it -- you do not need to change anything to get this layout.
  3. Add your questions as normal. Each one appears on its own screen at quiz time, in quiz order.
  4. Set a Time limit if you want the quiz timed. One timer runs across the whole quiz.
  5. Set the Passing score and any attempt limit as you would for any other layout.
  6. Save and publish the quiz.

What the student sees at quiz time:

  1. A start screen with the quiz title, description, and rules summary. The student clicks Start Quiz to create the attempt. If the quiz belongs to a course they have not enrolled in, they see an enroll prompt instead.
  2. One question fills the screen. The top bar shows a progress ring with the answered count and, when the quiz is timed, a timer.
  3. The student answers, then uses Previous and Next to move; on the last question the Next button becomes the submit action.
  4. The student can open the question map from the progress ring to jump to any question, and use the flag button to mark a question for review.
  5. When the student reaches submit with anything unanswered or flagged, a pre-submit review offers to jump back to those questions or submit anyway.
  6. The results screen shows the pass/fail icon, the score, the passing status, an optional score-by-category breakdown, and a per-question review. Depending on the quiz settings the student can move on to the next item, go back to the course, or try again if attempts remain.

Settings and options

These are set on the quiz editor (Learnomy > Quizzes > edit a quiz) and stored on the quiz.

Setting Field name Values Notes
Display template display_template focused (default) The default Free layout. A new quiz uses it with no configuration.
Time limit (quiz time limit) minutes One timer for the whole quiz. When under 60 seconds remain the timer turns urgent; at zero the attempt auto-submits.
Passing score passing_score percentage Same passing rule as every other layout.
Attempt limit attempt_limit number When attempts remain, the results screen shows Try Again; otherwise it shows an "all attempts used" message.

The question map, flag-for-review, and pre-submit review are built into the layout -- there is nothing to configure for them.

For developers

  • REST: the quiz create and update endpoints (learnomy/v1/quizzes) accept display_template (enum includes focused) and return it on the quiz resource.
  • Database: display_template is a column on lrn_quizzes (default focused).
  • Template override: the layout renders from templates/quiz/focused.php. Copy it to theme/learnomy/quiz/focused.php in your theme to override it through the Template Loader.
  • Frontend store: the player uses the shared learnomy/quiz Interactivity store in assets/js/quiz-view.js. Question data is fetched from the REST API when the student clicks Start Quiz -- the template renders only the shell (start screen, question container, navigation, results).

Grading Submissions

Learnomy grades most question types automatically the moment a student submits. Essay questions require a human reviewer. This page explains both paths and how to use the Grading inbox.

Auto-grading

When a student submits a quiz, Learnomy auto-grades every question type it can handle:

Question type How it is graded
Multiple choice (single) Full marks for the correct option; zero otherwise.
Multiple choice (multiple) Partial credit using the fraction weights set on each answer.
True / False Full marks for the correct choice; zero otherwise.
Short answer Case-insensitive exact match against the accepted answers.
Fill in the blank Each blank checked independently; partial credit per blank.
Essay Not auto-graded. Routed to the Grading inbox.

Once all responses in an attempt are auto-graded, the attempt status changes from Submitted to Graded and the student can see their results.

Manual grading: the Grading inbox

Essay questions -- and any question type that the site configuration routes to manual review -- land in the Grading inbox.

Go to Learnomy > Grading to open the inbox.

Grading inbox

Who sees what

The inbox is ownership-scoped:

  • Administrators see pending attempts from all quizzes on the site.
  • Instructors see only attempts for quizzes attached to courses they co-instruct.

Grading inbox header

Reading the inbox

Each row shows:

  • Student -- name and avatar.
  • Quiz -- the quiz title.
  • Submitted -- the date and time the attempt was submitted.
  • Pending -- how many responses in this attempt still need a grade.
  • Status -- "Needs grading" badge.

The inbox is paginated. The counter at the top shows the total number of attempts currently waiting.

Grading inbox table

Grading an attempt

  1. Click Grade on any row. The grader opens.

    Grading detail header

  2. The grader shows one card per response that needs manual review. Already auto-graded responses are shown as read-only for context, labelled Graded.

  3. For each ungraded response:

    • Read the Student answer displayed in the card.

      Grading answer card

    • Enter a Score between 0 and the maximum marks for that question. The maximum is shown next to the input field.

    • Write optional Feedback to send back to the student. The feedback is shown with the student's results after the attempt is finalized.

    • Click Save grade.

      Grading grade form

  4. The card flips to read-only showing the score and feedback you entered.

  5. When the last ungraded response in the attempt is saved, a banner appears: "All responses are graded. This attempt has been finalized." The attempt status in the database changes from Submitted to Graded. The student can now see their full results.

The attempt is automatically removed from the inbox once it is finalized.

How finalization works

The backend checks after every graded response whether any ungraded responses remain. When none remain, it finalizes the attempt: applies the grading method (highest / first / latest / average) chosen on the quiz, calculates the total score, sets the pass/fail flag, and updates the student's course progress.

This means you do not have to submit or confirm anything at the end. Saving the last grade triggers finalization automatically.

Rubric grading (Pro)

Pro feature. Available in Learnomy Pro with the Advanced Quizzes extension active.

When the Advanced Quizzes extension is enabled, essay responses can be graded against a rubric instead of (or alongside) a freeform score. A rubric is a set of named criteria, each with a maximum point value, that the instructor defines per question in the question editor.

Where rubric grading appears

There is no separate rubric page or Grade with rubric link to click. The rubric scorer renders inline, right under each essay response, on the same grading surfaces you already use:

  • The wp-admin Grading screen (Learnomy → Grading).
  • The frontend instructor Grading route.

Open a quiz for grading as usual. For any essay response whose question has a rubric defined, a Grade with rubric panel appears directly beneath the student's written response, showing one numeric input per criterion with the maximum points for each criterion next to the input.

Enter a score for each criterion. The scores are combined automatically into the total points for that response. When all responses on the attempt are graded, the attempt is finalized in the same way as the standard grader.

If a question has no rubric defined, no rubric panel is shown for that response - grade it with the standard freeform score instead. Define a rubric per question in the quiz advanced settings.

Access

Inline rubric grading requires the learnomy_manage_quizzes capability. Administrators always have this capability. Instructors have it for quizzes attached to courses they co-instruct.

Defining rubrics

Rubrics are defined in the question editor under the Advanced Quizzes settings for each essay question. Each rubric entry has a criterion name and a maximum point value. A question can have any number of criteria.


Tips

  • Students whose quiz contains essay questions will see a "Pending review" message on the results screen. Their attempt count and course progress are not updated until grading is complete. Grade promptly to keep students moving through the course.
  • The inbox shows the count of pending attempts in a badge next to the Grading menu item in the sidebar. This updates each time the page loads.
  • If a student contacts you saying their result is missing, check the inbox. An ungraded essay response is the most common reason an attempt stays in "Submitted" status.
  • Scores are entered in points (not percentages). The quiz converts the raw score to a percentage using the total available marks from all slot marks combined.
  • If grading reveals that a student was cut off unfairly - a crash mid-attempt, a cap set too low - you can hand them one more go without changing the quiz for anyone else. Open their profile and use Grant 1 attempt under Quiz attempt limits. Instructors get this on quizzes they can grade, which is the same permission that let them open this inbox.

Classic

The Classic layout puts every question on one scrollable page. The student scrolls to answer, and a bar at the top tracks the answered count and the timer while a single submit button sits at the bottom. It suits short quizzes, surveys, and practice runs.

What you can do

  • Show the whole quiz on one page so students can scan every question and answer in any order by scrolling.
  • Track progress with an answered-count bar at the top of the page.
  • Run the quiz timed, with a timer in the top bar that turns urgent in the last minute and auto-submits at zero.
  • Warn the student about any unanswered questions before submit, with a jump-to-first-unanswered shortcut.
  • When the quiz groups by category, show category headings inline and a jump-nav to skip to each category's questions.
  • End on the same results screen as the other layouts: score, pass/fail status, an optional per-category breakdown, and a per-question review.

Classic first view

How to use it

  1. Open the quiz in Learnomy > Quizzes and edit it (or add a new one).
  2. In the quiz settings, find Display template and choose Classic.
  3. Add your questions as normal. Every question appears on one scrollable page in quiz order.
  4. Set a Time limit if you want the quiz timed. One timer runs across the whole quiz.
  5. Set the Passing score and any attempt limit as you would for any other layout.
  6. Save and publish the quiz.

What the student sees at quiz time:

  1. A start screen with the quiz rules, including one extra line noting that all questions are shown at once and to scroll to answer. The student clicks Start Quiz to create the attempt.
  2. Every question renders on one page. The top bar shows the answered count and, when the quiz is timed, the timer.
  3. The student scrolls and answers in any order; each answered question is marked with a check.
  4. If any questions are still unanswered near the bottom, a notice appears with a Jump to first shortcut to the first unanswered question.
  5. The student clicks Submit at the bottom of the page (the single submit point), or the quiz auto-submits if the timer reaches zero.
  6. The results screen shows the pass/fail score, the passing status, an optional score-by-category breakdown, and a per-question review.

Settings and options

These are set on the quiz editor (Learnomy > Quizzes > edit a quiz) and stored on the quiz.

Setting Field name Values Notes
Display template display_template classic Selecting it renders every question on one scrollable page.
Time limit (quiz time limit) minutes One timer for the whole quiz. When under 60 seconds remain the timer turns urgent; at zero the attempt auto-submits.
Passing score passing_score percentage Same passing rule as every other layout.
Attempt limit attempt_limit number When attempts remain, the results screen shows Try Again; otherwise it shows an "all attempts used" message.

The category headings and category jump-nav appear only when the quiz groups questions by category and has more than one category. See Group by category.

For developers

  • REST: the quiz create and update endpoints (learnomy/v1/quizzes) accept display_template (enum includes classic) and return it on the quiz resource.
  • Database: display_template is a column on lrn_quizzes.
  • Template override: the layout renders from templates/quiz/classic.php. Copy it to theme/learnomy/quiz/classic.php in your theme to override it through the Template Loader.
  • Frontend store: the player extends the shared learnomy/quiz Interactivity store in assets/js/quiz-view-classic.js. All questions are held in state.allQuestions and rendered together; each carries its own answered/selected state and text value so answers can be entered in any order.

Large-exam Autosave and Resume

Learnomy saves a student's progress through a quiz as they work and lets them pick up exactly where they left off if they close the tab, lose their connection, or come back later. It is built for long, high-stakes exams where losing an hour of answers is not acceptable. It is on for every quiz attempt with nothing to switch on.

What you can do

  • Let students take a multi-hour graded exam without fear of losing answers to a dropped connection, a closed tab, or a dead battery.
  • Have a student reopen an in-progress attempt and land on the same question, with every earlier answer already filled in.
  • Keep the exam fair on resume: the question order, the answer-option order, and the countdown clock are frozen at the moment the attempt started, so reopening cannot re-roll a random question set or win back time.
  • Give students a question map, a flag-for-review marker, and a pre-submit review screen so they can navigate a long exam and check their work before submitting.
  • Show instructors an advisory "submitted after the time limit" note on the grading screen when an attempt comes in past its deadline.

Resume your attempt

How to use it

For students, there is nothing to turn on. It works like this:

  1. Start the quiz from the start screen. The attempt is created, and the question order plus the countdown deadline are locked in at this moment.
  2. Answer questions as normal. Every answer, every move to the next or previous question, and every flag you set is saved in the background a moment after you make it. There is no Save button to press.
  3. If you need to stop, just close the tab or walk away. Nothing is lost.
  4. When you come back, open the same quiz again and start it. The player detects your in-progress attempt and restores it: your saved answers are pre-filled, you land back on the last question you were viewing, your flagged questions are still flagged, and the timer shows the time that was left, not a fresh full clock.
  5. Use the question map (the overview panel) to jump between questions, and flag any question for review while you work.
  6. On the last question, if anything is still unanswered or flagged, the pre-submit review screen opens so you can jump back and finish. A fully answered, unflagged attempt submits straight away.

Because the deadline is fixed at start, a timed exam cannot be paused by closing it. If the deadline passes while you are away, the attempt submits with whatever answers were saved when you return, the same as if the timer had run out on screen.

Settings and options

Autosave and resume runs automatically for every attempt, so there is no per-quiz "enable autosave" checkbox. The behaviour is shaped by settings you already configure on the quiz plus a few developer filters.

  • Time limit (per quiz, on the quiz editor) - sets the countdown. The deadline is frozen at start, so the remaining time is what a student sees on resume. See Building a Quiz.
  • Navigation (Free or Sequential, per quiz) - in Sequential mode students cannot jump back through the question map; the map and jump actions are disabled.
  • The saved progress lives on the attempt itself (the reserved saved_state_json field on lrn_quiz_attempts). There is no separate table and no migration, so upgrading does not change your database structure.
  • Autosave covers every feedback timing (after each question, after submit, and the rest). It does not change how or when answers are graded - the authoritative grading input is still the answers sent at submit.

For developers

  • Save endpoint: POST /learnomy/v1/attempts/{attempt_id}/state - fire-and-forget from the player (debounced ~400ms). Accepts current_index, answers, flagged, and remaining (seconds left on the client clock). Stored for resume only; never used as the scoring source.
  • Start/resume payload: POST /learnomy/v1/quizzes/{quiz_id}/start returns is_resume, saved_answers, current_index, and flagged when an in-progress attempt is reopened, plus the frozen question and answer-option order.
  • Storage: lrn_quiz_attempts.saved_state_json (longtext), read/written via QuizAttempt::get_runner_state() / save_runner_state(). Keys include frozen_order, frozen_options, deadline, current_index, flagged, answers, and (on late submit) late_submission / seconds_over.
  • learnomy_quiz_timer_resume_policy (filter, default wall_clock) - return pause_on_resume to stop the clock while a student is away and continue from the remaining time on resume. Receives $quiz and $attempt.
  • learnomy_quiz_submit_grace_seconds (filter, default 60) - grace window past the frozen deadline before an attempt is marked with the advisory late_submission flag. Receives $attempt.
  • learnomy_tab_switch_flag_threshold (filter, default 3) - number of tab switches before the attempt is flagged on the instructor grading review. Set to 0 to disable the flag styling.

Rich Question Stems

A rich question stem is an optional formatted passage, code block, or image shown above the answer choices for any question type. It gives students the material they need to answer, without cluttering the plain question prompt.

What you can do

  • Add a formatted passage (headings, lists, bold, links) above the answers of any question.
  • Paste a code block for programming or scripting questions.
  • Insert an image, chart, or diagram that the question refers to.
  • Use one stem across every question type: multiple choice, true/false, short answer, fill in the blank, essay, and the Pro types.
  • Author the stem in wp-admin (Question Bank editor) or in the Pro front-end course builder. The same stem renders in every quiz that uses the question.
  • Keep the stem separate from the question prompt, so a reading passage or dataset is shown once and the actual question stays short.

Rich question stem editor

How to use it

  1. Go to Learnomy > Question Bank.
  2. Click Add Question to create a new question, or click Edit on an existing one.
  3. In the Question Details section, type the question prompt into the Question Text field as usual.
  4. Below it, find the Passage / Context (optional) field. This is the rich stem editor.
  5. Click into the editor and add your content. It supports formatting, so you can add a passage, a code block, or an image that appears above the answers.
  6. Fill in the rest of the question (type, answers, category, difficulty, status) as normal.
  7. Save the question. The stem is stored on the question, so every quiz that uses this question shows the same stem.
  8. Preview the quiz as a student. The stem renders above the answer choices in every quiz layout (focused, classic, sectioned, conversational, and the review board).

Instructors using Learnomy Pro can author the same stem from the front-end course builder when adding or editing a question. The stem field behaves the same way and writes to the same place.

Settings & options

  • Field label: Passage / Context (optional). Found in the Question Bank editor under Question Details.
  • Storage: the stem is saved to its own stem column on the lrn_questions table (added in migration 3_12_0). It never overlaps the fill-in-the-blank token body, which stays in the content field.
  • Passage / Context vs Additional Context: the rich Passage / Context field supports formatting, code, and images. The separate Additional Context (plain text) field is plain text only. Use the rich field when you need formatting.
  • Sanitizing: stem content is cleaned on save through the shared content engine, so it stores as canonical, round-trip-safe HTML and renders the same way lessons and courses do. When the HTML content model is switched off (via the learnomy_store_content_as_html filter), the stem falls back to wp_kses_post sanitizing.
  • Rendering: the stem is rendered server-side and escaped before display. It is shown above the answers in all five quiz templates and is hidden automatically when a question has no stem.

For developers

  • Storage column: lrn_questions.stem (longtext), introduced in includes/db/migrations/class-migration-3_12_0.php.
  • Sanitizing helper: \Learnomy\prepare_question_stem( string $raw ): string in includes/functions.php. It routes through \Learnomy\Content\Content_HTML::for_storage() when HTML storage is enabled, otherwise wp_kses_post.
  • Kill switch: the learnomy_store_content_as_html filter controls whether the stem (and all content) stores as canonical HTML or plain kses'd markup.
  • REST field: send stem (string, Editor.js JSON or HTML) on the question create and update endpoints under the learnomy/v1 namespace (for example POST /learnomy/v1/questions and the per-question update route). It is defined in question_args() in includes/api/class-questions-controller.php with no arg-level sanitize, because sanitizing happens at the storage layer so block JSON is not stripped.
  • Editor field: the wp-admin mount point is #lrn-question-stem with the hidden serialising input name="stem" (#lrn-question-stem-input), wired in assets/js/admin/question-editor.js and rendered by includes/admin/views/question-editor.php.
  • Templates: each quiz layout renders the stem into .lrn-quiz-question__stem from the per-item stemHtml value (see templates/quiz/classic.php, focused.php, sectioned.php, conversational.php, and review-board.php).

Question Map and Flag for Review

On a long exam the student needs a way to see where they are, jump to any question, and mark questions to come back to before they submit. Learnomy gives every quiz attempt a clickable question map, a flag-for-review marker on the current question, and a pre-submit review screen that catches anything unanswered or flagged. It is on for every quiz attempt with nothing to switch on.

What you can do

  • Let a student see, at a glance, which questions are answered, flagged, current, and not yet seen across a 50 or 150 question exam.
  • Let a student jump straight to any question from a grid map instead of clicking Next dozens of times.
  • Let a student flag a hard question and move on, then come back to every flagged question at the end.
  • Catch mistakes before submit: the pre-submit review screen shows the count of unanswered and flagged questions and offers one-click jumps back to them.
  • Keep flags and position through a resume: a flagged question is still flagged, and the map still shows it, when a student reopens an in-progress attempt.
  • Keep timed exams honest: the map and jump actions only work when the quiz allows free navigation. Sequential quizzes hide them.

Question map and flag toggle

How to use it

For students, there is nothing to turn on. It works like this:

  1. Start the quiz from the start screen and begin answering. A progress ring at the top of the player tracks how much of the exam is answered.
  2. Click the progress ring to open the question map. The map is a grid with one cell per question, numbered in order.
  3. Read the map by color using the legend above it: Answered, Flagged, Current, and Unseen. The cell you are on is marked current, and any question you flagged is marked flagged.
  4. Click any cell to jump straight to that question. The map closes and the player moves to it. Click the ring again at any time to reopen the map.
  5. To flag the question you are on, click the Flag for review button in the navigation row. The button switches to Flagged, and that question turns flagged on the map. Click it again to remove the flag.
  6. When you reach the end, if anything is still unanswered or flagged the pre-submit review screen opens instead of submitting straight away. It shows a summary line such as "3 unanswered - 2 flagged".
  7. From that screen, use Review unanswered to jump to the first unanswered question and Review flagged to jump to the first flagged question. Fix what you need to, then submit. A fully answered, unflagged attempt submits straight away with no review screen.

Flags and your place in the exam are saved as you work, so if you close the tab and reopen the same quiz, your flagged questions are still flagged and the map is intact. See Large-exam Autosave and Resume.

Settings and options

The map and flags run automatically for every attempt, so there is no per-quiz "enable question map" checkbox. The behavior is shaped by settings you already configure on the quiz.

  • Navigation (Free or Sequential, per quiz, on the quiz editor) - the map and jump actions require free navigation. In Sequential mode a student must move through questions in order, so the clickable map and the jump-back actions are disabled. See Building a Quiz.
  • Display template (per quiz) - the focused (one question at a time) template is where the full question map, flag button, and pre-submit review screen appear. The classic all-on-one-page template shows its own category jump-nav instead. See Taking a Quiz.
  • A question counts as answered on the map once it has a meaningful answer filled in. An empty or blank answer keeps the cell in the unseen or current state and counts toward the unanswered total on the pre-submit screen.
  • Flags are advisory. They are the student's own working notes for navigation. They are never sent to the grader and never affect the score. Only the answers matter for grading.

For developers

  • State save endpoint: POST /learnomy/v1/attempts/{attempt_id}/state - the player posts current_index, answers, flagged (an array of question ids), and remaining here, debounced, so the map position and flags survive a resume. Stored for resume only, never used as a scoring source.
  • Start/resume payload: POST /learnomy/v1/quizzes/{quiz_id}/start returns flagged and current_index alongside the saved answers when an in-progress attempt is reopened, so the map and flag button restore to where the student left off.
  • Storage: the flag list lives in the reserved lrn_quiz_attempts.saved_state_json field (keys include flagged, current_index, and answers). There is no separate flags table and no migration.
  • The question map, flag state, and pre-submit summary are computed client-side in assets/js/quiz-view.js (the questionMap, flaggedCount, unansweredCount, and preSubmitSummary getters) and rendered by templates/quiz/focused.php. A theme can override the template at theme/learnomy/quiz/focused.php.

Group Questions by Subject

A single quiz can pull questions from several subjects and keep each subject together during the attempt, then show a per-subject score on the results screen. This turns one quiz into a multi-subject exam (for example a certification paper with a JavaScript section, a React section, and an HTML and CSS section) without building a separate quiz per subject.

What you can do

  • Run one exam that covers several subjects and keep each subject's questions together instead of interleaved, so a candidate answers all of one subject before moving to the next.
  • Show a per-subject score breakdown on the results screen ("JavaScript 4 / 5 (80%)", "React 3 / 5 (60%)") on top of the overall pass or fail score.
  • Reuse the same question categories you already manage in the Question Bank, so a question tagged "Algebra" counts toward the Algebra subject in every quiz that uses it.
  • Keep it optional and additive: the setting is off by default, and a quiz with it off behaves exactly as before (no subject headings, no breakdown).
  • Turn it on for any layout: the all-at-once (Classic) layout gets subject headings, the one-at-a-time layouts show the current question's subject above the prompt, and the Pro Review board layout turns its navigator into collapsible per-subject groups with an answered count.
  • Author the setting from three places that stay in sync: the wp-admin quiz editor, the REST API, and the Pro front-end course builder.

Group questions by subject

How to use it

  1. Give your questions a category. In Learnomy > Question Bank, open each question and set its Category (for example JavaScript, React, HTML and CSS). Question categories are their own hierarchy, separate from course categories. A question with no category falls into an "Uncategorized" group.
  2. Open the quiz in Learnomy > Quizzes and edit it, or add a new one.
  3. Add the questions you want from the bank as normal. They can be added in any order; grouping is applied at attempt time.
  4. In the quiz settings, tick Group questions by category. The help text reads "Show questions under their category heading, and a per-category score on the results screen."
  5. Optional: choose a Display template. Grouping works on every layout; the Pro Review board layout adds a collapsible per-subject navigator with an answered count per subject.
  6. Save and publish the quiz.

What the student sees at attempt time:

  1. On the Classic (all-at-once) layout, the questions are ordered by subject and each subject run has one heading above it, for example an "Algebra" heading over its questions and a "Geometry" heading over the next set. The heading is shown once per subject, not repeated on every question.
  2. On the one-at-a-time layouts (Focused, Sectioned, Conversational), the current question's subject is shown as a small label above the question, so the candidate always sees which subject they are in.
  3. On the Pro Review board layout, the left navigator splits into collapsible groups, one per subject, each with an answered / total count.
  4. After the student submits, the results card shows a Score by category block: one row per subject with its name and its earned / possible (percentage). Those per-subject scores reconcile to the overall attempt percentage.

Settings and options

These are set on the quiz editor (Learnomy > Quizzes > edit a quiz) and stored on the quiz.

Setting Field name Values Notes
Group questions by category group_by_category 0 (off, default), 1 (on) When on, questions are ordered and headed by their question category, and the results screen adds a per-category score breakdown.
Display template display_template classic, focused, sectioned, conversational, review-board (Pro) Grouping is honoured by every template. Only the Pro Review board renders the collapsible per-subject navigator.
Question category (set per question in the Question Bank) - The category a question belongs to is what the grouping and the per-subject score are built from. Managed in Learnomy > Question Bank, not on the quiz.

Notes on behaviour:

  • The per-subject Score by category breakdown only appears when the quiz's feedback timing already reveals per-question correctness. If the quiz is set to hide answers, the breakdown is withheld too, so it cannot leak which answers were right.
  • Grouping needs the quiz to have more than one category to show anything meaningful. With a single category (or none) the attempt just runs as a normal quiz.
  • Turning the setting off and retaking removes all subject headings and the breakdown; a normal quiz is completely unaffected.

For developers

  • Database: group_by_category is a column on lrn_quizzes (tinyint(1), default 0), added in migration 3_11_0 (no new table). The per-question subject comes from lrn_question_categories via each question's category_id.
  • REST: the quiz create and update endpoints (learnomy/v1/quizzes) accept group_by_category as a boolean (string "1" is coerced) and return it on the quiz resource. POST /learnomy/v1/quizzes/{id}/start returns each item in data.questions[] with category_id and category_name, ordered category-contiguous. GET /learnomy/v1/attempts/{id} includes a category_breakdown key only when group_by_category = 1 and the feedback timing exposes the per-question review.
  • One query for the breakdown: QuestionResponse::score_by_category( $attempt_id ) is a single grouped query (responses joined to questions and categories), so the per-subject score has no per-row N+1.
  • Frontend store: the shared learnomy/quiz Interactivity store holds state.groupByCategory and exposes state.currentCategoryLabel for the one-at-a-time layouts. The Classic layout binds a real <h2 class="lrn-quiz-classic__category-heading"> per subject run. The Review board player (assets/js/quiz-view-review-board.js) adds the grouped navigator via state.groupedQuestionList (gated by state.showGroupedList).
  • Template override: each layout renders from templates/quiz/{classic,focused,sectioned,conversational,review-board}.php. Copy the one you use to theme/learnomy/quiz/<layout>.php to override it through the Template Loader.

Quiz Modes

Every quiz runs in one of three modes -- Graded, Practice, or Survey. The mode decides whether the quiz is scored, whether it counts toward course completion, and whether it collects answers at all. You set it once on the quiz editor.

What you can do

  • Run a Graded quiz that is scored, passes or fails against a passing percentage, and gates course completion when the course requires a quiz.
  • Run a Practice quiz that is still auto-scored so students see how they did, but never blocks course completion -- a low-stakes self-check.
  • Run a Survey that collects student answers without grading them, with no score and no pass/fail.
  • Switch a quiz between modes at any time from the quiz editor.
  • Use any mode with any quiz layout and any question types.

Quiz Modes first view

How to use it

Step 1 -- open the quiz editor

Go to Learnomy > Quizzes and edit a quiz (or add a new one).

Step 2 -- pick the mode

Find the Mode dropdown in the quiz settings and choose one of:

  • Graded -- the default. The quiz is scored on submit and produces a pass/fail result.
  • Practice -- scored on submit so the student sees their result, but the result never holds up course completion.
  • Survey -- answers are collected on submit with no grading, no score, and no pass/fail.

The help text under the field states the rule directly: "Graded counts toward completion, practice doesn't, survey collects answers without grading."

Step 3 -- set the passing score (Graded only)

For a Graded quiz, set the Passing score percentage. Practice and Survey quizzes ignore the passing score, because Practice always counts as passed and Survey has no score.

Step 4 -- save

Save the quiz. The mode takes effect on the next attempt.

Settings and options

The Mode is stored on the quiz and drives what happens when a student submits:

Mode Value Scored? Pass/fail? Counts toward completion?
Graded graded Yes, auto-graded on submit Yes, against the passing score Yes -- for a course that requires a quiz, the student must pass
Practice practice Yes, auto-graded so the student sees a result No gate -- always treated as passed No -- never blocks completion
Survey survey No -- responses are stored, not graded No No -- a survey is always satisfied once submitted

Notes:

  • A Graded quiz with essay or manual-grade questions holds at "submitted" until an instructor grades the open responses, then finalises to "graded". See Grading Submissions.
  • A Survey never fires the pass/fail outcome, so integrations that react to a pass or fail are skipped for surveys.
  • The passing-score rule is the same across every layout; the mode, not the layout, decides whether it applies.

For developers

  • Storage: lrn_quizzes.mode -- one of graded (default), practice, survey.
  • REST: the quiz create and update endpoints (learnomy/v1/quizzes) accept mode (enum graded | practice | survey) and return it on the quiz resource.
  • Grading path: Learnomy\Services\Quiz_Service auto-grades on submit for graded/practice and stores responses without grading for survey; Progress_Service treats practice and survey quizzes as always-passed so they never gate course completion.
  • Hooks: learnomy_quiz_submitted fires for every submitted attempt; learnomy_quiz_passed / learnomy_quiz_failed / learnomy_quiz_graded fire only for graded (and practice) attempts -- surveys are skipped because they have no pass/fail.

Question Pools and Per-Question Timers

Question pools draw a random set of questions from your question bank each time a student starts the quiz, so no two attempts are identical. Per-question timers put a countdown on individual questions and enforce it on the server. Both are configured on the quiz's Advanced settings.

Pro feature. Question pools and per-question timers are part of the Advanced Quizzes extension in Learnomy Pro.

What you can do

  • Draw a random selection of N questions from the question bank at the moment each student starts the quiz, so every attempt is a different paper.
  • Filter the pool draw by question category, difficulty range, and question type, so the random set still matches the exam you intend.
  • Give students a fresh, unpredictable question set on each attempt to make sharing answers far less useful.
  • Put a countdown on individual questions, set in seconds per question.
  • Have the timer enforced on the server at submit -- a question whose time ran out is treated as timed out, not just visually flagged in the browser.
  • Show a live per-question countdown badge on the quiz player, with auto-advance on timeout on the Focused layout.

Question Pools and Per-Question Timers first view

How to use it

Set up a question pool

  1. Make sure Learnomy Pro is active and the Advanced Quizzes extension is enabled.
  2. Build up your Question Bank with categorised questions, and set a difficulty (1 to 5) on questions you want to draw by difficulty. See Question Bank.
  3. Open the quiz and go to its Advanced settings. Open the Pool card.
  4. Choose the criteria for the draw: the question categories to draw from, an optional difficulty range, an optional question type, and the count (how many questions to select, 1 to 200).
  5. Save. From now on, each time a student starts the quiz, Learnomy selects that many random questions from the bank matching your criteria and builds the attempt from them.

Set a per-question timer

  1. In the same Advanced settings, open the Timer card.
  2. Set the number of seconds for a question. A value of zero means no timer on that question.
  3. Save. On the quiz player each timed question shows a countdown badge; on the Focused layout the player auto-advances when a question's time runs out.
  4. Timers are also enforced on the server when the attempt is submitted, so a client that ignores the countdown still has over-time questions marked as timed out.

Settings and options

Setting Where Values Notes
Pool categories Advanced settings > Pool card question category IDs The categories the random draw pulls from.
Difficulty range Advanced settings > Pool card min and max, each 1 to 5 Restricts the draw to questions within the difficulty band.
Question type Advanced settings > Pool card a question type, optional Restricts the draw to one type when set.
Count Advanced settings > Pool card 1 to 200 How many questions to select per attempt.
Per-question timer Advanced settings > Timer card seconds (0 = none) Countdown for that question, enforced on the server at submit.

The pool draw runs at attempt start, before any conditional logic, so the drawn set defines the questions the rest of the attempt works from.

For developers

  • Config storage: question pools, per-slot timers, conditional-logic rules, and rubrics are all stored in one dedicated table, lrn_pro_aq_config (config_type + object_id, unique together), managed by Learnomy_Pro\Extensions\Advanced_Quizzes\Models\Aq_Config (get() / save() / remove()). Earlier releases stored each of these as an individual wp_options row (learnomy_quiz_pool_{quiz_id}, learnomy_quiz_slot_timer_{slot_id}, and so on) -- one option per quiz/question/slot, unindexed and unbounded. A one-time migration (Aq_Config::migrate_from_options(), run on the advanced-quizzes extension's activation) moves any existing rows into the table and deletes the old options, so a site upgrading from an earlier version keeps its pools and timers intact. Pool_Service (create_pool / get_pool / resolve_pool) and the timer REST handlers both read and write through Aq_Config, not wp_options, directly.
  • Pool draw: resolve_pool() selects the questions through the Free model Learnomy\Models\Question::pool_match( $filters, $count ); it is wired into attempt start via the learnomy_pro_quiz_filter_questions filter (priority 5, before conditional logic).
  • Timer REST: GET / POST /learnomy-pro/v1/quiz-slots/{id}/timer read and save a slot's seconds.
  • Timer enforcement: timer data is injected into the player through the learnomy_quiz_player_context filter; the server enforces elapsed time on the learnomy_quiz_submitted action. The live countdown badge and Focused-layout auto-advance are driven by assets/js/quiz-player-timers.js.

Quiz Proctoring

Proctoring gives a graded quiz light, honest-effort integrity checks without a webcam or a third-party service. Learnomy records when a student leaves the quiz tab, warns them on screen, flags the attempt for graders, and can auto-submit the quiz once the student switches away too many times. It works on any quiz layout and is built into the free plugin.

What you can do

  • Record every time a student switches to another browser tab or window during a quiz.
  • Show the student an on-screen warning the moment a tab switch is detected.
  • Flag attempts with a high tab-switch count for the grader, so suspicious submissions stand out in the grading queue.
  • Auto-submit the quiz automatically once the student switches away more than a number you choose.
  • Tell candidates the rules up front with a monitored notice on the quiz start screen.
  • See the tab-switch count, the submission IP address, and a late-submission signal on each attempt in the grading detail.

The Quiz settings tab showing Track Tab Switches and the Auto-submit after N tab switches field

How to use it

  1. Go to LMS Settings > Settings > Quiz.
  2. Turn on Track Tab Switches. This starts recording tab switches on every quiz attempt.
  3. In Auto-submit after N tab switches, enter the number of switches allowed before the quiz submits itself. Leave it at 0 to only warn and record, never auto-submit.
  4. Save. Any quiz the student takes now shows a monitored notice on the start screen, warns on each tab switch, and (if a limit is set) submits automatically once the limit is passed.

When a student switches tabs during a timed exam, they see a "tab switching detected, this activity is recorded" warning. If you set the limit to, for example, 2, the quiz submits on the second switch and the student lands on the results screen with a note that it was submitted for them.

What graders see

Open Learnomy > Grading and pick a submitted attempt. The exam-integrity panel shows:

  • Tab switches - how many times the student left the quiz tab.
  • IP address - where the attempt was submitted from.
  • Late submission - a signal when the attempt was submitted after the time limit.

Attempts whose tab-switch count is at or above the flag threshold are highlighted so you can review them first.

Settings and options

  • Track Tab Switches (learnomy_settings[quiz_track_tabs]) - the master switch. When off, tab switches are never recorded and nothing auto-submits.
  • Auto-submit after N tab switches (learnomy_settings[quiz_tab_switch_limit]) - the auto-submit threshold. 0 means no auto-submit.

For developers

  • learnomy_tab_switch_auto_submit_limit - filter the auto-submit limit, e.g. to vary it per quiz. Receives (int $limit, object|null $quiz).
  • learnomy_tab_switch_flag_threshold - filter the tab-switch count at which the grading view flags an attempt (default 3). Receives (int $threshold, object $attempt, object $quiz).
  • The limit is resolved by \Learnomy\Services\Quiz_Defaults::tab_switch_auto_submit_limit() and sent to the player as tab_switch_limit in the quiz start payload.
  • Enforcement is server-authoritative: POST /learnomy/v1/quiz-attempts/{id}/tab-switch records each switch and returns auto_submit: true once the count reaches the limit, so a tampered client-side threshold cannot bypass it.

Exam Integrity Report

When you grade a quiz attempt, Learnomy shows a short integrity readout at the top of the Grade attempt screen. It gathers the honest-effort signals recorded during the exam - the tab-switch count, the submission IP address, and a late-submission signal - in one place so a suspicious attempt stands out while you review it. Every signal is advisory, not proof of misconduct.

What you can do

  • See how many times the student left the quiz tab during the attempt, shown right on the Grade attempt screen.
  • Spot a flagged attempt at a glance: once the tab-switch count reaches the threshold the signal turns to a warning shield instead of a check.
  • See the IP address the attempt was submitted from, so submissions from an unexpected location are visible.
  • See a "submitted after the time limit" signal, with the number of minutes over, when a timed attempt came in late.
  • Read a built-in reminder that these signals are advisory only and not proof of misconduct, so the readout is never mistaken for a verdict.
  • Tune how many tab switches count as suspicious with a developer filter, or turn the flag styling off entirely.

The Grade attempt screen showing the integrity readout with IP, tab switches, and a late-submission signal

How to use it

  1. Go to Learnomy > Grading.
  2. Open a submitted attempt from the list to reach the Grade attempt screen.
  3. Read the integrity row directly under the student and quiz title. It shows up to three signals:
    • IP: the address the attempt was submitted from. Always shown when an address was recorded.
    • Tab switches: how many times the student left the quiz tab. Shown when tab tracking is on. A flagged count shows a warning shield; a count under the threshold shows a check shield.
    • Submitted after the time limit: shown only for a timed attempt that came in late, with the minutes over.
  4. When a signal is flagged, a short line reminds you it is advisory only, not proof of misconduct. Treat it as a prompt to look closer, not as a decision.
  5. Grade the attempt as normal. The integrity row is context for your review - it does not change the score on its own.

The tab-switch count only appears when the tab-switch recorder is turned on. To turn it on and set the auto-submit limit, see Quiz Proctoring.

Settings & options

The integrity report has no settings of its own - it reads signals recorded during the exam. The one setting that controls whether the tab-switch signal appears lives on the Quiz settings tab:

Setting What it does
Track Tab Switches (learnomy_settings[quiz_track_tabs]) When on, the tab-switch count is recorded on every attempt and the Tab switches signal appears on the Grade attempt screen. When off, that signal is hidden.
Auto-submit after N tab switches (learnomy_settings[quiz_tab_switch_limit]) Optional. How many switches are allowed before the quiz submits itself. See Quiz Proctoring. Does not change the report, only the enforcement.

The IP address and late-submission signals need no setting - they are recorded automatically. The IP is stored on the attempt (ip_address), and the late signal comes from the attempt's saved runner state (late_submission and seconds_over).

For developers

Filter:

  • learnomy_tab_switch_flag_threshold - the switch count at or above which the tab-switch signal is flagged. Default 3. Passed the threshold, the attempt row, and the quiz. Return 0 to disable the flag styling entirely (the count still shows, just never flagged).

Where the report is assembled (Learnomy\Admin\Admin::render_quiz_grading_detail()):

  • Learnomy\Services\Quiz_Defaults::is_tab_tracking_enabled() - whether the tab-switch recorder is on; gates the Tab switches signal.
  • $attempt->tab_switches - the recorded switch count.
  • $attempt->ip_address - the submission IP shown in the readout.
  • QuizAttempt::get_runner_state( $attempt_id ) - returns the saved runner state; late_submission and seconds_over drive the late signal.

The signals render in includes/admin/views/quiz-grading-detail.php (the lrn-grading-meta header row).

REST:

  • POST /learnomy/v1/quiz-attempts/{id}/tab-switch - records a tab-switch event during the attempt; this is the count the report later reads. Fired by the quiz player, not called by graders.

Certificates

This section covers everything to do with certificates in Learnomy.

Certificate Templates

Certificate templates are the visual designs Learnomy prints when a student earns a certificate. You design a template once using the built-in designer, then assign it to any course that awards certificates.

Overview

The Certificate Designer is a canvas-based editor that lets you compose a PDF-ready certificate layout without any external tools. It lives at Learnomy > Certificates > Design New Template (direct link: wp-admin/admin.php?page=learnomy-certificate-designer). Each template stores its design as JSON and renders to a PDF or printable HTML page when a certificate is issued.

The curated certificate library

You do not have to design a template before your instructors can award certificates. Every Learnomy install ships with four ready-made templates, seeded automatically the first time the certificate templates table is available - no setup step required.

Template Type Style
Course Completion - Elegant Course completion Centered serif layout with a gold double border
Course Completion - Modern Course completion Left-aligned sans-serif layout with a blue accent bar
Quiz Completion - Elegant Quiz completion Centered serif "Certificate of Achievement" wording, shows the student's score
Quiz Completion - Modern Quiz completion Left-aligned sans-serif layout with a green accent bar, shows the student's score

All four are shared with All instructors by default (see Instructor availability below), so they appear in the Certificate Template dropdown for every course and every instructor immediately. Once seeded, each behaves like any other template - edit it in the designer, restrict who can use it, or delete it if you do not want it.

Seeding is idempotent: it runs once per plugin version and only creates a design that is not already present, matched by an internal design key rather than by title. Re-running it, or updating to a version that adds a new curated design, never creates duplicate rows. If you delete one of the four, it stays deleted - the seeder does not bring it back.

For developers

Add more designs to the curated library with the learnomy_certificate_designs filter:

add_filter( 'learnomy_certificate_designs', function ( array $designs ): array {
    $designs[] = [
        'design_key'  => 'my-addon-design',       // unique - keeps seeding idempotent
        'title'       => __( 'My Add-on Certificate', 'my-addon' ),
        'kind'        => 'course_completion',      // or 'quiz_completion'
        'orientation' => 'landscape',
        'design_json' => [ /* same shape as the designer's own pre-built layouts */ ],
    ];
    return $designs;
} );

Opening the designer

  1. Go to Learnomy > Certificates.

    Certificates list header

  2. Click Design New Template in the top-right corner. To edit an existing template, use the Edit row action in the table.

    Certificate designer

The designer opens with a canvas on the left and a sidebar panel on the right.

Template panel: name, orientation, and background

The Template panel at the top of the sidebar controls the canvas itself.

Designer sidebar panel

  1. Enter a Name for the template. This name appears in the course settings dropdown so you can tell templates apart.
  2. Choose Orientation: Landscape (default) or Portrait.
  3. Set a Background Color using the color picker. The default is white (#ffffff).
  4. To use an image as the background, click Select next to Background Image. This opens the WordPress media library. Choose an uploaded image (a certificate border frame, a branded pattern, or a full bleed illustration) and click Select. The canvas updates immediately. To remove the image, click Clear.

Instructor availability

By default, a template you build from scratch is private to admins - instructors will not see it in their certificate picker until you decide to share it. The curated library templates (above) are the exception: they ship already shared with all instructors.

Set who can use a template from the Availability & Type panel in the designer sidebar, just below the Template panel.

  1. Certificate type - Course Completion or Quiz Completion. This is a label only, so instructors can tell templates apart in the picker; it does not change when or how a certificate is issued.
  2. Who can use this template:
    • Admins only (default for a new template) - private; only admins see it.
    • All instructors - every instructor can select it.
    • Specific instructors - choose individual instructors from a search-and-select list. Only the selected instructors (plus admins) can select the template.

Choosing Specific instructors reveals a search box and a multi-select list of your site's instructors. The list is capped at 500 instructors by default so a large roster never renders an unbounded dropdown - use the search box to find anyone beyond that. An instructor you already granted access to stays visible in the list even if your site has more instructors than the cap.

Admins always see every active template on every picker, no matter its availability setting. The Admins only / All instructors / Specific instructors scope only filters what instructors see.

Where the scoped list appears

The same scoped list feeds every certificate picker an instructor can reach:

  • The wp-admin course editor's Certificate Template dropdown (Completion and Certificate section).
  • The Pro front-end course builder's certificate step.
  • The Pro Learning Paths editor's certificate selector.

An instructor sees the curated library plus any template an admin shared with them (all-instructors or a specific grant). Admins see every active template on all three surfaces.

For developers

// Raise the "Specific instructors" picker cap above the default of 500.
add_filter( 'learnomy_certificate_instructor_choice_limit', function ( int $limit ): int {
    return 1000;
} );

The scoped list is resolved once, in \Learnomy\certificate_pick_list( $user_id ) (includes/functions.php), and consumed by all three pickers above so they can never drift from each other. It returns every active template for a user who can manage settings (admins), or the curated-plus-granted subset for anyone else.

Choosing a pre-built template

The Pre-built Templates panel gives you three starting points:

Template Description
Classic Centered serif layout with a decorative gold border
Modern Left-aligned layout with a bold accent bar
Minimal Clean layout with generous whitespace

Click a card to load its element arrangement onto the canvas. You can then change any element after loading.

Adding and editing elements

The Add Elements panel has six element types:

Button What it adds
+ Text A static text block (course name label, "Certificate of Completion" heading, etc.)
+ Merge Tag Dynamic text that fills in at issue time (student name, course name, issue date, credential ID, site name)
+ Image / Logo An uploaded image such as a logo or signature image
+ QR Code A QR code that links to the public verification URL for this certificate
+ Line / Border A horizontal or vertical decorative line
+ Signature A signature block with a name and title below the signature image

Click any button to add an element at a default position on the canvas. Click an element on the canvas to select it.

Editing a selected element

When an element is selected, the Selected Element panel appears in the sidebar.

Designer header with save and preview controls

The panel shows editable properties for the selected element (position, size, font, color, content, and element-specific options). Use the Duplicate button to copy the element, or Delete to remove it.

Elements are absolutely positioned on the canvas. Drag to reposition them.

About fonts in the PDF

The Font menu offers Georgia, Times New Roman, Inter, Helvetica, Arial, Verdana and Trebuchet MS.

PDF files can only carry a small set of standard typefaces, so the downloaded certificate uses the closest standard match rather than the exact font on your screen: a serif choice prints as Times, and a sans-serif choice prints as Helvetica. Arial and Helvetica are the same widths, so an Arial design prints exactly as you laid it out. The others keep the same style of face but may set slightly wider or narrower, which can change where a long line breaks.

Because of this, treat the Preview PDF button as the accurate view of what a learner receives. The canvas is for layout; the PDF is the certificate.

Long recipient names

A recipient's name is never broken across two lines. If a name is longer than the space the template allows, it is fitted onto one line automatically, and only reduced in size if the name is too long to fit the page at its designed size. Nothing needs configuring for this.

Saving and previewing

  • Click Save Template to save the current canvas state. The autosave indicator shows the last-saved time.
  • Click Preview PDF (active after the first save) to generate a sample PDF with placeholder merge-tag values so you can see the final output before assigning the template to a course.

Assigning a template to a course

A certificate template only issues certificates when it is assigned to a course with certificates enabled.

  1. Go to Learnomy > Courses and open the course editor for the course you want.
  2. Open the Completion and Certificate section in the course settings panel.
  3. Check Award a certificate on completion.
  4. The Certificate Template dropdown appears. Select the template you just designed from the list. Use the search box to find a template by name if you have many.
  5. Save the course.

If no templates exist yet, the course settings show a link to the designer instead of the dropdown.

Tips

  • You do not need to design anything to get started - the four curated library templates are available to every instructor from day one.
  • A template can be assigned to multiple courses at the same time.
  • If a student's course is deleted after a certificate was issued, the certificate remains verifiable. The card reads "Course archived (certificate still verifiable)" on the student's certificates page, and the public verification page shows "Course no longer available" - the credential ID and verification URL keep working either way.
  • The QR Code element encodes the public verification URL (/verify/<uuid>). Adding one makes the printed or downloaded PDF self-verifying without needing the website open.
  • Background images render at their full canvas size. Use landscape-ratio images for landscape templates and portrait-ratio images for portrait templates.

Issuing and Verifying Certificates

A Learnomy certificate

Learnomy issues certificates automatically when a student completes a course. Every certificate carries a UUID, an Ed25519 cryptographic signature, and a public verification URL that anyone can open to confirm it is genuine.

Overview

You do not manually issue certificates. The system issues them at the moment a student's course completion is recorded. The admin Certificates list at Learnomy > Certificates shows every issued certificate, with filters to search, narrow by status, and take bulk actions. Certificates that are no longer valid can be revoked individually or in bulk.

How automatic issuance works

When a student satisfies the completion requirement set on a course (all lessons completed, lessons plus final quiz, a percentage threshold, or manual completion), the following happens automatically:

  1. Learnomy records the completion and fires the learnomy_course_completed action.
  2. Certificate_Service checks whether the course has Award a certificate on completion enabled and a template assigned.
  3. If both conditions are met, the service calls Certificate::issue(), which writes a new row to lrn_certificates with a UUID v4 identifier, a status of active, and a timestamp.
  4. The certificate is signed using Ed25519. The private key is stored in WordPress options (encrypted with AUTH_KEY). The public key is embedded in the signed payload.
  5. The student receives a certificate email notification (if enabled) and the certificate appears immediately on their My Certificates page at /account/certificates/.

If a student retakes a course and Reset progress on retake is on, a new certificate is issued upon the next completion.

The admin Certificates list

Go to Learnomy > Certificates to see all issued certificates.

Certificates list overview

The table columns are:

Column Contents
Student Avatar, display name
Course Course title
Certificate ID First eight characters of the UUID (hover or copy for the full ID)
Issued Issue date in your WordPress date format
Expires Expiry date, or "Never" if the certificate has no expiry
Status Active or Revoked
Actions View (opens the public verification page) and Revoke

Certificates table

Filtering and searching

The filter bar at the top of the list lets you narrow results by:

  • Status: All statuses, Active, Revoked, or Expired.
  • Course: A dropdown of courses that have at least one certificate issued.
  • Student or certificate ID: Free-text search. Learnomy resolves names and email addresses to user IDs, then searches. You can also paste a full or partial UUID.

Pagination keeps the active filters as you move through pages.

Revoking a certificate

Revoking marks a certificate as no longer valid. The public verification page immediately shows "Certificate Revoked" to anyone who visits the URL.

Revoke a single certificate

  1. Find the certificate in the list.
  2. Click the Revoke action in the row. Revoke is only shown for active certificates.

Bulk revoke

  1. Check the checkboxes on the rows you want to revoke.
  2. Choose Revoke from the Bulk Actions dropdown.
  3. Click Apply.

A revoked certificate cannot be reactivated from the admin UI. To reissue, the student needs to complete the course again (or the admin can issue one via the REST API).

The public verification page

Every certificate has a unique public URL:

/verify/<uuid>

No login is required to visit it. When a valid certificate UUID is in the URL, the page shows:

Valid certificate verification result

The result block shows the recipient name, course title, issue date, credential ID, and a "Valid" status badge.

Verification result detail

The page also shows:

  • A rendered preview of the certificate design (scaled to fit the browser viewport).
  • A Download button for the PDF (if the certificate template renders a PDF).
  • A Print button that opens a print-ready HTML version.
  • A Share section with a LinkedIn "Add to Profile" link and a copy-link button.
  • A QR code (if the template includes a QR Code element) linking back to this same URL.

What happens for invalid IDs

Scenario What the visitor sees
UUID matches an active certificate "Certificate Verified" result block
UUID matches an active certificate whose course was later deleted "Certificate Verified" result block - the course name reads "Course no longer available" and the certificate stays valid
UUID matches a revoked certificate "Certificate Revoked" message
UUID does not match any certificate "Certificate Not Found" message
No UUID in the URL A form to enter a credential ID manually

Deleting a course never invalidates certificates already issued for it. The credential ID, signature, and verification URL keep working; only the course name display changes.

Verification count

Each time the verification page is loaded for a valid UUID, Learnomy increments the verification_count and updates last_verified_at on the certificate row. You can see these values via the REST API or WP-CLI. They are useful for spotting unusual spikes that could indicate credential sharing.

Student experience

Where a student meets their certificate

A student does not have to go looking. Learnomy offers the certificate at the moment it is earned, in three places:

  • The quiz result screen. When a student passes a quiz and holds an active certificate for that course, the result card shows a View Certificate action alongside Back to Course. This covers the common case where passing the final quiz is what completes the course - the certificate is issued during that submission, so it is offered on the same screen that announces the pass.
  • The course completion screen. A completed course shows View Certificate once one has been issued, or Get Certificate when the course awards certificates and none has been issued yet.
  • The account page, described below, which is the permanent home for every certificate a student has earned.

The result screen only offers a certificate the student actually holds. Passing a mid-course section quiz shows no certificate action, because nothing has been earned yet - a button leading to an empty list is worse than no button.

The account certificates page

Students access their certificates at /account/certificates/. Each certificate shows as a card with the course name, issue date, short credential ID, and action buttons.

Student certificates page

Each card has:

  • Verify - opens the public verification URL.
  • Add to LinkedIn - pre-fills the LinkedIn "Add Certification" form with the course name, organization, issue year/month, and credential ID.
  • Share - copies the verification URL to the clipboard.
  • Download - downloads the PDF.

Certificate card actions

If a certificate's course has been deleted, the card still shows and remains verifiable. The title reads "Course archived (certificate still verifiable)" so the student understands the context.

Tips

  • Certificates are attached to the course, not to a lesson or quiz. A student must complete the entire course to trigger issuance.
  • Expiry is optional. If you set an expires_at on a certificate (via the REST API or Pro features), Learnomy's daily cron job flips the status to expired automatically once the date passes.
  • The verification URL is stable for the lifetime of the certificate. Share it on resumes, LinkedIn, or anywhere that needs a clickable credential link.
  • Revoking is permanent in the admin UI. Use it only when a certificate was issued in error or a student's completion was fraudulent.

Credly Integration

Learnomy Credly integration settings

Pro feature. Requires Learnomy Pro with the Certificates Pro extension active.

Credly is a digital badge platform. When the Credly integration is connected, Learnomy pushes a digital badge to the student's Credly account every time a certificate is issued. Students receive an email from Credly inviting them to accept the badge and add it to their profile, LinkedIn, and other networks.

Overview

Connecting Credly requires three things from your Credly account: an API bearer token, your organization ID, and a badge template mapped to each course. The connection settings live in LMS Settings > Credly.

The section only appears in the settings sidebar when Learnomy Pro is active and the Certificates Pro extension is enabled.

Setting up the connection

Go to LMS Settings and click Credly in the sidebar.

Step 1 - Get your Credly credentials

  1. Log in to your Credly account at credly.com.
  2. Go to Account Settings > API.
  3. Generate a new API bearer token and copy it.
  4. Go to your Organization settings and copy your Organization ID. It looks like a UUID: 0193a3b4-7e2c-0000-0000-000000000000.

Step 2 - Enter credentials in Learnomy

In the Connection section of the Credly settings panel:

  • API Token -- paste your Credly API bearer token. The field is a password input. Once saved, the stored value is encrypted and the field shows eight placeholder characters. Leave it as-is on subsequent saves to keep the existing token.
  • Organization ID -- paste your Credly organization ID.

Step 3 - Configure behavior

In the Behavior section:

Setting What it does
Enable Credly Push digital badges to Credly when certificates are issued. Turn this off to pause badge delivery without removing your credentials.
Auto-push Badges Automatically send the badge push request to Credly the moment a certificate is issued. When off, no badge is sent on issue; a badge can only be pushed later through the REST API (POST /certificates/{id}/push-credly), so leave this on unless you are driving pushes from your own integration.

Step 4 - Save and test

Click Save Changes. Then click Test Connection to verify that Learnomy can reach the Credly API with the credentials you entered. A success or failure message appears next to the button without reloading the page.

What happens when a badge is issued

When Auto-push Badges is on and a student earns a certificate:

  1. Learnomy issues the certificate and generates the unique verification URL.
  2. Learnomy calls the Credly API to issue the badge to the student's email address.
  3. Credly sends the student an email inviting them to accept the badge.
  4. The student accepts the badge on Credly and can share it from there.

If the Credly API call fails (for example, if the student's email is not registered on Credly), the failure is logged in the admin but the Learnomy certificate is still issued normally.

Tips

  • The API token is stored encrypted in the database. It is never exposed in admin HTML or REST responses once saved.
  • Turning off Enable Credly pauses badge pushes without clearing your credentials. You can re-enable it later and push the next batch normally.
  • If a student does not receive a Credly invitation, check that their WordPress account email matches a Credly account. Credly matches by email address.
  • Credly badge templates are configured in Credly, not in Learnomy. Each Learnomy course can be assigned to a specific Credly badge template from the course editor when Certificates Pro is active.

Certificate Hash Verification

Learnomy records a tamper-evident fingerprint for each certificate, so an employer who scans the certificate's QR code lands on a public page that proves the credential has not been altered since it was issued.

This is not a public blockchain. Fingerprints are written to a local, tamper-evident registry on your own site. Nothing is submitted to a public chain today. Some internal option, class and CSS names still contain the word "blockchain" for backward compatibility, but the feature is a hash registry and must not be sold to buyers as blockchain anchoring.

What you can do

  • Anchor every certificate automatically the moment it is issued, so its fingerprint is recorded without any manual step.
  • Anchor an already-issued certificate on demand from the admin, for certificates that were issued before you turned the feature on.
  • Show a "Tamper-evident hash verified" or "Certificate hash recorded" badge on the public certificate verification page, below the standard certificate details.
  • Let an employer scan the certificate's QR code and confirm the credential is genuine and unchanged, with no login and no account.
  • Keep the anchor as a permanent historical record even after a certificate is revoked or expires -- the verification page reports the revoked status while the original anchor stays in place.
  • Read the anchor status over the REST API, so the mobile app or an external verifier can check a certificate programmatically.

Certificate hash verification first view

How to use it

Step 1 -- turn on certificate hashing

There is no admin screen for this switch yet. Anchoring is turned on through the Certificates Pro settings endpoint - send POST /wp-json/learnomy-pro/v1/certificates-pro/settings with {"blockchain":{"enabled":true}} as an administrator. The setting is stored in the learnomy_pro_blockchain option and read on every certificate issue.

Step 2 -- issue a certificate

When a student earns a certificate while the feature is on, Learnomy computes a SHA-256 fingerprint of the certificate's fixed fields (its UUID, the student, the course, the issue date, and the Ed25519 signature from the free plugin) and records it in the anchor registry. Nothing else changes in your normal certificate flow.

Step 3 -- anchor older certificates (optional)

Certificates issued before you enabled the feature are not anchored yet. Anchor one on demand by calling the admin anchor endpoint for that certificate. A certificate that is already anchored is left as-is (the endpoint returns an "already anchored" response rather than re-anchoring).

Step 4 -- verify a certificate

Open the certificate's public verification page at /verify/{uuid}/, or scan the QR code printed on the certificate, which points to the same page. Below the standard certificate details you will see:

  • Tamper-evident hash verified -- the certificate's current data still matches the fingerprint recorded at issue time.
  • Certificate hash recorded -- the certificate is anchored, but its current record could not be matched (for example the certificate row is missing, or its data changed). The anchor still proves when it was issued.

A shortened form of the fingerprint is shown next to the badge.

Settings and options

  • Enable certificate hashing -- stored in learnomy_pro_blockchain (enabled) and set through the Certificates Pro settings endpoint; there is no wp-admin control for it. When off, no new certificate is anchored and no badge appears.
  • Automatic vs manual anchoring -- with the setting on, new certificates anchor automatically at issue. Existing certificates are anchored one at a time through the admin anchor endpoint.
  • Where the anchors are stored -- version 1.0 keeps the fingerprints in a local, tamper-evident registry (a non-autoloaded WordPress option). The registry holds up to 500 entries; when full, the oldest entries are evicted as new ones are written. Sites that need more should plan for the dedicated table backend noted in the code. The public API is designed so a future release can submit fingerprints to a public timestamping service (OpenTimestamps) without changing how you use the feature.
  • Revoked and expired certificates -- a recorded fingerprint is immutable and is never removed. When a certificate is revoked or expires, Learnomy records a revocation overlay so the verification page and the Open Badges assertion report the credential as no longer valid, while the anchor remains as historical proof of the original issue.

For developers

Class Learnomy_Pro\Extensions\Certificates_Pro\Blockchain (internal name retained for backward compatibility):

  • anchor_certificate( object $certificate ): array -- computes the SHA-256 fingerprint and writes the registry entry (hash, anchored_at, certificate_id).
  • verify_anchor( string $uuid ): array -- returns anchored, and when anchored also hash, anchored_at, and verified (true when the current data still matches the recorded fingerprint).

Anchor lifecycle wiring (Certificates_Pro\Extension): anchoring runs on the free learnomy_certificate_issued handler when learnomy_pro_blockchain[enabled] is set; the verification-page badge is appended via the free learnomy_certificate_verify_after action.

REST (namespace learnomy-pro/v1):

  • GET /certificates/{uuid}/verify -- public. Returns the verification payload including a blockchain block (anchored, hash, anchored_at, verified) when the certificate is anchored.
  • POST /certificates/{id}/anchor -- admin only. Anchors a certificate; returns HTTP 409 already_anchored if it already has a fingerprint.
  • GET /certificates/{id}/anchor-status -- admin only. Returns the current verify_anchor() result.
  • GET|POST /certificates-pro/settings -- admin only. Reads and writes the Credly and certificate-hash settings; this is where blockchain.enabled is turned on (the key keeps its legacy name).

Stored per-certificate markers: lrn_cert_blockchain_hash_{id}, lrn_cert_blockchain_at_{id} (recorded at anchor time), and lrn_cert_revoked_{id} (the revocation overlay read by the verify page and the Open Badges assertion).

Open Badges

Every Learnomy certificate is also available as an Open Badges 3.0 credential -- a portable, standards-based JSON file that a student can import into an external badge wallet such as Badgr, and that any verifier can read.

What you can do

  • Publish each certificate as a W3C Open Badges 3.0 (OpenBadgeCredential) assertion at a public URL, ready for import into external badge wallets.
  • Let a student download the machine-readable badge file straight from the certificate verification page.
  • Carry the course into the badge as its achievement -- the course title, description, completion criteria, and featured image become the badge's Achievement definition.
  • Carry your site's identity into the badge as the issuer -- the site name, URL, admin email, and site icon.
  • Include the free plugin's Ed25519 signature as a cryptographic proof inside the badge, so the assertion is verifiable, not just descriptive.
  • Signal revocation and expiry inside the assertion, so a wallet or verifier reading the badge sees when a credential is no longer valid.

Open Badges first view

How to use it

Step 1 -- issue a certificate

Open Badges assertions are generated on demand from any issued certificate. No separate export step is needed -- once a student has a certificate, the badge URL exists.

Step 2 -- find the badge file

Each certificate exposes its assertion at a public URL of the form /wp-json/learnomy-pro/v1/certificates/{uuid}/badge.json. The certificate verification page also carries a Download Open Badge (JSON) button that saves this file directly.

Step 3 -- import into a wallet

The student opens their badge wallet (for example Badgr) and imports the badge, either by pasting the assertion URL or by uploading the downloaded badge.json file. The wallet reads the achievement, issuer, and proof from the standard fields.

Step 4 -- verify

A verifier fetching the assertion sees the credential subject (the recipient, identified by a hashed identity), the achievement (the course), the issuer (your site), the issue date, and the Ed25519 proof pointing back at the certificate's verification page.

Settings and options

  • No per-certificate configuration -- the assertion is built from data you already entered: the course (title, description, featured image), your site settings (name, URL, admin email, site icon), and the certificate's own issue date and signature. Keep those accurate and the badge is accurate.
  • Recipient privacy -- the recipient's email is never published in the clear. The assertion carries a hashed identity with a salt derived deterministically from the certificate UUID, so an external wallet can re-compute and confirm the identity at any time while the raw email stays private.
  • Expiry -- when a certificate has an expiry date, the assertion includes an expirationDate.
  • Revocation -- when a certificate is revoked or expires, the assertion is marked revoked with a reason, so a verifier reading the badge sees the credential is no longer valid even though the issuance proof remains as a historical record.
  • Proof -- when the certificate carries the free plugin's Ed25519 signature, the assertion includes an Ed25519Signature2020 proof whose verification method links to the public /verify/{uuid}/ page.

For developers

Class Learnomy_Pro\Extensions\Certificates_Pro\Open_Badges:

  • generate_assertion( object $certificate ): array -- builds the full Open Badges 3.0 OpenBadgeCredential array (context https://w3id.org/openbadges/v3), including the credential subject, achievement, issuer, issue date, optional expiry, revocation overlay, and Ed25519 proof.
  • generate_badge_class( object $course ): array -- the Achievement definition for a course (id via route_url('course', ...), name, description, criteria narrative, issuer, and the course featured image as the badge image).
  • generate_issuer(): array -- the issuer Profile from get_bloginfo('name'), home_url(), admin_email, and the site icon.
  • get_assertion_url( string $uuid ): string -- the public assertion URL (rest_url('learnomy-pro/v1/certificates/{uuid}/badge.json')).

REST (namespace learnomy-pro/v1):

  • GET /certificates/{uuid}/badge.json -- public, no auth. Returns the Open Badges 3.0 assertion JSON for import into external wallets.

Verification-page wiring (Certificates_Pro\Extension): a <link rel="openbadges"> pointing at the assertion is injected into the verification page head for active certificates, and the Download Open Badge (JSON) button is rendered on the learnomy_certificate_verify_after action. The revocation overlay read by the assertion is the same lrn_cert_revoked_{id} marker used by blockchain verification.

Share a Certificate on LinkedIn

Every certificate a student earns comes with share actions so they can add it to their LinkedIn profile, copy a public verification link, or download the certificate. This works out of the box in the free version, with no extra setup.

What you can do

  • Add an earned certificate straight to a student's LinkedIn profile with a pre-filled "Add to Profile" form (course name, your site as the issuing organization, issue month and year, and a link back to the public verification page).
  • Copy the certificate's public verification link to the clipboard to paste anywhere - email, chat, a resume, or another social network.
  • Download the certificate (PDF where available, otherwise HTML).
  • Let anyone open the public verification page to confirm the certificate is genuine, with no login required.
  • Share works from two places: the student's own Certificates page in their account, and the public verification page for that certificate.

Certificate share actions on the account certificates page

How to use it

Step 1 - earn a certificate

A certificate is issued automatically when a student completes a course that has certificates enabled. Once issued, it shows up on the student's Certificates page.

Step 2 - open the Certificates page

The student goes to My Account -> Certificates (/account/certificates/). Each earned certificate is a card showing the course title, the issue date, and a short certificate ID.

Step 3 - use a share action

Each certificate card has an actions row:

  • Verify - opens the public verification page for the certificate.
  • Add to LinkedIn - opens LinkedIn's "Add to Profile" screen in a new tab, pre-filled from the certificate.
  • Share - copies the public verification link to the clipboard.
  • Download - downloads the certificate file.

Step 4 - add it to LinkedIn

Clicking Add to LinkedIn sends the student to LinkedIn's certification form with these details already filled in:

  • The course title as the certification name.
  • Your site name as the issuing organization.
  • The month and year the certificate was issued.
  • A link back to the public verification page so viewers can confirm it.

The student reviews the pre-filled form on LinkedIn and saves it to their profile.

The Share button copies the certificate's public verification URL. On secure (HTTPS) sites the copy happens instantly; on plain HTTP sites it falls back to a legacy copy, and if that is blocked the student is shown the link to copy by hand. The same LinkedIn and copy actions also appear on the public verification page itself, so anyone viewing a shared certificate can re-share or add their own.

A note on archived courses

If the course behind a certificate has been deleted, the certificate is marked "archived" and the Add to LinkedIn button is hidden (LinkedIn needs a course name and issuer). The certificate stays independently verifiable, and the Verify, Share, and Download actions still work.

Settings & options

There are no dedicated settings for LinkedIn sharing - the share actions appear automatically on every earned certificate whenever the Certificates module is enabled. The LinkedIn form is populated from the certificate's own data (course title, issue date, certificate ID) and your site name from Settings -> General.

The LinkedIn "Add to Profile" link is built against https://www.linkedin.com/profile/add with these parameters:

Parameter Value
startTask CERTIFICATION_NAME
name The course title
organizationName Your site name (get_bloginfo('name'))
issueYear / issueMonth The certificate's issue date
certUrl The public verification page URL
certId The short certificate ID (first 8 characters of the UUID)

For developers

Hooks:

  • learnomy_certificate_verify_after - fires after the verified-certificate result block on the public verification page. Passed the verified certificate row. Extensions use this to render extra actions (for example the Pro certificates extension adds its own "Add to LinkedIn" and Open Badge buttons here).

Frontend:

  • The Share (copy link) button uses the class lrn-copy-link. On the account page it is handled by assets/js/student-dashboard.js (clipboard with an execCommand fallback and a manual-copy prompt). On the public verification page it is driven by the Interactivity API store learnomy/certificate-verify (actions.copyLink) in assets/js/certificate-verify-view.js.

REST:

  • GET /learnomy/v1/certificates/{uuid}/download - downloads the certificate file (PDF where available, HTML otherwise). This backs the Download action.

URLs:

  • The public verification page is the certificate-verify route (/verify/{uuid}/), built with \Learnomy\route_url( 'certificate-verify', array( 'slug' => $uuid ) ). This is the link the LinkedIn form and the copy action both point to.

Download and Print a Certificate

Every certificate a student earns can be downloaded as a PDF or opened in a print-ready view. This works out of the box in the free version, both from the student's account and from the public verification page, with no extra setup.

What you can do

  • Download a certificate as a PDF straight from the student's My Certificates page.
  • Download the same PDF from the public verification page that anyone with the link can open.
  • Open a print-ready version of the certificate and send it to a printer or save it as a PDF from the browser.
  • Keep working even when the PDF library is not installed - the download falls back to a clean printable HTML page instead of failing.
  • Let students print without logging in, because the download and print links are keyed to the certificate's unguessable ID rather than a login session.

Download and print a certificate first view

How to use it

Step 1 - open your certificates

A student opens My Account and goes to the Certificates page (/account/certificates/). Each certificate earned shows as a card with the course name, the issue date, and a short credential ID.

Step 2 - download the PDF

On the certificate card, select Download. This opens the certificate as a PDF in a new tab, which the browser then saves or displays. The file is named certificate-<id>.pdf.

If the site does not have the PDF library available, the same Download link serves a clean, print-ready HTML page instead, so the student always gets a usable copy.

Step 3 - print from the verification page

Anyone can also download or print from the public verification page at /verify/<id>/ (no login needed). Below the certificate preview there are two actions:

  • Download - saves the certificate as a PDF. This button only appears when the PDF library is available.
  • Print - opens the print-ready version in a new tab and automatically brings up the browser's print dialog.

Holding Ctrl, Cmd, or Shift while selecting Print opens the printable view in a background tab without popping the print dialog, so power users can save it for later.

Step 4 - save as PDF from the print view

The print-ready page includes its own Print / Save as PDF button. Selecting it opens the browser print dialog, where the student can either send the certificate to a printer or choose "Save as PDF" as the destination. When the PDF library is present, the same page also shows a Download PDF button that grabs the true PDF directly.

Settings & options

There are no separate on/off settings for downloading and printing - both are always available for any issued certificate. Two things shape the output:

What Effect
PDF library (Dompdf) present When available, Download serves a real PDF and the verification page shows the Download button. When missing, downloads fall back to the print-ready HTML page and the PDF-only button is hidden.
Certificate template The visual design, page orientation, and optional QR code come from the template assigned to the course. See Certificate Templates.

The printable page is built for clean output: it forces exact background colors for print and hides its own action buttons under an @media print rule so only the certificate itself lands on the page.

For developers

REST endpoint (public - access is controlled by the unguessable certificate UUID, not a login):

  • GET /learnomy/v1/certificates/{uuid}/download - serves the certificate file. The format query parameter accepts pdf (default) or html. A revoked or missing certificate returns HTTP 404. When format=pdf is requested but the PDF library is unavailable, the endpoint transparently serves the print-ready HTML instead.

Backing implementation:

  • Certificate_Renderer::render_pdf( $certificate, $template ) - renders the PDF binary via Dompdf; returns an empty string when Dompdf is not installed (the controller then falls back to HTML).
  • Certificate_Renderer::render_html( $certificate, $template ) - renders the certificate as HTML.
  • Certificate_Renderer::dompdf_available() - the check that decides whether the PDF download link is shown on the verification page.

Front-end print action:

  • Interactivity API store learnomy/certificate-verify, action printCert - opens the format=html view in a new tab and triggers window.print(), while honouring Ctrl/Cmd/Shift-click.

The Student Experience

This section covers the student-facing experience in Learnomy, from the account hub to the lesson player to billing.

Student Account Hub

The Learnomy student dashboard

The student account hub is the central home for a learner once they are logged in. It brings together a dashboard summary, a full course library, certificates, billing, and account settings under one persistent navigation menu.

Overview

The hub lives at /account/. Every page inside it shares the same two-column layout: a vertical account navigation on the left and the page content on the right. The account navigation appears on all account pages so students can move between sections without going back to the main site. The navigation is grouped into a Learning block and an Account block so the list stays scannable as more sections are added.

Learning:

Section URL What it shows
Dashboard /account/ Stats summary, recent activity, course previews, certificate widget
My Courses /account/courses/ Full enrolled course library with tab filters
Quiz Attempts /account/quiz-results/ Every quiz attempt with score and pass/fail. Opening an attempt shows the full question review - grouped by section or category on large quizzes, with filter chips to show only incorrect or pending answers - and a Download PDF button exports the review as a printable document (site admins can export any student's attempt from the same screen when helping with support).
Certificates /account/certificates/ All earned certificates with download and share actions

Pro adds more Learning entries when the matching module is active: Notes & Bookmarks, Spaces, Learning Paths, Cohorts, Assignments, and Grades (/account/grades/ - weighted gradebook breakdown).

Account:

Section URL What it shows
Order History / Billing /account/billing/ Active subscriptions, time-limited access, and order history
Account details /account/details/ Profile, email, password, notification preferences
Memberships /account/membership/ Current plan and plan management (when memberships are enabled)

Dashboard

The dashboard at /account/ is the first page a student sees after logging in.

Dashboard overview

The header greets the student by name and shows a brief summary line.

Dashboard header

Stats row

Four stat cards give a quick snapshot:

  • Courses Enrolled - total enrollments across all states.
  • In Progress - courses the student has started but not finished.
  • Completed - courses marked as completed.
  • Avg Quiz Score - the student's average grade across all scored quiz attempts, displayed as a percentage.

Dashboard stats

Continue Learning

When a student has an in-progress course, a "Continue Learning" card appears below the stats row with a direct link to pick up where they left off.

Continue learning card

Courses preview

A grid of the student's enrolled courses is shown below the continue-learning card, giving quick access to the courses they are working through.

Courses preview grid

Recent activity

A feed of recent course and lesson activity, including completions and quiz attempts.

Recent activity

If the student has no enrollments yet, each widget shows an empty state with a link to the course catalog.

My Courses

The full course list is at /account/courses/.

My Courses page

Tabs

The tab bar at the top filters the list:

  • All - every enrollment regardless of state.
  • In Progress - courses started but not finished.
  • Completed - courses the student has finished.
  • Wishlist - courses saved to the wishlist but not yet enrolled.

Each tab shows its count next to the label.

My Courses tabs

The list loads 40 courses per tab. When more exist, a Load more button appears at the bottom of the tab panel to fetch the next page via REST without reloading the page.

Course cards

Each enrolled course is shown as a card with the course title, progress (if in progress), and quick-access actions.

Course card

Course grid

Account details

The account details page at /account/details/ lets students update their profile and notification preferences.

Account details overview

Profile section

Students can change their Display name, First name, and Last name.

They can also set a profile photo. The profile section shows the current avatar with an upload control: pick an image and Learnomy crops it to a square and stores it as the account avatar, which then replaces the Gravatar across dashboards, the lesson player, certificates, and instructor profiles. A remove option clears the custom photo and falls back to the Gravatar. Uploaded photos are saved immediately through the account API, separate from the Save button on the rest of the form.

Profile fields

Email section

To change the email address, the student types the new address and enters their current password. Learnomy sends a confirmation link to the new address. The email does not change until the student opens that link. While a confirmation is pending, a notice shows the pending address.

Email field

Password section

To change the password, the student enters a new password (minimum 8 characters) and confirms with their current password. Leaving the new password blank keeps the existing password unchanged.

Password fields

Notification preferences

Below the profile form, students choose which emails they receive and whether the mobile app sends push notifications. Four toggles are available:

  • Enrollment and welcome emails
  • Course progress, completions, and certificates
  • Comments, replies, and new reviews
  • Push notifications on your mobile device

The first three control email categories; the fourth is a single switch for mobile push notifications. Transactional emails (receipts and account-security messages) are always sent and do not appear in this list.

Notification preferences

Each toggle saves immediately when changed. Unchecking a category stops that type of email. Checking it turns it back on.

Page titles

Every account page has its own browser and search-results title instead of just showing your site name - for example, /account/certificates/ carries the title "My Certificates", /account/billing/ carries "Billing", and /account/details/ carries "Account Details". This applies to every Learning and Account section listed above, including the Pro modules (Notes & Bookmarks, Spaces, Learning Paths, Cohorts, Assignments).

For developers

Any add-on that registers its own account route can title it through the learnomy_route_title filter, which Learnomy calls whenever a route has no built-in title:

add_filter( 'learnomy_route_title', function( $title, $route, $data ) {
    if ( 'account-my-custom-section' === $route ) {
        return __( 'My Custom Section', 'my-textdomain' );
    }
    return $title;
}, 10, 3 );

A route with no matching case and no filtered title still falls back to a readable label derived from the route name, so a custom account page never shows a bare site name.

Tips

  • The account navigation is the same across all account pages. Students can switch between Dashboard, My Courses, Certificates, Billing, and Account details without navigating away.
  • The Dashboard stats derive from live enrollment and quiz data. They update immediately after a student completes a lesson or submits a quiz.
  • A new registration banner appears on the dashboard the first time a user with no prior student role visits /account/. It clears once they navigate away.

Taking a Course

A Learnomy course page

This page covers the enrolled learning experience: how to navigate to a course after enrolling, how the lesson player works, how to mark lessons complete, and how Learnomy tracks progress so a student can always pick up where they left off.

Overview

Once a student is enrolled, the course detail page at /courses/<slug>/ switches to an enrolled view that shows the curriculum with progress indicators. Each lesson opens in a full-screen player. Progress is tracked per lesson and rolls up to a course-level percentage. Learnomy remembers the last lesson a student was on so they can resume from the dashboard.

The enrolled course page

After enrolling, the course detail page shows an "Enrolled" badge and a curriculum with per-lesson checkmarks and a progress bar.

Enrolled course view

Progress bar

The progress bar at the top of the curriculum section shows the percentage of lessons completed.

Course progress bar

The percentage is recalculated each time a lesson is marked complete.

Curriculum with checkmarks

Every lesson in the curriculum shows a checkmark when completed.

Enrolled curriculum

Lessons that require a previous lesson to be finished first (sequential mode) appear locked until the prerequisite is met.

Final quiz

When a course has a final quiz attached, it appears at the bottom of the curriculum.

Final quiz in curriculum

The lesson player

Clicking a lesson in the curriculum opens the full-screen lesson player.

Lesson player

The player uses a learning-first layout with a collapsible curriculum sidebar on the left, the lesson content in the centre column, and a discussion panel on the right where students can ask questions and read replies.

Curriculum sidebar

The left sidebar shows the full course curriculum (sections and lessons) with completion states.

Player sidebar

  • Completed lessons show a filled checkmark.
  • The current lesson is highlighted.
  • Locked lessons (in sequential mode) are shown with a lock icon and cannot be clicked until the prerequisite lesson is completed.

Click the close icon at the top of the sidebar to collapse it and give the content more room. An open-panel button appears on the edge of the screen to reopen it.

Lesson content

The main content area shows the lesson body.

Player content body

The content type determines what the player shows:

Lesson type What renders
Text / rich content Editor.js body with formatted text, headings, lists, images, and embedded media
Video An embedded video player (Plyr.io) that tracks watch percentage
Audio An embedded audio player
PDF An inline PDF viewer

Learnomy tracks video watch percentage in the background. If the course or lesson requires a minimum watch percentage before the lesson can be marked complete, the "Mark complete" button stays inactive until that threshold is reached.

Prev and Next buttons at the bottom of the content area move between lessons.

Player nav controls

The buttons are labeled with the adjacent lesson titles. Clicking Next after marking the current lesson complete moves directly to the next lesson. Clicking Next without marking complete advances to the next lesson but leaves the current one unmarked.

Actions bar

The actions bar at the top of the content column holds the mark-complete button and other controls.

Player actions bar

Marking a lesson complete

  1. Work through the lesson content.
  2. Click Mark complete in the actions bar or at the bottom of the content area.

Learnomy saves the completion, updates the progress percentage, and checks the lesson checkmark in the sidebar. The "Mark complete" button changes to show "Completed."

If the lesson has a completion requirement (a minimum video watch percentage, a required quiz pass, or manual confirmation), the button is inactive until the requirement is satisfied. The requirement is shown below the button.

Course progress

Progress is stored per lesson per student. The course-level percentage is derived from the ratio of completed lessons to total lessons.

When a student returns to the course later, the Dashboard shows a "Continue Learning" card pointing to the last lesson they were on. Clicking it jumps directly back into the player at that lesson.

Leaving a course

A student who joined a free course themselves can leave it again without asking anyone. On the course page, under the Start / Continue learning button in the sidebar, there is a quiet Leave this course link.

Clicking it asks for confirmation first, and the confirmation says what is actually at stake. A student who has made progress is told the percentage they will lose; a student who has not started is simply told they can rejoin. On confirming, they are removed from the course and sent back to My Courses.

Rejoining is allowed, but progress does not come back. A student who leaves at 60% and enrolls again later starts from zero. This is worth saying out loud when a student asks, because "you can rejoin at any time" sounds like nothing is lost.

The link lives on the course page itself and nowhere else -- deliberately not on the course cards in My Courses, where a screen full of cards each carrying a control that removes access is a mis-click waiting to happen.

What cannot be left this way

Only a free course the student joined themselves can be left. If access came from anywhere else, the link does not appear at all:

How the student got access Can they leave from the course page?
Enrolled themselves in a free course Yes
Bought the course No
A membership or subscription that includes the course No
Enrolled by an administrator, individually or in bulk No
Enrolled from an outside system through the incoming webhook No

The reason is money and entitlement. Leaving a course they paid for would forfeit the access without refunding anything, and access that comes from a membership is not the course's to give up -- it ends when the membership ends. The same rule is enforced on the API, so an app or integration cannot work around it: a request to leave a paid or membership enrollment is refused with an explanation rather than honoured.

Students in this position should be pointed at you, not at a button. An administrator can remove any enrollment from the Students screen, and can pair that with a refund where one is due. See Course Access and Enrollment for how removal works on the admin side and what happens to the student's record afterwards.

The completion screen

When a student finishes the last required element (all lessons, or lessons plus the final quiz, depending on the course completion setting), the course page shows a completion screen.

The completion screen shows the instructor's custom completion message if one is set, or a default congratulations message. If the course awards a certificate, a View Certificate (or Get Certificate) button appears here.

If the course allows retaking, a Retake Course button also appears. When retaking is configured to reset progress, the button warns the student that their previous progress and quiz attempts will be cleared.

Pre-enrollment: what non-enrolled students see

A student who is not yet enrolled sees the course detail page in its sales view.

Non-enrolled course hero

The curriculum shows a preview of section titles and a subset of lessons.

Curriculum preview for non-enrolled

An enrollment box shows the price and a button to enroll or purchase.

Enroll box

The instructor bio and course reviews are visible to everyone.

Instructor section

Reviews section

Tips

  • Sequential mode is controlled per-course by the instructor. In sequential mode, lessons must be completed in order. When sequential mode is off, students can open any lesson in any order.
  • The lesson player is fully keyboard-accessible. Sidebar navigation, the mark-complete button, and the prev/next controls are all reachable by keyboard.
  • The player works in the Learnomy mobile web app (WebView mode) as well as the browser. The layout adapts to mobile viewports.
  • If a student tries to access a lesson URL directly without being enrolled, the player shows an "Enrollment Required" gate instead of the lesson content.

Certificates and Billing

This page covers the two student account pages that deal with credentials and payments: My Certificates and Billing.

My Certificates

The certificates page is at /account/certificates/. It lists every certificate a student has earned, with actions to verify, share, download, and add to LinkedIn.

Getting there

From any account page, click Certificates in the account navigation.

Certificates page header

Certificate cards

Each certificate appears as a card.

Certificate card

The card shows:

  • The course name.
  • The issue date.
  • The short credential ID (the first eight characters of the UUID in a code block).

Card actions

Each certificate card has four actions.

Certificate card actions

Action What it does
Verify Opens the public verification page at /verify/<uuid> in the browser. Anyone with this link can confirm the certificate is genuine.
Add to LinkedIn Opens LinkedIn's "Add Certification" dialog pre-filled with the course name, your site name as the organization, the issue year and month, the credential ID, and the verification URL.
Share Copies the verification URL to the clipboard.
Download Downloads the certificate as a PDF.

Empty state

If the student has not earned any certificates yet, the page shows a message: "Complete a course to earn your first certificate."

Certificates from deleted courses

If a course has been deleted after a certificate was issued, the certificate remains on this page and stays valid. The card title shows "Course archived (certificate still verifiable)". The credential ID, verification URL, and download all continue to work. The LinkedIn button does not appear for archived certificates.

Anyone who opens the certificate's public verification link at /verify/<uuid> sees the course named "Course no longer available" instead - the verification page and this account page use different wording for the same situation, but both agree the certificate is still genuine.

The full certificates page

Full certificates page

Billing

The billing page is at /account/billing/. It shows active subscriptions, time-limited course access, and the full order history.

Getting there

From any account page, click Billing in the account navigation.

Billing page header

Active subscriptions

The first section lists all active subscriptions.

Subscriptions section

Each subscription card shows:

  • The plan or course name.
  • Whether it is a "Membership plan" or "Course subscription."
  • The status badge (Active, Past due, Cancelled, etc.).
  • The renewal date.
  • The next payment amount.
  • The payment provider (Stripe or PayPal).

Subscription card

If a subscription is set to cancel at the end of the billing period (the student cancelled but access continues until the period ends), the card shows a note: "Ends at the current period. Access continues until then."

Two buttons are available per subscription:

  • Manage - links to the membership manage page where the student can cancel or change their plan.
  • Manage payment method - opens the Stripe Customer Portal (shown for Stripe subscriptions only) where the student can update their card on file.

If the student has no subscriptions, the section shows an empty state with a "Browse plans" link.

Time-limited access

When a student has enrolled in a course with a fixed access period (a course that expires after a set number of days), those enrollments appear in the "Time-limited access" section.

Each card shows the course title, the access expiry date, and how many days remain. When fewer than 7 days remain, the badge turns to a warning color. When access has expired, the badge shows "Expired" and a Renew access button replaces the Continue button.

This section only appears when the student has at least one time-limited enrollment.

Order history

The order history table lists all completed transactions.

Order history

Columns:

Column Contents
Date Transaction date
Item Course or membership name. Clickable if the enrollment is still active
Amount Amount charged. Struck through if refunded
Status Transaction status badge
Receipt A download link for the PDF receipt

Refunded transactions appear in the table with the original amount struck through and a note showing the refund date below the amount.

Full billing page

If the student has no transactions, the section shows an empty state with a "Browse courses" link.

Billing history

Pro feature. Requires Learnomy Pro with the Membership Pro extension active.

Below the order history, a Billing history section shows what happened to the student's membership, as opposed to what they were charged for it. The order history above answers "what did I pay". It cannot answer "why did my plan change mid-month", "when was I paused", or "who gave me this", because none of those are payments.

Three columns, ordered by when each change was recorded rather than by the date shown, so a plan change scheduled for a future date sits with the day it was made:

Column Contents
Date When the change took effect
Change Plan changed, Paused, Resumed, Refunded, or Gift applied
Details For a plan change, the plan it moved from and the plan it moved to. For a gift, the plan granted. Otherwise a dash.

The section lists the 25 most recent changes across all of the student's subscriptions, and only appears once something has actually happened to one of them. A student who subscribed and stayed on the same plan sees nothing here, rather than an empty heading that reads like a broken feature.

Students only ever see their own history. You see the same events per subscription in wp-admin, under Change history on the subscription detail page, so a member asking "why did my plan change" is answered from the record they are already looking at. See Subscriptions.

Tips

  • The verification URL is a permanent link. It works whether the student is logged in or not, making it safe to put on a resume or portfolio.
  • Receipts are downloadable as PDFs from the order history table. They are also emailed to the student at the time of purchase (unless the student has opted out of transactional emails).
  • Students cannot cancel a Stripe subscription from within the Learnomy billing page directly. The Manage button on the subscription card links to the membership management page, which has the cancel option. For Stripe subscriptions, the Manage payment method button opens the hosted Stripe Customer Portal.
  • PayPal subscriptions show the payment provider as "Paypal" in the billing details. Cancellation for PayPal subscriptions goes through the membership manage page as well.

Managing Students (Admin)

Learnomy students admin

The Students section of the WordPress admin gives you a bird's-eye view of every learner on your site and a per-student detail page where you can see their full history and take management actions.

The Students list

Go to Learnomy > Students.

The list shows every user who has enrolled in at least one course. Each row shows the student's name and email, how many courses they are enrolled in, their completion rate (as a percentage), their last active date, and the date they joined. (Lifetime spend is not a column here - it appears on the per-student detail page's Learning summary card.)

Use the search field at the top to find a student by name or email. Next to it, the inactivity dropdown filters the list by how long a student has been inactive - "Any activity", "Inactive 7+ days", "Inactive 14+ days", or "Inactive 30+ days".

Reaching an inactive student

Finding the lapsed students is only half the job. There are two ways to get an email to them, and they use the same message.

The automatic nudge

Learnomy checks once a day for students whose active, uncompleted courses have gone quiet past your inactivity threshold, and emails them the Re-engagement Reminder - a short note pointing back at their dashboard. A student is nudged once per lapse, not once a day.

The threshold is Inactive after (N) days on LMS Settings > Emails, and it defaults to 14 days, matching the "Inactive 14+ days" option in the filter above. Turning off the Re-engagement Reminder toggle in the same settings screen stops the email, but not the daily sweep: the in-app notification is still written and the learner is still marked as nudged. So a lapse that passes while the toggle is off does not produce an email later either - that learner waits a full threshold window before becoming eligible again. Full configuration is in Emails and Announcements.

Sending one yourself

Instructors get the same message as a one-click action. The Students at Risk card on the instructor dashboard - it appears on the Overview and in the Students section - lists up to five of their own students who have not made progress in 14 days or who never started at all, each row showing how long they have been quiet.

Click Send reminder on a row to email that one student straight away. The button confirms with "Reminder sent" and does not need the daily sweep to come round.

Two things this respects, so nobody gets mail they have refused:

  • If you have switched the Re-engagement Reminder off in settings, the button sends nothing.
  • If that student has opted out of promotional email on their own account page, they are skipped.

An instructor only ever sees, and can only ever nudge, students enrolled in courses they author. View all inactive students at the bottom of the card opens the full list.

The card's own 14-day threshold is fixed and separate from the Inactive after setting, so it keeps naming the same people whichever threshold you choose for the automatic sweep.

The student detail page

Click a student's name, or click View in the row actions, to open the student detail page.

The detail page collects all of that student's Learnomy data in one place. Administrators see every card and every management action. Instructors who reach the page via their own course data see the same cards but without the management controls.

Profile card

Shows the student's email, WordPress username, registration date, and the date they were last active on the site.

Learning summary card

A four-row summary at a glance:

Field What it shows
Courses enrolled Total number of courses the student is enrolled in.
Courses completed Completed count and completion rate as a percentage of enrollments.
Certificates earned Number of certificates issued to this student.
Lifetime spend Total amount the student has paid across all transactions.

Grant course access (admin only)

A dropdown lets you manually enroll the student in any published course they are not already enrolled in. Manual grants bypass payment, so this works for paid courses and membership-gated courses. The enrollment is recorded with source manual.

If the student is already enrolled in every published course, the card shows a notice instead of the dropdown.

Course enrollments

A table of every course the student is enrolled in, with columns for:

  • Course - links to the course editor.
  • Status - enrollment status badge (active, completed, expired, etc.).
  • Progress - percentage of lessons marked complete.
  • Source - how the enrollment was created (purchase, manual, membership, etc.).
  • Enrolled - the date the enrollment was created.
  • Completed - the date the course was completed, or a dash if not yet complete.

Row actions (admin only):

  • Reset progress - clears the student's lesson completions and quiz attempts for that course. The enrollment remains active; the student starts from the beginning.
  • Remove access - deletes the enrollment entirely. The student loses access to the course and their progress for it is removed.

When a student holds more than one grant

A student can reach the same course through more than one route: they might buy it and also receive it through a membership or a Space. Learnomy stores one enrollment record per route, so both grants can be managed on their own.

That means two screens answer two different questions, and they are labelled accordingly:

  • The Enrollments panel on the course editor counts people. Each student appears once, with a chip for every grant they hold, for example Free and Membership. Paid grants (purchase, subscription, membership) are highlighted so you can tell a paying student from a comped one at a glance.
  • The Enrolled students table on the course overview lists records. A student with two grants appears once per grant, each row with its own Remove access action, so you can revoke the purchase and leave the membership in place. When the two numbers differ, a line above the table explains why.

Removing one grant does not remove the others. If a student still has access after you revoke a purchase, check the course overview for a second row - a membership or Space grant is usually still active.

Certificates

A table of every certificate Learnomy has issued to this student: the course it was issued for, the issue date, the certificate status, and a link to the public verification page. An empty state appears if no certificates have been earned yet.

Quiz attempts

A table of all quiz attempts this student has made, showing the quiz name, score percentage, pass/fail result, attempt status (Submitted, Graded, etc.), and the date the attempt was submitted.

Quiz attempt limits - granting an extra attempt

Below the attempts table, a Quiz attempt limits card shows one row per quiz this student has attempted that has an attempt cap. Quizzes with unlimited attempts are not listed, because there is nothing to grant.

Each row shows what the cap actually is for this student - the quiz's own limit plus anything already granted to them - against how many attempts they have used. That is the state you need before deciding to hand out another.

Two actions sit on each row:

  • Grant 1 attempt - gives this student one more attempt at that quiz. It affects only this student and only this quiz; the quiz's setting is unchanged for everyone else.
  • Take back 1 attempt - reverses a grant.

Use this when a student was cut off by a genuine problem - a browser crash, a power cut, a mis-set cap - rather than lowering the cap for the whole cohort.

Who can use it, per row: an administrator sees the action on every row. An instructor sees it only on quizzes belonging to courses they can grade. Rows they cannot act on show a dash, so the table still lines up.

The granted total is stored per student, so repeated grants accumulate, and the student's own quiz screen reflects the new figure immediately - the start gate, the results screen and the "Try Again" button all read the same effective limit.

To change the cap for everyone taking a quiz, edit Maximum attempts on the quiz itself instead. See Building a Quiz.

Payments

A table of every payment transaction associated with this student: date, transaction type, amount, payment provider, and status. An empty state appears if no payments are on record (for example, a student who only enrolled via manual grant or a free plan).

Removing a student

The Remove student button in the page header (admin only) deletes all of the student's course enrollments. Their WordPress user account is not deleted and they can re-enroll in courses afterwards. Use this to clear a test account or when a student requests removal of their learning data.

A confirmation prompt appears before anything is deleted.

Tips

  • The student detail page is almost entirely read-only for instructors. They can see a student's enrollments, quiz attempts, and certificates for courses they co-instruct, but they cannot grant course access, reset progress, or remove the student. The one action they do get is Grant / Take back 1 attempt, and only on quizzes belonging to courses they can grade.
  • The completion rate in the Learning summary card is calculated as completed courses divided by enrolled courses. A student enrolled in 5 courses who completed 2 shows 40%.
  • Resetting progress does not affect the enrollment status or the enrollment record. The student keeps access to the course and can work through it again from the beginning.
  • The Payments table shows all transactions linked to the student's user ID. It includes refunds as well as purchases.

Course Search Autocomplete

As a student types in the course catalog search box, Learnomy shows a live dropdown of matching course names so they can jump straight to a course instead of waiting for a full page of results.

What you can do

  • Find a course by typing part of its title and picking it from a dropdown, without pressing Enter or loading a results page.
  • Get results after only two characters, updated as you keep typing.
  • Click any suggestion to go directly to that course's detail page.
  • Still run a full catalog search: pressing Enter submits the search form and the server re-renders the filtered grid, using the same query the suggestions came from, so the dropdown and the results page always agree.
  • See a clear "Searching..." state while matches load and a "No matching courses" message when nothing matches.

Course search autocomplete dropdown

How to use it

  1. Go to the course catalog at /courses/. The search box appears in the left filter sidebar (labelled "Search") and, in the top filter bar variant, above the course grid.
  2. Start typing a course name. Once you have typed at least two characters, a dropdown opens below the box.
  3. Wait a moment. Typing is debounced, so Learnomy waits briefly after your last keystroke and then fetches up to six matching courses. While it loads, the dropdown shows "Searching...".
  4. Read the suggestions. Each row is a matching course title. If nothing matches, the dropdown shows "No matching courses".
  5. Click a suggestion to open that course's detail page directly.
  6. Or press Enter to submit the full search. The catalog reloads showing every matching course in the grid, not just the first six.
  7. Click away from the box to close the dropdown. Returning focus to the box (with two or more characters still typed) reopens the last set of suggestions.

Settings & options

This feature works out of the box on the catalog and has no dedicated admin settings screen. The values it uses are:

  • Minimum characters - the dropdown starts fetching at two characters. Below two characters it stays closed.
  • Suggestion count - up to six courses are shown in the dropdown. The suggestion request is GET /learnomy/v1/courses with the search and limit (6) parameters.
  • Search field - the catalog search input uses the query parameter s, has autocomplete="off", and reuses the same course search as the full results page. Matching uses the course FULLTEXT search with a prefix wildcard, so "mind" finds "Mindful".
  • Dropdown messages - the "Searching..." and "No matching courses" labels come from window.lrnCatalog.i18n (searching and noResults), so they are translatable.
  • Suggestion links - each suggestion's URL is built from window.lrnCatalog.courseUrlTemplate, which resolves to the course route for the matched slug.

For developers

  • REST endpoint: suggestions are fetched from GET /learnomy/v1/courses?search=<query>&limit=6 (namespace learnomy/v1), the same endpoint the catalog grid uses.
  • Interactivity store: the behaviour lives in the learnomy/catalog store (assets/js/catalog-view.js). Relevant actions are search (debounced input handler), fetchSuggestions, openSuggestions, and hideSuggestions; state keys include suggestions, suggestOpen, suggestLoading, and the suggestVisible / suggestEmpty / suggestStatusLabel getters.
  • Template: the dropdown markup is templates/catalog/partials/search-suggest.php (a role="listbox" with role="option" items), included by both templates/catalog/sidebar.php and templates/catalog/grid.php. It is overridable from a theme at theme/learnomy/catalog/partials/search-suggest.php.
  • Search backend: matching runs through the course FULLTEXT search over lrn_courses (title plus the description_plain projection).
  • Catalog slug: the /courses/ base is filterable via learnomy_base_slug (see \Learnomy\base_slug() in includes/functions.php, which the catalog route binds to); suggestion links are built through \Learnomy\route_url( 'course', [ 'slug' => ... ] ).

Notes and Bookmarks

Learnomy Pro gives every enrolled student a personal notebook and a bookmark list that live right beside the lesson they are studying. Notes are searchable, taggable, and gathered into one account hub, so a learner with hundreds of them can still find the right one.

What you can do

  • Write more than one note on a single lesson, each with its own optional title and body, and edit any of them inline with autosave.
  • Organise notes with your own tags, reused from an autocomplete of tags you have created before.
  • Bookmark any lesson with a single toggle, so you can jump back to it later.
  • Search every note you have written across all your courses with full-text search, then narrow by course or by tag.
  • Manage everything from one place -- a first-class Notes & Bookmarks section in My Account at /account/notes/, with a paginated newest-first list.
  • See a Notes & Bookmarks summary widget on your student dashboard with live counts that links into the account hub.
  • Keep your notes and bookmarks private to you -- they are never shared with other students or the instructor.

Notes and Bookmarks first view

How to use it

Step 1 -- open a lesson you are enrolled in

The notes panel appears in the lesson player for any lesson in a course you are enrolled in. It is not shown to logged-out visitors or to people who are not enrolled.

Step 2 -- add a note

Type in the note field and Learnomy saves it for you -- there is no separate Save button. A status indicator shows "Saving" then "Saved". You can give the note a title, and you can add more than one note to the same lesson.

Step 3 -- tag a note

Add tags to a note to group it with related notes. As you type, Learnomy suggests tags you have used before so your vocabulary stays consistent. Tags are your own -- each student has their own tag list.

Step 4 -- bookmark the lesson

Use the bookmark toggle in the panel to mark the lesson. Toggle it again to remove the bookmark. Each lesson can be bookmarked once.

Step 5 -- find notes later from My Account

Open My Account -> Notes & Bookmarks (/account/notes/). Use the search bar to full-text search your notes, filter by course or by tag, switch between the Notes and Bookmarks lists, and page through the results. From a note you can jump straight back to its lesson, or delete it.

Settings and options

  • Multiple notes per lesson. There is no fixed limit on how many notes a lesson can hold. A single note's body can be up to 10,000 characters.
  • Per-student tags. Tags belong to the student who created them and are reused across all their courses.
  • Account list page size. The account hub shows 20 notes or bookmarks per page with previous / next navigation and an accurate total count.
  • Search behaviour. The account search uses MySQL full-text search across each note's title and content, with a plain match for very short terms.
  • Enable the feature. Notes & Bookmarks is a Pro extension. A site owner turns it on from the Modules screen (LMS Settings > Modules); it is off by default until enabled.
  • Privacy requests. Notes, tags, and bookmarks are included in a student's WordPress personal-data export and are removed on a personal-data erasure request.

For developers

REST API (namespace learnomy-pro/v1, every request is scoped to the current user):

  • GET /notes -- list / search the caller's notes (q, course_id, tag_id, page, per_page).
  • POST /notes -- create a note (lesson_id, title?, content, tags[]?).
  • GET /notes/{id}, PATCH /notes/{id}, DELETE /notes/{id} -- read, update, delete a single note (owner only).
  • GET /lessons/{lesson_id}/notes -- this lesson's notes, used by the in-lesson panel.
  • GET /note-tags, POST /note-tags, DELETE /note-tags/{id} -- the caller's tags and counts.
  • GET /bookmarks -- list the caller's bookmarks (course_id, page, per_page).
  • POST /bookmarks/{lesson_id} -- toggle a bookmark; DELETE /bookmarks/{lesson_id} -- remove it.

Capability:

  • learnomy_save_notes -- auto-granted to every logged-in user with read. This is the gate on all notes / bookmarks endpoints; lesson-scoped writes additionally require access to the lesson's parent course.

Storage:

  • Four dedicated indexed tables -- lrn_pro_notes, lrn_pro_note_tags, lrn_pro_note_tag_map, lrn_pro_bookmarks. Notes carry a FULLTEXT index on (title, content).

Front routes and hooks consumed:

  • Registers the account-notes front route (/account/notes/) and a My Account nav item.
  • Renders the panel on the learnomy_lesson_player_aside action and a student-dashboard widget on learnomy_dashboard_widgets.
  • Cascades note and bookmark deletion on the learnomy_lesson_deleted action.

Notifications

Learnomy shows logged-in learners a notification bell with a dropdown feed of their unread in-app alerts -- enrollments, course completions, quiz results, certificates, payments, announcements, and more. This is the on-site alert center, separate from any email a learner receives.

What you can do

  • See a bell with an unread badge on Learnomy frontend pages while logged in.
  • Open a dropdown feed of your most recent unread notifications, each with an icon, title, message, and a relative time such as "3 hours ago".
  • Mark a single notification read by clicking it, or clear the whole list with Mark all read.
  • Get alerts for the events that matter to a learner -- new enrollment, course completed, quiz result, certificate issued, payment receipt, membership changes, and site announcements.
  • Rely on it being private -- the feed only ever shows the current user's own notifications.

Notifications first view

How to use it

Step 1 -- log in and find the bell

The bell is added to Learnomy frontend pages for logged-in users. When you have unread notifications, a count badge appears on the bell.

Step 2 -- open the feed

Click the bell to open the dropdown. It lists your most recent unread notifications, newest first. If there are none, it shows "No new notifications."

Step 3 -- read and clear

Click any notification to mark it read. Use Mark all read in the panel header to clear every unread notification at once. The unread badge updates as you go.

Settings and options

  • In-app, not email. The bell is the on-site notification center. It is distinct from a learner's email notifications and email preferences, which are configured separately.
  • Unread-focused. The dropdown shows unread notifications (up to the most recent ones); reading or marking all read empties the list.
  • Dark mode. Learnomy follows your theme's colour scheme. It does not show its own dark-mode toggle, because a plugin-only switch on top of a theme is a way for the two to disagree, not a feature. If your theme has no dark mode of its own and you want to give members the switch, a developer can turn it on with add_filter( 'learnomy_standalone_dark_mode', '__return_true' ); and the toggle then appears next to the bell.
  • Notification types. Each type has its own icon -- for example a graduation cap for enrollment, an award for a certificate, a megaphone for an announcement -- and a human-readable label used on admin surfaces such as the Email Log.

For developers

REST API (namespace learnomy/v1, all endpoints require a logged-in user):

  • GET /notifications -- recent notifications for the current user (limit, default 20, max 50).
  • GET /notifications/unread-count -- the current user's unread total.
  • PATCH /notifications/{id}/read -- mark one notification read (owner only; returns 404 for a row that is not the caller's).
  • POST /notifications/mark-all-read -- mark every unread notification read.

Model and data:

  • Learnomy\Models\Notification -- create, read, and mark-read logic over the lrn_notifications table. Notifications are keyed to a recipient user_id with type, title, message, is_read, and created_at.
  • Notification::type_label( $type ) -- resolves a type slug to a human label, filterable with learnomy_notification_type_label.

Frontend:

  • The bell partial (templates/partials/notification-bell.php) is included on frontend pages for logged-in users and is driven by the Interactivity API store learnomy/notifications.

Wishlist

The wishlist lets a logged-in learner save courses for later with a single heart button, then find them again from a Wishlist item in My Account. It is a lightweight "come back to this" list, separate from enrollment.

What you can do

  • Save any course for later by clicking the heart button on its course card.
  • Remove a course from your wishlist by clicking the same heart again -- it toggles.
  • Reach everything you saved from the Wishlist item in the My Account menu.
  • See the heart update instantly, with the change saved to your account so it persists across devices and sessions.

Wishlist first view

How to use it

Step 1 -- log in

The heart button only does something for logged-in users, since a wishlist belongs to an account.

Step 2 -- click the heart on a course card

Each course card has a heart button. Click it to add the course to your wishlist; the heart fills in. Click it again to remove the course.

Step 3 -- open your wishlist

In My Account, open the Wishlist item to see the courses you have saved. From there you can go to any course to enroll or buy when you are ready.

Settings and options

  • Logged-in only. The heart toggle acts on the current user's account. The assets that power it load only where they are needed and only for logged-in users.
  • Where the Wishlist lives. The Wishlist is a My Account menu item that deep-links to the wishlist tab of your Courses page.
  • Persistence. Your wishlist is stored on your account, so it is the same on any browser or device where you are logged in.
  • Accessible labels. The heart button carries an accessible label that switches between "Add to wishlist" and "Remove from wishlist" and reflects its pressed state.

For developers

REST API (namespace learnomy/v1, logged-in user):

  • POST /courses/{id}/wishlist -- add a course to the current user's wishlist.
  • DELETE /courses/{id}/wishlist -- remove a course from the wishlist.
  • GET /wishlist -- the current user's wishlisted course IDs.

Data and helpers:

  • Stored in the learnomy_wishlist user-meta. Read it through \Learnomy\wishlist_ids( $user_id ), the single tolerant reader (do not decode the meta directly).

Frontend:

  • The heart button uses the Interactivity API store learnomy/wishlist (assets/js/wishlist-view.js). Localised labels can be supplied via window.lrnWishlistI18n or per-button data-i18n-add / data-i18n-remove attributes.

Instructors and Earnings

Everything about recruiting, approving, and supporting the instructors on your platform.

Becoming an Instructor

Anyone with a WordPress account on your site can apply to become an instructor. Admins review applications and approve or reject them from a dedicated queue. This page covers the applicant experience and the admin workflow for processing those applications.

Overview

The application flow lives at /become-instructor/. It is a gated frontend form: logged-in users see the form; guests are redirected to log in first. Once approved, the user gains the lrn_instructor WordPress role and can start creating courses in wp-admin.

The admin queue is at Learnomy > Applications (?page=learnomy-applications).


Applying to Become an Instructor

  1. Go to your site's /become-instructor/ page. If you are not logged in, you will be redirected to the login screen first.

    Become Instructor form

  2. Fill in the required fields:

    • About You - Your background and professional experience.
    • Areas of Expertise - Separate multiple areas with commas (e.g., Web Development, Data Science).
    • Course Idea - Describe the first course you want to create: topic, target audience, and what students will learn.
    • Teaching Experience (optional) - Any past experience teaching online or in person.

    Application form fields

  3. Click Submit Application.

After submitting, the page confirms that your application is under review. You will receive an email notification once a decision is made.

If your application was rejected

The page shows the rejection reason (if one was provided) and displays the form again so you can update your details and reapply.


Admin: Reviewing Applications

Navigate to Learnomy > Applications. The queue opens on the Pending tab by default.

Pending applications list

Status tabs

Three tabs filter the list:

Tab What it shows
Pending Applications waiting for a decision
Approved Applications that have been approved
Rejected Applications that have been rejected

Each tab shows a count in parentheses.

Status tabs

Reading an application row

Each row shows the applicant's name, email, a truncated bio, their course idea, the date they applied, and their current status.

Application row

Click the applicant's name to open the full application detail page, where the complete bio, expertise, teaching experience, course idea, and preferred payout method are visible.

Approving an application

  1. Locate the application on the Pending tab.

    Pending applications table

  2. Click Approve in the Actions column. A confirmation prompt appears.

  3. Confirm. The user's role is immediately upgraded to lrn_instructor. The application moves to the Approved tab and a notification email is sent to the applicant.

Rejecting an application

  1. Click Reject in the Actions column of a pending row.

  2. Confirm. The application moves to the Rejected tab. If you entered a rejection reason, the applicant sees it on the /become-instructor/ page when they return.

Approved applications have no further actions available in the queue. Rejected applications are also read-only in the queue (the applicant can re-apply from the frontend, which creates a new pending entry).

Bulk actions

Check one or more rows, then choose Approve or Reject from the Bulk Actions dropdown and click Apply. The queue supports up to 20 rows per page; use the pagination controls at the bottom to reach older applications.

Approved applications view


Tips

  • The form includes a honeypot field to block automated bot submissions. No CAPTCHA is required.
  • Once approved, an instructor authors courses and lessons in wp-admin editors (Learnomy > Courses), not on the frontend form.
  • The application record is preserved even after approval, so you can view what the instructor originally submitted from their detail page in Learnomy > Instructors.

Instructor Dashboard

The Learnomy instructor dashboard

The instructor dashboard at /instructor/ is an approved instructor's home base. It shows a real-time snapshot of course activity, student progress, and revenue, and it links directly into wp-admin for course authoring.

Overview

The dashboard is only accessible to users with the lrn_instructor role (or admins). Anyone who visits /instructor/ without that role sees either an "Application Under Review" notice (if they have a pending application) or a prompt to apply.

The dashboard itself is a read view: it surfaces what is happening across the instructor's courses, not a form for editing them. Course authoring happens elsewhere, and where depends on whether the front-end builder is active - see Create and edit courses below.


Create and edit courses

Instructors do not author courses on the dashboard. There are two authoring paths, and which one an instructor gets is decided by the site, not by them.

Without the front-end builder (the default)

Create and edit both happen in wp-admin:

  1. Click Create in the dashboard header, or go to Learnomy > Add New Course (?page=learnomy-new-course).
  2. Fill in the course details and save.
  3. To edit later, open My Courses and click the course title, or the Edit action on the row. Both open the wp-admin course editor.

This is what a Free site does, and it is what a Pro site does whenever the front-end builder extension is switched off.

With the Pro front-end builder

When the Front-end Builder extension is enabled, the same Create and Edit controls point at the front end instead - /instructor/create-course/ and /instructor/edit-course/{id}/ - and the instructor never leaves the site's theme. The builder also has dedicated lesson and quiz editors underneath those URLs.

Nothing in the dashboard changes shape when this is on; only the destinations of the Create and Edit links change. Full walkthrough: Front-end Course Builder.

The wp-admin editor stays available either way. An administrator can always edit any course from Learnomy > Courses, which is the fallback if an instructor reports the builder misbehaving.

For developers. The edit destination is one filter, learnomy_instructor_course_edit_url, exposed by Free and defaulting to the wp-admin editor. Pro's front-end builder overrides it. Anything that links an instructor to "edit this course" must go through that filter rather than building a wp-admin URL directly, or it will strand builder sites in the admin.


The Dashboard Header

The header greets the instructor by name and shows their avatar. Two action buttons are always visible:

  • Create - Opens the Add New Course page in wp-admin (?page=learnomy-new-course), or the front-end builder's create screen when that extension is enabled.
  • Catalog - Opens the public course catalog.

Dashboard header


Stats Row

Immediately below the header, four stat cards summarize the instructor's account at a glance.

Stats row

Card What it shows
Total Courses Total number of courses the instructor authors or co-teaches
Total Students Total enrolled students across all their courses
Published Number of courses currently in Published status
Revenue Lifetime commission earnings (the instructor's share of completed sales)

If the instructor has not authored any courses yet, the dashboard shows site-wide aggregates and displays an informational notice explaining the fallback.


My Courses Table

The courses widget lists the instructor's courses with title, status, and student count.

My courses section

Click a course title to open it in the course editor - wp-admin by default, or the front-end builder when that Pro extension is enabled (see Create and edit courses). The table shows the first few courses; if you have more, a link to the full courses list in wp-admin appears.

My courses full view

Each row carries three actions:

Action What it does
Edit Opens the course in the editor - wp-admin, or the front-end builder when that Pro extension is on.
View Opens the public course page in a new tab.
Duplicate Makes a draft copy of the course.

Duplicating a course

Duplicate is the fastest way to start a course that is mostly like one the instructor already runs: a new cohort, a second language, a shorter version of a long program. It is the same action administrators have had in wp-admin (#creating-a-course-lrn)); it is now on the instructor dashboard as well, which matters because most instructors never open wp-admin at all.

Click Duplicate on a row. There is no confirmation prompt, because nothing is changed or removed - a copy is added. The list reloads to the first page with a "Course duplicated" message, and the copy sits at the top.

The copy takes the original's title with (Copy) appended, gets its own new URL, and is always created as a draft. Nothing is published and no student sees anything until the instructor opens the copy, edits it and publishes it deliberately.

What the copy keeps:

  • Description, short description, content, thumbnail and intro video
  • Category and difficulty level
  • Pricing and access settings, including sale price and access duration
  • Prerequisites and completion rules
  • SEO title, meta description and share image
  • The instructor team, with each member's revenue share and who is primary
  • Every section and every lesson inside it, drafts included

What the copy does not get:

  • Students, enrollments and their progress
  • Reviews, the star rating and the review count - the copy starts unrated
  • The student count, which starts at zero
  • Quizzes, files attached to lessons, and course tags
  • The published status - the copy is always a draft

The quiz exclusion is worth flagging to instructors before they duplicate a heavily assessed course. The lessons arrive, the quizzes do not, and they have to be rebuilt on the copy.

Duplicate succeeds for the course's main author, for administrators, and for a co-instructor marked as primary on that course.

A co-instructor who is not primary sees the button - co-instructed courses appear in My Courses and the row actions are not filtered by permission - but the action fails with "Could not duplicate that course. Please try again." A site can grant it to all co-instructors through the learnomy_co_instructor_can_manage filter.


Completion Funnel

The funnel widget shows how students progress through the instructor's courses.

Funnel section

Four stages are tracked as both counts and percentages of the enrolled cohort:

  1. Enrolled
  2. Started (at least one lesson completed)
  3. Halfway (50% or more complete)
  4. Completed (course completion criteria met)

Use this to spot where students drop off. A steep drop between Enrolled and Started often means the first lesson needs work.

Funnel chart


Recent Enrollments

This widget lists the most recent students to enroll in any of the instructor's courses.

Recent enrollments section

Each row shows the student's name and the course they enrolled in.

Recent enrollments full


Earnings Widget

The earnings widget shows three figures and a withdrawal button.

Earnings section

Figure Meaning
Available to Withdraw Cleared commissions not yet paid out
This Month Earnings recorded in the current calendar month
Lifetime Total earnings since the account was created

Earnings summary

The Request Withdrawal button opens a withdrawal request. See Commissions and Earnings for the full withdrawal flow.

Withdraw button


Revenue Trend

The revenue trend widget charts the instructor's earnings over recent periods so they can see whether revenue is growing or declining.

Revenue trend section

Revenue trend chart


Announcements

The Announcements widget lets instructors send a broadcast message to every student enrolled in their courses. Type a title and message, optionally check Also send email, then click Send. The message is delivered as an in-app notification and, if the email option is checked, by email.

Below the compose form, the widget shows the three most recently sent broadcasts with the recipient count and time elapsed since sending.

Announcements section

Announcements view


Public Instructor Profile

Each approved instructor has a public profile page at /instructor/<username>/. This is separate from the dashboard and is visible to all visitors without login.

The public profile shows the instructor's avatar, headline, bio, and the list of their published courses.

Public profile header

Public profile courses

Instructors update their profile information at /account/instructor-profile (the Instructor profile link in the account navigation). An admin can also edit it from the instructor detail page in wp-admin.


Tips

  • Instructors author all course content (courses, lessons, quizzes, questions) in wp-admin, not on this dashboard.
  • The dashboard respects ownership scope: an instructor sees only their own courses' data. A WordPress administrator who visits the dashboard sees site-wide data and an informational notice.
  • Widget sections are rendered through a registry. Pro extensions can add new sections via the learnomy_dashboard_widgets filter.

Commissions and Earnings

Learnomy instructor earnings

When a student pays for a course, Learnomy splits the revenue between the instructor and the platform. This page explains how that split is calculated, how instructors track their earnings, and how withdrawal requests work.

Overview

Commission rules live at LMS Commerce > Earnings (?page=learnomy-earnings). The global default split is set in LMS Settings > Commissions. Override rules on the Earnings page let you give specific courses or instructors a different rate.

Note: The Earnings page appears only when the Commissions module is enabled, and the LMS Commerce top-level menu only appears when at least one commerce module is active. If you don't see Earnings, enable the Commissions module on the Modules page first.


How Commission Splits Work

Every completed transaction is split using the first matching rule in this priority order:

  1. Per-course rule - A rule tied to a specific course overrides everything else for that course.
  2. Per-instructor rule (Pro) - A rule tied to a specific instructor applies to all their courses that do not have a per-course rule.
  3. Default split - The global rate configured in Settings applies when no override rule matches.

The instructor receives their percentage of the net base amount (the customer's payment minus any fee or tax recorded at checkout). The platform keeps the remainder.


Setting the Default Split

Go to LMS Settings and find the Commissions section. Enter your Platform Commission (%), the share the platform keeps from each transaction. The instructor's share is the remainder.

For example, entering 30 means the platform keeps 30% and the instructor receives 70% of every transaction. The default is 30%.


Adding Commission Override Rules

Open LMS Commerce > Earnings and scroll to the Commission Rules card.

Commission rules

The card shows the active default split and a form for adding override rules.

  1. In the rule-type dropdown, choose Per course.

    Per instructor (Pro) rules require Learnomy Pro. The option is visible in the dropdown but grayed out on Free.

  2. Select the course from the Choose a course dropdown.

  3. Enter the instructor's percentage in the rate field.

  4. Click Add rule. The new rule appears immediately in the table below.

To change an existing rule, update the percentage directly in the table's rate field and click Save next to that row. To remove a rule, click the delete action next to it (the default split then applies again for that course or instructor).

Pro feature. Available in Learnomy Pro.

Per-instructor rules apply to all of an instructor's courses that do not already have a per-course override. To set one, choose Per instructor (Pro) from the rule-type dropdown, pick the instructor, enter the rate, and click Add rule.


Revenue Summary Cards

At the top of the Earnings page, four cards give a quick revenue overview.

Revenue cards

Card What it shows
This Month Total completed-transaction revenue in the current calendar month
Last Month Revenue in the previous calendar month
Lifetime Revenue All-time completed transaction total
Pending Withdrawals Aggregate of all pending withdrawal requests

There is also a Recurring Revenue row above these cards showing active subscriber count, monthly recurring revenue, and trial conversions this month.


Instructor Earnings Widget (Frontend)

On the instructor dashboard at /instructor/, the Earnings widget shows three figures:

  • Available to Withdraw - Cleared commissions not yet paid out.
  • This Month - Earnings recorded in the current calendar month.
  • Lifetime - Total earnings since the account was created.

Earnings widget summary

Requesting a Withdrawal

  1. On the instructor dashboard, click Request Withdrawal in the Earnings widget.

    Request withdrawal button

  2. Enter the amount you want to withdraw and your preferred payout method, then submit the request.

The request enters the Withdrawal Requests queue in wp-admin as a Pending entry. The instructor receives a notification when the admin processes it.

A site-configurable minimum withdrawal amount applies. The Request Withdrawal button is visible at any balance, but the form enforces the minimum when the request is submitted.


Admin: Withdrawal Requests Queue

Go to LMS Commerce > Earnings and scroll to the Withdrawal Requests card.

Withdrawal requests

Use the Pending / Processed / Rejected / All tabs to filter the list.

Approving a withdrawal

  1. Find a pending withdrawal on the Pending tab.

  2. Click Approve. A confirmation prompt appears.

  3. Confirm. The withdrawal status changes to Processed. The instructor's pending commission records are cleared. An email notification is sent to the instructor.

    Approving a withdrawal is a recording step only. The actual funds transfer happens outside Learnomy (bank transfer, PayPal, or another method) unless Stripe Connect is active.

Rejecting a withdrawal

  1. Click Reject on a pending row.

  2. A prompt asks for a rejection reason. Enter the reason (the instructor will see it in their notification).

  3. Click OK to confirm. The withdrawal moves to Rejected status and the instructor's available balance is restored.

Viewing withdrawal details

Click an instructor's name in the table to open the withdrawal detail page, which shows the full amount, method, commission records included, and any status history.


Recent Transactions Table

Below the revenue cards, the Earnings page shows a paginated list of completed transactions.

Recent transactions

Each row shows the student, course, total amount (with a net-base note if a fee or tax was deducted), the platform's share, the instructor's share, and the transaction date. Use the Export CSV button in the page header to download the full transaction history.


Tips

  • Commission splits are calculated and frozen at the time of each transaction. Changing a commission rule later does not retroactively alter older transactions.
  • The per-instructor rule requires Learnomy Pro. Free supports per-course overrides and the global default only.
  • A refund reverses the commission records for that transaction (marked reversed, not deleted), so your earnings totals stay accurate.

Managing Instructors

The Instructors list in wp-admin gives you a searchable, sortable view of every approved instructor on your site. Clicking an instructor opens a detail page with their full profile, the courses they teach, their earnings summary, and the application they originally submitted.

Overview

The Instructors list is at Learnomy > Instructors (?page=learnomy-instructors). Only users who have been granted the lrn_instructor role appear here. Pending applicants are in Learnomy > Applications, not in this list.


The Instructors List

Instructors list

The page header shows the Instructors title and the total count of instructors on your site.

Instructors list header

Use the search field to find an instructor by name or email. Type your search term and click Search. Click Clear to remove the filter.

Table columns

Instructors table

Column What it shows
Instructor Avatar, display name (links to the detail page), email, and headline
Courses Number of courses they author or co-teach
Lifetime earned Total instructor commission paid to date
Pending payout Current balance awaiting a withdrawal request
Joined The date their WordPress account was created

The Instructor and Joined columns are sortable. Click the column heading to sort ascending; click again to reverse.

Each row

Instructor row

Click an instructor's name to open their detail page. The email address is a mailto link.

The list paginates at 20 rows per page. Navigation controls appear at the bottom.

Suspending an instructor

The Suspend action pauses an instructor. While they are suspended:

  • they no longer appear in the public instructor directory, and their public profile page stops resolving
  • their courses stop accepting new enrollments, and the enrol button on those courses is replaced with a short note

Everything else is left alone, on purpose:

  • their courses stay published
  • students already enrolled keep full access and keep their progress
  • earnings already owed to them are unaffected, and any pending payout still stands

Suspended instructors show an amber Suspended badge in the list, and the row action becomes Resume. Suspension is fully reversible - resuming puts them back in the directory and reopens their courses to new enrollments.

You can still enrol somebody into a suspended instructor's course yourself, and the instructor can still see their own courses and earnings. Suspension is about who may start learning with them, not about locking anyone out of what they already have.

There is no "delete instructor" action. Removing the profile row would erase their biography and public details permanently while leaving their published courses and enrolled students exactly where they are, so the list would no longer show somebody who is still teaching on your site. If you need an instructor gone for good, suspend them first, then move or unpublish their courses and settle anything you owe them before removing the WordPress user account.


Instructor Detail Page

Click any instructor's name from the list to open their full detail page.

Instructor detail overview

Page header actions

The header area contains several quick-access buttons:

  • All instructors - Returns to the Instructors list.
  • View public profile - Opens the instructor's public profile page in a new tab.
  • Email instructor - Opens your email client with the instructor's address pre-filled.
  • Verify / Unverify - Grants or removes the verified badge on the instructor's public profile.

If the instructor has a pending application, Approve and Reject buttons also appear here so you can act directly from the detail page.

Profile card

Profile card

The profile card shows the instructor's email, join date, and any profile data they have filled in: headline, expertise, bio, location, website, Twitter handle, LinkedIn URL, and verification status.

Earnings card

Earnings card

The earnings card shows:

  • Lifetime earned - Total instructor share received to date.
  • Pending payout - The current cleared balance waiting for a withdrawal.
  • Commission rate - The effective rate for this instructor (instructor % / platform %). If an override rule applies, a Custom badge appears; otherwise it says (default).

On Free, per-instructor custom rates are not available. The note in the card explains how to set one if Pro is active. On Pro, an Adjust commission rate link goes directly to the Commission Rules section on the Earnings page.

Signup application

If the instructor submitted an application before being approved, a Signup application card appears below the profile and earnings cards.

It displays the original application data: status, submission date, expertise, teaching experience, bio, and course idea. A preferred payout method is shown if one was recorded on the application. If the application was rejected at some point, the rejection reason is also shown.

Courses table

Courses table

The courses card lists every course the instructor is involved with. Each row shows the course title, their role (Main author or Co-instructor), the course's published status, and its student count.

Recent withdrawals table

The withdrawals card shows the instructor's most recent withdrawal requests, each with the date requested, amount, payout method, and status.



Reassigning the lead author and adding co-instructors

You assign instructors from the course editor, not from the Instructors list. Open a course at Learnomy > Courses, click Edit, and scroll the main column to the Instructors card.

This card appears only when all of these are true:

  • You are editing an existing course (it does not show while creating a new course).
  • You are a site administrator. Instructors do not see this card, because reassigning the lead author and the revenue split is a site-owner decision.
  • The Allow multiple instructors per course site setting is on. It is off by default. Turn it on under LMS Settings.

Course editor instructors card

Reassign the lead author

The lead author is the course owner. They are shown on the course, and all sales commission goes to them.

  1. Open the course and go to the Instructors card.
  2. In the Primary instructor (author) dropdown, choose the person who should own the course. The list shows approved instructors and administrators.
  3. Click Update Course at the top of the sidebar to save.

Use this when you build a course on behalf of a presenter and then hand it over: the chosen user becomes the primary instructor and the commission recipient.

Add a co-instructor

Co-instructors get credit on the course and on their public profile, plus read-only access to student progress and reports. Commission from sales still goes only to the primary instructor.

  1. In the Instructors card, find the list of people below the primary dropdown.
  2. Tick the checkbox next to each person you want to add as a co-instructor. Each row shows a Co-instructor badge; the lead author's row shows a Primary (gets commission) badge and is locked so it cannot be unticked.
  3. Click Update Course to save.

On large sites the list shows the first 200 instructors. If the person you need is not shown, use the Instructors screen to confirm their account, then return to the course editor.


Deleting an instructor

Learnomy does not have its own "delete instructor" button. You remove an instructor's WordPress account the normal way, from Users > All Users in wp-admin, and Learnomy reacts to that deletion automatically.

  1. Go to Users > All Users and select the instructor's account for deletion.
  2. WordPress asks what to do with content owned by that user. You can attribute it to another user or leave it for Learnomy to reassign (see below).
  3. Confirm the deletion.

What happens to their courses

As soon as the account is deleted, Learnomy reassigns every course the instructor owned (as primary author) or co-taught to a real user instead of leaving the course showing "Unknown":

  • If you chose to attribute the deleted user's content to another user on the WordPress delete-user screen, courses go to that user.
  • Otherwise, courses go to whichever administrator performed the deletion.
  • If the deletion happened without a logged-in admin (for example, via WP-CLI), courses go to the site's first administrator account.

A one-time notice appears in wp-admin confirming how many courses were reassigned and to whom. The reassignment covers both the course's primary-author record and any co-instructor credit the deleted user held.

If you would rather show a custom label than the site name for any courses that still show no instructor, use the learnomy_unknown_instructor_label filter.

add_filter( 'learnomy_unknown_instructor_label', function( $label, $course ) {
    return 'Learnomy Academy';
}, 10, 2 );

What else is removed

Deleting the WordPress account also removes the instructor's own learner data if they had any: quiz attempts, certificates earned as a student, enrollments, reviews, and progress records. Their instructor earnings and course data are not part of this cleanup - only the courses are reassigned as described above.

What an instructor sees in wp-admin

The Courses, Lessons, Quizzes and Question Bank lists show an instructor their own work. "Their own" means two things, and both count:

  • Anything they created, wherever it lives.
  • Anything on a course they manage, whoever created it. If you add a final exam to an instructor's course yourself, it appears in their Quizzes list, because they are the person answering for it.

The course filter dropdown on those lists offers exactly the courses they manage, so it can never list a course with nothing in it or leave one out.

The Question Bank is the exception. A question does not belong to a course - it can be used in any quiz on the site - so the bank is scoped to the person who wrote each question. An instructor sees their own questions, and administrators see all of them.

Who counts as managing a course is the lead author, plus any co-instructor you marked Primary. Ordinary co-instructors get credit and read-only reporting, not management, unless your site opts in with the learnomy_co_instructor_can_manage filter (see Commissions and Earnings for the credit side). Administrators always see everything.

Tips

  • The verification badge is a trust signal on the instructor's public profile. It does not affect their wp-admin access or commission rate.
  • When an instructor is approved via the Applications queue, they appear in the Instructors list immediately.
  • An instructor can be a main author on some courses and a co-instructor on others. Both show up in the courses table.
  • Only the primary instructor receives commission. Co-instructors get credit and read-only reporting access, not a payout.
  • Deleting an instructor's account is permanent. If you only want to stop them from managing courses, consider changing their role instead of deleting the account.

Pro feature. Available in Learnomy Pro.

Stripe Connect Payouts (Pro)

Learnomy Stripe Connect payouts

Stripe Connect lets Learnomy Pro automatically transfer instructor commissions directly to each instructor's own Stripe account. Instead of manually approving withdrawal requests and sending money through a third-party tool, payouts are triggered from wp-admin (or on a schedule) and land in the instructor's bank account via Stripe.

Overview

The Stripe Payouts page lives at LMS Commerce > Stripe Payouts (?page=learnomy-stripe-payouts). It has four sections: Connect Settings (API keys and payout policy), a summary stats row, a paginated payouts table, and an Outstanding Refund Debts panel.


Step 1: Configure Stripe Connect Settings

Before any instructor can connect or receive a payout, you need to configure the platform's Stripe credentials.

  1. Open LMS Commerce > Stripe Payouts.

    Stripe Payouts page

  2. Find the Connect Settings card at the top of the page.

  3. Check Enable Stripe Connect payouts.

  4. Enter your Secret key (starts with sk_live_ or sk_test_).

  5. Enter your Publishable key (starts with pk_live_ or pk_test_).

  6. Enter your Connect client ID (starts with ca_). This is found in your Stripe Dashboard under Settings > Connect.

  7. Set the Payout schedule: Monthly, Every two weeks, or Weekly. This controls how often automatic payouts run.

  8. Set the Minimum payout amount. Instructors with a balance below this threshold are held until the next cycle.

  9. Check Run payouts automatically on the schedule if you want payouts to fire without a manual trigger.

  10. Click Save settings. A status confirmation appears next to the button.

When you leave an API key field blank and save, the previously stored key is preserved. Submitting the form does not wipe keys you did not re-enter.


Step 2: Instructor Onboarding (Connect Account)

Each instructor must connect their own Stripe account before they can receive automated payouts. The onboarding link redirects the instructor through Stripe's OAuth flow.

At the end of the flow, Stripe returns an authorization code. Learnomy exchanges the code for the instructor's Stripe account ID and stores it encrypted (libsodium + AUTH_KEY). The instructor is now connected.

Before any transfer is attempted, Learnomy checks the instructor's Stripe account status to confirm charges_enabled and payouts_enabled are both true. If Stripe requires additional KYC information, the payout is held and an error is recorded rather than producing a silent failure.


Summary Cards

Below the Connect Settings card, four summary cards give a live overview of payout activity.

Summary cards

Card What it shows
Pending Total amount of pending payouts waiting to be transferred
Total Paid Cumulative amount successfully transferred to instructors
Failed Count of payouts that failed or were reversed
Connected Instructors Number of instructors who have completed Stripe onboarding

Processing Payouts

Manual trigger

Click Process Payouts in the toolbar above the payouts table. The button shows a loading state while the transfers run. A live status region announces the outcome to screen readers.

Processing finds all instructors with unpaid commissions above the minimum payout threshold, creates a Stripe Transfer to each connected account, and records the result.

Automatic schedule

When Run payouts automatically on the schedule is enabled, Learnomy runs the same process automatically according to the configured cadence (monthly, every two weeks, or weekly). The automatic run skips the cadence check if a manual run was triggered during the same period.


The Payouts Table

Payouts table

Each row in the payouts table shows:

Column What it shows
ID Internal payout row identifier
Instructor The instructor who received (or is owed) the payout
Amount The transferred amount
Period The billing period (YYYY-MM) this payout covers
Status Current payout status (see below)
Stripe Transfer The Stripe transfer ID, linked directly to the Stripe Dashboard
Date When the payout row was created

Payout statuses

Status Meaning
pending Transfer not yet attempted
paid Transfer successfully created in Stripe
failed Transfer attempt failed; see the inline error row below the payout
reversed A paid transfer was later reversed (typically due to a refund)

Filtering the list

Use the All / Pending / Paid / Failed / Reversed filter buttons to narrow the table.

Retrying a failed payout

When a payout fails, an error row appears directly below the failed row in the table, showing the Stripe error message.

Click Retry in the error row to re-attempt the transfer. Learnomy uses an idempotency key derived from the instructor and the exact commission set, so a retry cannot produce a duplicate transfer in Stripe. On success, the failed row is removed and a new paid row is recorded.

After a successful retry, the page reloads with a success notice. If the retry fails again, the error notice updates with the new error.


Outstanding Refund Debts

Refund debts section

When a customer is refunded and the original Stripe transfer to the instructor cannot be fully reversed (for example, because the instructor has already withdrawn the funds), Learnomy records the shortfall as an Outstanding Refund Debt in this panel.

Each debt row shows:

  • The instructor who owes the amount
  • The amount owed
  • The refunded transaction it came from
  • The original payout it is associated with
  • The reason for the debt
  • The date it was recorded

Learnomy automatically deducts outstanding debts from the instructor's next payout cycle (oldest debt first). A fully recovered debt disappears from this panel. A partial recovery reduces the row amount; the remainder carries over to the next cycle.

If the instructor's gross payout after debt deduction falls below the minimum payout threshold, the entire cycle is held until the next period.

When there are no outstanding debts, the panel shows an empty state.


Tips

  • Keep Stripe in test mode (sk_test_ / pk_test_) during setup. Test-mode keys let you run the full flow without real money moving.
  • The minimum payout amount prevents micro-transfers that Stripe would charge fees on. Set it to at least $25.
  • Stripe accounts that have not completed identity verification (charges_enabled = false or payouts_enabled = false) are silently skipped during a payout run. The KYC check result is cached for one hour per instructor to avoid repeated API calls.
  • The Stripe transfer ID in each paid row links directly to the corresponding transfer in your Stripe Dashboard, making reconciliation straightforward.

Solo Academy Mode

Solo Academy mode turns Learnomy into a single-teacher academy. When you are the only instructor and never plan to recruit others, this one switch removes the entire instructor-marketplace surface (sign-up, commissions, and revenue-share) so your students and your admin menus only show what applies to you.

What you can do

  • Run the site as a one-teacher academy where you author and own every course.
  • Hide the public instructor application page so visitors cannot apply to teach.
  • Remove the commission, earnings, and withdrawal screens, which have no meaning when there is no split to pay out.
  • Drop the co-instructor assignment control from the course editor.
  • Clean up your admin sidebar by hiding the Instructors and Applications pages.
  • Turn it all back off later with one toggle if you decide to open up to more instructors.

Solo academy mode enabled in settings

How to use it

  1. In wp-admin, go to LMS Settings > Settings (?page=learnomy-settings). The page opens on the General section.

  2. Scroll to the Academy Mode card, described as "For a one-teacher academy with no instructor marketplace."

  3. Turn on the Solo academy toggle, labelled I am the only instructor (solo mode).

  4. Click Save Changes at the bottom of the section.

Once saved, Learnomy immediately:

  • 404s the instructor application routes. /become-instructor/ and /register-instructor/ stop resolving, and any nav-menu link you added to them is removed automatically so you never dead-link.
  • Turns the commissions module off. The whole earnings, commission, and withdrawal surface disappears from the frontend and admin. Your stored Modules settings are not changed; the module is simply forced off while solo mode is on.
  • Hides co-instructor assignment. The course editor no longer offers to add a second instructor or split revenue.
  • Hides the admin Instructors and Applications pages under the Learnomy menu, since there is no roster to manage and no applications can arrive.

To leave solo mode, switch the same toggle off and click Save Changes. The instructor application page, commission screens, and admin pages come back, and any commission module you had enabled before is honoured again.

Settings & options

Setting Where What it does
Solo academy toggle LMS Settings > Settings > General > Academy Mode card Stored as learnomy_settings[solo_mode]. Off by default. When on, it is the single master switch for everything above.

The toggle drives three helper functions that the rest of the plugin reads:

  • solo_mode() - true when the toggle is on. Everything else keys off this.
  • instructor_registration_enabled() - false in solo mode, which is what 404s the become-instructor and register-instructor routes and blocks instructor sign-up through the REST auth endpoint.
  • multi_instructor_enabled() - forced false in solo mode, which is what hides co-instructor assignment and revenue-share (a solo academy has no second instructor to split with).

Solo mode only hides marketplace features. Courses, lessons, quizzes, certificates, students, memberships, and every other part of Learnomy work exactly as before.

For developers

All three switches are filterable, so you can force the behaviour per site (for example, in a must-use plugin) without touching the stored option:

  • learnomy_solo_mode (bool) - force solo mode on or off site-wide.
  • learnomy_instructor_registration_enabled (bool) - override whether visitors may apply to become instructors. Defaults to the inverse of solo_mode().
  • learnomy_multi_instructor_enabled (bool) - override whether co-instructors and revenue-share are offered.

Related internals: the commissions module is forced off through the learnomy_module_enabled filter, instructor route gating happens in Router::gate_module_routes(), and dead nav links are stripped via wp_nav_menu_objects. The helper functions live in includes/functions.php.

Instructor Public Profile

A Learnomy instructor public profile

Every instructor gets a public profile page at /instructor/{username}/ that shows their headline, bio, expertise, links, and published courses. Instructors edit it themselves from a self-service editor in their account, so the site owner does not have to maintain it.

What you can do

  • Give students a public page at /instructor/{username}/ with your name, avatar, headline, bio, expertise tags, social links, and a grid of your published courses.
  • Show at-a-glance stats -- how many courses you teach, how many students, and your average rating when you have one.
  • Let students page through all of your published courses when you have more than one page of them.
  • Edit your own profile from My Account -> Instructor profile (/account/instructor-profile/) -- no admin access needed.
  • Link out to your website, Twitter / X, and LinkedIn.

Instructor Public Profile first view

How to use it

Step 1 -- open your profile editor

Go to My Account -> Instructor profile (/account/instructor-profile/). This is reached from the instructor (teaching) side of your account, not the learner menu.

Step 2 -- fill in "About you"

Add a Headline (a short professional tagline), a Bio, and your Expertise as a comma-separated list of skills. The expertise list is shown as tags on your public profile.

Fill in your Website, Twitter / X handle, and LinkedIn URL. Each link becomes an icon in the social row on your public page. Leave any of them blank to hide that icon.

Step 4 -- save and view

Click Save profile. The page shows a link to your public profile so you can open it in a new tab and see exactly what students see.

Settings and options

  • Public URL. Your profile lives at /instructor/{username}/, based on your username. The base segment (instructor) is set by the site's route configuration.
  • What appears is what you filled in. Empty fields are hidden -- for example the bio, expertise row, or a social icon only show when you have entered them.
  • Stats are automatic. Course count, student count, and average rating are computed from your published courses; average rating only shows once you have ratings.
  • Missing instructor. A profile URL whose username matches no user returns a 404.
  • Course list pagination. Your published courses are shown in a grid with previous / next paging when they run past one page.

For developers

Templates:

  • templates/instructor-profile.php -- the public page, populated by Instructor_Profile_View_Data.
  • templates/account-instructor-profile.php -- the self-service editor, populated by Account_Instructor_Profile_View_Data.

Routes:

  • instructor-profile -- the public route (/instructor/{username}/), built with \Learnomy\route_url( 'instructor-profile', [ 'slug' => $user->user_login ] ).
  • account-instructor-profile -- the editor route (/account/instructor-profile/).

REST API and storage:

  • POST /account/instructor-profile (namespace learnomy/v1) -- saves the current instructor's headline, bio, expertise, website_url, twitter_handle, and linkedin_url to the Free lrn_instructors row the public profile reads.

Pointing an instructor's name somewhere else:

  • learnomy_instructor_profile_url -- filter (string $url, WP_User|null $instructor) for the link behind an instructor's name on course and lesson surfaces. Plenty of sites already have a better page for a person than this one -- a BuddyPress or BuddyNext member profile, a WordPress author archive, a corporate bio -- and this repoints them in one place instead of per template.
// Send instructor names to the BuddyPress member profile instead.
add_filter( 'learnomy_instructor_profile_url', function ( string $url, ?object $instructor ) {
    if ( ! $instructor || ! function_exists( 'bp_core_get_user_domain' ) ) {
        return $url;
    }
    return (string) bp_core_get_user_domain( (int) $instructor->ID );
}, 10, 2 );

Returning an empty string is supported and means "do not link instructor names at all" -- the templates render the name as plain text, which is what a course with no instructor already does.

Instructor Students Roster

The Students roster is an instructor's list of everyone enrolled across the courses they teach. It lives on the instructor dashboard at /instructor/students/ and lets an instructor search the roster and open any student to see how that learner is progressing in their courses.

What you can do

  • See a single list of every distinct student across all the courses you author or co-teach, even when the same student is in more than one of your courses.
  • Search the roster by name or email to find one learner quickly.
  • Read per-student aggregates at a glance: how many of your courses they are enrolled in and how many they have completed.
  • Open any student to a detail view that shows each of your courses they are in, their progress percentage, whether they have completed it, and when they enrolled.
  • Stay scoped to your own students only. You never see a learner unless they are enrolled in one of your courses, and you cannot open another instructor's student by guessing a URL.
  • Work at scale. The list is paginated in the database (20 per page), so a large roster loads a page at a time instead of everything at once.

Instructor students roster first view

How to use it

Step 1 - open the roster

From the instructor dashboard, click Students in the instructor navigation. This opens the roster at /instructor/students/. Only users with the instructor role (or an admin) can reach it.

Step 2 - find a student

Use the Search by name or email box at the top and press Search. The roster filters to matching students. Clear the box and search again to return to the full list.

Step 3 - read the roster columns

The roster is a scanning tool: it answers who and how many, and the student page answers which. Each row shows:

  • Student - avatar, display name, and email.
  • Enrolled - how many of your courses the student is enrolled in.
  • Completed - how many of those they have completed.

Step 4 - page through a large roster

When there are more students than fit on one page, a pagination control appears under the table. The list shows 20 students per page. Your search term is carried across pages.

Step 5 - open a student

Click the student's name or the View button. The detail view shows a header with their name, email, and an "enrolled / completed" summary, followed by an Enrolled in your courses table. Each row is one of your courses with the student's Progress percentage, a Completed or In progress status badge, and the Enrolled date. The course title links to the course page.

Only your own courses appear in the detail view. If the student is not in any of your courses, you see a "Student not found" message instead - this is the ownership guard, not an error.

Settings & options

This is a built-in part of the instructor dashboard. There is no separate setting to enable it, and it does not use a learnomy_settings[...] key. Access is governed by roles and course ownership:

  • The viewer must have the lrn_instructor role or the manage_options capability (admin).
  • Instructors are scoped to courses where they are the author or a co-instructor. Admins see every student across the site (no course filter).
  • Completion counts and course names in both the list and the detail view are scoped to the viewer's own courses, so a count reflects enrollments in your courses, not the student's platform-wide history.
  • The list shows 20 students per page (PER_PAGE in the view-data resolver).

For developers

View-data resolver: Learnomy\Services\View_Data\Instructor_Students_View_Data::resolve() backs the instructor-students route and the templates/instructor-students.php template. It runs in three modes: list, detail (via ?student=N), and denied. Query args read from the request are s (search), paged (1-based page), and student (detail student id).

Route and navigation:

  • Route name instructor-students, path {instructor_slug}/students, registered in includes/class-routes.php. Resolve its URL with Learnomy\route_url( 'instructor-students' ).

Model methods used (no per-row queries):

  • Learnomy\Models\Enrollment::get_distinct_user_ids_paginated( $course_id, $per_page, $offset, $search, $scope_course_ids ) - the DB-paged roster.
  • Learnomy\Models\Enrollment::counts_for_users( $user_ids, $scope_course_ids ) - per-student enrolled/completed aggregates.
  • Learnomy\Models\Enrollment::get_distinct_for_user( $student_id, $limit ) - the detail view's enrollment rows.
  • Learnomy\Models\Course::get_for_instructor( $user_id, $limit, $offset ) - resolves the instructor's course scope (null scope = admin).

Filters:

  • learnomy_instructor_students_list - filter the roster rows before render. Passed ( $students, $scope_course_ids, $is_admin ). Use it to anonymise learner identity (name / email / avatar) while keeping the metrics; $is_admin is passed so consumers never mask data for admins.
  • learnomy_instructor_student_detail - companion filter for one student's detail record. Passed ( $student, $is_admin ).

Ability / REST (read-only, admin or manage_own_courses):

  • learnomy/list-students - paginated student roster with enrollment and completion counts, scoped to the caller's courses, with optional course_id, search, page, and per_page inputs.

Course Approval Workflow

By default, instructors can publish their own courses the moment they are ready. If you run a marketplace-style academy where every course needs a look before it goes live, Learnomy has a course-approval gate: a site-wide toggle, a per-instructor override, and an Approve / Reject workflow on the Courses list.

Overview

There are three parts to this feature:

  1. A global setting at LMS Settings > Settings > Instructors that controls whether instructors can publish without review.
  2. A per-instructor override on the instructor's detail page, for requiring approval from one specific instructor even while the global setting allows self-publish.
  3. A Pending Review tab on the Courses list where you approve or reject submissions.

Turn on approval site-wide

Instructor Permissions settings

  1. Go to LMS Settings > Settings, then open the Instructors section.
  2. Find Self-Publish Courses. The toggle is labeled Allow instructors to publish courses without admin approval and is on by default.
  3. Turn it off to require admin approval for every instructor. From then on, when an instructor submits a course it is held as Pending Review instead of going live, and you approve or reject it from the Courses list.
  4. Click Save Changes.

You can also require approval for a single instructor while leaving this setting on for everyone else. See the next section.

Require approval for a single instructor

Use this when most of your instructors are trusted to self-publish but one specific instructor's courses should always be reviewed first.

  1. Go to Learnomy > Instructors and click the instructor's name to open their detail page.
  2. In the page header, click Require course approval.
  3. Confirm the prompt.

This instructor's course submissions are now routed to Pending Review even if the site-wide Self-Publish Courses setting is on. The button toggles: click Allow self-publish to remove the override for that instructor.

Reviewing pending courses

Courses list

  1. Go to Learnomy > Courses.
  2. Click the Pending Review tab. It shows every course currently awaiting your decision, whether it is held because of the global setting or a per-instructor override.
  3. For each course, two row actions are available:
    • Approve - Publishes the course immediately. Confirm the prompt to proceed.
    • Reject - Prompts you for a rejection reason, then returns the course to Draft status so the instructor can revise it.

The instructor's experience

When approval is required, an instructor submits their course as Pending Review from the course editor instead of publishing it directly. The course stays off the catalog until you approve it. If you reject it, the course returns to Draft and the instructor can make changes and resubmit.

Tips

  • The global setting and the per-instructor override work together: turning the global setting off requires approval from everyone, while the per-instructor override only tightens the rule for one person on top of whatever the global setting allows.
  • Approving a course from the Pending Review tab publishes it the same way the Publish action does anywhere else in Learnomy - it fires the normal course-published event, so anything that listens for a new course (webhooks, notifications) still triggers.
  • Rejecting a course does not delete anything. It only changes the status back to Draft; all curriculum content is untouched.

Commerce

The Commerce category covers everything related to money in Learnomy: how to accept payments, create membership plans, manage subscriber accounts, run discount promotions, and read the transaction ledger.

Commerce Overview

The Learnomy commerce overview

The LMS Commerce menu is where you manage every money-related part of your academy: membership plans, subscriber accounts, discount coupons, and the full transaction ledger. This page describes the Commerce home screen and what lives in each sub-page.

Overview

Go to LMS Commerce in the WordPress admin sidebar. The menu only appears when at least one commerce module is enabled on LMS Settings > Modules (Memberships, Subscriptions, or Coupons). If no module is on, the screen shows an empty state with a direct link to the Modules page.

Sub-pages available under Commerce:

Sub-page What it does
Overview This landing screen with revenue KPIs and navigation cards
Memberships Create and manage membership plans
Subscriptions View and manage member subscription accounts
Coupons Create and manage discount codes
Earnings Revenue, instructor commissions, and payout requests
Transactions Complete ledger of every charge, refund, and renewal

šŸ”¶ Pro feature. Membership Analytics and Stripe Payouts are available in Learnomy Pro.

The Commerce Overview screen

The overview screen gives you a quick read on money flow before drilling into a sub-page.

Commerce Overview

KPI summary cards

Four stat cards sit at the top of the page.

Summary cards

  • Monthly Recurring Revenue (MRR) - the billed amount from all active subscriptions in the current month.
  • Active subscribers - the count of subscriptions with status active or trialing.
  • Lifetime revenue - the total of all completed transactions since the site started.
  • Total transactions - the count of all recorded transaction rows (purchases, renewals, and refunds).

Below the KPIs, each enabled Commerce sub-page appears as a clickable card with a one-line description.

Commerce hub cards

Click any card to go directly to that sub-page. The card for a sub-page only appears when the corresponding module is enabled.

No modules enabled

If you land on Commerce before enabling any module, the page shows an empty state with a button to open Modules.

Commerce empty state header

Go to LMS Settings > Modules, toggle on Memberships or Coupons, and save. The Commerce menu and its cards will populate.

Tips

  • The Commerce menu is conditional: it disappears entirely if all commerce modules are deactivated. Re-enabling any one module brings it back.
  • Pro extensions (Membership Analytics, Stripe Payouts) add their own cards to the hub automatically.
  • Third-party plugins can add cards and descriptions via the learnomy_commerce_card_descriptions filter.

Membership Plans

Learnomy memberships

Membership plans let students subscribe for recurring or one-time access to a bundle of courses. You set the price, billing interval, trial period, and which courses subscribers can access. Students see and compare plans on the public membership pricing page.

Overview

Go to LMS Commerce > Memberships. From here you create new plans, see how many subscribers and courses each plan has, toggle a plan active or inactive, and export the full list to CSV.

The plans list

Membership plans list

The list shows every plan with columns for Title, Price, Trial, Courses (count), Subscribers (count), Status, and Actions.

Plans table

Status tabs at the top filter by All, Active, or Inactive. Use the search field on the right to find a plan by name.

Row actions offer three options:

  • Edit - opens the plan editor.
  • Subscribers - jumps to the Subscriptions list pre-filtered to this plan.
  • Delete - permanently deletes the plan. Existing subscriptions referencing the plan show "Deleted plan" in their records.

You can select multiple rows with the checkboxes and use Bulk Actions to Activate, Deactivate, or Delete a set of plans at once.

Starting from a template

If you have no plans yet, the page shows a template picker instead of the empty table.

Memberships header

Four templates are available:

Template Price Trial Courses
Monthly Basic $9/month 14 days Up to 5
Yearly Premium $79/year None Unlimited
Free Trial $29/month after trial 14 days Unlimited
Custom Blank None None

Selecting a template pre-fills the editor. Every field is editable before saving.

Creating or editing a plan

Click Add New (or Edit on an existing plan) to open the plan editor.

Plan edit screen

The editor has a two-column layout: a main column of accordion sections on the left, and a Publish sidebar on the right.

Essentials accordion

Essentials section

  • Plan Title (required) - the name students see on the pricing page and in their account.
  • Description - shown below the title on the pricing page card. Plain text.
  • Price - the amount charged each billing interval, or the single charge when the interval is One-time.
  • Billing interval - Daily, Weekly, Monthly, Yearly, or One-time (lifetime access). See One-time (lifetime) plans below.
  • Trial days - the number of free days before billing starts. Set to 0 to disable the trial.
  • Pricing mode - Single price (one billing rate) or Monthly + Yearly (show both on the pricing page with a toggle).
    • If you choose Monthly + Yearly, two additional fields appear: Price monthly and Price yearly. Students pick the option at checkout.
  • Courses included - a searchable checklist of all your courses. Subscribers are auto-enrolled in every checked course. Leave all unchecked to give access without any course pre-enrolment.

Included courses picker

One-time (lifetime) plans

Choosing One-time (lifetime access) as the billing interval sells the plan as a single purchase rather than a subscription. The buyer is charged once, no recurring payment is set up at the gateway, and the membership never expires.

What changes across the site when a plan is set to one-time:

  • The pricing card prices it as one-time instead of "/ month", and the button reads Buy for $X rather than the recurring Subscribe for $X / month (or / year, / week, / day).
  • Checkout asks the gateway for a single charge, not a subscription. Nothing repeating is created at Stripe or PayPal, so there is no renewal to fail, retry, or cancel.
  • The member's subscription record is stored with no period end at all, which is what keeps it out of the nightly expiry sweep. A lifetime membership is never expired by Learnomy.

Everything else about the plan works normally: included courses, course limit, thumbnail, features, and the Learning Space scope all behave the same way.

Because there is no renewal, a coupon on a one-time plan is simply a discount on the purchase, with none of the first-payment-only rules that apply to recurring plans. See Coupons.

Features & Branding accordion

  • Thumbnail - the plan's image at checkout, and the small preview in the Memberships list. It is not shown on the pricing page card: that card is a price comparison and stays text only. Enter a URL or click Choose image to pick from the media library.
  • Features - bullet points shown on the pricing page card. One line per feature. Use the trash icon to remove a row and Add feature to add another.
  • Highlight on pricing page - marks this plan as the featured/recommended option (displayed with a visual emphasis on the pricing page).

Advanced accordion

  • Slug - the URL-friendly identifier. Auto-generated from the title; edit it to control the URL.
  • Currency - the currency code for this plan (USD, EUR, GBP, INR, AUD, CAD, JPY, BRL, or any code on your site).
  • Interval count - allows billing every N intervals, e.g. every 2 months. Defaults to 1.
  • Course limit - the maximum number of courses a subscriber can access at one time. Leave blank for unlimited.
  • Learning Space - scopes the plan to one Learning Space. Defaults to Not scoped to a space, which is a normal plan. See below.
  • Status - Active or Inactive. Inactive plans are hidden from the pricing page and cannot be purchased.

Scoping a plan to a Learning Space

Pro feature. The dropdown only appears when Learnomy Pro's Spaces extension is active and at least one Learning Space exists. On a site without spaces the field is not shown at all, rather than shown empty.

Pick a space and the plan becomes a space plan. The scope is a restriction, not a grant: it does not add the space's courses to the plan. Subscribers still reach exactly the courses in the plan's own Included courses picker, and only while they are actually a member of that space. Somebody who buys the plan but does not belong to the space gets no course access from it, and somebody removed from the space loses the access the plan was giving them, without the subscription itself changing.

Use it to sell seats into one team or department rather than to the whole site. Leave it on Not scoped to a space for an ordinary plan.

Deleting a space does not delete the plans scoped to it. The plan survives, so you are never left billing against a product that vanished; point it at another space or unscope it.

Payment providers

You do not map a plan to a Stripe price or a PayPal billing plan by hand. Set the price and the billing interval on the plan, and Learnomy creates the matching recurring product on the gateway the first time it is needed, in whichever mode (test or live) your gateway is configured for.

Earlier versions asked you to paste four IDs here - a Stripe test price, a Stripe live price, and a PayPal sandbox and live plan ID. Those fields were removed: they were four chances to typo an identifier that only failed at checkout, and the gateway can tell us the answer itself. If you are following an older guide or screenshot that shows a "Payment Provider Mapping" accordion, that section no longer exists and nothing replaces it - you have already done the work by setting the price.

When WooCommerce is your gateway it uses a single reusable checkout product and reads the price dynamically at checkout, so nothing is configured per plan there either.

Saving a plan

Click Create Plan (or Update Plan when editing) in the Publish sidebar to save. The Save as Draft button saves the plan with status Inactive so it does not appear on the pricing page yet.

A success notice appears at the top of the page when the save completes.

The public membership pricing page

Students browse plans at /membership/. The page shows each active plan as a card with title, price, trial period, features bullet list, and a subscribe button.

Membership pricing page

Plans with Highlight on pricing page enabled are visually emphasized. Plans with Monthly + Yearly pricing show a toggle so students can switch between the two rates.

Annual plan card

Free plan card

Pro plan card

FAQ section

The FAQ section and a gift membership call-to-action appear below the plan cards when configured in LMS Settings > Membership Page.

Gift CTA

Editing the membership page content

Go to LMS Settings > Membership Page. Three cards control the public pricing page:

  • Membership page content - the Headline and Subtitle shown above the plan cards. Leave the headline blank to use the default.
  • Page sections - toggles for Show the FAQ section and Show gift redeem / buy-as-a-gift widgets (gifting requires Learnomy Pro).
  • Frequently asked questions - the FAQ editor itself.

The FAQ editor is an add/remove repeater, not a fixed grid of blank rows. Each question has a text field and an answer textarea, with a trash icon to delete that row. Click Add Question to append a new blank row. Blank rows (no question or answer entered) are ignored on save, so you don't need to manually clean up an empty row you added by mistake.

Gift memberships (Pro)

Pro feature. Requires Learnomy Pro with the Membership Pro extension active.

Members can buy a membership as a gift for someone else, and the recipient redeems a code to activate it. Both halves live on a dedicated, shareable page at /<membership-slug>/gift/, and the gift call-to-action on the pricing page links to it.

Buying a gift

On the gift page, the buyer picks a plan and a quantity. The running total updates live as quantity changes, and the buyer pays through your normal gateway (Stripe or PayPal) using the same on-page checkout as a regular purchase. The price is resolved on the server, so the displayed total cannot be tampered with. After payment, the buyer receives one gift code per quantity purchased to pass on to the recipient.

Redeeming a gift

The recipient enters their code on the gift page (or anywhere you place the [learnomy_gift_redeem] shortcode). A logged-in recipient who redeems a valid code immediately gets an active subscription to the gifted plan; an anonymous visitor is prompted to sign in first. Redemption is atomic, so a code can be claimed only once even if two people submit it at the same moment. Invalid, already-claimed, or expired codes return a clear plain-English message rather than failing silently.

For quick access, a Gift entry is added to the My Account navigation so members can buy or redeem from inside their account.

Billing and retries settings (Pro)

Pro feature. Requires Learnomy Pro with the Membership Pro extension active.

Go to LMS Settings and click Memberships Pro in the sidebar (its own settings nav group, anchor #membership-pro), then open the Billing & retries card. This is a separate section from Free's Membership Page content section (#membership-page), which controls the public pricing-page copy. These settings control what happens when subscription payments fail and how plan changes and refunds are handled site-wide.

Setting What it does
Payment retries How long Learnomy keeps retrying a failed subscription charge before giving up and cancelling. Options: Conservative (14 / 28 / 56 days), Standard (7 / 14 / 28 days), Aggressive (3 / 7 / 14 days), or Off (cancel immediately on the first failure).
Plan changes When on, switching plans mid-cycle credits the unused time from the old plan against the new charge. When off, the full price of the new plan is charged immediately.
Refunds When on, refunding a payment also cancels the subscription immediately. When off, the refund is recorded but the member keeps access until the end of the current billing period.

These settings apply to all membership plans on the site. You cannot override them per plan.

Tips

  • Inactive plans are hidden from /membership/ but their existing subscribers are unaffected.
  • Deleting a plan cancels no subscriptions automatically. Existing subscription rows show "Deleted plan" in their detail view.
  • The plan's slug controls the URL anchor on the pricing page. Changing the slug after subscribers have shared links will break those links.
  • Trial days only apply to new subscriptions. Existing subscribers are not affected when you add or change the trial setting.
  • The Courses included picker lists all courses (up to 500). For larger catalogs, use the search field to filter.
  • A one-time (lifetime) plan never expires and never renews, so nothing you do to the retry cadence, proration, or grace period affects it. Price it as a purchase, not as a discounted year.
  • If your site runs WooCommerce as the payment gateway without the WooCommerce Subscriptions extension, recurring plans cannot be billed at all. The plan editor warns you at the Billing interval field itself, and One-time is the way to keep selling that plan on such a site.
  • The Conservative retry cadence is recommended for sites with international subscribers where payment failures are often caused by temporary bank holds rather than genuine non-payment.

Subscriptions

Learnomy subscriptions

The Subscriptions screen shows every member subscription across all plans. You can filter by status or plan, cancel or extend a subscription on behalf of a member, and export the list as CSV.

Overview

Go to LMS Commerce > Subscriptions. Each row represents one member's subscription to one plan, with its current billing status, period dates, payment provider, and available admin actions.

The subscriptions list

Subscriptions list

Subscriptions table

Columns

  • User - avatar, display name (links to the subscription detail), and email address.
  • Plan - the membership plan name. Shows "Deleted plan" if the plan has been removed.
  • Status - the current subscription state (see below).
  • Period - the start and end dates of the current billing cycle.
  • Provider - which payment gateway processed the subscription (Stripe, PayPal, or WooCommerce).
  • Actions - available admin actions for this row.

Status values

Status label Meaning
In trial Free trial period is active; billing starts after the trial ends
Active Subscription is paid and current
Payment failed The most recent renewal charge failed; access continues for the grace period while the gateway retries
Ending soon The subscriber has cancelled but access runs until the period end
Cancelled Subscription was cancelled; access is revoked
Ended The subscription expired at the end of a fixed period

Filtering

Use the Status and Plan dropdowns to narrow the list. Click Filter to apply.

Subscriptions header

The Export as CSV button downloads all subscriptions matching the current filter. The file streams directly as a CSV (not a JSON-encoded cell).

Row actions

For Active, In trial, and Payment failed subscriptions:

  • Extend - opens a date picker to move the period end forward. Use this to grant a goodwill extension. What it does to the renewal schedule depends on the gateway; see Extending a period.
  • Cancel - cancels the subscription immediately. Access is revoked.

Cancelled and Ended subscriptions show no actions.

Grace period for failed payments

When a renewal payment fails, the subscription is marked Payment failed (past due) and the member keeps access for a grace period while the gateway retries the card. When the grace period runs out, the subscription is expired and access is revoked.

You set the length of that window in LMS Settings > Settings > Payments, in the Failed payments card:

Field What it is
Grace period How many days a past-due member keeps access before it is revoked. Any whole number from 0 to 90. The default is 7 days, which is the behaviour every site had before this became a setting. Set it to 0 to cut access off as soon as the paid period ends.

A member who cancels deliberately is not affected by this. They always keep access until the end of the period they paid for, with no grace on top.

Developers can override the value with the learnomy_subscription_grace_days filter.

Subscription detail

Click a subscriber's name in the list to open the full subscription detail page.

Subscription overview card

Shows status badge, plan name, amount per billing interval, current period dates, trial end date (if applicable), and whether the subscription is set to cancel at period end.

Subscriber and provider card

Shows the subscriber's name and email (linked to their student profile), the payment provider name, and the gateway's own subscription ID and price ID (for cross-referencing in Stripe or PayPal dashboards).

Extend period - a datetime input on this card lets you type a new period end time and click Extend period to apply it directly. See Extending a period for what this does to the gateway's renewal date.

Admin actions (header buttons)

  • Resume - available when the subscription is past-due or has cancel-at-period-end set. Resumes billing and clears the cancel flag.
  • Cancel at period end - marks the subscription to not renew. The member keeps access until the current period ends.
  • Cancel now - immediately revokes access.

Payments history

A table of every renewal and initial payment linked to this subscription, showing date, transaction type, amount, and status.

Change history

Pro feature. Requires Learnomy Pro with the Membership Pro extension active.

Below the payments table, a Change history panel lists what has happened to this subscription that was not a payment. It exists so you can answer "why did my plan change", "when was I paused", or "who gave me this" from the screen the member is asking you about, instead of from the database.

Three columns, ordered by when each change was recorded rather than by the date shown, so a plan change scheduled for a future date sits with the day it was made:

Column Contents
Date When the change took effect
Change Plan changed, Paused, Resumed, Refunded, or Gift applied
Details For a plan change, the plan it moved from and the plan it moved to. For a gift, the plan granted. Otherwise a dash.

The panel shows up to the 50 most recent changes for this one subscription, and does not appear at all when nothing has happened to it yet - an empty "Change history" heading would read as a broken feature rather than a quiet one.

The member sees the same events for themselves under Billing history on their account billing page, so the two of you are reading the same record. See Certificates and Billing.

Payment retry attempts are not in this table. Those are dunning attempts, and today they are readable over the REST API only; see Subscription Lifecycle.

Extending a period

Extend moves the current period end forward. What happens next depends on which gateway the subscription is billed through:

  • WooCommerce - the subscription's next payment date in WooCommerce moves with it, so the two screens agree about when the customer is next charged. This is deliberately forward-only: if the new end date is earlier than the existing next payment, WooCommerce is left alone. An extension can never bring a charge forward by accident.
  • Stripe and PayPal - unchanged. The extension moves the period end on the Learnomy record only; the gateway keeps its own renewal date. Adjust the schedule in the Stripe or PayPal dashboard if you need the charge to move too.

On WooCommerce the push is silent when the two records do not line up (no WooCommerce Subscriptions extension, no linked WooCommerce subscription, or a WooCommerce subscription that is mapped to a different Learnomy subscription, including another one belonging to the same member). The Learnomy period is extended either way, so the member never loses the access you granted them.

How customers manage their own subscription

Members manage their subscriptions from Account > Manage membership at /membership/manage/.

Membership manage page

The page shows each active plan with:

  • Plan name and status badge (Active, In trial, Payment failed, Ending soon)
  • Current period start and end dates
  • Next renewal date and next payment amount
  • Which gateway they pay through ("Billed via")

Status banners appear for trial reminders, payment failures, and pending cancellations.

Member subscription actions

Member actions available on the page:

  • Cancel subscription - sets cancel-at-period-end. The member keeps access until the period ends.
  • Update payment method - opens the payment provider's hosted portal to update card details (Stripe and PayPal only).
  • Resume subscription - shown when the subscription is set to cancel at period end; removes the cancel flag.

šŸ”¶ Pro feature. Plan switching (upgrading or downgrading between plans with prorated credit) is available in Learnomy Pro's Membership Pro extension.

The included courses for each plan are listed below the plan details so members can see what they have access to.

A payment history table (invoices) appears at the bottom of each plan section.

Tips

  • Cancelling from the admin side ("Cancel now") revokes access immediately, regardless of how much billing period remains. Use "Cancel at period end" for a graceful end.
  • Extending a period moves the WooCommerce next payment date with it, but never earlier than it already was. On Stripe and PayPal the extension moves the Learnomy period end only, and the gateway's own renewal date is unchanged.
  • A one-time (lifetime) membership has no period end and no renewal, so Extend has nothing to move on it.
  • A subscription in "Payment failed" status keeps course access for the grace period set in Settings > Payments (7 days by default) while the gateway retries. If retries fail, the gateway transitions the subscription to cancelled, which Learnomy receives via webhook and updates the status here.
  • The CSV export respects the active status and plan filter so you can export just past-due subscribers for a targeted follow-up.

Coupons

Learnomy coupons

Coupons are discount codes that students enter at checkout to reduce the price of a course purchase or membership subscription. You control the discount type, value, usage limits, minimum purchase, and active date window.

Overview

Go to LMS Commerce > Coupons. The page has two parts: a create/edit form at the top and the coupons list below.

Creating a coupon

Coupons page with create form

Create form detail

Fill in the fields and click Create Coupon.

Form fields

Field Description
Code The code students type at checkout. Example: SUMMER25. Must be unique.
Discount Type Percentage (%) deducts a percentage of the item price. Fixed Amount deducts a flat currency amount.
Value The discount amount: a percentage (e.g. 25) or a fixed amount (e.g. 10.00). Required.
Applies To Courses (course purchases only), Membership Plans (subscription purchases only), or Both.
Restrict to specific courses Optional. When Applies To includes Courses, a searchable field lets you limit the coupon to particular courses instead of your whole catalog.
Status Active means the coupon can be used now. Inactive disables the code without deleting it.
Usage Limit Maximum total redemptions. Leave blank for unlimited.
Per-User Limit Maximum redemptions per individual user. Leave blank for unlimited.
Min Purchase The minimum order amount (before discount) required to use the code. Leave blank for no minimum.
Start Date The date the coupon becomes usable. Defaults to today.
Expiry Date The date the coupon stops working. Always set a future date: a blank Expiry Date currently stores as an already-expired date and the coupon is rejected at checkout. A fix is planned.

Restricting a coupon to specific courses

Below the main fields, the Restrict to specific courses field lets you scope a coupon to only certain courses instead of your whole catalog. Type a course name into the search box; matching courses appear in a dropdown as you type. Selecting one adds it as a chip above the search box. Add as many courses as you need, and click the "x" on a chip to remove it.

Leave this field empty to apply the coupon to every course (subject to the Applies To setting). Once you add at least one course, the coupon only works at checkout for those courses.

Editing a coupon

Click the Edit row action on any coupon in the list. The create form above the table updates to show the coupon's current values, including any course restriction, and switches to edit mode. Change the fields you want and click Update Coupon. Click Cancel to discard edits and return the form to create mode.

The coupons list

Coupons table

Columns: Code (links to detail page), Type, Value, Applies To, Usage (used / limit), Status, Created, Actions.

Status tabs filter by All, Active, or Inactive.

Coupons header

Status values in the table:

  • Active - the coupon is usable (within its date window and usage limits).
  • Inactive - manually disabled.
  • Expired - the expiry date has passed; the coupon can no longer be used even if its status is Active.

Row actions:

  • Edit - populates the form at the top of the page.
  • Delete - permanently removes the coupon.

Coupon detail

Click a coupon code in the list to open its detail page.

Coupon detail

Coupon metadata card

Coupon meta card

Shows all the coupon settings at a glance: code, discount, applies-to scope, status, usage count vs limit, per-user limit, minimum purchase, and the active date window (start to expiry).

Redemptions

Redemptions table

Every time a student uses the coupon, a row appears here with the customer name (linked to their student profile) and the transaction ID (linked to the transaction detail). The section heading shows the total redemption count.

If the coupon has not been used yet, the table shows "This coupon has not been redeemed yet."

Coupon detail header

What students see: live reprice at checkout

Coupons are entered on the enroll/checkout page, and nowhere else. The course detail page and the membership pricing page each carried a coupon box until 1.9.4; both were removed. A code entered on a sales page promises a discount that page cannot honour - on the WooCommerce route the buyer saw a discounted total and was charged the full price - so the field now lives only on the one surface that actually applies it, across Stripe, PayPal, and the Test gateway. Buyers paying through WooCommerce use WooCommerce's own coupon box at the WooCommerce checkout instead.

At checkout, students type a code into the Coupon Code field and press Apply. When the code validates, the order summary updates in place without a page reload: a "Coupon" line appears with the code and the discount (either the percentage or the fixed amount off), and the total updates to the new price. The input row is replaced by a chip showing the applied code, with a remove button to clear it. If the code is invalid, expired, or restricted to other courses, the checkout shows the matching plain-English message instead of repricing (for a course-restricted coupon used on the wrong course, this is "This coupon does not apply to the selected item.").

Coupons on a membership subscription

A membership plan accepts a coupon. Set Applies To to Membership Plans (or Both) and the code can be entered at membership checkout like any other purchase.

The discount applies to the first payment only. Renewals are charged at the plan's regular price. The checkout says so under the coupon field: "Applies to your first payment. Renewals are billed at the regular price." This is what lets you run an early-adopter or launch offer without discounting the membership for the life of every subscription taken during it.

There is one limit worth knowing before you advertise a code. The gateway has to be able to bill a discounted first invoice and then charge the regular amount on renewal, and today Stripe is the only gateway that can. If a buyer applies a coupon to a recurring plan and then chooses PayPal or the Test gateway, the checkout refuses the code with "This coupon cannot be applied to a recurring purchase paid through ...", and asks them to remove it or choose another payment method. The refusal is deliberate: quoting one price and capturing another is never acceptable, so the checkout stops rather than overcharging.

A one-time (lifetime) membership plan is not affected by any of this. It is a single charge with nothing to renew, so a coupon behaves exactly as it does on a course purchase, on every gateway.

When the field does not appear

The coupon field only shows for a purchase a visitor can actually pay for at Learnomy checkout. It is not offered when:

  • the course or plan is bought through the WooCommerce gateway, where the Learnomy checkout is only a hand-off panel and WooCommerce's own coupon box applies discounts instead;
  • the course is closed, invite-only, or cohort-join only, so access is granted rather than sold;
  • the visitor's membership already covers the course, or the course is free, so there is no charge to reduce;
  • the Coupons module is switched off under LMS Settings > Modules, in which case no purchase on the site takes a code.

If a 100% coupon zeroes the total, the checkout swaps in a free-order button so the enrollment completes without going through a payment gateway.

Tips

  • Coupon codes are case-insensitive at checkout. SUMMER25 and summer25 are treated the same.
  • A coupon with an expired date shows as Expired in the status column even when its status field is Active. The code cannot be used once the expiry date has passed.
  • Setting both a Usage Limit and a Per-User Limit means both caps apply. A coupon with limit 100 / per user 1 allows 100 unique customers, each using it once.
  • Deleting a coupon does not remove existing transaction records. Those transactions retain the coupon reference in their detail view.
  • The minimum purchase is evaluated against the pre-discount price, not the final price.
  • On a membership plan, a Usage Limit counts first payments, not renewals. A code capped at 50 gives 50 people a discounted first month, and every one of them renews at full price after that.
  • If you are running a membership offer, check which gateways you have enabled before you publish the code. On a site selling recurring plans through PayPal alone, the code cannot be honoured and buyers will hit a refusal at checkout.

Transactions

Learnomy transactions

The Transactions screen is the complete financial ledger for your academy. Every course purchase, membership payment, subscription renewal, and refund appears here in one place. You can filter, search, issue refunds, and drill into the full detail of any individual transaction.

Overview

Go to LMS Commerce > Transactions. The list shows every transaction row with totals in the page subtitle.

The transactions list

Transactions list

Transactions table

Columns

  • ID - the internal transaction number (links to the detail page).
  • Customer - display name and email.
  • Item - the course title or membership plan name, plus the gateway's own transaction reference (provider transaction ID) in small text below.
  • Amount - the charged amount with currency code.
  • Type - the transaction category (Course, Membership, Membership renewal, Refund, etc.).
  • Provider - which gateway processed the charge (Stripe, PayPal, WooCommerce, Test).
  • Status - the current state of the row.
  • Date - date and time of the transaction.
  • Actions - Refund button for eligible rows.

Transaction statuses

Status Meaning
Completed Payment was collected and fulfilment ran (enrolment, subscription, etc.)
Refunded The full amount was refunded; the matching refund row links back here
Partially refunded Part of the amount was refunded; the matching refund row links back here
Pending Payment initiated but not yet confirmed
Failed The charge attempt failed; no fulfilment occurred

Refund rows (transaction type "Refund") appear in the list and show "Refund of #N" in the Actions column, linking back to the original transaction.

Filtering and searching

Transactions header

Use the Status dropdown to filter by Completed, Refunded, Pending, or Failed. Use the Provider dropdown to isolate transactions from a specific gateway (Stripe, PayPal, WooCommerce, Test, or Demo). Enter a name, email, order ID, or provider transaction reference in the search box and click Filter. Click Reset to clear all filters.

Issuing a refund

A Refund button appears in the Actions column for transactions that meet all of these conditions:

  • Transaction type is not itself a refund.
  • Status is Completed.
  • No refund has been issued for this transaction yet.
  • The gateway is Stripe, PayPal, or Test (WooCommerce refunds are issued from the WooCommerce order).

Click Refund to open a dialog showing the customer name, item, and amount, with a Refund amount field. The field is prefilled with the full charge, so a full refund is still one click and Enter. Lower it to refund only part of the charge. The amount must be greater than zero and no more than the amount charged.

What happens on refund:

  1. The gateway adapter calls the provider's refund API for the amount you entered.
  2. A new transaction row is written with type "Refund", the refunded amount as a negative figure, and the original transaction's ID stored in refund_of.
  3. The original row's status updates to "Refunded" for a full refund, or "Partially refunded" when you refunded less than the full charge.
  4. On a full refund, the commission split for that transaction is reversed: instructor commission rows are marked reversed (not deleted), and the platform commission row is also reversed. The net effect is that neither party keeps earnings from a refunded sale.
  5. On a full refund, the student's course enrolment or membership access is revoked. A partial refund leaves access in place.

Partial refunds do not reverse commission. The refunded amount is recorded in the ledger, so your revenue figures net out, but the instructor and platform commission rows for that sale are left untouched. If you need the split adjusted after a partial refund, do it manually. Reversal only runs on a full refund.

A transaction can be refunded more than once, up to the amount charged. Refund 25.00 of a 100.00 sale today and the Refund button stays on the row, now offering the remaining 75.00. The button only disappears once the sale is refunded in full.

Retrying a refund on an already-refunded transaction

A fully refunded transaction shows status "Refunded" and loses its Refund button. If a refund is attempted against it anyway (a stale page, or a second admin acting on the same row before it refreshes), Learnomy reports "This transaction has already been refunded in full" instead of the misleading "not completed" message that older versions returned.

A partially refunded transaction keeps its Refund button, and the dialog offers the remaining amount rather than the original charge, so you cannot accidentally return more money than is left.

Transaction detail

Click any transaction ID in the list to open the full detail page.

Transaction card

Shows amount, type (e.g. "Membership renewal"), status badge, provider name, the gateway's own transaction reference (for cross-referencing in Stripe or PayPal), and the date.

If the transaction is itself a refund, a "Refund of #N" row links to the original. If the transaction has been refunded, a "Refunded by #N" row links to the refund.

Customer and items card

Shows the customer (linked to their student profile), the item (course or membership plan linked to its editor), the linked subscription (if a membership payment), and the coupon applied at checkout (if any), with the discount amount.

Commission split

A table shows how the transaction amount was divided. For a course purchase, two rows typically appear: one for the Platform (the site owner's share) and one for the instructor. Each row shows:

  • Recipient name and type
  • Amount
  • Rate applied
  • Payout status (pending, paid, reversed)

When a full refund is issued, the commission rows for the original transaction are updated to "reversed". After a partial refund they stay as they were. The new refund transaction has no commission rows.

If no commission rules are configured for a transaction, the table shows "No commission records for this transaction."

Gateway metadata

When a transaction carries a metadata_json payload from the gateway (e.g. Stripe session data), a raw JSON block appears at the bottom of the page for debugging. This block is read-only.

Tips

  • WooCommerce transactions show with provider "Woocommerce". To refund them, go to the WooCommerce order (the gateway reference in the Item column is the WC order ID) and use WooCommerce's own refund flow. The Learnomy adapter listens to the woocommerce_order_refunded hook and records the refund row automatically.
  • Pending transactions are created when a student reaches the checkout but the payment has not yet been confirmed. They clear to Completed when the gateway webhook fires, or to Failed if the webhook reports failure. A backlog of Pending rows usually means the gateway webhook is not configured correctly.
  • The search field matches against customer name, email, the internal order ID, and the provider's own transaction reference. You can paste a Stripe pi_... or ch_... ID directly into search to find the matching row.
  • Partial refunds are issued from the Refund button here, not in the gateway dashboard. Commission reversal only happens on a full refund, so a partial refund does not currently claw back any of the instructor or platform split.

Payment Gateways

Learnomy payment gateway settings

Learnomy supports three real-money payment gateways: Stripe, PayPal, and WooCommerce. Each one handles the money collection independently; Learnomy handles enrolment, subscription creation, and commission calculation the same way regardless of which gateway the customer used.

You can also enable a Test Gateway for development and QA work.

Overview

Go to LMS Settings > Payments to configure gateways. Each gateway has its own settings card.

Pick one checkout route for the whole site: either WooCommerce, or Learnomy's own internal methods (Stripe and/or PayPal). Enabling WooCommerce makes it the exclusive checkout -- every purchase type (course, membership, gift, learning path, space, seat pack) goes through it, and Stripe, PayPal and the Test gateway stop being offered at checkout even if their toggles are still on. Without WooCommerce, enable Stripe and/or PayPal and the student picks between whichever of those are on.

The Payments tab also contains Checkout settings (business name, refund policy, guarantee days).

Stripe

Stripe processes credit and debit card payments for one-time purchases and recurring membership subscriptions.

The on-page checkout experience

Students pay without leaving your site. The card fields render directly on the Learnomy checkout page using Stripe's Payment Element, so there is no redirect to a Stripe-hosted page and customers stay in your branding the whole time. The form themes itself to match the page, including dark mode, and re-themes live if the student toggles light or dark mid-checkout.

The same on-page Payment Element backs every purchase type Learnomy sells: single courses, memberships, space seat packs, gift purchases, and learning paths. Your own checkout button submits the payment, so there is one consistent flow regardless of what is being bought.

The billing address is collected by Learnomy's own fields, above the payment method, for every gateway rather than only for card buyers. That satisfies the export-transaction requirement Indian card payments are subject to, and prevents the "missing address" decline. See Checkout and Purchase Flow.

The amount is fetched live and re-validated on the server before the charge, so a stale price in the browser can never be charged: if a price changes between page load and pay, the checkout self-heals to the current amount.

Delayed and bank-based payment methods

Some payment methods do not clear at checkout. A bank debit or a cash voucher tells Stripe "accepted" immediately but the money can take days to arrive: SEPA Direct Debit, Bacs, BECS, boleto, OXXO, Konbini.

Learnomy handles those correctly. The buyer is not enrolled when they submit -- they land on the thank-you page in a pending state that says the enrollment is being set up. The enrollment is created when Stripe confirms the funds actually cleared, and if the payment ultimately fails, nothing is granted and no paid-looking order is left behind.

You do not configure anything for this. Enable the methods you want in your Stripe Dashboard and they appear in the on-page Payment Element. The one requirement is the webhook: the async events below must be subscribed, because for these methods the webhook is the only signal that the money arrived.

Enable Stripe

Check Enable Stripe to activate the gateway. Uncheck to disable Stripe entirely even if API keys are saved.

Mode

Choose Test or Live. The mode toggle switches which set of credentials the gateway reads. Test mode uses Stripe test API keys and does not charge real cards. Live mode charges real cards.

A badge at the top of the Stripe card shows the current mode (TEST MODE or LIVE MODE).

Webhook URL

Copy the webhook URL shown in the settings card and add it to your Stripe account.

  1. In Stripe Dashboard, go to Developers > Webhooks > Add endpoint.

  2. Paste the Learnomy webhook URL.

  3. Subscribe to these events: checkout.session.completed, checkout.session.async_payment_succeeded, checkout.session.async_payment_failed, payment_intent.succeeded, invoice.payment_succeeded, invoice.payment_failed, customer.subscription.deleted, customer.subscription.updated, charge.refunded.

    The two async_payment_* events are what fulfil delayed methods such as SEPA or boleto. Without them, a buyer who pays by bank debit is charged and never enrolled. charge.refunded is what reverses access when you refund from the Stripe Dashboard rather than from Learnomy.

  4. Copy the Signing secret from the created endpoint and paste it into the Webhook Signing Secret field in Learnomy.

Test credentials

  • Test Publishable Key - starts with pk_test_. Enter it in plain text; it is not a secret.
  • Test Secret Key - starts with sk_test_. Stored encrypted. Leave blank to keep the saved value; the field shows bullet characters when a key is already saved.
  • Test Webhook Signing Secret - starts with whsec_. Stored encrypted.

Live credentials

The same three fields for the live environment (pk_live_..., sk_live_..., whsec_...).

Testing the connection

Click Test Stripe Connection after saving credentials. The button sends a test API call and returns a pass or fail message without creating any charge.

Membership plan setup

No Stripe product or Price setup is required. Set the plan's price and billing interval in the plan editor (the Essentials section) and save - Learnomy creates the matching recurring Stripe Price for you the first time it is needed, in whichever mode (test or live) your gateway is configured for.

Earlier versions asked you to create a Price in the Stripe Dashboard and paste its ID into a Payment Provider Mapping accordion. That accordion and those fields were removed on purpose: a hand-typed Price ID is a typo that only surfaces at checkout, and Stripe can create the Price from the figure you already entered. If you are following an older guide or screenshot that shows that accordion, it no longer exists and nothing replaces it - setting the price is the whole job.

For one-time course purchases, no Stripe product setup is required either. Learnomy creates a checkout session at the time of purchase with the exact course price.


PayPal

PayPal handles one-time course purchases and recurring membership subscriptions via its REST API.

Enable PayPal

Check Enable PayPal to activate the gateway. Requires both a Client ID and Secret to be configured.

Mode

Choose Sandbox or Live. Sandbox uses PayPal Developer test accounts. Live charges real PayPal balances.

Webhook URL

Copy the webhook URL and register it in the PayPal Developer Dashboard.

  1. Go to PayPal Developer > My Apps > your app > Webhooks > Add webhook.
  2. Paste the Learnomy webhook URL.
  3. Subscribe to the events you need: CHECKOUT.ORDER.APPROVED, PAYMENT.SALE.COMPLETED, BILLING.SUBSCRIPTION.ACTIVATED, BILLING.SUBSCRIPTION.CANCELLED, BILLING.SUBSCRIPTION.UPDATED.
  4. Copy the Webhook ID from the created webhook entry (a string like 1AB23456CD789012E) and paste it into the Webhook ID field in Learnomy.

Sandbox credentials

  • Sandbox Client ID - from PayPal Developer Dashboard under your app credentials.
  • Sandbox Secret - stored encrypted. Leave blank to keep the saved value.
  • Sandbox Webhook ID - the webhook ID from the PayPal sandbox webhook you created.

Live credentials

The same three fields for the live environment.

Testing the connection

Click Test PayPal Connection after saving credentials. Returns a pass or fail without creating a charge.

Membership plan setup

No PayPal Billing Plan setup is required. Set the plan's price and billing interval in the plan editor and save - Learnomy creates the matching PayPal billing plan (and its product) for you the first time a member subscribes, in whichever mode (sandbox or live) your gateway is configured for.

As with Stripe, the old Payment Provider Mapping accordion where you pasted a PayPal Plan ID was removed. The gateway is handed the terms you already entered, so there is nothing to create in the PayPal Dashboard and nothing to paste back into Learnomy.


WooCommerce

WooCommerce, when enabled, becomes the payment gateway for every Learnomy purchase type on the site: single course, membership plan, space, space seat, gift, and learning path. It is not possible to sell one purchase type through WooCommerce and another through Stripe or PayPal on the same site -- enabling WooCommerce takes over the whole checkout.

WooCommerce only collects the money. Learnomy handles enrolment, subscription, and commission the same way it does for Stripe and PayPal.

How it works

When a student checks out with WooCommerce:

  1. Learnomy adds one hidden virtual product ("Learnomy Checkout Item") to the WooCommerce cart and sets the line price to the exact amount (already adjusted for any sale or coupon).
  2. The student is redirected to the standard WooCommerce checkout page.
  3. The student pays using whatever payment method WooCommerce has configured (any WooCommerce payment plugin works).
  4. When the order reaches "processing" or "completed" status, Learnomy's adapter reads the purchase payload from the order, records a transaction, fires the standard enrolment or subscription actions, and calculates instructor commission.

For one-time purchases, Learnomy uses one reusable hidden virtual product across all purchase types. Recurring membership billing (when WooCommerce Subscriptions is active) uses a small set of period-keyed subscription products, one per billing interval (for example, monthly and yearly), with the price still set dynamically at checkout. Learnomy does not create a separate WooCommerce product per course or membership plan.

Prerequisites

  • WooCommerce must be installed and active.
  • The WooCommerce Checkout page must use the classic shortcode ([woocommerce_checkout]), not the block-based checkout. The block checkout does not support third-party payment gateways that operate outside the Store API.
  • The WooCommerce Cart page must also use the classic shortcode ([woocommerce_cart]).
  • If WooCommerce has "Coming soon" store mode enabled, disable it (set WooCommerce > Settings > General > Store notice off or use the woocommerce_coming_soon=no option). Coming-soon mode blocks the cart and checkout.

Enabling WooCommerce

  1. Install and activate WooCommerce.
  2. In Learnomy, go to LMS Settings > Payments.
  3. Check Enable WooCommerce and save.

WooCommerce now handles checkout for every purchase type. Stripe, PayPal and the Test gateway are no longer offered at checkout while WooCommerce is enabled, even if you leave their toggles on.

No API keys or per-plan mapping is required. WooCommerce uses its own installed payment methods (card, PayPal, bank transfer, etc.) for the actual charge.

Recurring subscriptions via WooCommerce Subscriptions (Pro feature)

šŸ”¶ Pro feature. Recurring membership billing through WooCommerce Subscriptions is available in Learnomy Pro.

When the WooCommerce Subscriptions extension is active, recurring membership plans are supported. Learnomy creates a period-keyed subscription product per billing interval (e.g. one for monthly, one for yearly) and sets the recurring price dynamically at checkout. No per-plan subscription products are needed.

Lifecycle events (cancellation, expiry, payment failure, reactivation) from WooCommerce Subscriptions are mapped back to the corresponding Subscription_Service methods in Learnomy, so the subscription status in Commerce > Subscriptions stays in sync.

Commission with WooCommerce

Instructor commission works exactly as with Stripe and PayPal. When the WooCommerce order completes, Learnomy fires learnomy_payment_completed and the commission service calculates the platform and instructor split using the same rules (per-course rate, per-instructor rate, or the default 30% platform / 70% instructor). The commission rows appear on the transaction detail page.


Test Gateway

The Test Gateway completes any checkout instantly without calling an external service. It records a completed transaction and triggers all the same fulfilment actions as a real gateway (enrolment, subscription creation, coupon redemption).

Enable it at LMS Settings > Payments > Test Gateway. Turn it off before going live. It is intended for development and QA only.

The test payment mode notice

While the Test Gateway is enabled, a single notice appears on Learnomy's admin screens: "Learnomy is in test payment mode." It explains that orders placed with the Test option are simulated, no real charge is taken, and they are recorded at $0 and kept out of your revenue. The notice links straight to Payment settings.

This replaces an earlier, more alarming warning that suggested customers could not purchase at all. That was misleading: test mode is a normal staging state (the same way WooCommerce has a sandbox mode), and the real safeguard isn't blocking checkout, it's that test orders can never inflate your numbers.

Test orders record at zero

Every order placed through the Test Gateway is recorded with an amount of 0.00, tagged as test mode in its metadata. This means:

  • Test purchases never add to your revenue totals, earnings, or instructor payouts.
  • The transaction row still exists and is fully visible in Commerce > Transactions, with provider "Test", so you can verify the buy-to-access flow end to end.
  • The original intended price is kept in the transaction's metadata for reference, but it is not what gets summed anywhere in reporting.

If you ever see $0 sales piling up from the Test provider, that's the signal test mode is still on, not a billing problem.


Checkout and Refund Policy settings

Below the gateway cards, the Checkout settings card contains:

  • Business Name - shown on receipt PDFs and invoice emails. Defaults to the site title.
  • Money-Back Guarantee - the number of guarantee days shown on course pages and at checkout. Set to 0 to hide it.
  • Refund Policy - plain-text refund policy shown at checkout and on receipts.

Tips

  • You can run Stripe in Test mode and PayPal in Live mode at the same time. Each gateway uses its own mode setting.
  • Stripe and PayPal secret keys are stored encrypted in the database using WordPress's AUTH_KEY. They are never sent back to the browser.
  • If a student's checkout redirects to WooCommerce but the cart looks empty, the most common cause is that WooCommerce's "Coming soon" store mode is active.
  • For WooCommerce refunds, go to the WooCommerce order (find the order ID in the transaction's gateway reference) and use WooCommerce's own Refund button. Learnomy detects the refund via webhook and creates the corresponding refund transaction row automatically.
  • With only the test gateway enabled, Learnomy does not warn that no payment gateway is configured -- a test-mode site can genuinely take (simulated) orders, so that warning would be untrue. Instead you get a calm notice: "Test gateway active. Purchases are simulated, so you can demo checkout now." Test-gateway orders are recorded at 0.00 and excluded from revenue, earnings, and payouts, so demoing checkout never inflates your numbers.

Tax (Flat Rate)

Learnomy can add one flat tax rate on top of every paid checkout, for basic compliance. It applies to both Stripe and PayPal. You turn it on, set a percentage, and name it (Tax, VAT, GST). The tax shows on the order summary and is charged on top of the course or plan price.

What you can do

  • Add a single percentage tax to paid Stripe and PayPal purchases with one toggle, so the same course costs the same whichever gateway your customer picks.
  • Set the rate to any value from 0 to 100 percent, with two-decimal precision (for example 20 or 7.5).
  • Name the tax whatever your region calls it -- Tax, VAT, GST -- and that label shows on the order summary.
  • Show the tax as its own line on the checkout order summary, with the total updating as the buyer applies a coupon.
  • Charge the tax on top of the price (exclusive), not carved out of it.
  • Leave per-region rates, exemptions, and tax reporting to the WooCommerce gateway, which brings its own tax engine, when a single flat rate is not enough. Learnomy only steps back from a WooCommerce-routed sale when WooCommerce's own Enable taxes setting is switched on; until then, Learnomy keeps applying this flat rate through WooCommerce too, so a sale is never accidentally untaxed.

Tax (Flat Rate) first view

How to use it

Step 1 -- open the Tax card

Go to LMS Settings > Settings, open the Payments section, and find the Tax card.

Step 2 -- turn on tax collection

Switch on Collect tax (labelled "Add a flat tax rate to paid checkouts"). When it is on, the tax shows on the order summary and on the secure card form, and is charged on top of the price. Both Stripe and PayPal charge it, on one-time purchases and on subscriptions.

Step 3 -- set the rate

In Tax rate (%), enter a single percentage applied to every paid purchase, for example 20 for 20%. The field accepts values from 0 to 100 in steps of 0.01.

Step 4 -- name the tax

In Tax label, enter what to call the tax on the order summary, for example Tax, VAT, or GST. If you leave it blank it falls back to "Tax."

Step 5 -- save and check the checkout

Save the section, then open a paid course checkout. The order summary shows a tax line with your label and rate, and the total is the price plus tax. If the buyer applies a coupon, the tax recalculates on the discounted price and the total updates.

Settings and options

The Tax card lives in the Payments section of LMS Settings > Settings.

Field What it is
Collect tax Master toggle. Stored as tax_enabled in learnomy_settings. When off, no tax line is shown and no tax is charged.
Tax rate (%) A single percentage applied to every paid purchase. Stored as tax_rate. Clamped to the 0-100 range.
Tax label The name shown on the order summary next to the rate. Stored as tax_label. Falls back to "Tax" when blank.

How the tax behaves at checkout:

  • Order summary -- when tax is on, a tax line shows the label, the rate in brackets, and the amount. The total is price minus any coupon, plus tax.
  • Exclusive -- the rate is added on top of the price, not extracted from it.
  • Stripe -- the tax is added to the amount Stripe charges. For subscriptions, Learnomy attaches a Stripe TaxRate so the tax is itemised on Stripe's own invoices.
  • PayPal -- the tax is added to the amount PayPal charges. On a one-time purchase it is sent as its own line in the amount breakdown, so the buyer sees it on PayPal's own review screen instead of a silently larger total. On a subscription it is applied to the billing plan. If you have also set a tax rate inside the PayPal billing plan itself, the Learnomy rate replaces it rather than stacking on top.
  • WooCommerce -- Learnomy checks whether WooCommerce's own Enable taxes setting (WooCommerce > Settings > Tax) is actually on. If it is, WooCommerce's tax engine handles the sale and this flat rate steps back, so the buyer is not charged twice. If WooCommerce's tax setting is off (the WooCommerce default), Learnomy applies this flat rate to the amount before it is handed to the WooCommerce cart, the same as it would for Stripe or PayPal, so a WooCommerce buyer is never left untaxed by default. See Add courses to your WooCommerce store for a live example.

For developers

Settings keys (all under the learnomy_settings option):

  • tax_enabled -- bool, the master toggle.
  • tax_rate -- float percentage in [0, 100].
  • tax_label -- string shown on the order summary.

Both gateways resolve the rate through the same two helpers in includes/functions.php, so a gateway can never drift out of step with the setting:

  • \Learnomy\tax_rate() -- the rate as a percentage in [0, 100]. Returns 0 when tax is off.
  • \Learnomy\tax_amount( float $subtotal ) -- the tax owed on a subtotal at that rate, exclusive, rounded to 2dp.

Stripe adapter (Learnomy\Adapters\Stripe_Adapter):

  • Tax on one-time PaymentIntents is added to the charged amount, and the tax amount rides along in the PaymentIntent metadata key learnomy_tax so the webhook can record it without re-reading settings.
  • For subscriptions, a Stripe TaxRate is resolved and cached per mode and rate in the learnomy_stripe_taxrate_{mode}_{rate} option, and applied as the subscription's default tax rate so Stripe itemises the tax on renewals.

PayPal adapter (Learnomy\Adapters\PayPal_Adapter):

  • One-time payments send an amount breakdown on the purchase unit (item_total plus tax_total), which PayPal requires to sum exactly to the order total. That is what itemises the tax on PayPal's review screen.
  • Subscriptions apply the rate via plan_overrides.taxes.percentage on the subscription, on top of the billing plan.

Related setting:

  • commission_tax_in_base (in learnomy_settings) controls whether collected tax is included in the commission split base. Leave it off so tax a gateway collects is set aside before the split.

Checkout and Purchase Flow

The Learnomy checkout and purchase flow

Learnomy gives buyers one on-page checkout that works with whichever gateway you have enabled. The buyer sees an order summary, enters a coupon if they have one, and pays without leaving the page for Stripe and the test gateway. After payment they land on a thank-you page; if they back out at the gateway they land on a cancelled page.

What you can do

  • Sell a course or a membership plan through one unified checkout at /enroll/?course_id=N or /enroll/?plan_id=N.
  • Take card payments on-page with Stripe (the card form renders inside the checkout, no redirect), or send buyers to PayPal or WooCommerce.
  • Show a clear order summary with the price, any sale discount, a coupon line, the tax line, and the total.
  • Let buyers apply a coupon code and see the total recalculate before they pay.
  • Skip the payment provider entirely when a coupon (or a fully discounted price) zeroes the total -- the buyer is enrolled directly.
  • Hide the payment-method selector when only one gateway is enabled, so a single method reads as a clean form instead of a one-option radio.
  • Land every buyer on a thank-you page that confirms the order, or a cancelled page if they abandon the gateway.

How to use it

Step 1 -- reach the checkout

A buyer reaches checkout by clicking Buy or Enroll on a course page or a membership plan card. That links to /enroll/?course_id=N (course) or /enroll/?plan_id=N (plan). Checkout requires the buyer to be logged in.

Step 2 -- review the order summary

The left side shows the item (thumbnail, title, instructor, and a Membership badge for plans) and, for paid items, a price breakdown: Price, any Sale discount, a Coupon line, the Tax line when tax is on, and the Total.

Step 3 -- apply a coupon (optional)

The buyer enters a code and clicks Apply. A valid code adds a coupon line and updates the total; feedback reads immediately. If a coupon or a fully discounted price brings the total to zero, no gateway is used -- Learnomy enrolls or subscribes the buyer directly, because payment providers reject a zero charge.

Step 4 -- fill in billing details

Above the payment method, the buyer supplies a billing address: first and last name, company, country, two address lines, city, state, postcode, email, phone, and a tax registration number (GSTIN in India, VAT ID in the EU -- one field covers both).

Six of those are required before a purchase can go through: first name, last name, country, address line 1, city, and postcode. The rest are optional.

The address belongs to the buyer, not to the order. It is stored on their user account, so the fields come back pre-filled on their next purchase and they never retype it. Each order also keeps its own frozen copy taken at the moment of purchase, so editing the address later never rewrites an old receipt.

The same details are collected whichever gateway the buyer uses. Before this, the address came from Stripe's own address widget, which meant a PayPal or WooCommerce buyer supplied no address at all, a company name and tax number could not be captured, and nothing was kept afterwards.

Site owners running WooCommerce get this for free in both directions: the fields use WooCommerce's own billing_* user-meta keys, so an address Woo already collected pre-fills a Learnomy checkout and vice versa. WooCommerce is not required -- without it these are ordinary user meta.

Step 5 -- pay

The right side shows the payment method:

  • Stripe -- the card fields render on the page, themed to your site. The buyer enters their details and clicks Complete Purchase without leaving the page.
  • PayPal -- the PayPal button renders in place; the buyer completes payment through PayPal.
  • WooCommerce -- the buyer is sent to the WooCommerce cart to finish.
  • Test -- a test-mode button simulates a successful payment with no real charge.

When only one gateway is enabled, the method selector is hidden and the single method is shown directly.

Step 6 -- land on the result page

  • Thank-you (enroll-thank-you route) -- confirms the purchase with an order summary and next-step buttons. If the webhook has not confirmed the payment yet, the page shows a "Setting up your enrollment" pending state that refreshes itself until the payment clears, then a failed state if it does not go through.
  • Cancelled (enroll-cancelled route) -- shown when the buyer abandons the gateway. No charge is made and nothing is recorded; the page offers to try payment again or browse other courses or plans.

Settings and options

The checkout adapts to what you have configured; there is no separate "checkout" settings page.

  • Which gateways appear comes from the Payments settings -- Stripe, PayPal, WooCommerce, and the Test gateway each have their own enable toggle. WooCommerce, when enabled, is the exclusive checkout route for every purchase type; otherwise the checkout shows whichever of Stripe, PayPal and Test are enabled. See Payment Gateways.
  • The tax line appears when tax is enabled. See Tax (Flat Rate).
  • Coupons are validated against your coupon rules. See Coupons.
  • Free items show a "Free Enrollment" panel with an Enroll for Free button instead of a payment form.
  • Already enrolled buyers see an "Already Enrolled" message with a link to the course instead of a second checkout, and a repeated paid checkout for a course the buyer already owns is blocked.

For developers

Routes (built through the Route Registry, never hardcoded):

  • enroll -- the checkout page (templates/checkout.php).
  • enroll-thank-you -- post-purchase landing (templates/checkout-thank-you.php).
  • enroll-cancelled -- gateway-abandon landing (templates/checkout-cancelled.php).

REST (Learnomy\API\Checkout_Controller, namespace learnomy/v1):

  • POST /checkout -- unified checkout; resolves the gateway from the request and returns a client_secret (Stripe on-page) or a redirect_url (PayPal / WooCommerce). Free / zeroed orders enroll directly.
  • POST /checkout/subscribe -- create a recurring subscription for a membership plan.
  • POST /webhooks/{gateway} -- public, signature-verified webhook ingestion (with a back-compat /checkout/webhooks/{gateway} alias).
  • Checkout_Controller::thank_you_url( $gateway_id, $context ) / ::cancelled_url( $context ) -- the canonical return-URL builders. Pro reuses these rather than building return URLs itself.

Return-page actions:

  • learnomy_thank_you_rendered -- fires after the thank-you page renders (transaction row, user id). Pro analytics use it for conversion pixels and drip triggers.
  • learnomy_cancelled_rendered -- fires after the cancelled page renders (course id, plan id). Used for abandoned-cart follow-up.

Subscription Lifecycle: Pauses, Plan Changes, Dunning and Refunds

Pro feature. Requires Learnomy Pro with the Membership Pro extension active.

Once someone subscribes, Membership Pro handles what happens next: members can pause and resume, switch plans with fair credit for unused time, and the site keeps their access safe when a renewal charge fails or a refund is issued. This page covers the ongoing lifecycle. For plans, gift memberships, and the retry-cadence and refund settings themselves, see Membership Plans.

What you can do

  • Let a member pause a subscription and resume it later, with the paid time they had left preserved.
  • Let a member switch to another plan and see a fair-credit proration quote -- credit for unused days on the old plan, charge for the new plan -- before they commit.
  • Apply a plan change immediately, or schedule it to take effect at the end of the current billing period.
  • Retry a failed renewal charge automatically on a cadence you choose, instead of cancelling on the first failure.
  • Cancel a subscription automatically only after the last retry fails.
  • Issue a refund against a subscription payment, with the option to cancel the subscription at the same time.
  • Keep a full audit trail of every pause, resume, plan change, refund and gift on the subscription, readable by both the member and the site owner. See Where the audit trail is visible.

Subscription Lifecycle first view

How to use it

Pause and resume (member)

  1. The member opens their subscription on the membership manage page (or the billing area of My Account).
  2. They click Pause. Access pauses now and no charge is taken during the pause.
  3. When they click Resume, access comes back and the billing period is extended by however long the pause lasted, so they do not lose paid time.

A pause can also carry an auto-resume date, in which case Learnomy resumes it for them on that date.

Change plan with proration (member)

  1. On the same manage surface, the member clicks Change plan.
  2. Learnomy shows the plans they can switch to and a live quote: a credit for the unused days on their current plan and a charge for the new plan, netted to a single "Due today" or "Credited to your account" figure.
  3. They choose when it takes effect:
    • Immediately -- the provider charge or credit runs now and they move to the new plan straight away.
    • At the end of my billing period -- they keep the current plan until the period ends, then the switch and the next charge happen on the new plan. Learnomy schedules this and executes it automatically when the period ends.
  4. On confirm, Learnomy applies the change, moves them onto the new plan's courses, and removes access to any courses that were only on the old plan.

Proration only runs when the site's plan-change setting is on; with it off, the new plan's price is charged in full.

Payment retries when a renewal fails (automatic)

When a renewal charge fails, the subscription goes past-due and Learnomy schedules retries on the cadence set in the Billing & retries settings:

  • Conservative -- retries at 14, 28, and 56 days.
  • Standard -- retries at 7, 14, and 28 days (default).
  • Aggressive -- retries at 3, 7, and 14 days.
  • Off -- no retries; the subscription is cancelled on the first failure.

If a retry succeeds, the member is billed, the period is extended, and the remaining retries are cancelled. If the final retry fails, the subscription is cancelled. Every attempt is logged so you can see what happened.

Refund a payment (admin)

  1. From the transactions or refundable-transactions list, an admin issues a refund against a subscription payment.
  2. Learnomy calls the gateway's refund API and records a refund transaction linked to the original charge.
  3. If the "refund cancels the subscription" setting is on (the default), the subscription is cancelled immediately. With it off, the refund is recorded but the member keeps access until the end of the current billing period.

Where the audit trail is visible

Every pause, resume, plan change, refund and gift redemption is recorded, and both sides of the conversation can read it. None of these events is a payment, so none of them appears in an order or payments table - this is the record that explains them.

Surface Who sees it What it covers
Billing history, below the order history on the member's account billing page (/account/billing/) The member, for themselves only The 25 most recent changes across all of their subscriptions
Change history, below the payments table on a subscription in LMS Commerce > Subscriptions The site owner The 50 most recent changes on that one subscription

Both show the same three columns: the date the change took effect, what the change was (Plan changed, Paused, Resumed, Refunded, Gift applied), and the detail - for a plan change, the plan it moved from and the plan it moved to.

Neither surface appears when there is nothing to show, so a member who has never paused or switched sees no empty section. Payment retry attempts are a separate record and are not in these tables; see the admin views note below.

Member-facing detail is documented in Certificates and Billing; the owner's panel in Subscriptions.

Settings and options

The site-wide controls for retries, plan changes, and refunds live in Membership Pro's Billing & retries settings card, documented in Membership Plans. In short:

Setting What it controls
Payment retries The retry cadence (Conservative / Standard / Aggressive / Off) used when a renewal charge fails.
Plan changes Whether switching plans mid-cycle credits the unused time from the old plan (proration on) or charges the new plan in full (proration off).
Refunds Whether issuing a refund also cancels the subscription immediately.

These apply to all plans on the site and cannot be overridden per plan.

Admin views:

  • A Paused subscriptions tab lists paused members and lets an admin resume any of them.
  • Failed-payment subscriptions show up in LMS Commerce > Subscriptions filtered to Payment failed; the per-attempt retry history itself is available over the REST API only (GET /memberships/dunning-attempts/{sub_id}), not as an admin screen today.

For developers

Services (Learnomy_Pro\Extensions\Membership_Pro):

  • Pause_Service::pause( $sub_id, $resume_at = null ) / ::resume( $sub_id ) -- pause sets status paused; resume extends current_period_end by the paused duration. An optional auto-resume is scheduled via Action Scheduler (learnomy_pro_auto_resume_subscription).
  • Proration_Service::calculate( $sub, $new_plan, $effective = null ) -- returns the credit / charge / net quote (provider-independent). provider_for( $sub ) returns the Stripe / PayPal / Test proration provider that executes it.
  • Plan_Change_Service::change( $sub_id, $new_plan_id, $effective ) -- effective is immediate or scheduled (scheduled runs at current_period_end via Action Scheduler).
  • Dunning_Service -- listens on Free's learnomy_subscription_past_due; schedules retries (learnomy_pro_dunning_retry) per the learnomy_pro_dunning_cadence option; records each in lrn_pro_dunning_attempts.
  • Refund_Service::refund( $transaction_id, $amount, $reason ) -- refunds via the provider, records a refund transaction, and honours the learnomy_pro_refund_cancels option.

Options:

  • learnomy_pro_dunning_cadence (conservative / standard / aggressive / off).
  • learnomy_pro_proration_enabled (bool).
  • learnomy_pro_refund_cancels (bool).

Actions fired:

  • learnomy_subscription_paused, learnomy_subscription_resumed.
  • learnomy_pro_plan_changed, learnomy_pro_plan_change_queued, learnomy_pro_plan_changed_at_provider, learnomy_pro_plan_change_failed.
  • learnomy_pro_refund_issued.

Filters:

  • learnomy_pro_allow_pause -- gate whether a given subscription may be paused.
  • learnomy_pro_proration_provider -- swap in a custom proration provider for a subscription.

REST (learnomy-pro/v1):

  • POST /memberships/me/pause, and resume through Free's POST /learnomy/v1/memberships/me/resume (Pro participates via the learnomy_membership_resume_paused filter).
  • POST /memberships/me/change-plan (+ read-only /change-plan/options and /change-plan/quote).
  • GET /memberships/subscription-changes (member-scoped) returns the caller's own change history - the same rows behind Billing history and Change history.
  • GET /memberships/dunning-attempts/{sub_id} and /retry-now (admin).
  • GET /admin/refundable-transactions (admin); the refund itself runs through Free's canonical POST /learnomy/v1/admin/transactions/{id}/refund so learnomy_payment_refunded still fires.

Gift Codes and Team Seats

Gift codes let one person pay for a membership that someone else uses. Buy a batch of claim codes for a plan, share them, and each recipient redeems a code to start their own subscription. The same mechanism powers team seats: buy N seats of a plan in one purchase and hand each teammate a code.

What you can do

  • Sell a membership as a gift. A buyer pays for one or more seats of a plan and gets back a set of claim codes to share.
  • Buy team seats in bulk. One purchase of N seats produces N codes, so a manager can onboard a whole team from a single checkout.
  • Let recipients redeem a code from a shareable gift page, the My Account "Gift" tab, or a shortcode you drop on any page.
  • Issue codes by hand from the admin as comp memberships or promo giveaways, with no payment required.
  • Let each redeemed code start a normal subscription for the recipient, tracked as a gift so it shows up in the buyer's and recipient's records.
  • Turn the whole gifting UI on or off from LMS Settings without touching code.

Gift codes and team seats first view

How to use it

Step 1 - make sure gifting is turned on

Gifting is on by default. To confirm or change it, open LMS Settings -> Settings and find the membership gifting toggle (show_membership_gift). When it is off, the gift page, the "Gift this membership" call-to-action on the pricing page, and the "Gift" account-nav item all disappear.

Step 2 - buy seats (buyer path)

A logged-in buyer opens the gift page at /<membership-slug>/gift/ (also reachable from the "Gift this membership" link on the membership pricing page and the "Gift" tab in My Account). They choose the plan and the number of seats (1 to 500), then check out through your normal payment gateway. The plan must have a purchase price - a free plan cannot be gifted.

After payment completes, the codes are minted and shown to the buyer to share. If the same payment is ever replayed by the gateway, the buyer gets the same codes back instead of a second batch.

Step 3 - issue codes by hand (admin path)

An administrator can issue codes directly without a payment - useful for comp memberships or promotions. This runs through the admin gift endpoint, which mints the requested number of codes for a plan and optionally records another user as the buyer.

Step 4 - redeem a code (recipient path)

A recipient enters their code on the gift page, the My Account "Gift" tab, or wherever you placed the [learnomy_gift_redeem] shortcode. On a valid code the system starts a standard subscription to the plan for that user, recorded as a gift. Each code works once - two people trying the same code at the same time will only ever let one through.

Settings & options

Setting / option What it is
learnomy_settings[show_membership_gift] Master toggle for the gifting UI (gift page, pricing-page CTA, account-nav "Gift" item). Default on. Read on the Pro side through Learnomy_Pro\Settings_Helper::get_free_field().
Seats per purchase 1 to 500 per issue or checkout. Requests outside that range are rejected.
Code format 12-character uppercase alphanumeric claim codes, generated with WordPress's password generator (no ambiguous special characters).
Code expiry Codes expire 90 days after they are issued. An expired code cannot be redeemed.
Code status Each code is available, claimed, revoked, or expired. Redeeming flips available to claimed.

Storage: codes live in the dedicated lrn_pro_gift_codes table (indexed on the code plus status and buyer). Earlier versions kept them in an autoloaded option; that data is migrated into the table automatically on upgrade.

For developers

REST routes (namespace learnomy-pro/v1):

  • POST /memberships/gifts - admin issues codes for a plan. Optional buyer_id records who the codes are on behalf of. Admin-only.
  • POST /memberships/gifts/checkout - a logged-in buyer pays for N seats of a plan; returns through the gift page after checkout. Requires the buyer to be signed in.
  • POST /memberships/gifts/redeem - the current user redeems a code. Rate-limited and length-capped before any lookup.

Service (Learnomy_Pro\Extensions\Membership_Pro\Gift_Service):

  • issue( int $plan_id, int $quantity, int $buyer_id = 0, array $context = [] ) - mint codes; pass context['source_txn_id'] to make a paid issue idempotent against replayed webhooks.
  • redeem( string $code, int $user_id ) - claim a code and start the subscription (provider = 'gift').
  • list_for_buyer( int $buyer_id ) - codes a buyer holds, with claim status.

Model (Learnomy_Pro\Extensions\Membership_Pro\Models\Gift_Code): claim_atomically() is the single-row status-guarded UPDATE that prevents two simultaneous redeems from double-claiming one code.

Actions:

  • learnomy_pro_gift_issued - fired with ( array $codes, int $plan_id, int $buyer_id ) after codes are minted.
  • learnomy_pro_gift_redeemed - fired with ( string $gift_code, int $redeemer_user_id, int $subscription_id ) after a successful redeem.

Paid gifts hook Free's learnomy_purchase_completed with purchase_type = 'gift', which the extension turns into claim codes once payment settles.

Front-end surfaces: the [learnomy_gift_redeem] shortcode, the membership-gift route (/<membership-slug>/gift/, gated to the membership module), the learnomy_pricing_before_faq gift call-to-action, and the learnomy_account_nav_items "Gift" tab.

Settings

Configure Learnomy to match your site's needs: commerce, communications, content, and advanced options.

Settings Overview

The Learnomy settings screen

The Settings page is where you configure every aspect of how Learnomy behaves on your site. It lives under LMS Settings in the WordPress admin sidebar.

Overview

Go to LMS Settings in the sidebar. The settings page opens with a left-hand navigation panel. The sections are grouped, not one long list:

Group Sections
General General, URLs & Pages, Labels, Layouts, Registration Fields
Commerce Payments, Instructors, Commissions, Membership Page
Communication Emails, Reviews
Content Course Comments, Certificate Defaults, Quiz Settings, Learning Standards
Advanced API & Performance, Data Management

Clicking a section label scrolls you to that section without reloading the page.

Settings page with sidebar navigation

With Learnomy Pro active, its extensions add their own groups at the bottom, each one appearing only when that extension is enabled:

Group Sections
Pro License, White Label
Memberships Pro Billing & retries
Integrations Credly

The navigation only shows sections for modules that are enabled on your site. For example, the Payments, Instructors, and Commissions sections disappear if the corresponding modules are turned off on the Modules page, and a group with no visible sections left does not render its heading.


General

Go to LMS Settings and click General in the sidebar.

General section

Course Setup card

Setting What it does
Container Width Sets the maximum width of the Learnomy content area on the frontend. Options are 960 px, 1100 px, 1200 px, 1400 px, or Full Width.
Completion Rules Strict means students must meet every rule (watch time, video percentage, scroll, quiz) before marking a lesson complete. Advisory shows the rules but lets students mark complete at any time. Off removes rules entirely. Admins and a course's own instructor always bypass this setting.

Currency and Payments card

Set the default currency for course pricing and all transactions. Changing this only affects how amounts are formatted and stored going forward; it does not convert existing transactions.

Getting Started Guide card

If you previously dismissed the onboarding checklist on the Learnomy dashboard, click Show the guide again to bring it back.

Setup Wizard card

Click Open setup wizard to re-run the onboarding flow with your current settings pre-filled. Use Reset demo data to delete all demo content (tagged lrn_demo_*) and recreate it from scratch. Only demo data is affected.


URLs & Pages

Go to LMS Settings and click URLs & Pages in the sidebar. (This tab was called just "Pages" in earlier versions - the fields are your Learnomy URL slugs, not WordPress Pages.)

Pages section

URL Slugs card

These fields let you localize or rename the URL segments for all Learnomy pages. For example, a German site might use kurse instead of courses. Leave any field blank to keep the default.

Field Default Example URL
Course URL Base courses /courses/
Lesson URL Segment lesson /courses/my-course/lesson/intro/
Course Quiz URL Segment quiz /courses/my-course/quiz/final/
Category URL Segment category /courses/category/design/
Instructor URL Base instructor /instructor/jane/
Certificate Verify URL Base verify /verify/uuid/
Standalone Quiz URL Base quiz /quiz/my-quiz/
Membership URL Base membership /membership/
My Learning Spaces URL my-spaces /my-spaces/ (Pro Spaces required)
Enroll URL Base enroll /enroll/
Account URL Base account /account/

Saving any slug change automatically refreshes your WordPress permalink rules.

Frontend Pages card

A read-only table listing every Learnomy page on your site, its URL, whether login is required, and a View link for static pages.

Theme Override card

To customize any Learnomy template, copy it from the plugin's templates/ folder into your-theme/learnomy/ and edit from there. The path is displayed on screen so you know exactly where to place the file.


Labels

Go to LMS Settings and click Labels in the sidebar.

Labels section

Rename core content types across your whole site without writing any code. Each entity has a singular and a plural field. Leave either blank to keep the default. To translate labels into another language, use a .po translation file instead of this panel.


Layouts

Go to LMS Settings and click Layouts in the sidebar.

Layouts section

Pick the structural layout for each page type: the course catalog, single course page, lesson player, course quiz, and standalone quiz. Switching a layout never changes the page URL.


Registration Fields

Go to LMS Settings and click Registration Fields in the sidebar.

Add your own custom fields to the sign-up form and the account profile page without writing any code, for example a license number, company name, phone number, or a consent checkbox. Each field saves to the member's profile, can be marked required, and shows on the student detail page in the admin.

For the field types, the Show on locations, and how values are captured and viewed, see Custom Account Fields.


Payments

Go to LMS Settings and click Payments in the sidebar. This section only appears when the Payments module is active.

Payments section

Stripe card

Enable or disable Stripe, toggle between Test and Live mode, and enter your keys. Secret keys and webhook signing secrets are write-only fields: once saved, they show a "Saved" badge and the field stays blank for security. Paste your Stripe webhook URL into Stripe Dashboard under Developers > Webhooks > Add endpoint. The required events are checkout.session.completed, invoice.payment_succeeded, and customer.subscription.deleted.

Stripe credentials

PayPal card

Same pattern as Stripe: enable, choose Sandbox or Live mode, enter Client ID, Secret, and Webhook ID. The Webhook ID is found in PayPal Developer Dashboard under your app > Webhooks, and is required for signature verification.

PayPal credentials

WooCommerce card

The Use WooCommerce toggle routes every Learnomy purchase (course, membership, gift, learning path, space, seat pack) through the WooCommerce cart and checkout instead of Learnomy's own Stripe/PayPal checkout. It requires WooCommerce to be installed and active; installing WooCommerce on its own changes nothing until this is turned on. Recurring membership plans need the WooCommerce Subscriptions extension -- without it, the card lists which recurring plans cannot check out through WooCommerce, and those plans need to switch to a Lifetime (one-time) interval to keep selling. WooCommerce is exclusive when enabled: it takes over checkout for every purchase type, and the Stripe/PayPal settings are kept (not deleted) but sit unused with a notice linking back to this card until you switch WooCommerce off again.

Checkout card

Setting What it does
Business Name Appears on receipt PDFs and invoice emails. Defaults to your site name.
Money-Back Guarantee Number of days for your refund guarantee. Set to 0 to disable. Shown on course sales pages and at checkout.
Refund Policy Plain text shown at checkout and on receipt PDFs.

Test Gateway card

A development-only adapter that completes every checkout without hitting any external service. Turn this off before going live. When enabled, paid courses show a "Test Gateway" option alongside Stripe and PayPal.

Tax card

Setting What it does
Collect tax Labelled "Add a flat tax rate to paid checkouts". Applies to both Stripe and PayPal, on one-time purchases and on subscriptions. WooCommerce uses its own tax engine instead.
Tax rate (%) A single percentage applied to every paid purchase, 0 to 100, for example 20 for 20%.
Tax label What to call the tax on the order summary, for example Tax, VAT, or GST. Falls back to "Tax" when blank.

See Tax (Flat Rate) for the full behaviour at checkout.

Failed payments card

Setting What it does
Grace period How many days a past-due subscription keeps access after a failed renewal before it is expired. Any whole number from 0 to 90. Default 7. Set to 0 to cut access off as soon as the paid period ends.

Membership Page

Go to LMS Settings and click Membership Page in the sidebar.

Membership Page section

Control the headline, subtitle, and FAQ shown on the public membership pricing page at /membership/. You can also toggle the FAQ section and the gifting widgets on or off. The gifting widgets require Learnomy Pro.


Instructors

Go to LMS Settings and click Instructors in the sidebar. This section only appears when the Commissions module is active.

Instructors section

Setting What it does
Self-Publish Courses When enabled, instructors can publish courses without admin approval. When off, courses go to pending review.
Multi-Instructor Allows multiple instructors to be assigned to a single course with a configurable revenue share per instructor.

Commissions

Go to LMS Settings and click Commissions in the sidebar. This section only appears when the Commissions module is active.

Commissions section

Commission Rules card

Setting What it does
Platform Commission (%) The percentage the platform keeps from each sale. The instructor receives the remainder. Default is 30.
Payout Period How often commissions are grouped for payout: Monthly, Bi-weekly, or Weekly.

Payment Processing Fee card

When Deduct fee is on, the gateway fee is taken off the sale amount before the commission split, so both platform and instructor share the processing cost. Set the percentage fee (typically 2.9%) and the fixed per-sale amount (typically 0.30). The Tax in base toggle controls whether gateway-collected tax is included in the commission base.

Withdrawals card

Setting What it does
Minimum Withdrawal Instructors must accumulate at least this amount before requesting a payout.
Payout Methods Enable PayPal and/or Bank Transfer as payout options for instructors.

Emails

Go to LMS Settings and click Emails in the sidebar. This section only appears when the Notifications module is active.

Emails section

Email Sender card

Set the sender name and email address used for all Learnomy notifications.

Email Notifications card

Turn individual notification types on or off. The following notifications are available:

  • Enrollment Welcome
  • Course Completed
  • Certificate Issued
  • Quiz Graded
  • Instructor Application Approved / Rejected
  • Withdrawal Approved
  • Membership Created / Cancelled / Renewed / Expired
  • Trial Ending Reminder
  • New Course Review
  • New Lesson Comment
  • Reply to a Comment

All notifications default to on. Unchecking a type stops that email from being sent.


Reviews

Go to LMS Settings and click Reviews in the sidebar. This section only appears when the Reviews module is active.

Reviews section

Setting What it does
Enable Reviews Allow students to leave course reviews.
Auto-Approve When on, reviews go live immediately without admin moderation. When off, new reviews land in the Reviews queue for manual approval.

Course Comments

Lesson comments are configured in the Course Comments section - go to LMS Settings, click Course Comments in the sidebar, and find Enable Lesson Comments.

Courses section, including the lesson-comments toggle

Setting What it does
Enable Lesson Comments Allow students to post comments on individual lessons.

Course-level comments are planned for a future release and do not appear here.


Certificate Defaults

Go to LMS Settings and click Certificate Defaults in the sidebar. This section only appears when the Certificates module is active.

Certificate Defaults section

Setting What it does
Enable Certificates Issue a certificate automatically when a student completes a course.
Footer Text Text shown at the bottom of every certificate. Typically your organization name or legal text.

Quiz Settings

Go to LMS Settings and click Quiz Settings in the sidebar.

Quiz Settings section

Quiz Defaults card

These settings apply to every new quiz unless overridden at the individual quiz level.

Setting What it does
Default Pass Mark (%) Minimum score required to pass. Default is 50.
Max Attempts How many times a student can attempt a quiz. Set to 0 for unlimited.
Randomize Questions Shuffle question order on every attempt.
Show Correct Answers Choose when students see correct answers: after submission, only after passing, or never.

Anti-Cheat card

Setting What it does
Track Tab Switches Record when a student switches browser tabs during a quiz. The count is stored on the attempt record.
Default Time Limit (min) Time limit in minutes. 0 means no limit. When time runs out the quiz is submitted automatically.

API and Performance

Go to LMS Settings and click API and Performance in the sidebar.

API and Performance section

Setting What it does
Sensitive endpoint limit (req/min per user) Rate limit for write endpoints flagged as sensitive: login, password reset, instructor application, withdrawal request, coupon validate, quiz start, and comment post. Set to 0 to disable. Maximum 10,000.
Cache TTL (seconds) Object-cache lifetime for read endpoints. Set to 0 to disable caching. Maximum 86,400 (24 hours).

The Tools card provides two buttons: Send Test Email dispatches a test notification to the admin email address, and Flush Rewrite Rules regenerates WordPress permalink rules without requiring a visit to Settings > Permalinks.


Data Management

Go to LMS Settings and click Data Management in the sidebar.

Data Management section

Setting What it does
Audit Log Retention (days) Entries in the audit log older than this many days are removed automatically. Minimum is 30 days, default is 365.
On Uninstall When checked, all plugin data is deleted when the plugin is removed. Leave unchecked to preserve student progress, transactions, and other data if you reinstall later.

License

Pro feature. Requires Learnomy Pro.

Go to LMS Settings and click License in the sidebar.

Enter your Learnomy Pro license key here to activate automatic updates and support. Once saved, the panel shows the license status, the site domain it is registered against, and the expiry date.


Tips

  • Settings are stored as a single WordPress option (learnomy_settings). All sections share one Save Changes button per section. Saving one section does not overwrite other sections.
  • Sidebar sections whose module is disabled still exist in the page HTML so that deep-linked URLs continue to work, but they are hidden from the navigation.
  • Pro-only settings sections (License) appear in the sidebar only when Learnomy Pro is active.

Emails and Announcements

Learnomy sends automated email notifications for key learning events and lets you broadcast one-off announcements to all students or to the students of a specific course.

Overview

There are two distinct surfaces:

  • Email Settings (at LMS Settings > Emails) controls which automated notifications go out, who they come from, and lets you customize each notification's subject and body.
  • Email Log (at LMS Settings > Email Log) is an audit trail of every notification that was dispatched, with filtering and a one-click resend.
  • Announcements (at Learnomy > Announcements) lets you compose and send manual broadcasts to students.

Note: Announcements (Learnomy > Announcements) and the Email Log appear only when the Notifications module is enabled on the Modules page. If you don't see them, turn that module on first.


Configuring Email Notifications

  1. Go to LMS Settings in the sidebar.

  2. Click Emails in the left-hand settings navigation.

    Email Settings section

  3. Under Email Sender, enter the name and email address you want recipients to see. By default these use your site name and admin email.

  4. Under Email Notifications, use the toggle next to each notification to turn it on or off.

    Every email Learnomy sends through its own notification service has a row here. Learning Spaces (Pro) sends some of its member emails directly and those have no row, so they cannot be switched off from this screen. Rows that name an entity - Course, Quiz, Lesson, Instructor - follow your custom labels, so a site that renamed Course to Class reads "Class Completed".

    Notification Sent to
    Enrollment Welcome Student (on enrollment)
    Course Completed Student (on completion)
    Certificate Issued Student (on certificate generation)
    Quiz Graded Student (when a manual-graded quiz is finalized)
    Quiz Submitted Student (on every quiz submission)
    Instructor Application Approved Instructor applicant
    Instructor Application Rejected Instructor applicant
    Instructor Application Received Instructor applicant (on submission)
    Withdrawal Approved Instructor (on payout approval)
    Withdrawal Rejected Instructor
    Payout Sent Instructor (when the payout leaves)
    Payout Failed Instructor
    Payout Failed (to the site owner) Admin
    Payment Receipt Buyer (on a completed purchase)
    Lesson Unlocked (drip) Student (when drip releases a lesson)
    New Enrollment (to the Instructor) Instructor (on every enrollment in their course)
    New Instructor Application (to the site owner) Admin
    New Payment (to the site owner) Admin (on every sale)
    Membership Created Member (on subscription start)
    Membership Cancelled Member (on cancellation)
    Membership Renewed Member (on renewal)
    Membership Expired Member (on expiry)
    Trial Ending Reminder Member (trial nearly over)
    Re-engagement Reminder Student (when their courses have gone quiet - see The re-engagement reminder)
    New Course Review Admin (when a student submits a review)
    New Lesson Comment Admin (when a comment is posted)
    Reply to a Comment The commenter (when a reply arrives)

    Three of these fire on every single event and are the ones busy sites usually turn off first: New Enrollment (to the Instructor), New Payment (to the site owner), and Quiz Submitted.

    Each row also has a Customize copy link that expands a per-template editor for that notification's subject and body. See Customizing an Email Template below.

  5. Click Save Changes.


The re-engagement reminder

Students who stop halfway are the largest recoverable group on any academy. The Re-engagement Reminder is the one automated email aimed at them: a short "you stopped, here is your dashboard, come back" nudge sent to a student whose enrolled, uncompleted courses have gone quiet.

Configuring it

Two controls, both on LMS Settings > Emails:

Control Where What it does
Inactive after (N) days The Re-engagement card, above the notification list How many days of no activity make a student count as lapsed. Any whole number from 1 to 90. The default is 14.
Re-engagement Reminder toggle In the Email Notifications list Turns the email itself on or off. Switch this off and no reminder is ever sent, whatever the threshold says.

The threshold measures activity on a student's active, uncompleted enrollments. It is the same measure the Students list's inactivity filter uses, so a threshold of 14 targets exactly the people that filter shows you at "Inactive 14+ days".

Like every other notification, this one has a Customize copy link for rewriting its subject and body.

How often a student hears from you

Once per lapse, not once per day. Learnomy runs the sweep daily but records when it last nudged each student and will not nudge the same person again until a further full threshold window has passed. A student who lapses, gets the reminder, and stays away is not emailed again 24 hours later.

A single run sends at most 500 reminders, so a backlog larger than that is spread across consecutive daily runs rather than going out as one flood.

The student's own opt-out

The reminder is promotional, not transactional, so students can switch it off themselves. On their account page, under Notifications, the checkbox labelled "Comments, replies, and new reviews" covers it. Unticking that box stops the re-engagement reminder along with the comment and review emails it names.

An opted-out student is skipped even when the reminder is triggered by hand. Receipts and account-security messages are never suppressible and have no checkbox.

Sending one by hand

Instructors do not have to wait for the daily sweep. The Students at Risk card on the instructor dashboard lists their own at-risk students with a Send reminder button on each row, which sends this same email immediately. See Managing Students.

Both gates still apply to a manual send: if you have switched the Re-engagement Reminder off, or that student has opted out, nothing goes out.


Viewing the Email Log

The Email Log records every notification Learnomy attempted to send. It is useful for confirming a specific student received an enrollment email or for diagnosing delivery problems.

  1. Go to LMS Settings in the sidebar.

  2. Click Email Log under the Settings submenu.

    Email Log overview

  3. The page header shows the log name.

    Email Log header

  4. Use the filter bar above the table to narrow the log by recipient (email or name), a date range, or delivery status (All statuses / Sent / Failed / Skipped). Click Filter to apply your choices, or Clear to reset.

  5. The table lists every logged notification with six columns:

    Email Log table

    Column What it shows
    Recipient The recipient's name and email address (or "User #ID (deleted)" if the account no longer exists)
    Type The notification type, such as Enrollment or Quiz Graded
    Subject / Title The notification subject line or title
    Status sent, failed, or skipped. A failed row also shows the failure reason underneath the status badge, when it was captured
    Sent At Date and time the notification was dispatched
    Actions Resend, shown whenever the row has a reachable recipient email

    Rows written before the real delivery status column existed show skipped rather than a status that cannot be verified. A failed row logged before failure-reason capture existed shows a hint to resend the email to capture the reason going forward.

  6. Use the pagination controls at the bottom to move between pages. The log shows 25 entries per page.

Resending a notification

Click Resend on any row that has a recipient email to send that notification again immediately, using the same subject and body that were originally sent. Use this when a student says they never received an enrollment, certificate, or other notification email, or to retry a failed row after fixing your site's mail delivery.


Customizing an Email Template

Every automated notification's subject and body can be customized without writing code, right from the Email Notifications table.

  1. Go to LMS Settings > Emails.

  2. Find the notification you want to change in the Email Notifications table.

  3. Click Customize copy to expand the editor for that row.

    Field What it does
    Subject Overrides the default subject line. Leave blank to keep the default.
    Body Overrides the default message body. Leave blank to keep the default. Your text is placed inside Learnomy's branded email layout (logo, footer, unsubscribe link) automatically; you only edit the message itself.
  4. Personalize the copy with merge tags. These are available on every notification:

    Merge tag Replaced with
    {student_name} The recipient's display name
    {student_first_name} The recipient's first name (falls back to display name)
    {student_email} The recipient's email address
    {site_name} Your site name
    {site_url} Your site's home URL
    {login_url} The login page URL

    Many notifications also expose event-specific tags, such as {course_title} on a course-related email. The text above the Subject field for each row lists the tags available there.

  5. Click Save Changes.

A row with saved custom copy shows Custom copy instead of Customize copy, so you can tell at a glance which templates have been changed from their defaults. Clearing both the Subject and Body fields and saving restores the default template.


Sending an Announcement

Announcements let you push a message to students as an in-app notification and, optionally, as an email.

  1. Go to Learnomy > Announcements in the admin sidebar.

    Announcements page

  2. The page header confirms you are on the Announcements screen.

    Announcements header

  3. Fill in the Create Announcement form.

    Compose form

    Field What to enter
    Title A short, clear subject line for the announcement
    Message The announcement body
    Course Select a specific course to notify only its enrolled students, or leave it as All students to reach every student on the site
    Email Check Also send email to each recipient if you want the announcement delivered by email as well as as an in-app notification
  4. Click Send Announcement. The form submits and the announcement appears in the Sent Announcements list below.


Reviewing Sent Announcements

The Sent Announcements list below the compose form shows every announcement that has been sent.

Sent announcements list

Each row shows the title, the course it was targeted to (or "All students"), a short excerpt of the message, and the date sent. You can delete a past announcement from the row actions. Deleting removes it from every recipient's notification feed and cannot be undone.

The list is paginated at 20 entries per page.


Tips

  • Turning off a notification type stops future emails of that type but does not affect notifications already sent.
  • Raising the Inactive after threshold does not re-nudge anyone. It only changes who qualifies from the next daily run onward.
  • The Email Log captures notifications from the moment the status column was added (migration 3.8.0). Older rows show skipped because their actual delivery status was never recorded.
  • Resending a notification sends it again to the same recipient; it does not change or clear the original log row.
  • An announcement sent to a specific course notifies only students who are currently enrolled in that course. Students who enroll later do not receive it.
  • There is no way to edit a sent announcement. Delete it and create a new one if the content needs to change.
  • Custom copy overrides apply the next time that notification fires. They do not retroactively change notifications already logged.

Course Reviews

Course reviews let enrolled students rate and comment on the courses they have taken. Admins can moderate every review before it appears publicly, or enable auto-approval to publish reviews immediately.

Overview

Reviews are managed from Learnomy > Reviews in the admin sidebar. The page shows every review submitted on your site, grouped by status tab. You can approve, reject, mark as spam, or delete reviews individually or in bulk.

Review moderation behavior is controlled by two settings in LMS Settings > Reviews.


Turning Reviews On or Off

  1. Go to LMS Settings in the admin sidebar.

  2. Click Reviews in the left-hand settings navigation.

    Reviews settings section

  3. Check or uncheck Enable Reviews to turn the review system on or off site-wide.

  4. Check Auto-Approve if you want new reviews to go live immediately without a moderation step.

  5. Click Save Changes.


Moderating Reviews

  1. Go to Learnomy > Reviews in the admin sidebar.

    Reviews list overview

  2. The page header shows the Reviews title and a brief description.

    Reviews list header

  3. Use the status tabs at the top of the table to filter by All, Pending, Approved, Rejected, or Spam. Each tab shows the count of reviews in that status.

  4. The table lists each review with the following columns:

    Reviews table

    Column What it shows
    Review An excerpt of the review text, with row actions below
    Author The student who wrote the review
    Course The course being reviewed, linked to its editor
    Rating The star rating (1 to 5)
    Status The current moderation status pill
    Date When the review was submitted
  5. To take action on a review, use the row actions that appear under the review excerpt:

    Review row actions

    • Approve publishes the review and updates the course's average rating. Only available when the review is not already approved.
    • Reject hides the review from the public course page. Only available when the review is not already rejected.
    • Spam marks the review as spam. Only available when it is not already marked spam.
    • Mark Pending returns the review to the pending queue. Only available when the review is not already pending.
    • Delete permanently removes the review.

Using Bulk Actions

When you have multiple reviews to process:

  1. Check the box at the top of the table to select all visible reviews, or check individual rows.
  2. The bulk action bar slides in at the bottom of the screen showing how many reviews are selected.
  3. Choose an action from the dropdown: Approve, Reject, Mark as spam, Mark as pending, or Delete.
  4. Click Apply.

Searching Reviews

Use the search box at the top right of the reviews table to filter by keyword. The search runs against the review title and body. Click Search to apply it and Clear (or remove the search term and search again) to reset.

You can also toggle the sort order between newest first and oldest first using the sort button next to the search field.


Tips

  • When Auto-Approve is on, new reviews bypass the pending state entirely and go directly to approved. The Pending tab will show zero unless a review is manually moved back to pending.
  • Approved reviews update the avg_rating and review_count columns on the course record immediately. Rejecting or deleting an approved review recalculates both values.
  • Students can submit one review per course. Submitting a new review replaces any previous one.
  • The Anonymized status appears for reviews where GDPR erasure has removed the author's personal data but the review text has been kept.
  • The Reviews list is paginated and supports search, so it stays usable even with thousands of reviews.

Custom Account Fields

Custom account fields let you add your own questions to the sign-up form and the account profile page without writing any code. Use them to capture details Learnomy does not ask for by default, such as a license number, company name, phone number, or a consent checkbox.

Each field you define is saved to the member's profile, can be made required, and shows up in the admin when you open a student's detail page.

Where to manage fields

Go to LMS Settings in the WordPress admin sidebar and click Registration Fields in the left-hand navigation panel.

Registration Fields section in Settings

The section is called Registration & profile fields. It shows a list of every field you have added, an Add field button, and a Save Changes button. If you have not added any fields yet, it reads "No custom fields yet. Add one below."

Adding a field

  1. Go to LMS Settings and click Registration Fields.
  2. Click Add field. A new blank row appears.
  3. Enter a Label. This is the text shown to the member next to the input.
  4. Choose a Type (see the table below).
  5. Choose who is asked, and where, under Show on (see the table below). You can ask everyone, or only the people signing up as a student, or only the people applying to teach.
  6. Check Required if the member must fill the field in before the form can be submitted.
  7. If you picked the Dropdown type, a Dropdown options box appears. Enter one option per line.
  8. Click Save Changes.

Adding a custom field row

Field types

Type What it collects
Text A single line of plain text.
Email An email address.
Phone A telephone number.
Paragraph A multi-line text box for longer answers.
Dropdown A select menu. You supply the choices, one per line.
Checkbox A single on/off tick box, for consent or yes/no questions.

How the field key is created

Every field has a stable internal key that Learnomy uses to store the value. When you add a field, the key is generated from the label: spaces become underscores and the text is lower-cased, so a label of "Bar Number" produces the key bar_number. The value is then stored against the member as user meta named learnomy_field_bar_number.

The key is set once, when the field is first saved, and does not change afterwards even if you rename the label later. This keeps already-collected answers attached to the field.

Where fields appear and when they save

Fields render and save on both member-facing surfaces, controlled by the Show on setting:

Show on Where it appears Can the member change it later?
Everyone - asked at signup, editable in the account Both sign-up forms and Account details Yes
Everyone - asked once at signup Both sign-up forms No
Students only - asked once at signup The student sign-up form only No
Instructors only - asked on the application The instructor application, and that instructor's Instructor profile page Yes
Everyone - account only Account details only Yes

Pick a signup-only option for a question you ask once and never need again, such as "How did you hear about us?". Be deliberate about it: the member never sees that field again, so they cannot correct a typo, and neither can you from the admin screens, which show the answer but do not edit it.

The two audience options exist because the forms ask different people different things. A certification number belongs on the instructor application and has no business on student sign-up; a student ID is the reverse. Before this you could only put a field on both forms or neither.

Required fields are enforced on the server before the account is created or updated, so a missing required answer blocks the submission with a message naming the field. A field limited to one audience is only required for that audience - an instructor-only required field never blocks a student signing up.

A submission only writes the fields that were actually on that form, so updating the account page never clears a registration-only field, and vice versa.

A custom field on the account details page

Viewing captured values

Open Learnomy > Students and click a student to open their detail page. Any custom field values that member has filled in are listed in their profile, using the field label. A checkbox field shows as Yes or No, and an empty field shows a dash.

Custom field values on a student detail page

Editing and removing fields

  • Rename a field: change its Label and click Save Changes. The internal key stays the same, so existing answers are kept.
  • Change type, location, or required: adjust the controls on the field's row and click Save Changes.
  • Remove a field: click the trash icon at the end of the field's row, then click Save Changes. Removing a field stops it from appearing on the forms. Values already saved against members remain in the database under the field's user-meta key.

Developer note

Custom account fields are a no-code layer built on top of two extension hooks that developers can also use directly:

  • learnomy_account_fields (action) - fires where fields should render. It receives 0 on the registration forms and the current user ID on the account details page.
  • learnomy_account_fields_save (action) - fires where submitted values should be persisted. It receives the user ID and the request object.

The built-in builder is a first-party consumer of these same hooks, so admin-defined fields and any developer-registered fields render and save through one shared seam. Field definitions are stored in the learnomy_settings option under the custom_account_fields key, and values are stored as user meta prefixed learnomy_field_.

General Settings

The General section is the first thing you see on the Settings page. It sets the site-wide defaults for how courses look and behave, the default currency, and the launchers for onboarding and demo data.

What you can do

  • Set the maximum width of the Learnomy content area on the frontend (Container Width).
  • Set the site-wide completion rule that courses and lessons can override (Completion Rules).
  • Require login to view any course content, for a fully private academy (Private site).
  • Turn on Solo mode to run a single-teacher academy with no instructor sign-up, commissions, or revenue-share.
  • Set the default currency used for course pricing and transactions.
  • Re-show the Getting Started guide on the dashboard if you dismissed it.
  • Re-run the Setup wizard with your current settings pre-filled, or reset the demo academy.

General Settings first view

How to use it

Step 1 -- open General

Go to LMS Settings in the sidebar. The page opens on General by default; if not, click General in the left-hand navigation.

Step 2 -- configure the Course Setup card

  • Container Width -- pick 960px, 1100px, 1200px, 1400px, or Full Width for the frontend content area.
  • Completion Rules -- Strict, Advisory, or Off. This is the site-wide default; individual courses and lessons can override it.

Step 3 -- set Access Control (optional)

In the Access Control card, turn on Private site to require login for all course content. When enabled, logged-out visitors are redirected to the login page for the course catalog, individual courses, lessons, and quizzes. Use this for a fully private, members-only academy where sign-up and payment are handled off-site (an invoice, a bank transfer, or an external CRM). This setting is off by default -- your catalog and courses are public until you turn it on.

Step 4 -- set Academy Mode (optional)

In the Academy Mode card, turn on I am the only instructor (solo mode) to hide instructor sign-up, commissions, and revenue-share. Use this when you are the only teacher.

The same card has an Allow students to sign up themselves toggle (on by default). Turn it off to run a members-only academy, or to collect sign-ups through your own form (for example a Gravity Forms page) instead of the built-in one. When it is off, the student registration page returns a 404, the registration API rejects student sign-ups, and every "Sign up to enroll" button sends visitors to the login page instead of the disabled page -- so no link is ever left pointing at a page that no longer works. Students you create yourself in WordPress are unaffected. To send those buttons to your own form instead of the login page, point the learnomy_student_registration_url filter at it (see the developer guide on registration and security).

Step 5 -- set the currency

In the Currency & Payments card, choose your Currency. This affects how amounts are formatted and stored going forward; it does not convert existing transactions.

Step 6 -- save, then use the Actions

Click Save Changes to store the cards above. The cards under the Actions divider run immediately and are not governed by Save Changes:

  • Getting Started guide -- click Show the guide again if you previously hid the dashboard onboarding checklist.
  • Setup wizard -- click Open setup wizard to re-run onboarding, or Reset demo data to delete and recreate demo content (only data tagged lrn_demo_* is affected).

Settings and options

All fields save into the single learnomy_settings option.

Field Option key Values
Container Width container_width 960px / 1100px / 1200px / 1400px / none (Full Width). Default 960px.
Completion Rules completion_enforcement strict / advisory / off. Default strict.
Private site require_login_all_content On/off toggle. Default off.
Solo academy solo_mode On/off toggle.
Allow students to sign up student_registration On/off toggle. Default on. Off = register page 404s, sign-up API rejected, "Sign up" buttons go to login.
Currency currency USD / EUR / GBP / AUD / CAD / INR. Default USD.

The Getting Started guide and Setup wizard controls are one-off actions, not saved settings. Reset demo data only touches demo content; Open setup wizard links to admin.php?page=learnomy-setup&rerun=1.

Access Control / Private site

When Private site is on, a logged-out visitor who requests the course catalog, a category archive, a single course, a lesson, a quiz, or a standalone quiz is redirected to the WordPress login page instead of seeing the content. Logged-in users are unaffected -- this setting only gates anonymous access.

This is intended for closed academies that don't sell or advertise publicly: everyone who needs access already has (or is given) a WordPress account, and enrollment is arranged outside Learnomy's own checkout.

Turning this on does not change enrollment, pricing, or course visibility settings -- it only adds a login requirement in front of the routes listed above.

What else it hides

Redirecting the pages is not enough on its own, so Private site also closes the other ways an anonymous visitor could read the same information:

  • The REST API. The public course list and both public category endpoints return nothing to an anonymous caller. Hiding the courses while still publishing the taxonomy that describes them is not a private academy: a category list named "Q4 Onboarding" or "Unreleased Product Training" tells an outsider what you are building even when they cannot open a single course.
  • The sitemap. The course sitemap yields no URLs and no pages, so search engines are not handed a map of content nobody may read.
  • Course listing blocks. The course-grid and course-catalog blocks render a login notice to anonymous visitors instead of a list.

Signed-in users are unaffected in all three cases. This is the same one gate throughout, so a site that forces it on with the filter below gets every part of this behaviour, not just the redirect.

For developers

  • learnomy_require_login_for_content -- filter (bool) that runs after the Private site setting is read. Return true/false to force the gate on or off for the whole site regardless of the saved option, for example to key it off a different condition like a custom capability or a multisite network setting.
  • learnomy_content_route_names -- filter (string[]) for the list of route names gated by Private site. Defaults to catalog, category, course, lesson, quiz, standalone-quiz. Add or remove route names to change which frontend routes require login.

Pages and URL Slugs

The Pages section is where you localize or rename the URL segments for every Learnomy page, see the full list of frontend pages on your site, and find the folder to copy templates into for a theme override.

What you can do

  • Rename each URL segment (course base, lesson, quiz, category, instructor, and more) so links read in your language, for example kurse and lektion for German.
  • Change a slug and have Learnomy refresh your WordPress permalink rules automatically on save.
  • See a read-only table of every Learnomy frontend page, its URL, whether login is required, and a View link for static pages.
  • Leave any slug field blank to keep the built-in default.
  • Find the exact theme folder to copy a template into when you want to override the markup.

Pages and URL Slugs first view

How to use it

Step 1 -- open Pages

Go to LMS Settings and click Pages in the sidebar.

Step 2 -- edit the URL slugs

In the URL Slugs card, change any segment you want to localize or rebrand. Leave a field blank to keep its default. The fields are:

  • Course URL Base -- the prefix for course pages (default courses).
  • Lesson URL Segment -- the segment inside a lesson URL (default lesson).
  • Course Quiz URL Segment -- the segment inside a course quiz URL (default quiz).
  • Category URL Segment -- the segment inside a category archive URL (default category).
  • Instructor URL Base -- the base for the instructor dashboard and public profiles (default instructor).
  • Certificate Verify URL Base -- the base for public certificate verification links (default verify).
  • Standalone Quiz URL Base -- the base for quizzes not attached to a course (default quiz).
  • Membership URL Base -- the base for the public membership pricing page (default membership).
  • My Learning Spaces URL -- the Learning Spaces dashboard URL (default my-spaces; requires the Pro Spaces extension to render).
  • Enroll URL Base -- the base for checkout pages (default enroll).
  • Account URL Base -- the base for the account and billing pages (default account).

Step 3 -- save

Click Save Changes. Saving any slug change automatically refreshes your permalink rules, so the new URLs work right away.

Step 4 -- check the Frontend Pages table

The Frontend Pages card lists every Learnomy page with its live URL, an access badge (Public or Login required), and a View link for static pages. Pages whose URL contains a placeholder in braces (for example {course-slug}) are marked Dynamic and have no direct View link.

Step 5 -- override a template (optional)

The Theme Override card shows the folder path (your active theme's learnomy/ directory). Copy any file from the plugin's templates/ folder into that path and edit it there to customise the markup for that page.

Settings and options

Every slug saves into the learnomy_settings option under its own key.

Field Option key Default
Course URL Base base_slug courses
Lesson URL Segment lesson_slug lesson
Course Quiz URL Segment quiz_slug quiz
Category URL Segment category_slug category
Instructor URL Base instructor_slug instructor
Certificate Verify URL Base verify_slug verify
Standalone Quiz URL Base standalone_quiz_slug quiz
Membership URL Base membership_slug membership
My Learning Spaces URL my_spaces_slug my-spaces
Enroll URL Base enroll_slug enroll
Account URL Base account_slug account

The Frontend Pages table and Theme Override path are read-only; they have no save button.

Adding Learnomy pages to a navigation menu

Learnomy's frontend pages are not WordPress pages, so they do not appear in the usual Pages box under Appearance > Menus. They get their own box instead: Learnomy Pages. Tick the pages you want, click Add to Menu, and they behave like any other menu item.

What the box offers:

Group Items
Catalog Course Catalog
Account My Account, My Courses, Quiz Attempts, Certificates, Order History, Account Details, My Learning Spaces, Manage Membership
Instructor Instructor Dashboard, Instructor Courses, Instructor Students, Instructor Grading, Instructor Earnings, Instructor Profile
Sign-up Membership Pricing, Become an Instructor, Register as Student, Register as Instructor

Two things the box does deliberately:

  • It only offers pages that have a fixed address. A course or lesson URL needs a specific course, so those are not listed -- link to an individual course the way you would link to any other URL. Flow steps such as the checkout, thank-you and cancelled pages are absent for the same reason nobody would link to them: they are stages in a purchase, not destinations.
  • It hides pages belonging to a switched-off module. With the membership module off, Membership Pricing and Manage Membership do not appear, because those addresses 404 and a menu item pointing at one looks identical to a working one until a visitor clicks it.

Menu items you already built keep working after any of this: the items store a route, not a hardcoded URL, so changing a slug in the table above moves the menu item with it.

When another plugin answers a Learnomy URL

Most LMS plugins want the same addresses Learnomy wants. Tutor LMS, LearnDash and LifterLMS all publish a course archive, and the default address for all of them is some form of /courses/. WordPress does not arbitrate: it serves whichever rule was registered first, so with two LMS plugins active your course catalog can quietly become the other plugin's archive.

The same thing outlives the other plugin. WordPress only rebuilds its URL rules when permalinks are refreshed, so a plugin that is deactivated or deleted without that refresh leaves its rules behind, still owning the address. The symptom is distinctive: /courses/ shows your blog posts, while single course pages such as /courses/my-course/ still work.

Learnomy watches for both.

  • A rule left behind by a plugin that is gone is repaired automatically. The next time you open a Learnomy admin page, Learnomy refreshes the URL rules once and your catalog comes back. You do not have to do anything.
  • A collision with a plugin that is still active is reported, not resolved. You get a notice on Learnomy admin screens and a Site Health check naming the address and what is answering it. Learnomy will not take the address for itself, because doing so would break the other plugin's pages and only you know which one should win.

To settle a live collision, either change Course URL Base above to something free (academy, training, kurse) or turn off the other plugin. Either way the notice clears on its own.

Site Health carries a permanent check for this: Tools -> Site Health -> Status, under Learnomy course URLs. It is green when every Learnomy address - catalog, courses, lessons, quizzes and the account pages - reaches Learnomy.

For developers

Every slug is also exposed as a filter, so you can set it in code instead of the UI. The filter wins over the saved setting.

  • learnomy_base_slug, learnomy_lesson_slug, learnomy_quiz_slug, learnomy_category_slug, learnomy_instructor_slug, learnomy_verify_slug, learnomy_standalone_quiz_slug, learnomy_membership_slug, learnomy_my_spaces_slug, learnomy_enroll_slug, learnomy_account_slug -- one filter per slug (see \Learnomy\reslug_keys() in includes/functions.php for the full map).
  • Slugs feed the declarative Route Registry in includes/class-routes.php, which is the single source for both the rewrite rules and URLs. Build every front URL with \Learnomy\route_url( $name, $args ) -- never hardcode home_url('/courses/...').
  • learnomy_routes -- filter for registering additional routes (Pro extensions use this so route_url() can build their URLs too).
  • learnomy_nav_menu_pages -- filter (array of ['key','label','url']) for the rows offered in the Learnomy Pages navigation-menus box. Pro extensions append their own pages through it, so the box stays a single source of truth rather than each extension adding a box of its own.
  • learnomy_route_conflict_force_ownership -- return true to move every Learnomy rewrite rule ahead of every other plugin's in the final rules table. Off by default: it wins the address at the cost of the other plugin's, which is a decision for the site owner rather than for us. \Learnomy\Services\Route_Conflicts::detect() returns the current verdict, and check_now() re-runs it on demand.

Custom Labels

The Labels section lets you rename Learnomy's core nouns across your whole site without writing any code. Change "Course" to "Class", "Lesson" to "Module", "Student" to "Learner" or "Instructor" to "Coach", and the new words appear everywhere Learnomy names that thing.

Where a rename reaches

A rename is not a front-end skin. One resolver answers every surface, so the word you type replaces the built-in one in all of these:

Surface What changes
Front end Catalog, course, lesson and quiz pages, the student and instructor dashboards, account screens, buttons, and empty states.
wp-admin The admin sidebar rows themselves - Courses, Lessons, Quizzes, Categories, Students, Instructors, plus Pro's Spaces and Space Plans - along with page headings, settings tab labels, column headers, help text, and notices.
Emails Automated notification subjects and bodies.
Browser and SEO titles The <title> on Learnomy's own pages, plus the descriptions and structured data the SEO module emits.
Block editor Learnomy block panel titles and control labels in the editor sidebar.
JavaScript strings Confirmation dialogs, toasts, and inline validation messages.

Two things deliberately do not change, and both are load-bearing:

  • URLs. The /courses/ segment of your permalinks stays as it is, so a rename never breaks a link, a bookmark, or a search result you already rank for. Edit the slugs yourself under LMS Settings > URLs & Pages if you want the URL to follow the new wording.
  • WordPress role names, WP-CLI output, and the REST API schema. These are machine-facing identifiers and contracts, not copy.

What you can rename

Noun Default singular Default plural
Course Course Courses
Lesson Lesson Lessons
Quiz Quiz Quizzes
Section Section Sections
Topic Topic Topics
Category Category Categories
Student Student Students
Instructor Instructor Instructors
Learning Space (Pro) Learning Space Learning Spaces

The people nouns matter more than they look. Most sites are not academies: corporate training has trainees and trainers, coaching platforms have clients, associations have members and mentors. Renaming Course while the registration page still says "Register as a Student" only half-renames your site.

Learning Space appears only when Learnomy Pro is active with Spaces enabled, because Pro registers it. Its default is "Learning Space", not "Space", so switching Learnomy on does not rename anything you already had.

What you can do

  • Rename each entity to your own vocabulary using a singular and a plural field.
  • Leave any field blank to keep the built-in default name.
  • Apply your labels site-wide with no code and no template edits - front end, wp-admin, emails, page titles and the block editor all follow.
  • Keep the defaults for translation instead: use a .po translation file when you want another language rather than a rename.

Custom Labels first view

How to use it

Step 1 -- open Labels

Go to LMS Settings and click Labels in the sidebar.

Step 2 -- rename an entity

In the Entity Labels card, each row is one content type with two fields: a singular and a plural form. Type your preferred word in each. The greyed placeholder shows the current default, and the help text under each row reminds you of both defaults.

Step 3 -- leave blanks to keep defaults

Any field you leave blank keeps its default name. You only need to fill in the entities you want to rename.

Step 4 -- save

Click Save Changes. Your labels take effect immediately, everywhere. Reload any admin page and the sidebar rows will be reading your words back to you.

Settings and options

Labels save into the learnomy_settings option under a labels sub-array, one key per form:

  • Each entity has two keys: <entity>_singular and <entity>_plural (for example course_singular / course_plural).
  • The available entities and their default singular/plural forms come from \Learnomy\entity_label_defaults().
  • To translate rather than rename, use a .po translation file against the learnomy text domain instead of this panel.

For developers

  • \Learnomy\entity_label( string $key, string $form = 'singular' ) -- the single resolver every surface calls to read a label. It returns the saved custom label, falling back to the translatable default.
  • \Learnomy\entity_label_defaults() -- the canonical list of entities and their default forms.
  • learnomy_label_{key}_{form} -- a filter fired per resolved label (for example learnomy_label_course_singular). It runs after the stored setting and the i18n default, so a code override always wins.
  • window.lrnLabels and window.lrnLabel( key, plural ) -- the JS half of the resolver. Learnomy prints the resolved set inline on the wp-i18n handle, so it is available on admin pages, front-end routes and in the block editor with no dependency to declare. Write a renamable JS string as wp.i18n.sprintf( __( '%s saved', 'my-plugin' ), window.lrnLabel( 'course' ) ).
  • learnomy_entity_labels -- a filter on the whole set, for registering a noun of your own. Add an entry keyed by slug with singular and plural strings in your own text domain, and you get the complete feature: the Labels screen renders a row for it, the settings sanitizer accepts it, and entity_label() resolves it. Learnomy Pro registers "Learning Space" this way.
add_filter( 'learnomy_entity_labels', function ( array $labels ): array {
    $labels['cohort'] = array(
        'singular' => __( 'Cohort', 'my-plugin' ),
        'plural'   => __( 'Cohorts', 'my-plugin' ),
    );
    return $labels;
} );

Set your default to the words your feature already ships with. Registering a shorter or different word renames every existing site the moment your update lands.

Writing renamable copy

Never put an article in front of the placeholder. "Become a %s" renders "Become a Instructor" as soon as somebody picks a vowel-initial word, and no amount of formatting fixes it. Learnomy's own copy is article-free for this reason -- "%s application", "Apply as %s".

Layouts

The Layouts section is the one place to choose the structural layout for each major page type: the course catalog, the course page, the lesson player, the course quiz, and the standalone quiz. Switching a layout never changes the page URL.

What you can do

  • Pick a layout variant for the course catalog, single course page, lesson, course quiz, and standalone quiz.
  • Set how many course cards show per row on desktop for the catalog grid.
  • Set how many courses each page of the catalog shows before it pages.
  • Switch layouts freely without breaking any links -- the URL stays the same.
  • Keep tablet and mobile responsive automatically (the catalog grid drops to 2 then 1 column on smaller screens regardless of the desktop column count).

Layouts first view

How to use it

Step 1 -- open Layouts

Go to LMS Settings and click Layouts in the sidebar.

Step 2 -- pick a layout per surface

In the Layouts card, each row is one page surface with a dropdown of layout variants:

  • Course catalog
  • Course page
  • Lesson
  • Course quiz
  • Standalone quiz

Choose the variant you want for each. The list of variants is provided by the layout system and grows as more surfaces are migrated to it.

Step 3 -- set the catalog grid density

Use the Catalog columns dropdown to choose how many course cards appear per row on desktop (2, 3, 4, or 5). Tablet and mobile stay responsive and are not affected.

Step 4 -- set the catalog page size

Use Courses per page to choose how many courses each page of the catalog lists before paging. Any whole number from 1 to 60; the default is 12. This is the field name on a default install - like every entity noun in Learnomy it follows your Course label, so a site that renamed Course to Class sees "Classes per page".

The setting applies to the main catalog and to the category archives, which have always read this value; before 1.9.4 there was no field anywhere to change it, so every site ran on the built-in 12.

Page size and column count are independent. Twelve courses at 3 columns is four rows; the same twelve at 4 columns is three. Choose a page size that divides evenly by your column count if you want a full last row.

Step 5 -- save

Click Save Changes. The new layouts render on the front end right away, with no URL change.

Settings and options

Each surface stores its choice in the learnomy_settings option under that surface's setting key (defined by the layout system), plus the grid density:

Field Option key Notes
Course catalog per-surface setting_key from \Learnomy\layouts() Variant value from that surface's variant list.
Course page per-surface setting_key Variant value.
Lesson per-surface setting_key Variant value.
Course quiz per-surface setting_key Variant value.
Standalone quiz per-surface setting_key Variant value.
Catalog columns catalog_columns Integer 2-5. Default 3.
Courses per page catalog_per_page Integer 1-60. Default 12. Read by both the catalog and the category archives.

For developers

  • \Learnomy\layouts() -- returns the registered surfaces, each with its setting_key, default, and variants.
  • learnomy_layouts -- filter to register new surfaces or add variants (Pro and third-party layouts hook here).
  • learnomy_layout_{surface} -- filter applied to the resolved variant for a surface (for example learnomy_layout_archive), so code can override the chosen layout at render time.

Comment Settings

The Course Comments section controls whether students can post comments on lessons across your site. It is a single site-wide toggle that individual courses and lessons can override.

What you can do

  • Turn lesson comments on or off site-wide with one toggle.
  • Let students discuss a lesson directly under it when comments are enabled.
  • Keep the site-wide setting as the default while a course or lesson overrides it where needed.

Comment Settings first view

How to use it

Step 1 -- open Course Comments

Go to LMS Settings and click Course Comments in the sidebar.

Step 2 -- toggle lesson comments

In the Comment Settings card, switch Allow students to post comments on lessons on or off. This is the site-wide default.

Step 3 -- save

Click Save Changes. When on, a comment thread appears under each lesson in the player unless a specific course or lesson overrides it.

Settings and options

Field Option key Notes
Enable Lesson Comments enable_lesson_comments Site-wide toggle stored in learnomy_settings.

Course-level comments (separate from lesson comments) are planned for a future release, so that toggle is intentionally not shown here yet.

For developers

  • \Learnomy\Models\Lesson::get_effective_comments_enabled( int $lesson_id ) -- resolves the final on/off decision for a lesson by combining the site-wide enable_lesson_comments setting with any per-course or per-lesson override. Use this rather than reading the raw option, so overrides are honoured.

API and Performance

The API and Performance section controls rate limiting and caching for the REST API, the Progressive Web App (offline) mode, and two maintenance tools. It lives under the Advanced group in the Settings sidebar.

What you can do

  • Set a per-user rate limit on sensitive write endpoints (login, password reset, and more).
  • Set the object-cache lifetime for read endpoints, or disable caching entirely.
  • Turn the Progressive Web App (service worker and offline mode) on or off.
  • Send a test email to confirm your mail configuration works.
  • Flush WordPress rewrite rules without visiting Settings > Permalinks.

API and Performance first view

How to use it

Step 1 -- open API and Performance

Go to LMS Settings and click API & Performance in the sidebar.

Step 2 -- set the API limits

In the API & Performance card:

  • Sensitive endpoint limit (req/min per user) -- the per-minute cap for write endpoints flagged sensitive: login, password reset, instructor application, withdrawal request, coupon validate, quiz start, and comment post. Set to 0 to disable. Maximum 10000.
  • Cache TTL (seconds) -- the object-cache lifetime for read endpoints. Set to 0 to disable caching. Maximum 86400 (24 hours).

Step 3 -- choose whether to enable the PWA

In the Progressive Web App card, toggle Register the service worker and offline mode on Learnomy pages. Turn this off when Learnomy is only one section of a larger site (such as a community) where a site-wide service worker is unwanted.

Step 4 -- save

Click Save Changes to store the settings above.

Step 5 -- use the Tools (optional)

The Tools card has two buttons that run immediately:

  • Send Test Email -- dispatches a test notification to the admin email address.
  • Flush Rewrite Rules -- regenerates permalink rules without a visit to Settings > Permalinks.

Settings and options

Field Option key Notes
Sensitive endpoint limit api_rate_limit Requests per minute per user. 0 disables. Max 10000. Default 60.
Cache TTL (seconds) cache_ttl Read-endpoint object-cache lifetime. 0 disables. Max 86400. Default 3600.
Enable PWA enable_pwa Toggle. Defaults to on when the key is unset.

The Tools buttons are one-off actions, not saved settings.

For developers

  • \Learnomy\pwa_enabled() -- resolves whether the PWA is active, combining the enable_pwa setting with the filter below.
  • learnomy_enable_pwa -- filter that overrides the PWA on/off state in code. When false, the service-worker registration, /learnomy-sw.js, the manifest, and the offline routes are not loaded.

Data Management

The Data Management section controls how long the audit log is kept and whether all plugin data is deleted when Learnomy is removed. It is a small section with big consequences, so the uninstall option is off by default.

What you can do

  • Set how many days the audit log keeps entries before they are removed automatically.
  • Choose whether removing the plugin also permanently deletes all of its data.
  • Keep your student progress, transactions, and other data safe by default if you ever reinstall.

Data Management first view

How to use it

Step 1 -- open Data Management

Go to LMS Settings and click Data Management in the sidebar.

Step 2 -- set audit log retention

In the Data Retention card, set Audit Log Retention (days). Entries older than this are removed automatically. The minimum is 30 days; the default is 365.

Step 3 -- decide the uninstall behaviour

Set the On Uninstall toggle, Remove all plugin data when the plugin is deleted:

  • Off (default and recommended) -- deleting the plugin keeps all your data, so a reinstall picks up where you left off.
  • On -- deleting the plugin permanently drops every course, enrollment, certificate, and transaction. This cannot be undone. The warning note under the toggle spells this out.

Step 4 -- save

Click Save Changes.

Settings and options

Field Option key Notes
Audit Log Retention (days) data_retention_days Minimum 30. Default 365.
On Uninstall delete_data_on_uninstall Toggle. Off by default. When on, uninstall wipes all plugin data.

For developers

Uninstall is handled by uninstall.php, which only performs a destructive cleanup when opted in. Two flags are honoured for backward compatibility:

  • learnomy_settings['delete_data_on_uninstall'] === 1 -- the current canonical flag set by the toggle above.
  • learnomy_settings['uninstall_data'] === 'delete_everything' -- a legacy enum from pre-2026-05-16 installs, still respected.

When neither is set (or both are falsy) the safe default is to keep all data. When opted in, uninstall.php drops every lrn_* table, deletes all learnomy_* options, user meta, and transients, removes the lrn_student and lrn_instructor roles, and cancels all scheduled learnomy_* cron and Action Scheduler jobs.

API Keys

Learnomy supports API keys for REST access. There is no API Keys screen in wp-admin -- the standalone settings section was removed in Learnomy 1.5.0 -- but the feature itself ships and is managed over the REST API.

What changed in 1.5.0

Learnomy 1.5.0 removed the API Keys section from LMS Settings (along with its sidebar item). It was gated behind a feature flag and never reached general availability as a screen. Only the admin UI was retired; the authentication method was not.

The four ways to authenticate

Learnomy's REST API accepts four kinds of credential. The first one that resolves a user wins, and an already-authenticated session is never overridden (includes/api/class-api-key-auth.php:28-56):

Method Use it for How it travels
Cookie + nonce Same-site requests from your own theme or admin screens WordPress session
Application Passwords The Learnomy companion mobile app Standard WordPress Basic auth
API key Server-to-server integrations and scripts X-API-Key header
JWT Headless front ends and custom integrations Authorization: Bearer <token>

Application Passwords are WordPress core, so nothing in Learnomy needs enabling for the app to sign in.

Managing API keys

Keys are created, listed and revoked over REST by any logged-in user, for their own account:

  1. Create -- POST /wp-json/learnomy/v1/auth/api-keys. The plaintext key is returned once, at creation. Store it then; it is not retrievable later.
  2. List -- GET /wp-json/learnomy/v1/auth/api-keys. Returns metadata only, never the key itself.
  3. Revoke -- DELETE /wp-json/learnomy/v1/auth/api-keys/{key_id}, where key_id is the UUID from the list call.

Keys are stored against the owning user in wp_usermeta, not in a dedicated table.

Using an API key

Send it in the X-API-Key header:

curl https://example.com/wp-json/learnomy/v1/courses \
  -H "X-API-Key: <your-key>"

The header is the only accepted transport. A key passed as a query parameter is ignored (includes/api/class-api-key-auth.php:61-72), which keeps keys out of server access logs and browser history.

An API key acts as the user who created it and carries exactly that user's permissions. It is not a privileged or site-wide credential. Revoke a key the moment it leaks, and issue a fresh one.

When to use which

  • The companion mobile app, or anything acting as a real member: Application Passwords.
  • A cron job, a reporting script, or another server calling in as one fixed account: API key.
  • A headless front end that signs users in and needs short-lived, refreshable credentials: JWT (15-minute access token, 30-day refresh).

Content Moderation

Members can report a review, a lesson comment, or a course they believe needs a moderator's attention. Reports land in a queue where an admin can dismiss them or mark them as actioned.

Overview

This is a light, report-driven queue -- not an automated keyword or rule engine. Nothing is hidden or removed automatically when it is reported; a report simply flags the item for a human moderator to review. What you do about a reported item (edit it, unpublish it, contact the member) happens on that item's own screen -- the Reports queue is where you triage what needs a look.


How members report content

A logged-in member sees a Report button in three places:

Where What gets reported
Any course review The review
Any lesson comment (and its replies) The comment
A course's page The course itself

Clicking Report opens a short form asking for an optional reason, then submits the report. A member can report the same item only once -- reporting it again simply confirms it was already reported instead of creating a duplicate entry.


Reviewing the Reports queue

  1. Go to Learnomy > Reported Content in the admin sidebar. The submenu label shows an open-report count badge when there is a backlog.

  2. Use the status tabs above the table to filter by All, Open, Dismissed, or Actioned. Each tab shows its row count.

  3. The table lists every report with these columns:

    Column What it shows
    Reported item The review title, course title, or "Comment #ID", with an excerpt when available
    Type Review, Comment, or Course
    Reporter The member who filed the report
    Reason The reason the reporter gave, or "No reason given"
    Status Open, Dismissed, or Actioned
    Date When the report was filed
  4. For an Open report, use the row actions to:

    • Dismiss -- close the report with no further action (for example, you reviewed it and found nothing wrong).
    • Mark actioned -- close the report after you handled the underlying item (for example, you edited or removed the review, comment, or course).
  5. Click View item to open the reported review, course, or comment in its own screen so you can inspect or edit it before deciding.

Reports do not disappear after you resolve them -- they move to the Dismissed or Actioned tab, giving you a history of what was reported and how it was handled.


Settings and options

The Reports queue has no separate settings screen or toggle -- reporting is always available to logged-in members, and the queue is always visible to admins so a backlog stays actionable even if you later turn off reviews.

For developers

  • learnomy_report_submitted -- action fired after a member files a report. Receives $report_id, $object_type, $object_id, $user_id, $reason.
  • learnomy_report_resolved -- action fired after a moderator resolves a report. Receives $report_id, $status, $moderator ($status is dismissed or actioned).

Integrations

Connect Learnomy to external services, build on the REST API, and monitor every webhook delivery.

AI Agents and the Abilities API

Learnomy registers every major LMS action with the WordPress Abilities API, so AI assistants and automation agents like ChatGPT and Claude can discover your academy's capabilities and operate it safely, with the same permission checks a human user goes through.

Overview

Most LMS plugins expose data through a REST API and stop there. An AI agent still has to be told, by hand, which endpoint does what, what arguments it takes, and what it returns. Learnomy goes one step further: it publishes its actions as abilities, the WordPress standard for machine-discoverable capabilities.

Each ability ships with a typed input schema, a typed output schema, and a permission check. An agent can read the catalog, understand exactly how to call an action, and get a predictable response. Nothing is hard-coded against your site.

Abilities are available at the standard WordPress Abilities API namespace:

https://yoursite.com/wp-json/wp-abilities/v1/

This is a free feature. It is always on while the plugin is active, with no separate setup.

What an agent can do

Learnomy registers 54 abilities across 8 categories. Together they cover the full lifecycle of a course business, from authoring to enrollment to payouts and reporting.

Course Management

Ability What it does
learnomy/create-course Create a course
learnomy/get-course Read a course
learnomy/update-course Update a course
learnomy/delete-course Delete a course
learnomy/publish-course Publish a course
learnomy/duplicate-course Duplicate a course
learnomy/list-courses List courses
learnomy/get-course-stats Read course statistics

Learning and Progress

Ability What it does
learnomy/enroll Enroll a student in a course
learnomy/unenroll Remove a student from a course
learnomy/access-lesson Open a lesson
learnomy/complete-lesson Mark a lesson complete
learnomy/get-progress Read course progress
learnomy/restart-course Reset progress and restart
learnomy/get-enrollment Read a single enrollment
learnomy/list-enrollments List enrollments

Quizzes and Assessments

Ability What it does
learnomy/create-quiz Create a quiz
learnomy/start-quiz Start a quiz attempt
learnomy/submit-quiz Submit a quiz attempt
learnomy/grade-question Grade a question response
learnomy/get-quiz-results Read quiz results

Revenue and Memberships

Ability What it does
learnomy/subscribe Subscribe to a membership plan
learnomy/cancel-subscription Cancel a membership subscription
learnomy/list-membership-plans List membership plans
learnomy/validate-coupon Validate a coupon
learnomy/get-earnings Read instructor earnings
learnomy/request-withdrawal Request an earnings withdrawal

Administration

Ability What it does
learnomy/approve-instructor Approve an instructor application
learnomy/reject-instructor Reject an instructor application
learnomy/approve-course Approve a submitted course
learnomy/reject-course Reject a submitted course
learnomy/manage-plans Manage membership plans
learnomy/manage-settings Manage plugin settings
Ability What it does
learnomy/search-courses Search the course catalog

Authoring

Ability What it does
learnomy/create-section Create a section
learnomy/update-section Update a section
learnomy/reorder-curriculum Reorder curriculum
learnomy/create-lesson Create a lesson
learnomy/update-lesson Update a lesson
learnomy/delete-lesson Delete a lesson
learnomy/create-topic Create a topic
learnomy/create-question Create a question
learnomy/update-question Update a question
learnomy/add-question-to-quiz Add a question to a quiz
learnomy/update-quiz Update a quiz
learnomy/create-course-category Create a course category
learnomy/create-question-category Create a question category
learnomy/upload-media Set a course image
learnomy/create-certificate-template Create a certificate template

Reporting

Ability What it does
learnomy/get-analytics Read analytics
learnomy/get-quiz-analytics Read quiz analytics
learnomy/list-students List students
learnomy/get-at-risk-students List at-risk students
learnomy/get-instructor-report Read an instructor report

How discovery works

Every ability is registered with three things that make it safe for an agent to use:

  • Input schema. The arguments the action accepts, with types. An agent knows what to send without guessing.
  • Output schema. The shape of the response. An agent knows what it will get back.
  • REST exposure. Each ability sets show_in_rest, so it appears in the Abilities API catalog and can be called over HTTP.

An agent reads the catalog once, then calls actions by name with structured arguments. There is no scraping and no brittle endpoint mapping.

Permissions and safety

Abilities do not bypass your access rules. Every ability has its own permission check, evaluated through Learnomy's Permission_Engine before the action runs:

  • Public reads, such as learnomy/get-course on a published course, are allowed without authentication.
  • Authoring and management actions check the calling user's capability for the specific object. Updating a course requires manage_course on that course; publishing requires publish_course.
  • Administration actions, such as approving an instructor or changing settings, require the matching admin capability.

If the caller lacks permission, the ability is refused, exactly as the same action would be refused in the browser. An AI agent acts as a WordPress user and inherits that user's limits.

Outgoing Webhooks

Learnomy outgoing webhooks

Outgoing webhooks let Learnomy POST a signed JSON payload to an external URL every time a chosen event occurs. Use them to connect Learnomy to Zapier, your CRM, a custom backend, or any HTTPS endpoint.

Overview

Webhooks are managed from LMS Settings > Outgoing Webhooks in the admin sidebar. Each webhook has a delivery URL, a set of subscribed events, a secret key for signature verification, and a status. The Free plan supports up to 3 webhooks. Learnomy Pro removes this limit.


Adding a Webhook

  1. Go to LMS Settings > Outgoing Webhooks in the admin sidebar.

    Webhooks page overview

  2. The page header shows how many of your webhook slots are in use.

    Webhooks header with usage counter

  3. Fill in the Add New Webhook form.

    Add webhook form

    • Delivery URL: The HTTPS endpoint that will receive the POST. Must be a valid URL.
    • Events to Send: Check every event type you want this webhook to fire on. Use Select all to check every option at once.
    • Secret Key: A random 32-character key is pre-filled. Copy it now. For security, it is not shown again after you save. You can also click the regenerate button to get a new key, or copy the current value using the copy button.
  4. Click Add Webhook.

The webhook appears in the table below the form immediately.


Available Events

Events come from Learnomy's canonical event catalogue. The checkboxes in the form list every event that can fire an outgoing webhook. Pro's Webhooks Pro extension adds additional lifecycle events via the learnomy_pro_webhooks_event_slugs filter.


Viewing and Editing Webhooks

The webhooks table shows all configured webhooks.

Webhooks table

Column What it shows
URL The delivery endpoint (truncated to 60 characters; hover for the full URL)
Events Badge-style list of subscribed event types
Status Active, Paused, Disabled, or Failed
Last Triggered Date and time of the most recent delivery attempt, or "Never"
Actions Edit, Test, and Delete buttons

Edit: Opens the Add Webhook form pre-filled with the webhook's current URL and events. You can update the URL and events, then save. Note: editing does not change the secret key; the stored secret is preserved.

Test: Sends a test payload to the endpoint immediately. Use this to confirm your endpoint is reachable and that the signature verification in your receiver is working before waiting for a real event.

Delete: Permanently removes the webhook and stops all future deliveries to that URL. A confirmation prompt appears before the delete completes.


The Free 3-Endpoint Limit

Free installations can configure up to 3 outgoing webhooks. When you have reached the limit, the Add New Webhook form is replaced by a notice pointing to Learnomy Pro.

šŸ”¶ Pro feature. Available in Learnomy Pro. Learnomy Pro removes the 3-webhook cap so you can configure as many outgoing webhooks as needed. See the "What's in Pro" page under LMS Settings.


Verifying Signatures

Every outgoing payload is signed with HMAC-SHA256 using the secret key you set when creating the webhook. Learnomy adds an X-Learnomy-Signature header to each request. In your receiver, compute HMAC-SHA256(secret, raw_body) and compare it to the header value to confirm the payload came from your site.


Tips

  • A webhook with Failed status has encountered repeated delivery errors. Check your endpoint for downtime or errors, then use the Test button to confirm it is reachable again.
  • Once saved, the secret key is not shown again or retrievable through the admin UI. If you lose it, delete the webhook and create a new one.
  • Learnomy validates delivery URLs against a server-side allowlist to prevent SSRF attacks. Private IP addresses, localhost, and reserved ranges are rejected.
  • Pro's Webhook Delivery Log gives a per-webhook history of every outbound delivery, response code, and retry. See Webhook Delivery Log.

Incoming Payment Webhook Log

The Payment Log page is a log of every inbound webhook delivery Learnomy received from Stripe or PayPal. It shows whether each delivery was handled successfully, rejected, or produced an error, and lets you replay any event without waiting for the payment provider to resend it.

Overview

Learnomy records every inbound webhook before processing it. This log is the primary debugging tool for payment and subscription issues. Go to LMS Settings > Payment Log in the admin sidebar.


Viewing the Event Log

  1. Go to LMS Settings > Payment Log in the admin sidebar.

    Payment Log page

  2. The page header confirms you are on the Payment Log screen.

    Payment Log header

  3. The table shows the most recent 50 events. Use the pagination controls at the bottom to move between pages.

    Payment Log table

    Column What it shows
    Provider Stripe or PayPal badge
    Event Type The raw event type slug from the provider (e.g. checkout.session.completed)
    Event ID The provider's unique event identifier, truncated to 24 characters. Hover to see the full ID.
    Processed At Date and time the event was received
    Outcome The result of processing (see below)
    Retries How many replay attempts have been made on this event
    Actions Payload and Replay buttons

Filtering Events

Use the filter bar above the table to narrow the list by provider or outcome.

Payment Log filter bar

  • Provider: Show only Stripe events, only PayPal events, or all.
  • Outcome: Filter to a specific result: Handled, Duplicate, Rejected, Error, or Pending.

Click Filter to apply the selections. Click Clear to remove active filters and return to the full list.


Outcome Values

Outcome Meaning
Handled The event was processed successfully. Enrollment, subscription, or transaction records were created or updated as expected.
Duplicate The event ID had already been processed. Learnomy uses a unique constraint on (provider, event_id) to guarantee idempotency, so this outcome is normal when a provider resends.
Rejected The signature verification failed, the event type is not handled, or the payload was malformed. No data was changed.
Error Processing started but an exception occurred. The enrollment, transaction, or subscription record may be incomplete. Replay to try again.
Pending The event was logged but processing has not started yet. This is a transient state.

Viewing a Payload

  1. Find the event in the table.

  2. Click Payload in the Actions column.

    Payload modal

  3. A modal opens showing the raw JSON payload from the provider.

    Payload modal detail

    The modal also shows any error message if the outcome was Error. Press Escape or click the close button or the backdrop to dismiss it.


Replaying an Event

Use replay when an event ended in Error and you want to reprocess it without waiting for the provider to resend.

  1. Find the event in the table.
  2. Click Replay in the Actions column.

Learnomy deletes the original row (to bypass the idempotency guard), builds a synthetic request from the stored payload, dispatches it through the same adapter as the original, and returns the new outcome. If the event ends in Handled, the replay row replaces the original in the log. The Retries counter increments with each replay attempt.

If the replay surfaces a signature validation error alongside the new outcome, it means the webhook signing secret has been rotated at the provider since the original delivery. Update the signing secret in LMS Settings > Payments and replay again.


Tips

  • Learnomy logs the raw payload before calling any processing logic. Even events with Rejected or Error outcomes have their full payload stored, so you can inspect exactly what the provider sent.
  • The table is paginated at 50 rows per page. On high-volume sites, use the filters to narrow to Error outcomes before browsing.
  • Duplicate outcomes are expected and healthy when a provider's retry schedule fires after you have already processed an event. No action is needed.
  • If you see a large volume of Rejected events, verify that the webhook signing secret in Settings > Payments matches the one shown in your Stripe or PayPal dashboard. A mismatch causes every delivery to fail signature verification.

WP Fusion

šŸ”¶ Pro feature. Available in Learnomy Pro.

The WP Fusion bridge connects Learnomy to your CRM through WP Fusion. It does three things: it grants course access from CRM tags, it auto-enrolls (and unenrolls) students when a linked tag is applied or removed, and it writes Learnomy tags back to the CRM as students move through your courses, purchase a course, earn a certificate, or subscribe to a membership plan. It also syncs Learnomy custom account fields to the contact record in your CRM.

Prerequisites

  • WP Fusion is installed and active.
  • WP Fusion is connected to a CRM. The tag read and write features only work once a connection is configured. Until then the bridge stays inert and never blocks a Learnomy flow.

It auto-enables

The bridge is not a toggle on the Modules page. It enables itself when WP Fusion is active and disappears when WP Fusion is not, so there is nothing to switch on. When WP Fusion is active but not yet connected to a CRM, the bridge does nothing: tag writes are skipped and access gating fails open, so already-enrolled students are never locked out by a CRM that is unreachable or half-configured.

Where you configure rules

You configure WP Fusion tag rules inline in the Learnomy editors you already use. A WP Fusion (CRM) card appears on the course-detail hub, the lesson and quiz editor screens, and the membership plan editor. Each rule is a list of WP Fusion tags chosen with WP Fusion's own tag picker. Leaving a field empty means that rule is not applied.

The available rules per editor:

Editor Field What it does
Course Required tags A student must hold at least one of these tags to access the course.
Course Auto-enroll tag link Applying any of these tags enrolls the student; removing them unenrolls.
Course Apply tags on enrollment Added to the student when they enroll in this course.
Course Apply tags on completion Added to the student when they complete this course.
Course Apply tags on purchase Added to the buyer when they complete a purchase of this course. This is distinct from enrollment: it fires on the purchase itself, so it also catches a buyer who paid but has not started the course yet.
Course Apply tags on certificate earned Added to the student when they earn this course's certificate.
Lesson Required tags A student must hold at least one of these tags to open the lesson.
Lesson Apply tags on completion Added to the student when they complete this lesson.
Quiz Apply tags on pass Added to the student when they pass this quiz.
Quiz Apply tags on fail Added to the student when they fail this quiz.
Membership plan Apply tags on subscribe Added to the member when they subscribe to this plan, and re-applied on every renewal.
Membership plan Apply tags on cancel / expiry Added to the member when their subscription is cancelled or expires. The subscribe tags are removed at the same time.

The course card lives on the course-detail hub (not the course editor) and saves immediately with its own Save CRM tags button, because the tag multiselect needs more room than the edit-course sidebar has. The membership plan card sits below the plan editor form and saves the same way, so it is only available once the plan has been created and saved at least once.

WP Fusion course rules panel

Configuring access rules (required tags)

Required tags gate access. The contract is "has at least one of these tags", so list every tag that should grant access.

  1. Open the course (or lesson) in its Learnomy editor.
  2. In the WP Fusion card, add one or more tags to Required tags.
  3. Save the course (or lesson).

A student who does not hold any of the required tags is shown an Access Restricted message instead of the enroll prompt. If WP Fusion is active but cannot answer (not connected to a CRM), the gate fails open so existing students keep their access.

WP Fusion required-tags gate

Auto-enroll on tag

The Auto-enroll tag link on a course turns a CRM tag into an entitlement. When a student's tag set changes in WP Fusion:

  • If they now hold any of the course's linked tags and are not already enrolled through this bridge, they are enrolled. A previously cancelled enrollment is reactivated, so a member re-granted the tag regains access.
  • If they no longer hold any linked tag and were enrolled through this bridge, they are unenrolled.

Because the linked tag is treated as the entitlement, this can grant access to a paid course without a purchase. Only enrollments this bridge created are affected; manual, purchase, and membership enrollments are left untouched.

To set it up:

  1. Open the course in its editor.
  2. Add the tag (or tags) to Auto-enroll tag link in the WP Fusion card.
  3. Save the course.

Tags written back to the CRM

The bridge applies tags to the student's CRM contact on these Learnomy events, using whatever tags you configured for each:

  • Student enrolls in a course applies the course's Apply tags on enrollment.
  • Student completes a course applies the course's Apply tags on completion.
  • Student completes a lesson applies the lesson's Apply tags on completion.
  • Student passes a quiz applies the quiz's Apply tags on pass.
  • Student fails a quiz applies the quiz's Apply tags on fail.
  • Student purchases a course applies that course's Apply tags on purchase.
  • Student earns a certificate applies that course's Apply tags on certificate earned.
  • Member subscribes to a plan applies the plan's Apply tags on subscribe. The same tags are re-applied on every renewal, so a tag removed mid-cycle is restored automatically.
  • Member's subscription is cancelled or expires applies the plan's Apply tags on cancel / expiry and removes the plan's Apply tags on subscribe tags.

Tag writes are deferred

Applying or removing a tag is a call to your CRM. To keep the student's request fast, the bridge does not make that call inline. It hands the tag write to Action Scheduler, so the enroll, completion, or quiz request returns immediately and the CRM round-trip happens in the background. A slow or unreachable CRM never blocks the student. If a tag write was queued before WP Fusion finished connecting, the background worker re-checks the connection and simply skips the write rather than failing.

Custom field sync

If you use Learnomy custom account fields, the bridge tells WP Fusion to watch those field keys. When a student edits a watched field on their account, the change syncs to their CRM contact record through WP Fusion's normal field-sync mechanism.

Developer note

The required-tag gate copy is filterable. To change the title, message, or call-to-action shown when a student is blocked by a required tag, hook learnomy_pro_wpf_gate_copy:

add_filter( 'learnomy_pro_wpf_gate_copy', function ( array $copy ) {
    $copy['title']     = 'Members only';
    $copy['message']   = 'Join the membership to unlock this course.';
    $copy['cta_label'] = 'View plans';
    $copy['cta_url']   = home_url( '/membership/' );
    return $copy;
} );

The filter receives an array with key, title, message, cta_label, and cta_url.

Webhook Delivery Log

šŸ”¶ Pro feature. Available in Learnomy Pro.

The Webhook Delivery Log shows the complete outbound delivery history for a single webhook endpoint. Every time Learnomy attempts to POST a payload to your endpoint, the result is recorded here: the HTTP response code, the delivery status, and any retry attempts.

Overview

The delivery log is per-webhook, not a global view. Reach it from the row actions on the main Webhooks list: go to LMS Settings > Outgoing Webhooks, then use the View log action on any row. This action is added by the Webhooks Pro extension.


Viewing Delivery History

  1. Go to LMS Settings > Outgoing Webhooks in the admin sidebar.

  2. On the webhook you want to inspect, click the row actions overflow menu and choose View log.

    The page breadcrumb shows Webhooks / Delivery Log so you always know which webhook you are looking at.

    Delivery Log page

  3. The header shows the page title and the endpoint URL.

    Delivery Log header

  4. The table lists deliveries in reverse chronological order with cursor-based pagination.

    Delivery Log table


Filtering Deliveries

Use the filter buttons above the table to narrow the list by delivery status.

Delivery Log filter bar

Filter Shows
All Every delivery attempt regardless of outcome
Delivered Attempts that received a 2xx response from the endpoint
Failed Attempts that received a non-2xx response or timed out
Pending Attempts that are queued or in progress

Filters update the list immediately without a page reload.


Delivery Statuses

Status Meaning
Delivered The endpoint responded with an HTTP 2xx status code. The payload was accepted.
Failed The endpoint responded with a non-2xx code, the connection timed out, or the request was refused. Learnomy will retry failed deliveries on a back-off schedule.
Pending The delivery is queued and waiting to be sent. This is normal immediately after an event fires.
Retrying A failed delivery is being retried. The retry count shown next to the row indicates how many attempts have been made.

Retrying a Failed Delivery

When a delivery is in Failed status, Learnomy automatically retries it on a back-off schedule. If you need to retry immediately without waiting for the automatic schedule, use the retry button available in the delivery row.

A successful retry updates the delivery record to Delivered and records the new response code.


Click Back to Webhooks in the page header to return to the main Webhooks list.


Tips

  • The delivery log only records outbound attempts to the endpoint you configured. For inbound Stripe and PayPal webhook deliveries, see the Incoming Payment Webhook Log page.
  • If you see a sustained stream of Failed deliveries, check whether your endpoint is returning a 2xx response. Many endpoints accidentally return a redirect (301/302) or a non-success code even when they have processed the payload. Learnomy treats anything outside 2xx as a failure.
  • Delivery history is stored per webhook endpoint. If you delete a webhook, its delivery history is also deleted.
  • The log uses cursor-based pagination, which keeps performance consistent on endpoints that have thousands of delivery records.

REST API and App

Learnomy ships a full REST API that powers both the mobile/web app and third-party integrations. Everything students and instructors can do through the browser is also accessible through the API.

Overview

The REST API is built on top of the WordPress REST API framework. All Learnomy endpoints are registered under the namespace learnomy/v1, making them available at:

https://yoursite.com/wp-json/learnomy/v1/

The API is what the Learnomy mobile app and web app use to communicate with your site. If you are building an integration, a custom mobile app, or connecting Learnomy to another system, the REST API is the right layer to work with.


What the API Covers

The API provides endpoints for every major data type in Learnomy:

  • Courses, sections, lessons, and topics
  • Quizzes, questions, and quiz attempts
  • Enrollments and student progress
  • Certificates
  • Reviews
  • Memberships and subscriptions
  • Coupons
  • Earnings, commissions, and withdrawals
  • Notifications and announcements
  • Webhooks
  • Admin operations (student management, settings, revenue reports)

Learnomy Pro adds its own endpoints under the learnomy-pro/v1 namespace for Pro-only features such as Cohorts, Learning Paths, Spaces, Assignments, and Membership Analytics.


What a learner can read about themselves

Two additions in 1.9.2 close the gap between what the website shows a student and what an app could retrieve:

  • GET /account/quiz-attempts - the caller's own attempt history, paginated (per_page, offset), each row carrying its quiz and course title. This is the same list the website shows at /account/quiz-results/, read from the same source, so the two cannot drift. A row opens into the existing GET /attempts/{id} for the full review. The user is always taken from the session, never from the request.
  • GET /courses/{id}/lessons now returns a quizzes[] array on every lesson, always present even when empty. Each quiz carries a viewer block (is_locked, lock_reason, unlocks_at, lock_message) resolved on the server, exactly like the lesson's own.

That last point matters for anyone building a client: do not evaluate lock rules yourself. Whether a lesson or its quiz is open can depend on drip mode, a per-student unlock, a section date, course-level sequencing, or the previous lesson's completion requirement, in any combination a site owner chooses - and a rule added in a later release would render as "open" in an app already on someone's phone. Ask the server and render its answer, including its lock_message, which is already translated into the site's language.

Two things that changed in 1.9.4

Bulk course actions take one request, not one per course. POST /courses/bulk accepts an action plus a list of ids, and the action may be either a verb (publish, archive, trash, restore, draft, delete) or a canonical status (published, archived, trashed, draft). Both forms are accepted so existing callers keep working; verbs are normalised server-side. restore deliberately returns a course to draft rather than republishing it, so bringing something back from the trash never silently makes it public again.

Some routes only exist when their integration is present. The H5P completion route and the WP Fusion rule routes register only when their companion plugin is active, and return 404 otherwise. They are marked CONDITIONAL ROUTE in the generated catalogue, with the condition stated in the description. If you build a client against the spec, treat those as optional rather than assuming a 404 means a broken install.

Authentication

The API accepts four kinds of credential. They are resolved in order and an already-authenticated session is never overridden (includes/api/class-api-key-auth.php:28-56).

Method Use it for
Cookie + nonce Same-site requests from your own theme or admin screens
Application Passwords The companion mobile app
API key Server-to-server integrations and scripts
JWT Headless front ends and custom integrations

Application Passwords (what the app uses)

The Learnomy companion app signs in with WordPress core Application Passwords, not with Learnomy's JWT. This is deliberate and is the same across the Wbcom portfolio: the app relies on a core credential a site owner can see and revoke from the user's profile screen. Nothing in Learnomy needs enabling for it.

API keys

Long-lived keys for server-to-server integrations ship today. Create one with POST /wp-json/learnomy/v1/auth/api-keys, list with GET, and revoke with DELETE /wp-json/learnomy/v1/auth/api-keys/{key_id}. Send the key in the X-API-Key header; a key in a query parameter is ignored. There is no wp-admin screen for keys - the API Keys settings section was removed in 1.5.0 and keys are managed over REST. See API Keys.

JWT (obtaining a token)

Send a POST request to /wp-json/learnomy/v1/auth/token with a valid WordPress username and password. The response returns an access token and a refresh token.

Pass the access token in the Authorization header on every authenticated request:

Authorization: Bearer <token>

Tokens expire and can be refreshed using the /auth/token/refresh endpoint.


Response Shape

Every API response uses a consistent envelope format:

  • List responses: { "data": [...], "meta": { "total": N, "cursor_next": "..." } }
  • Single resource: { "data": { ... } }
  • Errors: { "error": { "code": "...", "message": "...", "details": [...], "request_id": "..." } }

This shape is the same whether you are calling a Free endpoint or a Pro endpoint.


Rate Limiting

Learnomy applies rate limiting to write endpoints flagged as sensitive (login, password reset, quiz start, coupon validation, and others). The default limit is 60 requests per minute per user. Administrators and instructors are exempt from the rate limit. You can adjust this in LMS Settings > API and Performance.


Where to Find More

This page is a conceptual overview. The full endpoint reference is part of the Developer Guide:

  • Full endpoint reference: docs/api/openapi.json in the plugin repository - an OpenAPI 3 document generated from the live route table, covering both namespaces. At 1.9.4 it describes 396 paths (learnomy/v1 217, learnomy-pro/v1 179) across 510 operations. This is the reference to build a client against; it is the same document the mobile app generates from.
  • Curated inventory with per-endpoint purpose notes: audit/manifest.json (222 entries). Free registers 226 register_rest_route call sites under includes/api/ - the three counts differ legitimately, because one call site can register several methods on one path, and the curated list is a documented subset rather than a total.
  • CAPABILITIES.md is the buyer-facing source of truth for what those endpoints add up to.
  • Code: REST controllers live in includes/api/ in the Free plugin and includes/extensions/*/rest/ in Learnomy Pro.

Tips

  • The REST API is always available as long as the plugin is active. There is no separate toggle.
  • All endpoints use permission_callback for access control. Anonymous requests to protected endpoints receive a 401 response. There are no unprotected write endpoints.
  • ETag headers on read endpoints allow your HTTP client to use conditional requests and avoid re-downloading unchanged data.
  • Application Passwords are the companion app's primary credential, not a fallback for JWT. Both work; pick per the table in Authentication above.

Developer hooks (1.2.0)

Learnomy 1.2.0 added two groups of extension points so integrations can ride the existing editors and the WordPress privacy pipeline without forking templates or touching lrn_* tables. Every hook below was confirmed in the plugin source.

Editor field seams

Courses, lessons, and quizzes are custom-table rows, not custom post types, so there is no meta-box path for third-party fields. These seams give integrations one render hook per editor plus one save action per entity. Render your inputs on the *_editor_fields action, then read $request and persist your own user meta or option storage on the matching *_saved action. Account fields work the same way across the registration forms and the account-details page.

This is what the WP Fusion bridge (Pro) and the built-in custom account fields feature build on. Learnomy's own Custom_Fields service is a first-party consumer of the account-field hooks, so admin-defined fields and developer-registered fields render through the same seam.

Hook Type Fires Signature Purpose
learnomy_course_editor_fields action Course editor sidebar (includes/admin/views/course-form.php:562) ( object|null $course ) - null on create Render extra inputs in the course editor.
learnomy_course_saved action Course create/update REST save (includes/api/class-courses-controller.php:552) ( int $course_id, WP_REST_Request $request ) Persist integration course fields after a course is saved.
learnomy_lesson_editor_fields action Lesson editor (includes/admin/views/lesson-editor.php:643) ( object|null $lesson ) - null on create; covers sub-lessons Render extra inputs in the lesson editor.
learnomy_lesson_saved action Lesson create/update REST save (includes/api/class-lessons-controller.php:513, :558, :622) ( int $lesson_id, WP_REST_Request $request ) Persist integration lesson fields after a lesson is saved.
learnomy_quiz_editor_fields action Quiz editor (includes/admin/views/quiz-editor.php:744) ( object|null $quiz ) - null on create Render extra inputs in the quiz editor.
learnomy_quiz_saved action Quiz create/update REST save (includes/api/class-quizzes-controller.php:781, :820) ( int $quiz_id, WP_REST_Request $request ) Persist integration quiz fields after a quiz is saved.
learnomy_account_fields action Registration forms and account-details page (templates/register-student.php:166, templates/register-instructor.php:261, templates/account-details.php:160) ( int $user_id ) - 0 at registration Render extra account/profile inputs.
learnomy_account_fields_save action Registration save and account-update REST save (includes/api/class-auth-controller.php:601, :757) ( int $user_id, WP_REST_Request $request ) Persist account fields on signup and on later edits.

The render hooks pass the entity being edited (or null / 0 when the row does not exist yet). Field storage stays in the consumer plugin; Learnomy only exposes the surface. A minimal round-trip:

// Render an input in the course editor.
add_action( 'learnomy_course_editor_fields', function ( $course ) {
    $value = $course ? get_post_meta( (int) $course->id, 'crm_tag', true ) : '';
    printf( '<input name="crm_tag" value="%s">', esc_attr( $value ) );
} );

// Persist it when the course is saved.
add_action( 'learnomy_course_saved', function ( $course_id, $request ) {
    update_post_meta( $course_id, 'crm_tag', sanitize_text_field( (string) $request->get_param( 'crm_tag' ) ) );
}, 10, 2 );

Privacy and anonymisation

Three additive filters let an extension hide individual learner identity (name, email, avatar) from the instructor roster while keeping counts and metrics intact. Each filter receives an $is_admin flag (or the admin scope), so a consumer can keep manage_options users at full visibility and only mask for instructors. These were added so the BTC Instructor_Privacy integration survives plugin updates. None changes behaviour unless something hooks it.

Hook Type Fires Signature Purpose
learnomy_instructor_students_list filter Instructor roster list (includes/services/view-data/class-instructor-students-view-data.php:131) ( array $students, array|null $scope_course_ids, bool $is_admin ) Anonymise rows in the instructor's student list.
learnomy_instructor_student_detail filter Instructor student-detail view (includes/services/view-data/class-instructor-students-view-data.php:219) ( array $student, bool $is_admin ) Anonymise a single student-detail record.
learnomy_course_students_rest_items filter GET /courses/{id}/students REST response (includes/api/class-enrollments-controller.php:521) ( array $items, int $course_id, int $user_id ) Anonymise course-student rows returned by the API.

The $is_admin argument is true when the caller has full visibility (no course scope), so consumers can short-circuit and never mask for administrators.


SCORM Packages

šŸ”¶ Pro feature. Available in Learnomy Pro.

Upload a SCORM 1.2 or SCORM 2004 package as a lesson. Learners play it inside the normal lesson player, and the package's own completion status and score flow straight into Learnomy progress, so a SCORM lesson counts toward course completion and certificates like any other lesson.

What you can do

  • Add a SCORM 1.2 or SCORM 2004 course package (a .zip) as the content of any lesson, alongside your video, text, quiz, and embed lessons.
  • Reuse existing SCORM content authored in tools like Articulate Storyline, Adobe Captivate, iSpring, or Rise, without rebuilding it in Learnomy.
  • Let learners launch and complete the package in the lesson player, with automatic resume: they pick up where they left off, at the bookmark and answers the package recorded.
  • Have the package's completed / passed status and score feed the same progress pipeline as native lessons, so course progress and certificates just work.
  • Optionally gate the Free Mark Complete button on that status (completion_mode = auto_standard) so a learner cannot advance until the package itself reports complete.
  • Track every learner's status, score, resume location, and time on task from a wp-admin report, and export it to CSV.
  • Preview a SCORM lesson as an instructor or admin without touching a learner's tracking record.

SCORM package playing as a lesson with the wp-admin tracking report

How to use it

SCORM is an opt-in module. It is never on by default, so the first step is to enable it.

  1. Go to LMS Settings > Modules and enable SCORM (listed under Integrations). Nothing SCORM-related loads until this is on.
  2. Open the lesson you want to hold the package (Learnomy > Lessons, or create a new one) and set its content type to SCORM.
  3. Save the lesson first. The uploader only appears once the lesson exists, because the package is stored against the lesson id. On an unsaved lesson you will see "Save the lesson first, then upload a SCORM package here."
  4. In the SCORM package field, click Upload a SCORM package (.zip) (or drag a .zip onto the dropzone). The uploader validates the file, then the server reads its imsmanifest.xml.
  5. On success the field shows a summary card: the detected version (SCORM 1.2 or SCORM 2004), the launch file, and the number of SCOs. Use Replace to swap in a new package later.
  6. Publish the lesson. Enrolled learners open it in the lesson player and the package runs in a sandboxed runtime. Their progress, bookmark, and score are saved as they go and restored on their next visit.
  7. (Recommended for compliance) Set the lesson Completion mode to Auto: SCORM / standards package. Mark Complete stays disabled until the package reports a completing status for that learner. This mode is available in both the wp-admin lesson editor and the Pro front-end builder. See Sections and Lessons.
  8. To see results, go to Learnomy > Pro Tools > SCORM Tracking. Filter by lesson or status, page through learners, and use the export link for a CSV.

You can also upload the package from the Pro front-end course builder and through the REST API (see For developers). All three surfaces attach the package to the same lesson.

Settings and options

  • SCORM module (LMS Settings > Modules) - the on/off switch for the whole feature. Category: Integrations. It requires the Growth tier or above.
  • Supported versions - SCORM 1.2 and SCORM 2004. The version is detected from the package manifest; other standards (for example AICC) are not supported here.
  • Content type - SCORM is added as a selectable lesson content type in the wp-admin lesson editor, the Pro front-end builder, and the API.
  • Maximum package size - defaults to 100 MB. Change it with the learnomy_scorm_max_package_mb filter (return the limit in MB). The uploader enforces the same limit client-side before the upload starts.
  • Tracking report access - the SCORM Tracking report is limited to users who can manage_options (the site owner), because it holds per-learner data. Widen who can see it with the learnomy_scorm_report_cap filter.
  • Tracking report (Learnomy > Pro Tools > SCORM Tracking) - per-learner rows showing status (colour-coded: green for passed/completed, amber for in progress, red for failed), score, resume location, time on task, and last-updated time. Filter by lesson and by status, paginated at 20 rows per page, with a nonce-protected CSV export.
  • Preview safety - an admin or instructor who is not enrolled can open and resume a SCORM lesson to check it, but their run is never written to a learner's tracking record. Only an enrolled learner's run is tracked.

Uploaded packages are stored in a hardened directory that refuses script execution, and their files are served only through a permission-checked route that verifies course access on every asset request. A package is never exposed at a public uploads URL.

For developers

The SCORM module lives in Learnomy Pro at includes/extensions/scorm/. It stores one package per lesson (lrn_pro_scorm_packages) and one CMI state per learner-lesson (lrn_pro_scorm_cmi).

REST routes (namespace learnomy-pro/v1):

Method Route Purpose Gate
POST /scorm/packages Import an uploaded package (multipart with lesson_id + file). Can manage the lesson's course.
DELETE /scorm/packages Remove the lesson's package (row + files). Can manage the lesson's course.
GET /scorm/cmi Read the learner's stored CMI state (init + resume). Can access the lesson's course.
POST /scorm/cmi Persist the learner's CMI state on commit. Must be enrolled in the lesson's course.

Content server route - SCO assets stream through the path-based route /learnomy-scorm/{lesson}/{hash}/{file}, gated on course access via Permission_Engine. The {hash} is the package's storage hash, so replacing a package automatically busts cached asset URLs.

Action - learnomy_scorm_cmi_saved fires after each CMI commit with ( int $user_id, int $lesson_id, array $state ). The Pro Content_Standards_Bridge listens to it and routes a completing status into Free lesson progress, which is the single path that also drives course progress, certificates, and any learnomy_lesson_completed listeners.

Filters:

  • learnomy_scorm_max_package_mb - max package size in MB (default 100).
  • learnomy_scorm_report_cap - capability required to view and export the tracking report (default manage_options).

The content type registers on the Free learnomy_lesson_content_types filter as scorm, so it participates in the standard lesson content-type pipeline rather than a parallel one.

xAPI (Tin Can) Learning Records

šŸ”¶ Pro feature. Available in Learnomy Pro.

xAPI (also called Tin Can API) lets Learnomy report learning activity to your Learning Record Store (LRS). As students enrol, complete lessons, and answer quiz questions, Learnomy builds standard ADL xAPI statements and delivers them to the LRS you configure, so the record of what each learner did lives in your own analytics system.

What you can do

  • Send a learning record to any xAPI 1.0.3 LRS (Learning Locker, Watershed, Veracity, SCORM Cloud, and similar) as learners move through your courses.
  • Capture four lifecycle events automatically:
    • Enrolled in a course (registered verb).
    • Completed a lesson (completed verb, with the owning course as parent context).
    • Quiz graded (passed or failed verb, with the scaled and raw score).
    • Question answered (answered verb, with correctness and the learner's response).
  • Choose how learners are identified in the LRS: by email address, or by an anonymous site account id when you must not send email addresses off-site.
  • Send a one-off test statement to confirm the connection before real learning traffic starts.
  • Review a delivery log of failed sends and test sends, and retry a failed delivery from the admin screen.

xAPI settings and delivery log in LMS Settings > Settings (xAPI panel in the left navigation)

How to use it

  1. Enable the xAPI module. Go to LMS Settings > Modules, find xAPI in the Integrations group, and turn it on. The module is off by default and is opt-in per site. Enabling it makes the xAPI card appear in the Learning Standards section; disabling it removes the card again.
  2. Open the xAPI card. Go to LMS Settings > Settings and open the Learning Standards section. The xAPI (Experience API) card is shown there.
  3. Enter your LRS connection details from your LRS provider:
    • LRS Endpoint - the xAPI endpoint URL. It must start with https://. Learnomy POSTs statements to {endpoint}/statements.
    • LRS Key - the Basic-auth key (client id or username) your LRS issued.
    • LRS Secret - the Basic-auth secret (password) your LRS issued. It is stored encrypted and is never shown again after saving. Leave it blank on a later save to keep the stored value.
  4. Choose how learners are identified in the Identify learners by dropdown: email address (mbox) or site account (anonymous id).
  5. Click Save Changes.
  6. Send a test statement. In the Delivery log card below the settings, click Send test statement. Learnomy sends one statement to your LRS immediately and shows the result inline. Confirm the statement arrives in your LRS.
  7. Let real activity flow. Once the connection is verified, statements are emitted automatically when students enrol, complete lessons, and answer quizzes. Delivery happens in the background, so a slow or unreachable LRS never delays the learner's page.

Settings & options

All fields live on the xAPI (Experience API) card in LMS Settings > Settings (xAPI panel in the left navigation). They are saved to the dedicated learnomy_xapi option group.

Field Stored as What it does
LRS Endpoint learnomy_xapi[endpoint] The LRS xAPI endpoint. Must be a valid https:// URL; a bad value is rejected and the previous value is kept. Statements POST to {endpoint}/statements.
LRS Key learnomy_xapi[key] The Basic-auth key (client id / username) from your LRS.
LRS Secret learnomy_encrypted[xapi_secret] The Basic-auth secret (password). Stored encrypted and write-only; shows a Saved pill when one is on file. Leave blank to keep the saved value.
Identify learners by learnomy_xapi[actor_mode] mbox (email address) or account (site homepage plus the WordPress user id as an anonymous name). Defaults to mbox.

What each event sends

Learnomy event Verb Activity type Extra data
Student enrolls in a course registered course -
Lesson completed completed lesson completion: true, parent course context
Quiz graded passed or failed assessment success, completion, scaled + raw score, parent course context
Quiz question answered answered cmi.interaction success (when known), the learner's response, parent course context

Every statement declares xAPI version 1.0.3 and uses the canonical ADL verb and activity-type IRIs, so a statement means the same thing in any LRS.

Delivery, retries, and the log

  • Delivery is asynchronous through Action Scheduler, so the learner's request never waits on the LRS network round trip.
  • If a send hits a timeout or a 5xx error, Learnomy retries with backoff: up to 4 attempts total (immediate, then after 5 minutes, 30 minutes, and 2 hours). Each retry sends the same statement id, so the LRS de-duplicates it.
  • A 4xx response (for example bad credentials) is treated as non-retryable and fails immediately.
  • After the final failed attempt, the statement is recorded in the audit log so it is never silently dropped. The Delivery log card lists the last 50 failed deliveries and test sends, with a Retry action on each failed row. Successful production statements are not listed here; they live in your LRS.

When it does nothing

If the module is enabled but no LRS Endpoint is saved, Learnomy emits nothing and skips every send, so a half-configured site never queues statements it cannot deliver.

For developers

Two filters and two admin-only REST routes are available.

  • learnomy_xapi_iri_base (filter) - override the identity base for activity IRIs. Defaults to home_url(). Set this once and keep it stable: it is the identity of every activity already recorded in the LRS, so changing it later forks the history.

    add_filter( 'learnomy_xapi_iri_base', function ( string $base ) {
        return 'https://id.myinstitution.edu/learnomy';
    } );
    
  • learnomy_xapi_statement (filter) - edit, enrich, or drop a single statement just before it is queued for delivery. Return null or an empty value to suppress emission for that event. Receives ( array|null $statement, string $event, array $ctx ).

    add_filter( 'learnomy_xapi_statement', function ( $statement, $event, $ctx ) {
        // Drop question-level statements, keep enrol / lesson / quiz.
        if ( 'question_answered' === $event ) {
            return null;
        }
        return $statement;
    }, 10, 3 );
    
  • REST routes (namespace learnomy-pro/v1, both gated to manage_options):

    • POST /xapi/test - send a one-off test statement and return the result. Backs the Send test statement button.
    • POST /xapi/retry - re-enqueue the statement stored on a failed-delivery log row (requires an audit_id). Backs the Retry row action.

H5P Interactive Content

H5P interactive content playing as a Learnomy lesson

šŸ”¶ Pro feature. Available in Learnomy Pro.

Attach interactive content you built with the official H5P plugin (interactive video, course presentation, drag-and-drop, branching scenario, and the rest) as a Learnomy lesson. The learner plays it inside the normal lesson player, and when the H5P content reports completion its result flows into Learnomy progress, so an H5P lesson counts toward course completion and certificates like any other lesson.

Learnomy does not host or re-implement H5P. This is a bridge: the official H5P WordPress plugin stores the content and runs it, Learnomy renders it through H5P's own [h5p] shortcode and captures the content's native completion signal.

What you can do

  • Pick any interactive content created in the H5P plugin and set it as the content of a lesson, alongside your video, text, quiz, embed, and SCORM lessons.
  • Reuse H5P content you already authored (or downloaded from the H5P Hub) without rebuilding it in Learnomy.
  • Let learners open and complete the interactive content in the lesson player, with H5P handling its own runtime, save state, and resume.
  • Have the content's completed or passed signal mark the lesson complete through the same progress pipeline as native lessons, so course progress and certificates just work.
  • Keep the H5P score where H5P keeps it (in the H5P plugin's own store), the same way a SCORM lesson keeps its score in the CMI store.

How to use it

H5P is an opt-in module. It is never on by default, and it only works when the official H5P plugin is installed, so the first steps are to install H5P and enable the Learnomy module.

  1. Install and activate the official H5P plugin (from h5p.org) and create at least one interactive content in H5P > Add New. If the H5P plugin is not active, Learnomy shows a notice on its admin screens ("Learnomy H5P is enabled, but the official H5P plugin is not active") with an Install H5P link, and the H5P lesson type never appears.
  2. Go to LMS Settings > Modules and enable H5P (listed under Integrations). Nothing H5P-related loads until this is on.
  3. Open the lesson you want to hold the content (Learnomy > Lessons, or create a new one) and set its content type to H5P interactive.
  4. In the H5P content field, use the Select H5P content dropdown to pick one of your existing H5P contents. The list shows each content's title. Use the Add new H5P content link if you still need to create it.
  5. Save the lesson. The selected content id is stored against the lesson (in the same video_url reference field the MediaShield content type uses), so there is no separate upload step.
  6. Publish the lesson. Enrolled learners open it in the lesson player and the H5P content runs in place. When the content reports that it is completed or passed, the lesson is marked complete automatically.

The picker lives in the wp-admin lesson editor. In the Pro front-end course builder the H5P panel shows a short hint ("Pick H5P interactive content for this lesson in the wp-admin lesson editor"), because the content list comes from the H5P plugin's own admin.

Settings and options

  • H5P module (LMS Settings > Modules) - the on/off switch for the whole feature. Category: Integrations. It requires the Growth tier or above.
  • H5P plugin dependency - the feature is soft-gated on the official H5P plugin being active (detected via H5P's H5P_Plugin class and its [h5p] shortcode). When H5P is not active the h5p content type is never registered, so it cannot be chosen, and a soft admin notice explains why. It is never fatal.
  • Content type - H5P interactive is added as a selectable lesson content type in the wp-admin lesson editor, the Pro front-end builder, and the API.
  • H5P content - the picker is a dropdown of your existing H5P contents (up to 500, ordered by title), read from the H5P plugin's own content query. The chosen content id is stored as a numeric reference on the lesson, not a URL.
  • Completion signal - completion is driven by the content itself. When H5P's native xAPI reports passed the lesson is marked passed; any other completion report marks it completed. There is no separate "mark complete" button for H5P lessons.
  • Score - the interactive content's score stays in the H5P plugin's own store. Learnomy records lesson completion, not the raw H5P score, matching how the SCORM bridge treats CMI score.
  • Preview - an admin or instructor can open an H5P lesson to check it. Only an enrolled learner's completion is written to progress, because the completion route requires the learner to be enrolled in the lesson's course.

For developers

The H5P module lives in Learnomy Pro at includes/extensions/h5p/. It owns no table and no runtime: the H5P content id is stored in the Free lrn_lessons.video_url column as a numeric reference (the same shape as the Free mediashield content type), and the content is rendered by H5P's own [h5p id="N"] shortcode.

REST route (namespace learnomy-pro/v1):

Method Route Purpose Gate
POST /h5p/complete Mark the H5P lesson complete. Params: lesson_id (required, absint) and verb (sanitize_key; passed maps to a passed status, anything else to completed). Authenticated learner enrolled in the lesson's course, and the lesson's content type is h5p.

The front-end capture script (assets/js/capture.js) listens to H5P's native xAPI statements and calls POST /h5p/complete when the content reports completed or passed.

Completion path - rest_complete() calls Content_Standards_Bridge::report_completion( $user_id, $lesson_id, $status, [ 'source' => 'h5p' ] ). That bridge is the single completion path shared with SCORM (and future content standards); it routes the completing status into Free lesson progress, which is the one path that also drives course progress, certificates, and any learnomy_lesson_completed listeners.

Free extension seams this bridge consumes:

  • learnomy_lesson_content_types - registers h5p as a lesson content type (only when the H5P plugin is active).
  • learnomy_lesson_content_type_label - labels h5p as "H5P interactive".
  • learnomy_lesson_numeric_ref_types - marks h5p so its video_url is treated as a numeric id (absint, not esc_url_raw), the same as mediashield.
  • learnomy_lesson_content_editor - renders the wp-admin content picker.
  • learnomy_lesson_render_content - renders the player via do_shortcode( '[h5p id="N"]' ).
  • learnomy_pro_builder_lesson_content_panel - the Pro front-end builder hint panel.

The module registers no hooks of its own. Because it participates in the standard learnomy_lesson_content_types pipeline, an H5P lesson behaves like any other lesson content type across the frontend, wp-admin, and the API.

LTI 1.3 Tool Provider

šŸ”¶ Pro feature. Available in Learnomy Pro.

Let another LMS (Canvas, Moodle, Blackboard, or any LTI 1.3 platform) launch a Learnomy course as an external tool. A learner clicks a link in their institution's LMS, lands directly in the mapped Learnomy course already logged in and enrolled, and when they complete a graded quiz the score is pushed back into the LMS gradebook.

What you can do

  • Register Learnomy as an LTI 1.3 tool in your institution's LMS and connect one or many platforms to it.
  • Let students launch straight from the LMS: no separate Learnomy sign-up, no second password. The launch itself is the enrollment, so the platform's roster controls who gets in.
  • Map each platform to a default Learnomy course, and optionally map individual LMS links (resource links) to different courses, so one platform can launch many courses from different modules.
  • Auto-provision a Learnomy account on first launch, or link the launch to an existing Learnomy account that shares the same email.
  • Push quiz grades back to the LMS gradebook automatically (LTI Advantage AGS), so instructors grade in one place.
  • Bypass Learnomy's own payment gate for LTI learners: an LTI launch is the institution's entitlement decision, the same way an admin manual enrollment is.

The LTI Platforms screen in wp-admin showing the tool URLs and a registered platform

How to use it

LTI is an opt-in module. It ships in beta and is never on by default, so the first step is to enable it. Connecting a platform is a two-way exchange: you paste Learnomy's tool URLs into the LMS, then paste the LMS's values back into Learnomy.

  1. Go to LMS Settings > Modules and enable LTI 1.3 (listed under Integrations, with a Beta badge). It requires the Growth tier or above.
  2. Open Learnomy > Pro Tools > LTI Platforms. The top card, Tool details (give these to the platform), shows the four values your LMS needs: the OIDC Login URL, the Launch (Redirect) URL, the Public Keyset (JWKS) URL, and the raw Public Key (PEM). Each field is read-only and selects on focus so you can copy it cleanly.
  3. In your LMS, register a new LTI 1.3 developer key / external tool and paste those URLs in. Use the JWKS URL when the LMS supports it; some platforms take the raw public key instead.
  4. Your LMS now gives you its own values back: an Issuer (iss), a Client ID, a Deployment ID, the platform's OIDC Auth Login URL, its OAuth Token URL, and its Platform JWKS URL.
  5. Back on the LTI Platforms screen, fill in the Add a platform form with those values. Set the Default course ID to the Learnomy course this platform should launch into. Click Add platform.
  6. (Optional) Use Resource link to course map to launch different courses from different LMS links. Enter one mapping per line as resource_link_id = course_id. A resource-link mapping is checked before the default course.
  7. Add a link/assignment in your LMS that points at the tool, then launch it as a student. The learner is redirected through OIDC login, the signed launch is validated, and they land in the mapped Learnomy course, logged in and enrolled.
  8. (Optional) To turn on grade passback, make sure the platform's OAuth Token URL is filled in and the LMS link is configured to accept grades. When a learner completes a graded quiz in that course, Learnomy sends the score to the LMS gradebook automatically.

Settings and options

  • LTI 1.3 module (LMS Settings > Modules) - the on/off switch for the whole feature. Category: Integrations. Status: Beta. Requires the Growth tier or above.
  • LTI Platforms screen (Learnomy > Pro Tools > LTI Platforms) - register and manage platforms. Limited to users who can manage_options (the site owner).
  • Tool details (read-only, per site) - OIDC Login URL, Launch (Redirect) URL, Public Keyset (JWKS) URL, and Public Key (PEM). The tool keypair is generated automatically when the module is activated, so these are ready immediately.
  • If the Public Key (PEM) field shows an error instead of a key, your server could not create an RSA key, which almost always means PHP is missing the openssl extension or your host has restricted it. Nothing else on this page will work until that is fixed: Learnomy cannot sign a launch without a key, so the JWKS URL serves an empty keyset and every launch from your LMS fails signature verification. Ask your host to enable the PHP openssl extension, then reload the page. The key is created automatically as soon as the server can make one, and no other setting needs changing.
  • Per-platform fields:
    • Name - a label for your own reference (for example "Acme University Canvas").
    • Issuer (iss) - the platform's issuer identifier. Required.
    • Client ID - the client id the LMS assigned to Learnomy. Required.
    • Deployment ID - optional but recommended. When set, every launch must carry a matching deployment_id or it is rejected.
    • OIDC Auth Login URL - the platform's authorize endpoint. Required.
    • OAuth Token URL - the platform's token endpoint. Needed only for grade passback (AGS).
    • Platform JWKS URL - where Learnomy fetches the platform's public keys to verify each launch. Required.
    • Default course ID - the Learnomy course a launch lands in, unless a resource-link mapping or a course_id custom claim overrides it.
    • Resource link to course map - optional. One mapping per line as resource_link_id = course_id. Checked before the default course.
  • Course resolution order - Learnomy picks the target course most-specific first: a per-resource-link mapping, then a course_id custom claim from the launch, then the platform default course.
  • User provisioning - a launch is identified by issuer plus subject and stored against a stable learnomy_lti_uid. On first launch Learnomy creates an account; if the launch carries an email that matches an existing account, it links to that account instead. New users are created with the subscriber role by default.
  • Enrollment source - LTI launches enroll with the lti source, which bypasses the payment gate. Enrollment is idempotent, so re-launching never double-enrolls.

For developers

The LTI module lives in Learnomy Pro at includes/extensions/lti/. RS256 and JWKS crypto are hand-rolled on OpenSSL (no bundled JWT dependency). The platform registry is one table, lrn_pro_lti_platforms, keyed unique on (issuer, client_id, deployment_id).

REST routes (namespace learnomy-pro/v1) - all three are public by the LTI spec (permission_callback is __return_true); security comes from the payload, exactly like a signed payment webhook:

Method Route Purpose
GET /lti/jwks Publish the tool's public JWK set so the platform can verify JWTs Learnomy signs (for example AGS client-assertions).
GET or POST /lti/login OIDC third-party login initiation. Confirms the issuer is registered, mints a single-use state + nonce, and redirects to the platform's authorize endpoint.
POST /lti/launch Validate the signed id_token, provision + enroll the learner, and 302 them into the mapped course.

Launch validation gauntlet (class-launch.php) - before anything is created, the launch runs: single-use state consume (blocks replay), platform lookup from the trusted state, signature verification against the platform JWKS, then the claim checks - iss, aud = client_id, exp/iat (60s leeway), nonce matches the minted one, deployment_id, message_type = LtiResourceLinkRequest, and version = 1.3.0. Any failure returns a WP_Error and nothing is provisioned.

Grade passback (class-ags.php) - at launch the AGS lineitem endpoint is stashed per user + course. The learnomy_quiz_graded action triggers Ags::on_quiz_graded(), which mints a tool-signed JWT client-assertion, exchanges it for a client_credentials access token at the platform token endpoint, and POSTs the score to the lineitem's /scores endpoint. Passback is best-effort: failures are logged, never fatal to grading.

Actions and filters:

  • learnomy_user_provisioned - fires when a launch creates a new account, with ( int $user_id, array $context ) where $context carries source (lti), issuer, platform_id, and claims.
  • learnomy_lti_link_by_email - filter (bool, default true) - whether a launch may link to an existing account by matching email.
  • learnomy_lti_new_user_role - filter (default subscriber) - the role for a newly provisioned LTI user.
  • learnomy_validate_enrollment_source - the Free filter the module hooks to authorize the lti enrollment source (bypassing the payment gate).

The delete action on the Platforms list routes through the shared admin row-action machinery (DELETE /learnomy-pro/v1/lti/platforms/{id}), so it uses the same confirm modal, toast, and in-place row removal as every other Learnomy admin list.

QTI Assessment Import

šŸ”¶ Pro feature. Available in Learnomy Pro.

Import an IMS QTI 2.x or 3.0 assessment (a .zip content package or a single .xml item) and turn it into native Learnomy questions. Imported items become real question-bank rows, so they play in the normal quiz player, grade through the normal grader, and appear in the normal reports, exactly like a question you typed in by hand.

What you can do

  • Bring assessments authored in a QTI-compliant tool or exported from another platform straight into your Learnomy question bank, without re-typing them.
  • Import a whole QTI content package (.zip) at once, or a single item (.xml).
  • Map each QTI interaction to a native question type: single-answer and multiple-answer choice become multiple choice, text-entry becomes short answer, and extended-text becomes essay.
  • Import match and order interactions as matching and ordering questions too, when the Advanced Quizzes module is enabled.
  • Preview a package before you commit: see how many questions of each type will be created and which items will be skipped, without writing anything to the database.
  • File the imported questions under a question category, and optionally create a graded quiz that groups them, in the same step.
  • Import a large package safely: every item is independent, so a handful of unsupported or malformed items are skipped and listed while the rest still import.

The wp-admin Import QTI screen next to an imported quiz running in the player

How to use it

QTI is an opt-in module. It ships as stable and is never on by default, so the first step is to enable it.

  1. Go to LMS Settings > Modules and enable QTI (listed under Integrations). Nothing QTI-related loads until this is on.
  2. Open Learnomy > Pro Tools > Import QTI (a submenu under the Learnomy menu). You need the capability to author questions to see this page.
  3. In the File field, click Choose file and pick a QTI content package (.zip) or a single item (.xml).
  4. Optionally, type a title in Create a quiz (optional). Leave it blank to add the questions to the bank only. With a title, a new graded, published quiz is created that groups the imported questions in order.
  5. Click Preview first. This runs a dry run: it parses the package and shows how many questions of each type would be created and which items would be skipped, without creating anything.
  6. When the preview looks right, click Import. The status line reports how many questions were created, and any skipped items are listed with the reason.
  7. The imported questions are now in your question bank (published). Add them to any quiz from the normal quiz editor, or, if you supplied a quiz title, open the new quiz and it is ready to play.

You can also import through the REST API (see For developers). Both surfaces run the same importer and create the same native questions.

Settings and options

  • QTI module (LMS Settings > Modules) - the on/off switch for the whole feature. Category: Integrations. Status: Stable. It requires the Growth tier or above.
  • Supported formats - IMS QTI 2.x and 3.0. Upload a content package (.zip) or a single item (.xml). The importer reads only the assessment item XML inside a package; the manifest and schema files are ignored.
  • Supported interactions - choiceInteraction (single answer) becomes multiple choice (single), choiceInteraction (multiple answers) becomes multiple choice (multi), textEntryInteraction becomes short answer, and extendedTextInteraction becomes essay (manually graded, no accepted answer). matchInteraction becomes matching and orderInteraction becomes ordering, but only when the Advanced Quizzes module is active (those two question types are provided by Advanced Quizzes). Any interaction with no mapping is skipped and listed, never imported as something the player cannot render.
  • File field - accepts .zip and .xml. A single .xml is treated as one item.
  • Create a quiz (optional) - a text field. Blank imports to the question bank only. A title creates a graded, published quiz that holds the imported questions in import order.
  • Category - imported questions can be filed under a question category so they are easy to find and reuse in the question editor.
  • Maximum package size - defaults to 50 MB, applied to both the uploaded file and the total uncompressed size of a .zip. Change it with the learnomy_qti_max_package_mb filter (return the limit in MB).
  • Import permission - the Import QTI page and the REST route are both gated by the same capability, learnomy_manage_own_courses (the same bar as authoring a question). Change who can import with the learnomy_qti_import_cap filter.
  • Preview (dry run) - the Preview button never writes to the database. It returns per-type counts and the skip list so you can check a package before importing.

Uploaded QTI XML is parsed in memory and turned into question rows; nothing from the package is written to disk or served publicly. The parser is XXE-safe, and a .zip is treated as hostile input: it is rejected if it exceeds the size limit, holds too many entries, expands past the size limit (a zip-bomb guard), or contains an unsafe file path.

For developers

The QTI module lives in Learnomy Pro at includes/extensions/qti/. It creates native questions through the Free models (Question, Answer, Quiz, QuizSlot), so there is no QTI table and no separate runtime, player, or grader.

REST route (namespace learnomy-pro/v1):

Method Route Purpose Gate
POST /qti/import Import an uploaded QTI package or item (multipart with a file field). Optional params: quiz_title, category_id, dry_run. Authenticated and can author questions (learnomy_qti_import_cap).

With dry_run true the response contains counts, preview, and skipped and creates nothing. On a real import the response contains created_count, skipped, and quiz_id (the id of the created quiz, or null when no title was given).

Filters:

  • learnomy_qti_import_cap - capability required to import QTI, for the page and the REST route (default learnomy_manage_own_courses).
  • learnomy_qti_max_package_mb - maximum package size in MB, applied to the upload and the uncompressed total (default 50).
  • learnomy_qti_interaction_mappers - the interaction-to-builder map. Register a callable for a QTI interaction key (for example match, order, or a custom one) to import interactions the core does not map, without forking.

Imported questions are created as published with the current user as author, and are added to the given category when one is supplied. Because they are ordinary questions, they participate in the standard quiz, grading, and reporting pipelines with no QTI-specific code path.

PWA and Offline Mode

The Learnomy installable account app

Learnomy can turn your LMS into an installable app with basic offline support. When it is on, a service worker caches the app shell so the interface still renders without a connection, an offline banner tells students when they have dropped offline, and a web app manifest lets them install Learnomy to their home screen.

What you can do

  • Register a site-wide service worker that speeds up static assets and buffers content for offline use.
  • Show a small offline banner at the top of the page when a student loses connection, and a "back online" confirmation when it returns.
  • Let students install Learnomy as an app through a web app manifest served at /learnomy-manifest.json.
  • Serve a themed offline fallback page (/learnomy-offline/) when a student navigates while offline.
  • Queue lesson-completion progress made offline and sync it automatically once the connection comes back.
  • Turn the whole thing off with one toggle when Learnomy is one section of a larger site where a site-wide service worker is unwanted.

How to use it

Step 1 -- confirm the PWA is on

Go to LMS Settings > Settings and find the Progressive Web App card. Enable PWA is on by default. When on, Learnomy registers the service worker and offline mode on Learnomy pages.

Step 2 -- let the service worker register

On the next front-end page load, the browser registers the service worker from /learnomy-sw.js with a scope covering all Learnomy routes. On install it pre-caches the app shell (core CSS and JS, the video player library, and the offline page).

Step 3 -- see offline mode in action

When a student goes offline, an offline banner appears at the top of the page reading that they are offline and changes will sync when they reconnect. Cached pages and assets keep working; a fresh navigation while offline falls back to the themed offline page. When the connection returns, the banner switches to a "back online" message and Learnomy flushes any queued progress.

Step 4 -- install as an app (optional)

Because Learnomy serves a web app manifest at /learnomy-manifest.json, supported browsers offer to install Learnomy to the home screen or desktop, opening it as a standalone app.

When to turn it off

Turn Enable PWA off when Learnomy is embedded in a larger site -- for example a community -- where a site-wide service worker would interfere with the rest of the site. Turning it off drops the service worker, the manifest, and the offline routes.

Running alongside BuddyNext

A browser allows only one service worker per site, so two plugins cannot both provide offline mode. When BuddyNext is active, Learnomy stands down and BuddyNext's app takes over: Learnomy registers no service worker and adds no manifest, and students install and use the BuddyNext app for the whole site. Nothing to configure -- it happens on its own.

Offline lesson completion is the one thing that needs both sides. Learnomy hands its progress queue to BuddyNext's service worker, and BuddyNext has to accept it, which needs a BuddyNext version that supports it. Until then, students on a site running both plugins get a "Could not save" message if they press Complete without a connection, rather than being told their progress was saved when it was not. Everything else works normally once they are back online.

If you would rather have Learnomy's own app on a site that also runs BuddyNext, a developer can force it back with the learnomy_enable_pwa filter described below. Only do that if you accept that whichever worker registers last is the one the browser keeps.

Settings and options

The single control lives on the Progressive Web App card in LMS Settings > Settings.

Field What it is
Enable PWA Registers the service worker and offline mode on Learnomy pages. Stored as enable_pwa in learnomy_settings. On by default (treated as on when the key is absent).

What gets cached, and how:

  • App shell (core CSS, core JS modules, the Plyr video library and its CSS) -- cache-first, so the interface renders offline.
  • REST API responses (/wp-json/learnomy/...) -- network-first with a cache fallback, so offline reads still work.
  • Plugin static assets (/plugins/learnomy/assets/...) -- cache-first, versioned by file modification time.
  • Page HTML -- never intercepted. Document navigations always go straight to the network so students and buyers always see the real, current page; a fresh navigation only falls back to the offline page when the network is unreachable.

Offline progress:

  • Lesson completions made while offline are queued in the browser (IndexedDB) and replayed to the progress sync endpoint via Background Sync, or on the next page load, when the connection returns.

For developers

Routes (registered in includes/class-routes.php, gated by pwa_enabled()):

  • service-worker -- served at /learnomy-sw.js with scope /.
  • pwa-manifest -- served at /learnomy-manifest.json.
  • offline -- the fallback shell at /learnomy-offline/ (slug filterable via learnomy_offline_slug).

When pwa_enabled() is false, all three routes are dropped and the service-worker registration script is not enqueued.

Gating:

  • Learnomy\pwa_enabled() reads learnomy_settings['enable_pwa'] (default true when the key is absent) and is overridable via the learnomy_enable_pwa filter.

Manifest filters (Router::serve_pwa_manifest()):

  • learnomy_pwa_app_name, learnomy_pwa_app_short_name, learnomy_pwa_theme_color, learnomy_pwa_icon, and learnomy_pwa_manifest (the whole manifest array).

Client scripts:

  • assets/js/sw.js -- the service worker (caching strategies, IndexedDB progress queue, Background Sync replay to /wp-json/learnomy/v1/progress/sync).
  • assets/js/sw-register.js -- registration, online/offline banner, and queue-flush helpers (learnomyQueueProgress).

SEO Plugin Handoff

Learnomy prints its own canonical links, Open Graph tags, meta descriptions, and Schema.org structured data on every course, catalog, category, instructor, and certificate page, so search engines and social previews work on a site with no SEO plugin installed. When you also run Yoast SEO or Rank Math, the two do not fight: they own the pages they can see, and Learnomy owns the pages they cannot.

Who owns what

On Learnomy's own pages, Learnomy owns the head tags. Courses, lessons, quizzes, categories, instructor profiles and certificate verification live in Learnomy's own database tables and are served by Learnomy's router. They are not WordPress posts, so Yoast and Rank Math cannot see them: they have no snippet editor for a course, no canonical of their own to print for one, and no way to know what the page is. If Learnomy stepped aside on those pages, nobody would print a canonical or an Open Graph tag at all, and the shared link would fall back to whatever the theme happened to emit.

On ordinary WordPress pages, your SEO plugin owns them. Learnomy prints nothing on a post, page, or archive it does not serve.

That split is the whole rule. There is nothing to configure.

Which pages Learnomy handles

Page Structured data Canonical, Open Graph, meta description, robots
Course (/courses/:slug/) Course (price, rating, instructor, course instance) Learnomy
Catalog (/courses/) ItemList of published courses Learnomy
Category archive CollectionPage Learnomy
Instructor profile Person Learnomy
Certificate verification (/verify/:uuid/) EducationalOccupationalCredential Learnomy

Because Learnomy owns the snippet on a course page, the per-course Search & Sharing fields are the ones that take effect there -- see Course SEO and Sharing. They are the only snippet control a course has, whether or not an SEO plugin is installed.

What Learnomy detects

Learnomy checks for Yoast SEO (WPSEO_VERSION or the WPSEO_Frontend class) and Rank Math (RankMath or RANK_MATH_VERSION). That detection decides what happens outside Learnomy's routes only. On a Learnomy route the answer is always the same: Learnomy prints its own tags, because it is the only thing on the site that knows what the page is.

Schema.org structured data is never affected by any of this. Learnomy always emits its course-specific JSON-LD -- Course, ItemList, CollectionPage, Person, EducationalOccupationalCredential -- because those are types a general-purpose SEO plugin does not generate.

For developers

learnomy_seo_yield_to_plugin (bool) is the final say on whether Learnomy suppresses its canonical, Open Graph, meta description and robots output. It receives the auto-detected value -- already forced to false on Learnomy's own routes -- and returns the decision.

// Hand Learnomy's own routes to another plugin as well, for a bespoke
// integration that genuinely knows what a course page is.
add_filter( 'learnomy_seo_yield_to_plugin', function ( bool $should_yield ) {
    return true;
} );

Set it to true only if something else really is printing a correct canonical and Open Graph set for course URLs. Without that, the pages go bare.

The JSON-LD output ignores this filter entirely.

Community Companions (BuddyNext and Jetonomy)

Learnomy Wbcom integrations

Learnomy is the LMS layer: courses, lessons, quizzes, certificates, memberships, and Spaces. Two companion plugins build a community and access layer on top of it:

  • BuddyNext - a social network and community for WordPress (profiles, activity feeds, notifications, communities).
  • Jetonomy - community spaces with an access and membership economy.

The integration code lives in those companion plugins. They consume Learnomy's events, REST API, and template extension points; Learnomy exposes the seams and stays fully standalone when neither is installed. You do not configure anything in Learnomy to enable them beyond having both plugins active.

BuddyNext

The Learnomy integration ships in BuddyNext Pro. It keeps to a credentials-and-milestones lens, so a member's community profile shows what they achieved, never in-progress noise.

  • Credentials on the profile. A member's completed courses appear as Certifications on their shared profile (linking to the certificate, or the course when no certificate was issued), and instructors get a teaching-credentials panel. Members with no LMS activity see nothing, so the profile stays clean.
  • Community milestones. Completing a course posts a milestone to the member's community feed ("completed the course ..."), and earning a certificate posts one that links to the public certificate verification URL.
  • Mirrored notifications. Learnomy notifications are mirrored into BuddyNext's notification center alongside Learnomy's own student and instructor notifications, with no duplicate email.
  • Space and course communities. A Learnomy Space or an individual course can be linked to a BuddyNext community so members join it automatically. This is managed from Learnomy's own Space and course screens. See Spaces for the detail.

These features require BuddyNext Pro. Without it, none of the above appears and Learnomy behaves normally.

Jetonomy

The Learnomy integration ships in Jetonomy Pro as a membership adapter, the same pattern Jetonomy uses for LearnDash, LifterLMS, Tutor, Sensei, and MasterStudy.

  • Enrollment and membership drive space access. Enrolling in a Learnomy course, or holding an active Learnomy membership plan, can grant a member access to a matching Jetonomy space. Jetonomy identifies each access level as lrn_course_{course_id} or lrn_membership_{plan_id}.
  • Access follows the enrollment. When a student unenrolls, an enrollment expires, or a subscription is cancelled or expires in Learnomy, Jetonomy removes the matching space access.
  • Reads through Learnomy's API only. The adapter goes exclusively through Learnomy's public model API and never reads Learnomy's tables directly, so it keeps working across Learnomy updates. The jetonomy_learnomy_max_levels filter caps how many courses and plans the admin autocomplete enumerates (default 500).

These features require Jetonomy Pro.

For developers

Both companions attach to standard Learnomy actions and filters, each gated on defined( 'LEARNOMY_VERSION' ) and Learnomy's public model classes so the companion stays standalone when Learnomy is absent. The events they build on:

Hook Fires when
learnomy_student_enrolled A student is enrolled in a course ($enrollment_id, $user_id, $course_id, $source).
learnomy_student_unenrolled A student is unenrolled ($enrollment_id, $user_id, $course_id).
learnomy_enrollment_expired A time-limited enrollment lapses (fired from cron).
learnomy_subscription_created / _cancelled / _expired A membership-plan subscription changes state.
learnomy_course_completed A student completes a course ($enrollment_id, $user_id, $course_id).
learnomy_certificate_issued A certificate is issued ($certificate_id, $user_id, $course_id).
learnomy_send_notification Any Learnomy notification is dispatched ($user_id, $type, $data with message + action_url), so a companion can mirror it without a partner-side hook.

For linking a Space to a community, Learnomy Pro also exposes learnomy_pro_space_front_header_end, learnomy_admin_space_detail_tabs, and learnomy_admin_space_detail_panels, plus Free's learnomy_course_sidebar_card_footer for a course-level card. See Spaces > For developers.

Reporting

Track the performance of your learning site: course revenue, enrollments, completions, and subscription health.

Analytics Dashboard

The Learnomy analytics dashboard

šŸ”¶ Pro feature. Available in Learnomy Pro.

The Analytics dashboard gives you a site-wide view of revenue, enrollment activity, course completions, and top-performing courses and instructors, for any reporting period you choose.

Overview

The Analytics dashboard is at Learnomy > Analytics in the admin sidebar. It is only visible when the Analytics extension is active in Learnomy Pro.


Choosing a Reporting Period

  1. Go to Learnomy > Analytics in the admin sidebar.

    Analytics dashboard overview

  2. At the top of the page, click one of the four period buttons to change the reporting window.

    Period selector

    Period What it covers
    7 Days The last 7 days
    30 Days The last 30 days (default)
    90 Days The last 3 months
    1 Year The last 12 months

    The page reloads with data for the selected window. A screen-reader announcement confirms which period is now active.


KPI Cards

The four stat cards at the top of the dashboard show headline numbers for the selected period.

KPI cards

Card What it shows
Total Revenue Sum of all completed transactions in the period
New Enrollments Number of new course enrollments in the period
Course Completions Number of courses marked complete by students in the period
Avg Rating Average star rating across your top courses

Revenue Trend

The Revenue Trend section shows an area chart of revenue over time for the selected period.

Revenue trend chart

Each point represents a day or week depending on the period length. Hover over a point to see the exact revenue amount for that interval. If there is no revenue in the period, the section shows an empty state message instead of a blank chart.

An accessible data table is included for screen reader users.


Top Courses

The Top Courses table lists your highest-earning or most-enrolled courses for the period.

Top courses table

Each row shows the course title, enrollment count, completion count, average rating, and revenue. Use this to identify which courses are driving growth and which may need attention.


Assignment activity

When the Assignments extension is active, the analytics engine also reports per-assignment submissions, graded count, average score, and late count (GET /analytics/assignments and /analytics/assignments/{id}). The same numbers appear in the instructor Reports Assignments section and in the shared CSV / PDF export. One grouped query per course - no N+1.


Exporting Analytics Data

Click Export CSV in the Export Data section at the bottom of the dashboard to download the current dashboard data as a CSV file.

Export button

The export covers the currently selected reporting period. Open it in any spreadsheet application. The download streams directly without a separate confirmation step.


Tips

  • Revenue figures show completed transactions only. Refunded transactions are not included in the totals.
  • The Average Rating in the KPI cards is calculated from the courses that appear in the Top Courses list for the period, not all courses site-wide.
  • Instructor-level analytics (earnings per instructor, top instructors by revenue) are available in the same view when the Commissions module is active. Look for additional columns in the Top Courses table.
  • When the page first loads with no data (a new site or a period with no activity), each KPI card shows zero and the Revenue Trend shows an empty state. This is expected.

Gradebook

šŸ”¶ Pro feature. Available in Learnomy Pro.

The Gradebook computes a weighted per-course grade from the scores you already collect - quizzes, assignments, and SCORM packages - and shows it to instructors and students. It does not invent a fourth score store. You set how much each source counts and the pass threshold; optionally override a student's final grade by hand with a required reason.

What you can do

  • Set per-course weights for quizzes, assignments, and standards (SCORM). A weight of 0 excludes that source. Weights are scaled to 100 on save.
  • See every enrolled student's weighted grade in a paginated admin grid: filter by pass/fail, sort, export CSV.
  • Override a final grade with a required reason (audit trail). Delete the override to restore the computed grade. Component scores are never rewritten.
  • Let students open Account → Grades (/account/grades/) for their own per-course breakdown and pass / in-progress badge.
  • Trust fixed query cost per page: one batched course scan feeds the grid and the class-average summary.

How to use it

  1. Enable the Gradebook module under LMS Settings → Modules (Reports / Pro).
  2. Open Learnomy → Gradebook (Reports section). You see every course with student count, class average, and pass rate for students who have graded activity ("N active" beside enrolled).
  3. Open a course to set weights and pass threshold, then review the student grid.
  4. To override: enter the final percentage, add a reason, save. The row shows an overridden badge. Revert removes the override only.
  5. Point students to /account/grades/ for their view.

How the grade is computed

Source Where the score lives
Quizzes Best/latest quiz attempt percentage for the course
Assignments Assignment score / max for the course
Standards (SCORM) SCORM CMI scaled score

A weighted source with no data yet counts as 0, so an incomplete student does not look like a perfect grade until they finish that work. Class averages on the course list cover students with graded activity, not the whole enrollment roster padded with zeros.

REST (for developers)

Namespace learnomy-pro/v1, ownership-gated:

Method Route Purpose
GET /gradebook/courses/{id} Paginated student grid
PUT /gradebook/courses/{id}/config Weights + pass threshold
GET /gradebook/courses/{id}/users/{user_id} One student
PUT / DELETE /gradebook/courses/{id}/users/{user_id}/override Set / clear override
GET /gradebook/courses/{id}/export CSV
GET /gradebook/me Current learner's grades

Membership Analytics

šŸ”¶ Pro feature. Available in Learnomy Pro.

Membership Analytics shows the health of your subscription business: monthly and annual recurring revenue, churn rate, subscriber flow, and cohort retention. Use it to track whether your membership is growing, shrinking, or holding steady.

Overview

The Membership Analytics dashboard is at LMS Commerce > Membership Stats in the admin sidebar. It is only visible when the Membership Pro extension is active in Learnomy Pro.

The page has two tabs: Overview (this page) and Paused (subscribers who have paused their plan).


Opening the Dashboard

  1. Go to LMS Commerce > Membership Stats in the admin sidebar.

    Membership Analytics overview

  2. The page header shows the title and a description: "Recurring revenue, churn, and cohort retention."

    Membership Analytics header

  3. The live view loads asynchronously from two REST endpoints (MRR and churn). If either endpoint fails, an error notice appears with a Try again button.


KPI Cards

The KPI section shows four headline numbers. The values update as soon as the REST calls complete.

KPI cards

Card What it shows
Active subscribers The current number of active paid members
Monthly recurring MRR: the sum of monthly subscription revenue from all active plans at their current billing amounts
Annual run rate ARR: MRR multiplied by 12
Churn (30 days) The percentage of active subscribers who cancelled in the last 30 days

If no members have subscribed yet, the KPI grid shows "No subscription data yet."


MRR over the Last 12 Months

This section shows a placeholder message in the current release. Historical MRR trend charting requires a data-history layer that is not yet shipped. The placeholder reads: "Historical MRR will appear once we have at least a month of subscription activity to compare against."

MRR chart section


New Subscribers vs Cancellations

This section shows a placeholder message in the current release. Monthly new-versus-cancelled flow charting requires the same historical-data layer. The placeholder reads: "Monthly new-vs-cancelled flow will appear once we have at least two months of subscriber changes to compare."

New vs cancellations section


Cohort Retention

Cohort retention and lifetime-value (LTV) data currently surface through the CSV export (see Exporting Data below). The on-screen cohort-retention grid is limited in this release: the live page fetches MRR and churn only, so the grid is not fully populated. Use the CSV export for cohort and LTV analysis.

Cohort retention grid

In the export, each cohort is a month in which subscribers joined. Each column is the number of months since that cohort signed up. The value in each cell is the percentage of the original cohort still active at that point.

Read it like this: a value of 80 in the "Month 3" column for the February cohort means 80% of subscribers who joined in February were still active after three months.


Exporting Data

Click Export CSV in the page header to download the current membership analytics data. The export uses the same data that populates the KPI cards and is useful for reporting outside Learnomy.


Tips

  • MRR is a point-in-time calculation based on active subscriptions and their billing amounts right now. It does not include paused subscriptions.
  • Churn is calculated over the last 30 days regardless of any period filter. There is no period selector on this page.
  • The Paused tab lists subscribers who have temporarily paused their plan. Paused subscriptions are not counted in Active subscribers or MRR.
  • Cohort retention requires at least two months of data before the second column populates. A new site will see mostly empty cells until subscribers have been active for a few months.

Subject-wise Quiz Score Breakdown

When a quiz groups its questions by category, the results screen shows a per-subject score panel so a student can see how they did in each subject area, not just their overall mark.

What you can do

  • Show learners a "Score by category" panel on the quiz results screen, one row per subject.
  • Read each subject as earned marks, possible marks, and a percentage (for example 2 / 2 (100%)).
  • Group questions under their subject heading while the quiz is being taken, so related questions stay together.
  • Use question categories (subjects) to organise a multi-subject exam such as a certification test that spans several topics.
  • Let instructors and admins see the same subject breakdown when they review a submitted attempt for grading.
  • Works across every quiz layout: focused, classic, sectioned, conversational, and review board.

Subject-wise score breakdown on the quiz results screen

How to use it

  1. Assign each quiz question a category. Question categories are the "subjects". Set a question's category in the wp-admin question editor (or in the Pro front-end builder). Questions with no category are grouped under General.

  2. Open the quiz in the quiz editor at Learnomy > Quizzes, then edit the quiz you want to break down by subject.

  3. Turn on Group questions by category. The help text reads: "Show questions under their category heading, and a per-category score on the results screen." Save the quiz.

  4. Take the quiz as a student. When grouping is on and the quiz has more than one question, Learnomy serves the questions grouped by subject (all of one subject's questions stay together) and shows the current subject heading above the question.

  5. Submit the quiz. On the results screen, the Score by category panel lists each subject with its score summary in earned / possible (percentage%) form.

  6. To review later, an instructor or admin can open the attempt. Staff always see the full subject breakdown. A student re-opening their own past attempt sees it only when the quiz's feedback timing allows per-question results (see below).

Settings & options

  • Group questions by category (group_by_category) - the quiz-level checkbox that turns the whole feature on. Off by default. When on, it drives both the in-quiz subject headings and the results-screen panel. It only takes effect when the quiz has more than one question.

  • Question category - each question's subject, stored on the question. Categories come from the question category hierarchy, which is separate from course categories. A question with no category is reported as General.

  • Feedback timing (quiz setting) - controls whether a student sees the breakdown when reading a past attempt. The panel is withheld when timing is never or after_attempt; with after_pass only passing students see it. Instructors and admins reviewing an attempt always see it, regardless of timing.

  • The panel is only shown when the graded attempt actually has responses to group. If there is nothing to score by subject, the panel is hidden rather than shown empty.

Each subject row is calculated from the student's responses: earned marks are the sum of the scores awarded, possible marks are the sum of the maximum marks for that subject's questions, and the percentage is earned divided by possible. Subjects are listed in alphabetical order.

For developers

  • The setting is saved through the quiz update endpoint, PATCH /learnomy/v1/quizzes/{id}, which accepts a boolean group_by_category param. (The route registers GET, PATCH and DELETE; there is no PUT handler.)
  • The quiz start endpoint, POST /learnomy/v1/quizzes/{id}/start, returns group_by_category and serves the questions category-contiguous when it is on. Each question carries a category_name.
  • On submit (POST /learnomy/v1/attempts/{attempt_id}/submit) and when reading an attempt (GET /learnomy/v1/attempts/{attempt_id}), the response includes a category_breakdown array. Submission is keyed by attempt, not by quiz; there is no /quizzes/{id}/submit route. Each row has category_id, label, earned, possible, answered, and percentage.
  • The aggregation runs in QuestionResponse::score_by_category( $attempt_id ), which groups the attempt's responses by the question's category_id.
  • The results panel is rendered by the Interactivity API store in assets/js/quiz-view.js via the categoryBreakdown and hasCategoryBreakdown getters; the summary string (earned / possible (percentage%)) is composed there so the template binds plain text.

Instructor Reports

Instructor Reports gives each instructor a front-end page at /instructor/reports/ showing how their own courses are performing -- enrollments, completion, average score, and where learners stop -- over a period they choose. Click a course to go deeper: its lesson drop-off, its roster, its assignments, and how each quiz in it is performing.

What you can do

  • See per-course performance for the courses you teach: new enrollments, completion rate, average quiz score, and the lesson where most learners stop.
  • Switch the reporting window between the last 7 days, 30 days, 90 days, 12 months, and all time.
  • Click a course to drill in: its lesson-by-lesson drop-off, its roster, its assignments, and how each of its quizzes is performing.
  • Find the hardest question in any of your quizzes, then see which answer learners are choosing instead of the right one.
  • Read the numbers from your own instructor dashboard on the front end, without needing wp-admin access.
  • Trust that the scope is your courses only -- an instructor sees their own courses and nothing else, and a course id typed into the address bar that is not yours is refused rather than returned empty.
  • Page through a large course list without the report slowing down, because metrics are computed only for the page you are viewing.

Instructor Reports first view

How to use it

Step 1 -- open your reports

Sign in as an instructor and go to /instructor/reports/, or open Reports from your instructor dashboard navigation. The page sits in the same teaching shell as the rest of your instructor pages.

Step 2 -- pick a period

Use the period selector to choose Last 7 days, Last 30 days, Last 90 days, Last 12 months, or All time. The default is the last 30 days. Changing the period reloads the table for that window.

Two more filters appear only when they have something to offer: a learning-space filter on sites using Spaces, and a cohort filter once you have opened a single course. A filter that could not return a row is not rendered at all, so an empty dropdown never sits there looking broken.

Step 3 -- read the table

Each row is one of your courses, with five columns. The course title is a link into that course's own report when the course has lessons.

  • Course -- the course title.
  • New enrollments -- students who enrolled during the selected period.
  • Completion -- what share of everyone enrolled on that course has finished it. This is a property of the course, not of the period, so it does not change when you switch the date range. See the note below.
  • Avg score -- average quiz score for the period, as a percentage.
  • Biggest drop -- the lesson where most learners stop, with how many.

Completion changed in 1.9.1, and your figures will move. It used to count only learners who enrolled inside the selected period, which meant someone who enrolled two months ago and finished last week was invisible to it. Because a course usually takes longer to finish than the window is wide, that made Completion read 0% on almost every course. It now counts everyone enrolled on the course against everyone who has completed it, so a course that read 0% may now show its real rate. The new number is the correct one.

Revenue is not in this table. It moved to Instructor Earnings, which answers money properly: per course, per period, with payout state. Reports answers teaching performance, which is what the rest of these columns are for.

Step 4 -- page through your courses

If you teach more courses than fit on one page, use the pagination below the table. Each page shows up to 20 courses.

Step 5 -- open one course

Click a course title to open its own report. That page answers where learners stop, who they are, and how your coursework is landing, in four sections:

  • Where learners stop -- one row per lesson, in the order learners walk the course: how many reached it, how many finished it, how many left there, and the average time spent. The steepest fall is marked so you do not have to scan for it.
  • Students -- the roster, 20 per page, with each learner's progress, lessons done, furthest lesson, and last activity. Click a name to open that one learner's lesson-by-lesson position.
  • Assignments -- submissions, how many are graded, and the class average, when the Assignments module is on and the course has submitted work. The average shows "Not graded yet" rather than 0% until something is graded. This section has its own pager.
  • Quiz performance -- described next.

Assignments and Quiz performance are hidden entirely when they have nothing to show. The lesson funnel and the Students list always render their heading; an empty roster shows "No Students on this page" rather than disappearing. If the whole page is empty, the message names the reason: no lessons yet, nobody enrolled, nobody has started, or a filter that removed everything.

Step 6 -- read Quiz performance

The Quiz performance section sits at the bottom of a course's report and lists every quiz in that course -- attached to a lesson, attached to a section, or standing alone -- including quizzes nobody has attempted yet. Four columns:

  • Quiz -- the quiz title.
  • Attempts -- every attempt started on that quiz, whatever state it is in.
  • Pass rate -- the share of graded attempts that passed. While no attempt is graded it reads "No attempts yet" rather than 0%, because "nobody has been graded" is not "everybody failed".
  • Hardest question -- the single question in that quiz that the fewest learners answered correctly, with that percentage beside it. This is the column the section exists for: it names the question to fix instead of leaving you to work out why a quiz is hard.

The section does not appear at all when the course has no quizzes.

Step 7 -- drill into a hard question

Click the question named in Hardest question to open its answer breakdown: how many graded responses it has, and for a multiple-choice question, every option with how many people chose it and what share of responses that is, most-chosen first. The correct answer is marked. If a wrong option was picked more often than the correct one, it is called out - that usually means a misleading distractor or a mis-keyed answer, and it is the fastest thing on this page to act on.

Open-ended questions (essay and similar) have no fixed options to tally, so they show a note instead of a table. Back to course report returns you to the course, keeping the period you were on.

Settings and options

  • Who can see it -- instructors (for their own courses) and administrators. "Their own courses" means courses they author plus courses they are assigned to as an instructor. Administrators see published courses across the site so the page is meaningful for them too. Anyone else is shown an "Instructor access required" message.
  • Quiz analytics access -- the per-quiz figures in the Quiz performance section were an administrator-only read before this release, which meant an instructor could not see the analytics for their own course. An instructor who owns the course now sees them. Another instructor's course is still refused.
  • Period -- 7 days, 30 days, 90 days, 12 months, or all time. Any other value falls back to 30 days.
  • Page size -- 20 courses per page, and 20 students or 20 assignments per page inside a course. The lesson funnel and the Quiz performance section are not paged: a course carries few of each, so both list in full.
  • Empty state -- every empty screen says which of the possible causes it is, and offers the matching next step. "No students match this filter" and "no one has enrolled yet" look identical as a blank table but call for opposite actions.

For developers

View-data Learnomy_Pro\Extensions\Analytics\Instructor_Reports_View_Data:

  • resolve( array $data ): array -- gates access (instructor or admin), then decides which of four levels to render: the course list, one course, one learner, or one question. Scope comes from the logged-in viewer via Report_Query::scope(), never from the request; a ?course= the viewer does not own is denied rather than returned empty, because an empty table would confirm the row exists. A ?question= they do not own is treated differently on purpose: it drops back to the course view rather than denying, so a stale bookmark lands somewhere useful.
  • Per-course metrics for the visible page come from the shared Pro reporting engine Analytics_Aggregate: course_new_enrollments(), course_completion_rate(), course_avg_score(), course_attempt_count(), each taking the course id and the period. Course titles are batch-loaded with the free Course::find_in().
  • course_quizzes() builds the Quiz performance section in three batched reads and no per-row query: the free Quiz::for_course() (every quiz in the course, lesson-attached, section-attached, or loose, including ones with no attempts), the free QuizAttempt::pass_stats_for_quizzes() for attempts and pass rate (the same batch the wp-admin quiz list uses, so the two surfaces cannot disagree), and Analytics_Aggregate::hardest_question_by_quiz() for the lowest-facility question per quiz.
  • question_detail() backs the drill-in, over Analytics_Aggregate::question_analysis().

The Pro REST route GET /learnomy-pro/v1/analytics/quizzes/{id} serves the same per-quiz analytics. Its check_quiz_permission() callback passes the learnomy_view_advanced_analytics capability (administrators) or an instructor whose course the quiz belongs to, resolved through Report_Query::scope() so ownership is answered exactly once. It previously required the capability alone, which is why instructors were refused their own courses' quiz analytics.

Template views/instructor-reports.php renders inside the shared .lrn-account-layout teaching shell and delegates each level to a partial in views/reports/. It reuses the free table, badge, and pagination partials -- no forked report logic and no new CSS. The route is instructor-reports (route_url('instructor-reports')), auth-gated through the free protected-routes list; list pagination uses the registry's /reports/page/N/ segment, while the assignments section carries its own apage query arg so the two pagers never fight.

Data Exports (CSV)

Learnomy's admin list pages can hand you a clean CSV download of what you are looking at -- transactions, students, subscriptions, plans, and your question bank -- ready to open in a spreadsheet.

What you can do

  • Export your transactions as a CSV audit and accounting feed.
  • Export the enrolled student list as a CSV.
  • Export subscriptions, filtered to exactly the status and plan you have on screen.
  • Export your membership plans catalog.
  • Export your question bank, filtered the same way the bank list filters it (and re-import a CSV to add questions in bulk).
  • Get a file that opens correctly in Excel or Google Sheets -- every row in its own cells, not crammed into one cell.
  • Export from large sites without silent truncation, because exports stream row by row instead of capping the result.

Data Exports (CSV) first view

How to use it

Step 1 -- open the list you want to export

Go to the matching admin page: Transactions, Students, Subscriptions, Membership Plans, or the Question Bank.

Step 2 -- filter to what you need (where supported)

On the Subscriptions and Question Bank lists, set the filters first (subscription status and plan; question bank category and type). The export mirrors the filters, so you get "exactly what you see" rather than the whole table.

Step 3 -- export

Use the export action on the page. The file downloads as an attachment with a dated filename (for example learnomy-transactions-YYYY-MM-DD.csv), so repeated exports do not overwrite each other.

Step 4 -- open it

Open the file in Excel, Numbers, or Google Sheets. Because the download is served as real text/csv, each value lands in its own cell.

Settings and options

  • What can be exported -- transactions, students, subscriptions, membership plans, and the question bank each have their own export. Earnings-side lists (commissions, withdrawals, and instructor applications) also export by requesting the list with format=csv.
  • Filters carry through -- subscriptions export honors the status and plan filters; the question-bank export honors the bank's category and type filters.
  • Dated filenames -- every export is named with the date (or date-and-time) so exports are self-labeling and never clobber a previous file.
  • Large lists -- the transaction, student, subscription, and plan exports iterate rows with a cursor rather than loading everything into memory, so a site with tens of thousands of rows exports fully instead of being cut off.
  • No spreadsheet corruption -- files are streamed as raw text/csv with a download disposition, so the whole file is never JSON-wrapped into a single cell.

For developers

The shared streaming mechanism lives in Learnomy\API\Api::stream_csv_responses(), hooked on rest_pre_serve_request. Any REST response whose Content-Type is text/csv and whose body is a string is emitted raw (with its own headers) instead of being JSON-encoded by WP_REST_Server. This fixes every CSV export at once while keeping REST auth and middleware intact.

Helper Learnomy\API\Csv_Formatter builds CSV bodies and downloadable responses: to_string( array $headers, array $rows ) and respond( array $headers, array $rows, string $filename ) (which sets text/csv, a dated Content-Disposition attachment filename, and no-store).

Export endpoints (namespace learnomy/v1, admin-gated):

  • GET /admin/transactions/export -- Admin_Controller::export_transactions_csv() (cursor-iterates Transaction::iterate_all()).
  • GET /admin/students/export -- Admin_Controller::export_students_csv() (streams the enrolled-user list).
  • GET /admin/memberships/export -- Membership_Controller::export_subscriptions_csv() (honors status + plan_id).
  • GET /admin/membership-plans/export -- Membership_Controller::export_plans_csv().
  • GET /questions/export -- Questions_Controller::export_csv() (honors the bank filters); paired with POST /questions/import (import_csv()) for bulk import.

List endpoints that emit CSV when called with format=csv (via Csv_Formatter::respond()): Admin_Controller::list_commissions(), list_withdrawals(), and list_applications().

Quiz Statistics Report

The quiz statistics report gives instructors and admins the headline numbers for a quiz: how many times it has been taken, the average score, and the pass rate. It also answers the question those three numbers always raise next - which question is everyone getting wrong.

What you can do

  • See how many times each quiz has been taken, right in the Quizzes list.
  • Read a quiz's pass rate to judge whether it is too easy or too hard, without confusing it with the passing score you configured.
  • Find the hardest question in a quiz on a screen, with Learnomy Pro, instead of reading it out of an API response.
  • Read the same figures from the command line with WP-CLI when you are working on the server.
  • Pull the numbers over the REST API to feed your own dashboard or an AI assistant.

Quiz statistics first view

How to use it

  1. Go to Learnomy > Quizzes. The list table shows one row per quiz with its Attempts count and its computed Pass Rate (the share of graded attempts that passed - not the configured passing score threshold). A quiz that has attempts but nothing graded yet shows a dash under Pass Rate, because "nobody has been graded" is not "everybody failed".

  2. Let students take and finish the quiz. Pass rate and average score are built only from attempts that have been graded, so an in-progress or ungraded (for example essay-pending) attempt does not move them yet.

  3. To find your hardest questions with Learnomy Pro, open Instructor Reports at /instructor/reports/, click the course, and read the Quiz performance section at the bottom of the page. It lists each quiz in the course with its attempts, its pass rate, and the single hardest question with the share of learners who answered it correctly. Click that question to see which answer people are actually picking. See Instructor Reports for the full walkthrough.

  4. Without Pro, or when you are already on the server, run wp learnomy inspect quiz <id>. It prints the same figures - attempts, pass rate, average score, and every question ordered hardest first - in the terminal.

  5. To feed the numbers into something else, use the REST endpoints under For developers below.

Who can see these numbers

  • Administrators see quiz statistics for every quiz on the site.
  • Instructors see the statistics for a quiz on a course they are the main author of, or are the primary instructor on. This is wider than it was: the per-quiz analytics used to be an administrator-only read, so an instructor got a permission error on the numbers for their own course.
  • A quiz on someone else's course is refused, and so is a logged-out visitor.
  • Co-instructors are refused, and this is worth knowing because it does not line up with the reports. Instructor Reports scopes by author OR co-instructor, so a co-instructor can open the course report but is refused that course's quiz statistics. If a co-instructor needs the numbers, make them the primary instructor on the course.
  • Students never see any of this. Their own score, and any per-question feedback, is governed by the quiz's feedback timing setting instead.

Settings & options

There is nothing to switch on. The report is read-only and is computed live from quiz attempts, so it is always available once a quiz has attempts.

What the numbers mean and where they come from:

  • Attempts (Quizzes list) - every attempt started on the quiz, whatever its state. It answers "has anyone sat this", so an in-progress or ungraded attempt is included.
  • Pass rate - the share of graded attempts that passed, as a percentage, shown as a dash while no attempt is graded. An attempt passes when its score meets the quiz's Passing score (passing_score on the quiz), so changing the passing score changes the pass rate. It is counted per attempt, not per student: a student who sat the quiz three times counts three times.
  • Average score - the average of each graded attempt's percentage, rounded to two decimals. In-progress and ungraded attempts are excluded.
  • Hardest question / facility - the share of responses to that question that were fully correct. Lower means harder. It is computed over graded and submitted attempts, and the question with the lowest share in a quiz is the one Pro surfaces as the hardest.
  • Answer distribution - for a multiple-choice question, how many people chose each option and what share of responses that is, most-chosen first. When a wrong option out-draws the correct one, it is called out. Open-ended questions (essay and similar) have no fixed options to tally, so they show a note instead of a table.
  • Discrimination - a point-biserial value available over the REST API. It only appears once a question has 10 or more graded responses, below which it is not statistically meaningful.

For developers

  • Quiz statistics: GET /learnomy/v1/quizzes/{id}/statistics. Returns quiz_id, attempt_count, avg_score, and pass_rate, all over status = 'graded' attempts only - so attempt_count here is the graded subset, not the Attempts column in the Quizzes list. Permitted for an administrator or an instructor of the quiz's course.
  • The quiz numbers are computed in QuizAttempt::get_statistics( $quiz_id ), a single aggregate query over the attempts table. The Quizzes list column uses QuizAttempt::pass_stats_for_quizzes() instead, one grouped query for the whole page, which returns total_attempts (all states), graded, passed, and pass_rate.
  • Per-question statistics: GET /learnomy/v1/questions/{id}/statistics. Returns question_id, times_answered, avg_score_pct, and discrimination (null until 10+ responses). The per-question numbers are computed in QuestionResponse::get_statistics( $question_id ); the discrimination value is calculated in the questions controller's calculate_discrimination().
  • Pro per-quiz analytics: GET /learnomy-pro/v1/analytics/quizzes/{id} returns attempts, pass rate, average score, average time, and per-question facility. Its permission callback admits the learnomy_view_advanced_analytics capability (administrators) or an instructor whose course the quiz belongs to, resolved through the same course scope Instructor Reports uses. Anyone else gets a 403.
  • The same data is exposed as an ability, learnomy/get-quiz-analytics (category learnomy-reporting), described as per-quiz attempt/pass statistics plus per-question difficulty. It takes a quiz_id and is permission-checked against manage access on the quiz's course.
  • WP-CLI: wp learnomy inspect quiz <id> prints attempts, pass rate, average score, and per-question facility ordered hardest first. Supports --format=table|json|yaml.

Pro Tools

> **Pro feature.** All pages in this section require Learnomy Pro.

Pro Tools Overview (Pro)

The Learnomy Pro Tools hub

šŸ”¶ Pro feature. Available in Learnomy Pro.

Pro Tools is a hub page inside wp-admin that gives you quick access to every Pro extension active on your site. It does not add any functionality by itself. Think of it as a single landing pad so you are never hunting through the sidebar for an advanced feature.

Overview

Go to Learnomy > Pro Tools to open the hub. Each enabled extension registers a card here via the learnomy_pro_tools_cards filter. Clicking a card takes you directly to that extension's admin page.

Pro Tools hub

If no Pro extensions are enabled, the hub shows an empty state with a link to LMS Settings > Modules where you can switch them on.

What is on the hub

Each card shows an icon, the extension name, and a short description. The extensions that appear on the hub are:

Assignments - Attach written assignments to any lesson. Students submit text from the lesson page; you grade inline from the admin.

Assignments documentation

Cohorts - Group students into named cohorts scoped to one course. Set start and end dates and a seat cap.

Cohorts documentation

Content Drip - Schedule when lesson content unlocks for enrolled students, either on a fixed date or a number of days after enrollment.

Content Drip documentation

Learning Paths - Chain multiple courses into an ordered sequence. Students enroll in the path and are auto-enrolled in every course step.

Learning Paths documentation

User Journeys - Track per-student skill scores, achievements, and a chronological learning timeline.

User Journeys documentation

Spaces - Create group or organisation accounts (mini-schools) with their own student rosters and assigned courses.

Spaces documentation

The Frontend Builder and White Label extensions are configured from LMS Settings rather than the Pro Tools hub. See Frontend Builder and White Label.

Tips

  • Extensions must be enabled on the Modules page before their cards appear here. Go to LMS Settings > Modules to toggle them.
  • Direct URLs such as ?page=learnomy-cohorts continue to work even when an extension is hidden from the sidebar, so browser bookmarks are safe.

Cohorts (Pro)

Learnomy cohorts

šŸ”¶ Pro feature. Available in Learnomy Pro.

Cohorts let you run the same course in scheduled batches. Each cohort is tied to one course, has a fixed start and end date, and caps the number of seats. Students added to a cohort are auto-enrolled in the course, and their enrollment row records which cohort they belong to so you can filter and report on them as a group.

Overview

Go to Learnomy > Pro Tools > Cohorts to manage all cohorts. The page shows a table with each cohort's name, linked course, member count, start date, end date, and status.

Cohorts list

Cohort status can be one of: Draft, Open (accepting members), In progress, Completed, or Archived. Status transitions are manual in this release.

Creating a cohort

  1. Click Add new in the top-right corner. The create form slides into view above the table.

    Cohorts list header

  2. Fill in the fields:

    • Cohort name (required) - a label for this batch, for example "Spring 2026 cohort".
    • Course (required) - select any published course from the dropdown.
    • Start date and End date - optional calendar inputs. Leave blank if the cohort has no fixed schedule.
    • Max students - defaults to 30. The wp-admin form accepts 1-999. (The REST API additionally accepts 0 for unlimited and does not enforce the 1-999 bound.)
    • Status - set to Open (accepting members) when you are ready to add students. Newly created cohorts default to Draft.
  3. Click Save cohort. The new cohort appears in the table immediately.

Adding and removing members

  1. In the cohort table, click Members in the Actions column to open the members view for that cohort.

    Cohort detail - members

  2. To add a student, type their email address into the Add member field and confirm. The student is added to the cohort and automatically enrolled in the cohort's course. Their enrollment row records the cohort ID.

    Cohort members list

  3. To remove a member, click Remove next to their row. This removes them from the cohort but does not unenroll them from the course.

Bulk CSV import is not available in this release. Add members one at a time using the email lookup.

Editing a cohort

Click Edit next to any cohort row. The same form reappears pre-filled with the current values. Update the fields and click Save cohort to apply changes.

Deleting a cohort

Click the overflow menu (three-dot icon) in the Actions column and choose Delete. Deleting a cohort removes the cohort record and clears the cohort_id from any related enrollment rows. Students remain enrolled in the course.

Cohort progress

The REST API exposes an aggregate progress endpoint at GET /learnomy-pro/v1/cohorts/{id}/progress (available to cohort owners and admins). It returns the overall completion rate and a per-member status list. A UI for this view is planned for a future release.

The learner experience

Students enrolled via a cohort see the course exactly as any other enrolled student would. There is no separate cohort-facing page in this release. If a student visits /cohorts/{id}/ they see the cohort name, linked course, dates, and capacity; enrolled members also see a Continue Learning button.

Tips

  • Status transitions are manual. When your cohort's start date passes, switch the status from Open to In progress yourself.
  • The enrollment gate fires when status is set to a mode other than Open. If your course settings require cohort membership, students who are not in an Open cohort cannot self-enroll.
  • Member counts and all list queries are paginated at the database level, so cohorts with thousands of members load at the same speed as cohorts with ten.

Learning Paths (Pro)

A Learnomy learning path

šŸ”¶ Pro feature. Available in Learnomy Pro.

Learning Paths let you chain multiple courses into a curated, ordered sequence. A student enrolls in the path once and is automatically enrolled in every course step. If the path is sequential, the student cannot access step N+1 until all required steps before it are complete. Finishing the last required step issues a path-level certificate.

Overview

Go to Learnomy > Pro Tools > Learning Paths to see all paths. The list shows each path's title, number of steps, enrolled student count, and status.

Learning paths list

Creating a learning path

  1. Go to Learnomy > Pro Tools > Learning Paths and click New Path. A create form appears above the list.

    Learning paths list header

  2. Enter the path title, choose whether it is sequential or flexible, and set the status to Published when ready. Click Save. The new path appears in the table.

  3. Click Edit next to the path to open the path editor. From there you can add course steps, reorder them with the Move up and Move down buttons, and mark steps as optional.

  4. Set a Cover image for the path (added in 1.8.0). It works the same way as a course thumbnail: pick from the media library, and it appears in all three places a path is shown - the /paths/ catalog card, the single path page above the header, and the path row in the admin list. A path with no cover image falls back to a placeholder, so setting one is optional.

Once a path is published, it appears at /paths/ for students to browse and at /paths/{slug}/ to view and enroll. Note the public URL uses the path slug, not its numeric id.

Paths can also be created via the REST API. Send a POST to /learnomy-pro/v1/learning-paths with title, is_sequential, and status, then add steps via POST /learnomy-pro/v1/learning-paths/{id}/steps with course_id, sort_order, and is_required.

The student experience

  1. A student visits /paths/ to browse the published paths catalog. Paths can be filtered by category and sorted by latest, most popular, or title.

    Learning paths list

  2. The student opens a specific path at /paths/{slug}/. The page shows the path title, description, the list of course steps in order, and an Enroll button.

    Learning path detail

  3. Clicking Enroll enrolls the student in the path and in every course step simultaneously. On a sequential path, the step tiles reflect which steps are locked and which are accessible.

    Learning path steps

  4. When the student completes all required steps, the path is marked complete and a path-level certificate is issued automatically.

Sequential enforcement

When is_sequential is true, the access check refuses entry to a step until every earlier required step is marked complete. Optional steps (is_required: false) do not gate the next step and can be visited at any time.

A learning path can require payment. It checks out through Learnomy's normal unified checkout with purchase_type=learning_path and the path ID, using whichever payment gateway your site has enabled (#payment-gateways-lrn)). After payment, the student is enrolled in the path and all its course steps. The learnomy_pro_learning_path_purchased action fires on successful purchase.

Steps that run in cohorts

A step can be a course that is delivered in cohorts. When a student reaches that step, they are enrolled in the course as normal:

  • if the course has exactly one open cohort, they are placed into it automatically and get the cohort experience - the shared schedule, the group, and anything else that cohort carries
  • if it has several open cohorts, they get access straight away and can pick a cohort from My Account -> Cohorts
  • if it has no open cohort, the course behaves like any other step

Students never have to join a cohort by hand to continue a path they have already enrolled in.

When a step becomes unavailable

If you unpublish or archive a course that is a required step of a published path, that path stops accepting new enrollments. The path page tells visitors that part of it is unavailable, and shows no enrol button.

This is deliberate. A student who has not already taken that course cannot be enrolled in it while it is unpublished, so they could never finish the path and never earn its certificate. Rather than sell a path that cannot be completed, Learnomy declines the sale until you fix it.

Nothing else changes:

  • the path stays published and stays in the catalogue
  • students already enrolled keep the path and every course they already have access to
  • their progress is untouched

The Learning Paths list in wp-admin flags any affected path with a warning telling you which action to take. You have two ways to clear it:

  • republish the course - the path returns to sale immediately, with nothing else to do
  • remove the step from the path - if the course is not coming back

Only you can know which of those is right, which is why Learnomy does not quietly drop the step or keep selling around it.

A path with no steps at all is handled separately: it never appears in the public catalogue in the first place.

Viewing path details in admin

The admin list shows the path title, step count, enrolled student count, and status. Click Edit to open a path and manage its steps.

Learning path header

Tips

  • Drag-and-drop step reordering in the admin editor is planned for a future release. Use the Move up and Move down row actions to reorder steps in this release.
  • Path-level prerequisites (paths that require completing other paths first) are planned for a future release.
  • The path-level certificate reuses the certificate template system from Free. Assign a certificate template via the REST API when creating or updating the path.

Spaces (Pro)

A Learnomy B2B space

šŸ”¶ Pro feature. Available in Learnomy Pro.

Spaces are organisation or group accounts inside your LMS. Think of each space as a mini-school: it has its own roster of students and its own set of assigned courses. When you add a member to a space, they are automatically enrolled in every course assigned to that space. When you add a new course to a space, every existing member is automatically enrolled in it.

A typical use case is a corporate client who buys access for their whole team. You create one space for them, assign the relevant courses, and add their staff. All staff get access immediately without you having to enroll each person one by one.

Overview

Go to LMS Commerce > Learning Spaces to see all spaces. Until the site has an active space plan it is not selling seats, so the page sits under Learnomy > Learning Spaces instead - a company running teams without selling anything reaches it there. The list shows each space's name, member count, assigned course count, status, and subscription status.

Spaces list

Filter the list using the links above the table: All, Active, Inactive, Recurring, Past Due, Cancelled, Expired.

Creating a space

  1. Click Add new. A create form slides into view.

    Spaces list header

  2. Enter a space name (for example, "Acme Corporation Training") and set the initial status to Active. Save the space. The new space appears in the table.

The space detail page

Click the space name or the Manage action to open the space detail page. The page has four tabs: Settings, Members, Courses, and Groups - the last three each carry a live count in their label. The header shows seat usage (for example, "12 / 50 seats used") or a plain member count when the space has no seat limit.

Space detail

Use the tabs to switch between managing members, assigning courses, and working with sub-groups. Groups are covered on their own page (see Groups below).

Assigning courses to a space

  1. Open the space detail page and switch to the Courses tab.

    The tab shows Assigned courses first, which is what members of this space can open, and Add courses below it.

  2. Under Add courses, search your catalogue by title and click Assign on the course you want.

    Space detail - assigned courses

    The list is paged and tells you how many courses exist in total, for example Showing 1-20 of 329 courses, so you can see at a glance whether what you are looking for is further in. Courses already in this space stay in the list marked Assigned, so you can tell "not added yet" from "already there".

    Every current space member is enrolled in the newly assigned course automatically.

  3. To add a whole track at once, pick a category from the dropdown and click Assign whole category. Every published course in that category, including its sub-categories, is added in one go.

    This is a one-time copy. Courses you add to that category later are not added to the space automatically; run Assign whole category again to top up. Courses already in the space are skipped, so re-running is safe.

  4. To remove a course from the space, click Remove next to it. Members keep their existing enrollments.

Adding members

  1. On the space detail page, open the Members tab.

    Space detail - members

  2. Type a member's email address into the add-member field, choose a role from the dropdown (Student or Manager), and click Add.

    The user is added to the space and auto-enrolled in all assigned courses. If the user account does not exist, an invitation is queued.

  3. To add many members at once, open Add many people at once and either paste addresses or load a CSV. You can also share an invite link (see below).

Importing a roster from a CSV

Open Add many people at once, then click Sample CSV to download a file with the columns the importer expects:

First Name,Last Name,Email
Dana,Whitfield,dana.whitfield@example.com
Amir,Haddad,amir.haddad@example.com

Fill it in, click Load a CSV, check the addresses that appear in the box, and click Add all.

Why the names matter. When you import someone who does not have an account yet, Learnomy creates one for them. With a first and last name it builds their username and display name from their name, so Dana Whitfield becomes dana.whitfield. Without names, it has nothing to work from but their email address, so their username is based on that instead. WordPress shows usernames in several places, so on a roster of students or association members that is not what you want. Pasting bare addresses still works for adding a colleague or two quickly; use the CSV whenever you are importing real people in bulk.

Export, edit, re-import. Export members (CSV) produces a file with those same three columns first, so you can export your roster, edit it in a spreadsheet, and load it straight back in. The extra columns in the export (role, joined date, progress) are there for your records and are ignored on import.

If a row has no usable email address it is skipped and the message names the line number, so you can fix that row rather than hunting through the file.

Changing a member's role

Each member row has a role dropdown set to Student or Manager. Change the dropdown to promote a member to Manager or demote a Manager back to Student. The change applies straight away. Use the Remove button on the row to take a member out of the space.

Suspending a member

When someone is behind on their dues, Suspend pauses their access without deleting them. Click Suspend on the member row; the row shows an amber Suspended badge and the action becomes Resume.

A suspended member:

  • keeps their membership. Their role, their join date, and who invited them are all still there, and come back exactly as they were when you resume.
  • stays on the roster and keeps their seat. They are still someone you bill, so they still count against the seat limit and still appear in reports.
  • gets nothing from the space. Every course the space grants is closed to them, including courses granted through a group they belong to.
  • sees an explanation, not a broken page. Their Spaces page says access is paused rather than silently showing an empty list.

Resuming puts everything back. This is the difference between Suspend and Remove: removing a member destroys the record you bill on and cannot be undone by re-adding them, because tenure and role are gone.

Suspend sits before Remove on the row and is deliberately not styled as a destructive action -- pausing is the routine, reversible one.

A Manager may suspend and resume students only, never the space owner and never another manager -- the same limit that applies to removal.

Suspending a whole association

To pause an entire company at once -- an unpaid invoice rather than one member behind on dues -- set the space itself to inactive from the spaces list. An inactive space grants nothing at all: not the courses assigned to it directly, and not the courses assigned to its groups. Members see the paused screen. Set it back to active and every grant returns.

A Manager added here is a space-wide manager. To put someone in charge of just one group instead, use a group-scoped manager from the Groups tab (see Groups).

On the Members tab, open Or share an invite link to copy the shareable invite link. The link is valid for 7 days, and multiple people can use the same link during that window.

Anyone who clicks the link lands on a branded invite page inside your site, not the bare WordPress login screen. The page shows the space name and description and handles every case on one screen:

  • A logged-out visitor sees the invite details with an inline Log in & accept form and a Create an account link. Both bring them back to the same page to finish joining.

    Where Create an account goes follows your own sign-up setting. If you have turned Allow students to sign up themselves off in the Academy Mode card of General Settings, there is no sign-up page to send an invited person to, so the link goes to the login page rather than a dead one. A site collecting sign-ups through its own form can point the link at that form instead, along with every other sign-up link on the site, with the learnomy_student_registration_url filter.

  • A logged-in visitor who is not yet a member sees an Accept invitation button.

  • An expired or already-used link shows a friendly "no longer valid" notice with a link to browse courses.

  • If the space has hit its seat limit, the page explains the space is full and asks the visitor to try again once a seat is free.

Once they accept, they are added to the space, auto-enrolled in every assigned course, and sent to the space page.

Letting people ask to join

Invite links are the normal way into a space. This is for the people an invite missed: someone who joined the team after the link was shared, or whose link expired. Without it their only option is to find the owner out of band.

It is on by default. Nobody is added by asking, so this puts a question in front of the owner rather than opening a door. To turn it off for a space that should only ever be joined by invitation, open the space in LMS Commerce > Learning Spaces, go to the Settings tab, and untick Let people ask to join under Joining.

While it is on:

  • Someone who can see the space but is not in it sees an Ask to join button on the space page, with an optional note for the owner. Nobody is added by asking.
  • The owner sees a Join requests card on their manage page, above the invite link, listing each person's name, email address and note. They press Approve or Decline.
  • The owner is emailed when somebody asks, and so is anyone with the Manager role in that space - they can approve too, so a request does not sit waiting while one person is away. The email carries the person's note and links straight to the manage page.
  • The person who asked is emailed if they are declined. Approving already sends them the normal welcome email for joining a space, so they hear either way.
  • Approving adds the person to the space and enrols them in its courses, the same as any other member.
  • If the space has no free seat, approving is refused and says so. The request stays in the queue, so nobody is lost. Add seats or remove a member, then approve again.

A person who asks twice does not create a second request; they are shown that their request is already waiting. Declining is not a ban - the person is not told who declined, and they can ask again, which is what you want when a request simply landed on the wrong space.

Site administrators can approve requests from the same screen, and keep that control even on a space where the setting is switched off - so requests that were already waiting when you turned it off can still be cleared.

Pending course requests

Separately from the above, a space owner can ask you to add a course to their space (see Let the owner request courses). When there are pending course requests, a notice appears at the top of the Spaces list page.

Spaces list - pending requests

Click the notice to see the queue, where you can Assign or Decline each request individually.

Space seat packs

If you sell seat packs (bundles of additional student seats), manage them from LMS Commerce > Space Plans, on the Seat Packs tab (the page has two tabs, Plans and Seat Packs). This page appears under LMS Commerce only when your site sells seats.

Space seat packs

The seat packs table shows each pack name, the number of seats it contains, the price, and its status. Use Add new to create a pack, and the row action menu to edit or delete existing ones.

Space seat packs table

Spaces can be configured to require a seat pack purchase. When a student attempts to join a space that has run out of seats, they are directed to buy more.

Subscriptions

Spaces support recurring subscriptions through whichever gateway your site checks out with: Stripe, or WooCommerce (with the WooCommerce Subscriptions extension active). When a subscription is active, it appears in the Subscription column of the spaces list. The cron job learnomy_pro_space_subscription_sweep runs daily to detect past-due or expired subscriptions and update space status accordingly. PayPal recurring subscriptions for space plans are planned for a future release.

The daily sweep processes cancelled subscriptions whose billing period has ended in bounded pages of 100 rows at a time, rather than one unbounded query, so a large backlog of expiring subscriptions is drained completely in one run instead of only the first batch being processed. Developers can change the page size with the learnomy_pro_space_expiry_batch filter (default 100).

Selling a subscription that only works inside a space

New in 1.9.4. A membership plan can be scoped to one Learning Space. Seat packs sell the space to the organisation; a space-scoped plan is the other shape of the same deal, where each person subscribes individually and their subscription is only worth anything while they are on that space's roster.

Scope a plan from the plan editor at LMS Commerce > Memberships, in the Learning Space field. The field only appears once at least one space exists, so a site not running spaces never sees a dropdown for a concept it does not have. See Membership Plans for the rest of the plan editor.

What that means on your side of the fence, as the person who runs the space:

  • The roster is the access control. A subscriber on a space-scoped plan reaches the plan's courses only while they are a member of the scoped space. Add them to the space and the plan works for them; remove them and it grants nothing, even though their subscription is still live and still billing.
  • It gates access, not the sale. Scoping does not stop somebody outside the space buying the plan, and does not hide it on your pricing page. Anyone who buys without being on the roster is paying for a plan that unlocks nothing until you add them. If the plan exists for one client, name it so that is obvious to everyone else looking at your pricing page.
  • Onboarding is unchanged. The invite link, join requests, and the CSV import are still how people get onto the roster. The plan decides what they pay; the space decides what they can open.

Offering a space on the sign-up form

Asking to join only helps somebody who already has your space's link. If you want new students to find you while they are creating their account, turn on Offer this space on the sign-up form in the space's Joining options.

The space then appears by name on the student sign-up page. Nothing else is shown - no description, no course list, no member or seat numbers. A new student can tick any organisations they belong to, and each one becomes an ordinary join request for that owner to approve or decline. Nobody is added to anything by signing up.

It is off by default, and worth a moment's thought before turning it on. The name becomes visible to anyone who opens your sign-up page, including people who never register. That is fine for a school, club or training provider that wants to be found, and wrong for a company space whose existence is private. Leave it off for those; invite links and direct adding are unaffected.

Two details worth knowing:

  • A space only appears if it also has Let people ask to join switched on. Listing a space that is closed to requests would show a choice that always fails.
  • There is no site-wide switch in Settings, because the per-space option is already off by default - nothing appears on the form until somebody deliberately opts a space in. A developer who wants to refuse the whole feature regardless can return false from the learnomy_pro_signup_picker_enabled filter, which hides it and stops requests being created even if one is posted directly.
  • Up to twelve organisations appear as tick boxes. Past that the student gets a single dropdown instead: they type the first letters of a name to jump to it, and each one they choose is added to a short list underneath that they can remove from. There is no need to hold any key, and nothing they have already picked can be cleared by accident.

The member experience

Members visit /spaces/{slug}/ on the frontend to see their space. The page shows the space name and clickable course cards for every assigned course. Clicking a card takes the student directly to the course page.

Two things on that page are private by default, for the same reason: a space is usually a company's training group, so neither its headcount nor what it trains its people on is public information. Members and managers always see both.

  • The member count is not shown to visitors. Turn on Show the member count to visitors in the space's options if you want it public -- a community space rather than a corporate one.
  • The course list is not shown to visitors (new in 1.9.1). A non-member sees how many courses the space teaches, not which ones. Turn on Show the course list to visitors to make it public.

Before 1.9.1 the course list was always public, so a visitor saw the full curriculum with a View Course link on each card while being told to ask the owner for an invite. Nothing was ever accessible that should not have been -- those are published courses already in your catalog, and a non-member clicking through lands on the ordinary sales page -- but the space was naming what a company trains on to anyone with the URL. If you were relying on that page being public, turn the new option on.

The Space Owner dashboard

A space owner or space-wide manager has their own self-service dashboard at /spaces/{slug}/manage/, separate from the wp-admin space detail page. It is a set of widgets covering the day-to-day running of the space:

  • Courses - the space's assigned courses, plus an Add a course search. An owner or manager can search and add a course themselves without asking an admin, but only from what they are entitled to: any free published course, or a course bundled in the space's plan. Admins can add any course from wp-admin as described above. If a wanted course isn't in the owner's entitlement, they can send a Request a course note to the site admin instead, and any pending requests show underneath.
  • Members - add, remove, and manage member roles, the same actions available from the wp-admin Members tab.
  • Groups - manage sub-groups when the space uses them.
  • Reports - per-course enrollments, completions, average progress, and spend across the space's members. Click Export CSV to download the per-course breakdown as a spreadsheet.
  • Billing, KPIs, Invite, and Announce widgets round out the dashboard with subscription/seat status, at-a-glance numbers, the shareable invite link, and a way to post an announcement to members.

Groups

A space can be split into sub-groups. A group is a sub-segment inside a space (a subject, a class, a department), and a member can belong to no group, one group, or several at once. A space with no groups behaves exactly like a flat space, so groups stay out of your way until you create the first one. You manage groups from the Groups tab on the space detail page (and space owners and managers can also manage them from the frontend manage page).

Groups let you assign a course to only certain members, and let you put a manager in charge of just one part of the space. They are covered in full on their own page.

See Space Groups for creating groups, adding members and managers to a group, assigning courses to a group, exclusive group-course access, and how course access combines across the space and its groups.

Space Manager role

A member with the Manager role helps run the space without needing WordPress admin access. There are two kinds of manager, and the owner picks which one per person:

  • Space-wide manager: manages the whole space. They can add and remove members, change member roles, assign and remove courses, manage groups, and view reports for the space, like the owner but without owning it.
  • Group-scoped manager: manages only the members and courses of their assigned group or groups. When they open the manage page they see only the groups they manage. They cannot create or delete groups, assign other managers, touch groups they do not manage, or change a course's access scope.

The Manager role is a member-row designation, not a WordPress capability. To make someone a space-wide manager, set their role to Manager on the Members tab. To make someone a group-scoped manager instead, add them as a manager of a specific group from the Groups tab. Scoping is enforced on every group action on both the frontend and the REST API. See Space Groups for the full detail.

Linking a Space to a community (with BuddyNext Pro)

If you also run BuddyNext Pro, a Space (or an individual course) can be linked to a BuddyNext community so that members join the community automatically. When someone is added to the Space or enrolls in the linked course, BuddyNext Pro adds them to the community; when they leave, it removes them from any community it created for the link.

This linking is a BuddyNext Pro feature. Learnomy does not manage communities itself. What Learnomy provides is the extension points BuddyNext Pro attaches to, so when both plugins are active you manage the link from Learnomy's own screens:

  • A Community card on the Space detail page (admin), and on the public Space header, added by BuddyNext Pro.
  • A community card in the single-course sidebar for a course-level link.

If BuddyNext Pro is not installed, these controls do not appear and the Space behaves normally. See the BuddyNext Pro documentation for how to create and manage the link.

Tips

  • Deleting a space does not unenroll its members from courses. Enrollments persist after a space is deleted.
  • Branded subdomains (for example, acme.yourdomain.com) and per-space email templates are planned for a future release.
  • Bulk onboard is available: paste a list or upload a CSV of emails on the Members tab (POST /spaces/{id}/members/bulk), or share the invite link / invite code. Seat limits are enforced on insert.
  • Owners and managers can download a compliance-style progress report as CSV from the space reports UI (GET /spaces/{id}/reports/export).
  • The shareable invite link is a signed URL (HMAC-SHA256 with a 7-day expiry). No token is stored server-side; the expiry and signature are verified from the URL itself.

Automating space membership

Everything below works with an application password, so Zapier, Make, WP Fusion or your own code can drive it. There are two ways in, matching the two modes:

Add someone directly - POST /wp-json/learnomy-pro/v1/spaces/{id}/members with user_email (or user_id) and an optional role. The person is added and enrolled immediately, with no approval step. Requires permission to manage the space.

Lodge a request for someone - POST /wp-json/learnomy-pro/v1/spaces/{id}/join-request with user_email (or user_id) and an optional note. The request lands in the owner's queue exactly as if the person had asked, and nobody is added until it is approved. Requires permission to manage the space. Called with no user field it lodges the request for whoever is authenticated, which is what the space page itself does.

This is what makes a signup form work without Learnomy owning the form: let your form plugin create the WordPress account, then have it call one of the two endpoints with the new user's email. Use the second if a human should still approve.

Webhooks fire on both sides so an automation can react rather than poll. Subscribe under LMS Settings > Outgoing Webhooks. The space-related events are:

Event Fires when
space.request_created Somebody asks to join (also fires for course and seat requests - read the type out of the payload)
space.request_resolved / space.request_declined A request is approved or turned down
space.member_added / space.member_removed Anyone joins or leaves, by any route
space.member_suspended / space.member_resumed Access is paused or restored
space.member_role_changed Somebody is made a manager, or returned to student
space.course_assigned / space.course_removed / space.course_scope_changed What the space teaches, and who it is scoped to
space.provisioned / space.seats_added A space is created from a plan, or buys more seats
space.subscription_renewed / _past_due / _expired / _cancelled Seat billing - the events to drive dunning or deprovisioning
space.trial_ended A trial finishes
space.created / space.updated / space.deleted / space.status_changed Space lifecycle
space_group.* Groups: created, updated, deleted, members added or removed, managers assigned or removed, courses assigned or removed

Subscribing to a space event does not scope it to one space. Every subscriber receives the event for every space, and the payload carries the space id - filter on it in your automation if you only care about one customer.

Beyond spaces, the same screen publishes 140 events in total - the full lifecycle of a course, lesson, quiz, enrolment, certificate, membership, subscription and payout, plus cohort membership (cohort.member_added and siblings), learning paths (learning_path.enrolled, .completed, .purchased), milestones (milestone.earned / .revoked), assignments (assignment.submitted, .graded, .resubmit_requested and the authoring events), membership and subscription billing (membership.renewed, membership.expired, membership.invoice_paid, subscription.created, .past_due, .plan_changed, and the plan catalogue under membership_plan.*), and money out (refund.issued, withdrawal.paid, withdrawal.failed, payout.reversed, gift.issued, gift.redeemed).

A few things are deliberately not published, so the list stays usable: SCORM progress commits, which fire many times per lesson and would bury everything else, and the raw Stripe relays, because the normalised payment.* and refund.issued events already describe the same facts once.

Every payload has the shape { event, timestamp, data }, where data is the argument list of the underlying action.

For developers

The columns shown in the space members widget can be customised with the learnomy_pro_space_member_columns filter, which receives the current column list and the space ID. Use it to add or reorder columns in the member roster.

Learnomy exposes extension points on the Space screens so companion plugins (such as BuddyNext Pro, above) can add their own controls without modifying Learnomy:

  • learnomy_pro_space_front_header_end (action) fires at the end of the public Space header, passing the space and whether the current user is a member.
  • learnomy_admin_space_detail_tabs (filter) lets a plugin add a tab to the admin Space detail page.
  • learnomy_admin_space_detail_panels (action) renders the matching tab's panel, passing the space and the active tab key.

The single-course sidebar exposes learnomy_course_sidebar_card_footer (action, passes the course) for a course-level card in the same way.

Space Groups (Pro)

šŸ”¶ Pro feature. Available in Learnomy Pro.

A group is a sub-segment inside a space. It is an additive overlay: members still belong to the space itself, and a group sits on top of that. A member can be in no group, one group, or several groups at once. A space with no groups behaves exactly like a flat space, so groups never get in your way until you create the first one.

This page is a companion to the main Spaces doc. Read that first to understand spaces, members, managers, and space-wide course assignment. This page covers only what groups add on top.

When to use groups

Use groups when one space needs internal structure. Two common cases:

  • A school running multiple subjects. The space is the school. Each subject (Math, Science, English) is a group. A teacher is put in charge of just their subject, and a course is assigned to just that subject's students.
  • A company with departments. The space is the company. Each department (Sales, Support) is a group. A team lead manages just their department and assigns courses to it.

The vocabulary stays general everywhere: Group, Manager, Member. A "subject", a "class", or a "department" is just a group, and a "teacher" or a "team lead" is just a manager.

If your space is small and everyone takes the same courses, you do not need groups. Assign courses space-wide and skip this page.

How a group relates to a space

  • A group always belongs to one space. Group names and slugs are unique within that space.
  • Members are added to the space first, then to a group. A direct member who is in no group is a fully valid state and still receives all space-wide courses.
  • A member's effective courses are the union of the space-wide courses (which everyone gets) plus the courses of every group they belong to. A member in Math and Science sees both groups' courses plus any space-wide courses, all on one dashboard.

Creating a group

You can manage groups from the frontend manage page (for space owners and managers) or from the wp-admin space detail page (for the site super-admin). Both surfaces share the same actions.

From the frontend manage page

  1. Open your space's manage page and go to the Groups panel.

    Groups panel

  2. In the Add group field, type a name (for example, "Math") and submit. The group appears in the list with a member count and a course count.

    The create form is shown only to the space owner and space-wide managers. A group-scoped manager sees only the groups they manage and cannot create new ones.

From wp-admin

  1. Go to Learnomy > Pro Tools > Spaces, open a space, and click the Groups tab. The space detail page has four tabs: Settings, Members, Courses, and Groups - the last three each with a live count.

    Space detail Groups tab

  2. Use the Add group form at the top of the tab to create a group.

When a space has no groups yet, the panel shows an empty state: "No groups yet. Create your first group above, then add members and assign courses to it."

Renaming a group

  1. Expand the group in the list.
  2. In the Group name field, edit the name and click Rename. The slug is regenerated from the new name and stays unique within the space.

A group-scoped manager can rename their own group. Creating and deleting groups is reserved for the owner and space-wide managers.

Adding members to a group

A group's members are shown as removable chips. New members are added through a search box rather than a long list, so the control works the same on a small group and on a space with thousands of members.

  1. Expand the group and find the Members section.

    Group members

  2. Type a name into Search members to add and pick a member from the results. They are added to the group and auto-enrolled in the group's courses.

  3. To remove a member, click the x on their chip. They are removed from the group and, if they no longer receive a course through any other path, unenrolled from that course (see How course access combines).

Members must already belong to the space before they can be added to a group. Add people to the space first (#spaces-lrn) doc), then place them into groups. All additions are still bounded by the space seat limit.

Adding managers to a group

A group manager is a member you put in charge of just that group. The manager controls is shown only to the space owner and space-wide managers, because assigning a manager is a space-level decision.

  1. Expand the group and find the Group managers section.
  2. Type a name into Search members to make manager and pick a member. They become a manager scoped to this group.
  3. To remove a manager, click the x on their chip.

A member can be made manager of more than one group. Promoting a member to a group manager does not give them control of the rest of the space.

Assigning courses to a group

  1. Expand the group and find the Courses section.

    Group courses

  2. Choose a course from the Choose a course dropdown and click Assign. The dropdown lists only the space's courses that are not already assigned to this group. Every current member of the group is enrolled in the course.

    Enrolling the group's members runs in the background in batches, so assigning a course to a large group does not block the page.

  3. To remove a course from the group, click the x on its chip. Members are unenrolled from that course unless they still receive it through another group or a space-wide assignment.

A group-scoped manager can assign and remove courses for their own group. The owner can do this for any group.

Exclusive group-course access

By default, a course added to a space is granted to everyone in the space. To make a course available to only certain groups, change its access scope.

  1. On the space's Courses widget, find the course and open its access scope control.

    Course access scope

  2. Choose between:

    • Everyone in the space (the default): every member gets the course, the same as a normal space-wide assignment.
    • Specific groups only: the course is no longer space-wide. It is reached only through the groups it is assigned to.

This is what makes the school case work. Set "Algebra I" to Specific groups only and assign it to the Math group, and only Math members can take it. Members of other groups, and direct space members in no group, do not see it.

Notes:

  • The access scope control is shown only to the owner and space-wide managers. A group-scoped manager does not see it, because it is a space-level decision.
  • Existing course assignments keep the Everyone in the space scope after you upgrade, so nothing changes until you opt a course in.
  • When you switch a course from Specific groups only back to Everyone in the space, every member gains it. When you switch a space-wide course to Specific groups only, members who do not receive it through any group are unenrolled, while members in a group that has the course keep it.

Scoped managers

There are two manager flavours, and the owner picks per person:

  • Space-wide manager: a deputy admin who manages the whole space, like the owner but without owning it.
  • Group-scoped manager: a manager who manages only the members and courses of their assigned group or groups.

A group-scoped manager, when they open the manage page, sees only the groups they manage. Within those groups they can add and remove members, assign and remove courses, and view their group's reports. They cannot:

  • create or delete groups,
  • assign other managers,
  • see or touch members and courses of groups they do not manage,
  • change a course's access scope.

This isolation is enforced on every group action, on both the frontend and the REST API, so a scoped manager cannot reach another group's data by any route.

How course access combines

A member's access to a course is the union of two paths:

  1. Space-wide courses: any course set to Everyone in the space. Every member gets these, group or not.
  2. Group courses: the courses of every group the member belongs to.

A member sees the combined set on their dashboard. There is no separate student screen for groups; the existing enrolled-courses list already renders the union.

Access always tracks what the member currently qualifies for. When a grant is removed (a course removed from a space or a group, or a member removed from a group or the whole space), the member is unenrolled from a course only if they no longer receive it through any remaining path. A member in Math and Science who is removed from Math loses Math-only courses but keeps Science courses and space-wide courses. A member removed from the space loses everything the space granted.

Deleting a group

  1. Expand the group and click Delete group.
  2. Confirm in the dialog.

Deleting a group removes its member, manager, and course assignments. Members stay in the space. They are unenrolled only from courses they received solely through this group; courses they still get space-wide or through another group are kept.

Managing groups at scale

The Groups panel is built to stay usable on large spaces:

  • A group's own members and managers are a bounded set and render as chips with resolved names.
  • The full space roster is never listed inside the panel. New members and managers are added through a search box that queries the roster on demand.
  • Courses are a bounded per-space set, so they render as chips plus an assign dropdown.
  • Member counts and course counts come from dedicated count queries, not from loading every row.
  • Enrolling a group's members when you assign a course runs in batches in the background.

Assignments (Pro)

Learnomy assignments

šŸ”¶ Pro feature. Available in Learnomy Pro.

Assignments attach written tasks to any lesson. An enrolled student sees the assignment widget below the lesson content, writes their response, and submits it. You then review each submission and award a numeric score with optional feedback. The student sees the result the next time they open the lesson.

Overview

Go to Learnomy > Pro Tools > Assignments to see all assignments. The table shows each assignment's title, the lesson and course it is attached to, the total submission count, and how many submissions are pending grading.

Assignments list

Creating an assignment

  1. Click Add new. The create form slides into view above the table.

    Assignments list header

  2. Fill in the fields:

    • Assignment title - a short description of the task, for example "Write a 300-word reflection".
    • Lesson - select any lesson from the dropdown. Only one assignment can be attached to each lesson.
    • Max score - the maximum numeric score for this assignment (for example, 100).
    • Due date - optional. When set, the due date appears in the student widget and is enforced: submissions after the deadline are rejected. The due date is a calendar day, and it is evaluated in your site's timezone: a "Due July 10" assignment keeps accepting work through 23:59:59 on July 10, site-local time, not midnight UTC.
    • Reference files - optional. Attach a brief, starter files, or a rubric from the media library. Students see these as Reference files on the lesson, alongside the assignment instructions, regardless of whether they have submitted yet.
  3. Click Save assignment. The assignment is now active on the selected lesson for all enrolled students.

You can also add or edit the assignment attached to a lesson from the lesson editor itself, in the Assignment panel next to the lesson's release settings.

The student experience

  1. An enrolled student opens the lesson. The assignment widget appears below the lesson content, showing the instructions and any Reference files or Reference links the instructor attached.

  2. The student types their submission text into the text area. They can attach it two ways, and can use both on the same submission:

    • Attach files (optional) - upload one or more files from their device, up to the maximum the site owner configured.
    • Add a link (optional) - paste a video, audio, or document URL instead of uploading a large file. This is meant for content that already lives somewhere else, like a hosted screen recording or a Google Doc.
  3. The student clicks Submit. The widget immediately shows a Submitted, awaiting grade confirmation, with an expandable "Show what you submitted" section listing their submitted files and links.

  4. After grading, the student returns to the lesson and sees their score (for example, 78 / 100), the instructor's feedback, and their submitted files and links in the widget. The same feedback also appears on the account Assignments page (My Account > Assignments), which lists every assignment the student has upcoming or submitted across all their courses.

  5. If an instructor requests a resubmission instead of grading, the widget shows a Resubmission requested notice with the instructor's note, and the submission form reopens even if the original due date has passed - the resubmission request supersedes the deadline.

Grading submissions

  1. From the Assignments list, click Grade in the row for an assignment that has pending submissions. You land on the grader view for that assignment.

    Assignment submissions grading

  2. The grader shows every submission for this assignment with the student's name, their submitted text, any uploaded files, and any submission links - a link opens in a new tab so you can watch or read it full-screen instead of in a cramped inline frame.

    Grading view

  3. For each submission, enter a numeric score in the score field (0 to the assignment's max score). Optionally add feedback text.

  4. Click Grade to save. The submission is marked graded. The student sees the score and feedback the next time they open the lesson.

  5. When all submissions are graded, the page shows "All caught up. No assignments to grade."

  6. Click Back to list to return to the full assignments table.

Grading against a rubric

New in 1.9.4. Instead of judging a whole submission with one number, you can break the task into named criteria and score each one. Learnomy works the assignment score out from the weighted result, and you can still overrule it.

Writing the criteria

Criteria belong to the assignment, not to each submission. Open the assignment in Add new or Edit and find the Grading rubric field under Max score:

  1. Click Add criterion. A row appears with three fields.

  2. Fill the row in:

    Field What it does
    Criterion The name the grader sees, for example "Clarity" or "Use of sources".
    Max The points this criterion is scored out of, 1 to 1000. Defaults to 10.
    Weight How much this criterion counts relative to the others. Defaults to 1 and accepts decimals such as 0.5 or 2.5.
  3. Add as many criteria as the task needs. Use the row's remove button to take one out.

  4. Save the assignment. A row left without a criterion name is dropped rather than stored, so an empty row you never filled in does nothing.

Leave the rubric empty for a straight numeric score. Nothing about the assignment changes until it has at least one criterion.

Scoring a submission against it

When an assignment has a rubric, a Rubric panel appears on each submission's grade form, below the score, the feedback field and the action buttons. It shows one input per criterion, capped at that criterion's Max, and a Calculated score line underneath.

Score each criterion and the calculated score updates as you type, and is written into the score field above. Saving stores that number exactly as if you had typed it in yourself. The button reads Grade on an ungraded submission and Update on one you have already graded.

How the total is worked out

Each criterion contributes the fraction you awarded it multiplied by its weight. Those are added up, divided by the total weight, multiplied by the assignment's Max score, and rounded to a whole number.

A criterion's Max only decides the scale you score it on. Weight decides how much it matters.

For example, on an assignment out of 100 with two criteria:

  • Clarity, Max 10, Weight 2, awarded 8 - that is 0.8 of the criterion, counting double.
  • Use of sources, Max 5, Weight 1, awarded 3 - that is 0.6 of the criterion.

((0.8 x 2) + (0.6 x 1)) / 3 = 0.733, and 0.733 of 100 rounds to a calculated score of 73.

Overriding the calculation

The score field stays editable. Type over the calculated number and your number is what saves.

Opening a submission you have already graded does not disturb the saved score either. The criterion inputs start at zero and the panel only recalculates once you touch one of them, so reviewing a graded submission never quietly re-scores it.

Because the saved result is a single score, the per-criterion numbers are working notes rather than a stored record: reopening a graded submission shows the score you saved, not the criterion scores that produced it. Put anything the student needs to see criterion by criterion into the feedback field.

Rubrics share a store with quiz rubrics

Assignment rubrics are kept in the same store as quiz rubrics. You do not need to do anything to enable that, and assignment rubrics keep working whether or not the Advanced Quizzes module is switched on - the store's table is created when Pro is activated and its code loads independently of the module toggle.

The gain from sharing the store is that both rubrics behave identically: the same criterion fields and the same weighted maths, so a quiz essay and an assignment judged on the same criteria produce the same number. See Grading Submissions for the quiz essay half.

Assignment reporting

When the Analytics extension is active, assignment activity joins the same reporting engine as revenue and enrollments:

  • Instructor Reports include an Assignments section (paginated, 20 per page) with submissions, graded count, average score, and late count per assignment.
  • REST: GET /analytics/assignments and GET /analytics/assignments/{id} under learnomy-pro/v1.
  • Shared CSV and PDF export cover assignment stats with the rest of the analytics export.

See Analytics Dashboard and Gradebook for how assignment scores feed the weighted course grade.

Who can grade

An assignment can be graded by the course author or any WordPress administrator. The grading REST endpoint checks this ownership rule.

Empty states

  • If no assignments exist yet, the list shows "No assignments yet. Click 'Add new' to create one."
  • If an assignment has no submissions, the grader shows "All caught up. No assignments to grade."

Tips

  • Students can submit text, file attachments, a link, or any combination. Files are uploaded to the WordPress media library; links are stored as-is and are meant for content that already lives elsewhere (a hosted video, a cloud document).
  • Grading uses a numeric score, either typed in directly or calculated from a rubric. Add criteria in the Grading rubric field on the assignment to score against a rubric, and see Grading against a rubric above.
  • The due date is displayed in the assignment widget and enforced at the REST layer, evaluated in your site's timezone. Submissions after the deadline are rejected with an error - unless the instructor has explicitly requested a resubmission, which reopens the form regardless of the deadline.
  • Reference files and reference links you attach to the assignment definition are visible to every enrolled student on the lesson, whether or not they have submitted yet - use them for the brief, starter files, or a marking guide the students should read. That is a document you attach; the Grading rubric field above is the scoring tool you grade with.
  • The assignment widget on the lesson page is injected via the learnomy_lesson_player_aside hook, so it appears in the lesson sidebar area regardless of which course template is active.

User Journeys (Pro)

šŸ”¶ Pro feature. Available in Learnomy Pro.

User Journeys tracks each student's skill development over time. Every time a student completes a course or passes a quiz, the system recalculates their skill score for that course category. The result is a per-student profile showing skill progress bars, earned achievements, and a chronological activity timeline. Administrators can review any student's profile; students can view their own journey from their dashboard.

Overview

Go to Learnomy > Pro Tools > User Journeys to see a list of all students who have activity on the site.

User journeys list

The table shows each student's name, email address, number of distinct skills tracked, number of courses completed, number of quizzes passed, and their last activity date.

User journeys list table

If no students have activity yet, the page shows "No journey data yet. Skill scores accumulate as students complete courses and pass quizzes."

Viewing a student's journey

  1. Click View Journey in the row for any student. You land on the student journey detail page.

    User journey detail

  2. The detail page is divided into three sections:

    Skills - a list of skill categories with a progress bar for each, showing the student's current score in that category.

    User journey skills

    Achievements - a list of milestones the student has earned, such as first enrollment, first course completion, and first certificate.

    User journey achievements

    Timeline - a chronological list of activity events: enrollments, course completions, quiz attempts, and certificates earned.

How skill scores are calculated

Skill scores are tied to course categories. When a student completes a course, the system recalculates their score for that course's category. When a student passes a quiz attached to a course, the score is recalculated again. The category name is used as the skill name in the student-facing view.

This happens automatically through two hooks:

  • learnomy_course_completed - recalculates the skill for the completed course's category.
  • learnomy_quiz_graded - recalculates the skill on quiz pass.

There is no manual skill assignment in this release. Scores are derived from completion and quiz performance only.

The student experience

Students see their journey from the My Journey tab on their account dashboard (/account/). The tab shows their skill scores, achievements, and recent activity timeline. No configuration is needed; the tab appears automatically when the User Journeys extension is active.

Tips

  • Skill categories match your course categories. If your categories are "Marketing", "Design", and "Development", those are the skill names students see.
  • Achievements are calculated automatically from enrollment, completion, quiz, and certificate activity. Configurable skill thresholds and a skill-tree builder are planned for a future release.
  • Finding students who have gone quiet does not need this extension. Learnomy itself flags them and emails them: set how many days count as inactive under LMS Settings > Emails, filter the admin students list by inactivity, and send a reminder from the instructor dashboard. See Emails and Announcements and Managing Students.
  • Cohort journey comparison (side-by-side skill scores for a group) is planned for a future release.
  • The REST API exposes full journey data at GET /learnomy-pro/v1/users/{id}/journey, /skills, /timeline, /achievements, and /recommendations. The mobile app and third-party integrations can consume these endpoints directly.

Frontend Course Builder (Pro)

The Learnomy front-end course builder

šŸ”¶ Pro feature. Available in Learnomy Pro.

The Frontend Course Builder lets instructors create and manage courses entirely from the front end of your site, with no access to wp-admin required. An instructor follows a four-step wizard to create a course, then uses the persistent editor to build out curriculum, configure settings, and publish.

Overview

The builder lives at two routes:

  • /instructor/create-course/ - the four-step creation wizard.
  • /instructor/edit-course/{id}/ - the persistent editor for an existing course.

Both routes are login-required. A visitor who lands on /instructor/create-course/ without the right capability is redirected to the course catalog with ?access=instructors_only.

The builder is enabled for all users with the learnomy_frontend_course_edit ability, which is granted to users with the lrn_instructor role.

Creating a course: the four-step wizard

Step 1 - Course Info

The first step collects the core course details.

  • Course Title (required) - the public name of the course.
  • Description - a rich-text editor (powered by Editor.js) for the course overview. Supports paragraphs, headings, lists, code blocks, and quotes.
  • Category - a dropdown of existing categories. Type a new name in the field to create a category inline without leaving the builder.
  • Course Thumbnail - upload an image from your device. A preview appears once uploaded.

Click Next (or the step indicator) to advance. The wizard remembers your progress as you move between steps.

Save Draft in the top-right header saves your work at any point without publishing. The course is created in the database as a draft on the first save, and the browser navigates to the editor at /instructor/edit-course/{id}/.

Step 2 - Curriculum

Build the course structure. Add sections (groupings of lessons) and loose lessons (lessons not inside a section).

  • Click Add Section to add a named section.
  • Inside a section, click Add Lesson to add a lesson title. The lesson is saved when you save the draft, or when you finish editing it in the editor.
  • Lessons outside sections are listed as loose lessons below the sections.
  • Drag sections and lessons to reorder them. Dragging uses native HTML5 drag-and-drop and saves the new order immediately.

Step 3 - Settings

Configure access and completion rules.

  • Access Type - Free, Paid, Membership, or Paid or Membership.
  • Price - shown when Paid access is selected.
  • Difficulty - Beginner, Intermediate, or Advanced.
  • Completion Type - how the course is marked complete: all lessons, lessons and quiz, percentage, or manual.

Step 4 - Review and Publish

Review a summary of what you have entered. Click Publish to set the course status to Published and redirect to the persistent editor. A success notice confirms publication.

Editing an existing course

The persistent editor at /instructor/edit-course/{id}/ covers everything you can do after the course is created.

From the editor, the instructor can:

  • Edit course info (title, description, category, thumbnail)
  • Add, rename, and delete sections
  • Add, edit, and delete lessons (lesson content uses Editor.js for rich text)
  • Add quizzes to the curriculum
  • Set up content drip rules (when each lesson unlocks after enrollment)
  • Choose a certificate template
  • Set prerequisites (a multi-select picker of other courses)
  • Add tags
  • Add or remove co-instructors

Every change is saved via the same REST API used by the mobile app. There is no separate save button in the editor; saves happen on each action.

The Create Course and Edit Course links on the instructor dashboard point to the frontend builder when this extension is active. Instructors do not need to know about /wp-admin/.

Question types in the builder

When an instructor adds a quiz to a course via the editor, the question type dropdown is populated from the learnomy_question_types filter. Available types depend on which extensions are active:

Type Available in
True / False Free
Single choice Free
Multiple choice Free
Short answer Free
Essay Free
Fill in the blank Free
Matching Advanced Quizzes (Pro)
Ordering Advanced Quizzes (Pro)
Numeric Advanced Quizzes (Pro)
Math Advanced Quizzes (Pro)
Calculated Advanced Quizzes (Pro)

The matching, ordering, numeric, math, and calculated types only appear in the dropdown when the Advanced Quizzes extension is enabled.

Tips

  • Instructors author lesson content in the persistent editor after publishing. The creation wizard does not include an Editor.js content panel; content editing starts in the editor.
  • Every time a lesson is saved, a revision snapshot is stored. Instructors can open the revision history panel to browse previous versions and restore any saved state.
  • If an instructor pastes a YouTube URL into the lesson editor, the embed saves. Re-editing the lesson reconstructs the embed block. Full media-embed round-tripping improvements are planned for v1.1.
  • The builder is a frontend route (/instructor/create-course/ and /instructor/edit-course/{id}/). It uses the WordPress Interactivity API for step navigation and real-time state, with no React or jQuery.

No screenshots available

No dedicated frontend-builder screenshots are included in this release. The feature shares the course-builder and course-editor templates. Refer to the ../images/courses/ and ../images/lessons/ folders for adjacent UI screenshots, or capture them from /instructor/create-course/ on a test site.

White Label (Pro)

Learnomy white-label settings

šŸ”¶ Pro feature. Available in Learnomy Pro.

The White Label extension lets you replace Learnomy's name, icon, and accent color with your own brand across every admin page the plugin creates. Students see a custom attribution on the frontend. The settings are saved once and apply everywhere automatically.

Overview

White Label settings live in LMS Settings > White Label. The section appears in the settings sidebar under the Pro nav group after the extension is enabled.

If none of the fields are filled in yet, the card shows "No customizations yet. Fill in the fields below to brand your LMS."

Settings

Brand Identity

Plugin Name - replaces the word "Learnomy" in the admin sidebar menus. For example, enter "My Academy" and the three menus rename to "My Academy", "My Academy Commerce", and "My Academy Settings". The "Commerce" and "Settings" suffixes are preserved; only the "Learnomy" token is replaced.

Admin Logo URL - paste the URL of a custom icon image (SVG or PNG recommended). This replaces the default icon in the WordPress sidebar next to the menu label. The image must be hosted somewhere accessible; the field accepts any public URL.

Color

Accent Color - pick a hex color using the color swatch or type it directly into the hex field. The chosen color overrides --lrn-accent and --lrn-accent-hover across both the admin and frontend. The hover variant is automatically darkened by 15% from the base color.

Clearing this field restores the default accent color.

Text and Attribution

Footer Text - the text shown in the WordPress admin footer on Learnomy pages. For example, "Powered by My Academy". Leave blank to show the default WordPress footer text on Learnomy pages.

Hide "Powered by Learnomy" on the frontend - a toggle. When on, the Learnomy attribution is removed from learner-facing pages.

Advanced

Custom CSS - a textarea for additional CSS injected into both admin and frontend pages. Maximum 5,000 characters. Use this for minor style tweaks that the accent color alone does not cover.

Saving changes

Click Save Changes at the bottom of the card. A status message ("Changes saved.") confirms success. If the save fails, a "Save failed. Please try again." message appears.

Settings are stored in a single WordPress option (learnomy_pro_white_label) and read on every page load, so changes take effect immediately without a cache clear.

Scope of branding

The white label extension rebrand applies to the three Learnomy top-level menus:

Original label After white label with name "Acme"
Learnomy Acme
LMS Commerce Acme Commerce
LMS Settings Acme Settings

The custom icon applies to all three menu items. The accent color and custom CSS apply to every admin and frontend page the plugin renders.

Tips

  • The plugin name field only replaces the "Learnomy" token in labels. If a label does not contain the word "Learnomy", the entire label is replaced with the plugin name instead.
  • The accent color is applied via a CSS custom property (--lrn-accent). It takes effect on every element that uses this token, including buttons, links, progress bars, and tab indicators.
  • Custom CSS is stripped of HTML tags before saving. It is not sanitized for valid CSS; take care with untrusted input.
  • There is no media-library picker for the admin logo URL field in this release. Upload your logo to the WordPress media library, copy its URL, and paste it in.
  • To remove a branding value, clear the field and save. The extension falls back to the Learnomy defaults.

No dedicated screenshots available

No dedicated white-label admin UI screenshots are included in this release. The settings card renders inside the standard LMS Settings page. Refer to ../images/settings/ for the settings page layout, or capture the White Label section from your live site.

Advanced Quiz Types

Advanced Quizzes is the Pro module that adds five more question types on top of the six that ship with Learnomy, plus per-question timers, random question pools, essay rubrics, and typeset math in the quiz player. It is a Content-category module you turn on from the Modules page.

What you can do

  • Author five extra question types that Free does not offer: matching, ordering, numeric (with a tolerance), math expression (algebraically-equivalent answers grade correct), and calculated (each attempt gets randomized variable values).
  • Grade all five automatically. Numeric answers can be within an absolute or percentage tolerance, and a math answer like 2(x+1) is accepted for 2x+2 because equivalence is checked by sampling, not string match.
  • Build a quiz from a random pool: keep 50 questions in the bank and pull 10 at random for each attempt, optionally filtered by category and difficulty.
  • Put a separate time limit on any single question, on top of the quiz-level timer. The limit is enforced on the server for every layout, and the focused layout also auto-advances when a question's time runs out.
  • Grade essay responses against a rubric of criteria, each scored out of a max you set, from a dedicated admin screen.
  • Show LaTeX math written in a question stem, an answer option, or an explanation as typeset math in the quiz player, rendered with a bundled copy of KaTeX.

Advanced quiz settings first view

How to use it

Step 1 - turn on the module

Go to LMS Settings > Modules, open the Content category, and switch on Advanced Quizzes. The change takes effect immediately - no save button. Until the module is on, the extra question types do not appear in the question editor and the Advanced settings do not render.

Step 2 - author an advanced question type

Open the Question Bank or a question editor and change the question's Type. With the module active the type picker now also lists Matching, Ordering, Numeric, Math expression, and Calculated. Picking one swaps in that type's answer editor:

  • Numeric - enter the correct value and a tolerance (absolute or percent) so near-miss answers still count as correct.
  • Math expression - enter the correct expression; the student's answer grades correct if it is algebraically equivalent.
  • Calculated - define variables with ranges; each attempt seeds its own values, substitutes them into the stem, and grades against the freshly computed answer.
  • Matching and Ordering - author the pairs or the correct order; both are graded automatically.

Step 3 - open the Advanced settings on a quiz

Edit a saved quiz. In the Pro frontend builder the quiz drawer has an Advanced accordion (badged Pro); the same panel also appears in the wp-admin quiz editor. It holds three cards: Question pool, Per-question timers, and Essay rubrics.

Step 4 - set up a random pool (optional)

In the Question pool (random selection) card, choose one or more Categories, set the Min difficulty and Max difficulty, and use Pick how many questions to set how many are drawn per attempt. Use Refresh preview to see how many questions match, then Save pool. If fewer questions match than you asked for, the quiz draws as many as are available. Leave the filters empty to draw from every question.

Step 5 - add per-question timers (optional)

In the Per-question timers card, set a separate time limit on any question. Blank or 0 means no per-question limit, and the quiz-level timer still applies. The limit is enforced when the attempt is submitted.

Step 6 - define essay rubrics (optional)

In the Essay rubrics card, define grading criteria for each essay question, each with a maximum point value. Graders then score each criterion against that max.

Step 7 - grade essay responses

When a student submits an essay answer, grade it from Learnomy > Grading. That menu item shows a count of responses awaiting grading. The screen lists pending responses and lets a grader score each one inline against the rubric you defined.

Settings & options

Random pool config, per-question timers, essay rubrics, and conditional-logic rules are all real, saved, configurable settings - not placeholders. They are stored in a dedicated database table (lrn_pro_aq_config, one row per quiz/question/slot, keyed by config type and object id) rather than as individual WordPress options. Sites upgrading from an older version migrate their existing learnomy_quiz_{type}_{id} option rows into the table automatically, one time, the first time the module runs after the upgrade - nothing to do on your end. The new question types reuse existing answer columns, so none of this touches the question or answer tables.

What Config type
Random pool config (categories, difficulty range, count) pool, per quiz
Per-question (per-slot) time limits slot_timer, per quiz slot
Essay rubric criteria and max points rubric, per question
Conditional-visibility rules conditions, per question (see note below)
Numeric / math / calculated answer config Reuses Free's answer row content (correct value) plus the overloaded group_id string: tol: for numeric tolerance, domain: for math sampling, range: for calculated variables

The module itself is toggled on the LMS Settings > Modules page (Content category, slug advanced-quizzes); it is off by default. Managing pools, timers, and rubrics requires the learnomy_manage_quizzes capability.

Conditional logic

Conditional logic - showing or skipping a question based on how the student answered an earlier one - is a planned feature, not yet available to authors. The quiz drawer's Conditional logic card shows a Coming soon badge instead of an authoring form: the underlying storage exists (rules save through the same lrn_pro_aq_config table as pools and rubrics), but the quiz player does not yet re-evaluate the question set as a student answers, so a saved rule cannot be trusted to fire correctly. The authoring controls return once that runtime piece ships.

For developers

Filters:

  • learnomy_question_types - the single registry the module hooks to add its five type slugs (matching, ordering, numeric, math, calculated) as slug => label. It is idempotent; an existing entry for a slug wins.
  • learnomy_quiz_auto_grade_result - re-scores an attempt to include the Pro types, dispatching each response to the matching Grader::grade_* method.
  • learnomy_pro_quiz_filter_questions - used at attempt start to inject the random pool draw (priority 5); the conditional-logic listener is registered on the same filter at priority 10.
  • learnomy_quiz_player_context - two listeners: one injects per-question timer data, the other substitutes a calculated question's {var} placeholders with this attempt's deterministic values before render.

Actions:

  • learnomy_pro_quiz_advanced_settings - fired by the Pro frontend builder; the module renders the Advanced accordion here.
  • learnomy_quiz_editor_advanced_panel - the wp-admin quiz editor's seam for the same panel, so the front-end builder, wp-admin, and REST stay in lockstep.
  • learnomy_quiz_submitted - the module enforces per-slot timers on submit here.
  • learnomy_quiz_graded - fired with attempt_id and score after grading.

REST routes (namespace learnomy-pro/v1, all requiring learnomy_manage_quizzes):

  • GET|POST /quizzes/{id}/pool-config - read or save the random pool configuration.
  • POST /quizzes/{id}/pool-preview - preview how many questions the current filters match.
  • GET /quizzes/{id}/slot-context and GET|POST /quiz-slots/{id}/timer - read slot context and read or set a per-question timer.
  • GET|POST /questions/{id}/rubric - read or save an essay question's rubric.
  • GET|POST /questions/{id}/conditions - read or save conditional rules (authoring UI deferred).
  • POST /responses/{id}/rubric-grade - submit a rubric score for a response.
  • GET /question-categories - list categories for the pool filter.

AI Authoring

AI Authoring lets administrators and instructors draft courses and quiz questions with AI, right inside the builders they already use. AI is an assistant: it proposes, you review, and everything it creates is saved as a draft for you to approve. Nothing is ever published or deleted automatically.

What you can do

  • Generate a course curriculum from a short brief, on the course builder.
  • Generate quiz questions from a topic, on the quiz editor.
  • Add more or refine with instructions ("add a section on advanced topics", "focus on real-world scenarios") - the AI is aware of what the course or quiz already has and adds complementary content instead of repeating it.
  • Summarize reports in plain language.
  • Govern usage with per-instructor limits, a site-wide cap, and a usage panel.

AI Authoring is available to administrators (in wp-admin) and instructors (on the front-end builder). Students never see it.

Before you start: connect a provider

AI Authoring runs on the AI provider you connect once in WordPress core under Settings > Connectors. The API key is stored by WordPress and shared across every plugin - Learnomy never stores it.

Open Learnomy → Settings → AI authoring. The Connection card shows whether a provider is connected and links straight to where to fix it if not.

AI connection status

  • Connected and ready to use (green): you are set.
  • Not connected yet (amber): click Go to Settings > Connectors, add a provider key, then come back.

Use Test AI connection to send one tiny prompt and confirm the provider and model actually work. If your provider's newest model is not available on your account, the test shows the exact error - set a Model (a balanced model such as a Haiku- or Sonnet-class model is the right choice for text authoring) and test again.

Turn it on and choose who can use it

On the same AI authoring tab:

  1. Enable AI authoring.
  2. Who can use it - Administrators are always allowed; tick Instructors to let them use it too.
  3. Draft-first - leave on so AI content is always created as drafts (recommended).

Generate a course curriculum

Open a course in the builder (admins: Learnomy → Courses → Course Builder; instructors: the front-end course builder) and go to the Curriculum step. Click Generate with AI.

Generate a course with AI

  1. Describe the course (topic, who it is for, how many sections). Add Instructions to steer focus, depth, or what to add.
  2. Click Suggest an outline. AI proposes a curriculum - nothing is written yet.
  3. Review it and remove anything you do not want.
  4. Click Generate draft curriculum. Each section and lesson is created as a draft, with a starter lesson body you can edit.
  5. The builder reloads so you can review the new drafts.

It only adds. Generating into a course that already has content adds complementary sections as drafts - it never changes or removes what is already there.

Generate quiz questions

Open a quiz in the editor (admins: Learnomy → Quizzes → edit a quiz; instructors: the front-end quiz editor) and click Generate questions with AI.

Generate quiz questions with AI

  1. Set the topic (it is pre-filled from the quiz title), how many questions, and which question types. Add Instructions to steer difficulty or style.
  2. Click Suggest questions. AI drafts them for review - nothing is added yet.
  3. Review each question (stem and options, with the correct answer marked) and remove any you do not want.
  4. Click Add to quiz. The questions are created as drafts and added to this quiz.

Running it again returns new questions - the AI knows which questions the quiz already has and will not repeat them.

Summarize a report

Go to Learnomy → Analytics and click Summarize with AI. You get three to five plain-language points about your courses and students, ending in one recommended action.

  • The button only appears when AI is enabled and a provider is connected. If it is missing, check Settings → AI.
  • Nothing is generated when the page loads. One click is one generation, and it counts against your usage limits.
  • The summary is ownership-scoped: an instructor's covers their own courses and students, an administrator's covers the whole academy.
  • It covers all of your teaching to date. It does not follow the reporting period buttons on that page - those change the revenue and enrolment cards, not the summary.

Usage limits and governance

The AI provider is billed to you, so you control how much instructors can use. On Learnomy → Settings → AI authoring → Usage limits:

  • Generations per instructor / month - the default monthly allowance (0 = unlimited).
  • Site-wide monthly cap - a ceiling across all instructors (0 = no ceiling). Administrators are never limited.
  • Per-request caps - the most questions per generate and lessons per outline, so a single call cannot drain your budget.
  • Rate limit - requests per minute per user, to guard against runaway loops.
  • Actions instructors may run - which AI actions are available to instructors.
  • When an instructor hits their limit:
    • Stop them (recommended) - they see a clear message and cannot generate more until you raise their limit.
    • Let them continue, but flag it - generation still works and the instructor is flagged Over limit in the usage list, so you decide what to do.

The usage panel

The AI usage this month panel shows the site total versus your cap, and every instructor's usage with a per-row monthly limit and an OK / Near limit / Over limit status. Set an instructor's limit inline (blank uses the default, 0 blocks them), then click Save limits.

AI usage and per-instructor limits

How it stays safe and consistent

  • Draft-first - AI content is created as drafts; you approve before anything goes live.
  • Additive - the wizards add content; they never delete or overwrite existing courses, lessons, or questions.
  • Same as hand-authored - AI writes through the same internal authoring path as the manual editors, so AI-created content behaves identically and shows up consistently on the front end, in wp-admin, and over the API.
  • Permission-scoped - instructors can only generate into their own courses and quizzes; administrators can manage all.

For developers

AI generation is exposed under the learnomy-pro/v1 REST namespace (admin / allowed-instructor only): POST /ai/propose-outline, POST /ai/generate-course, POST /ai/propose-questions, POST /ai/generate-questions, POST /ai/summarize-report, and the admin-only read-only GET /ai/usage.

Useful hooks:

  • learnomy_pro_ai_request_timeout (filter) - HTTP timeout in seconds for AI calls (default 60).
  • learnomy_pro_ai_generate_text (filter) - short-circuit the model call (return a string) to inject a custom transport or a test response.
  • learnomy_pro_ai_overage (action) - fires when an instructor generates past a limit under "Let them continue" mode ($user_id, $kind, $used, $limit); hook it to email or Slack your admins.

Something unclear? Open a support ticket → · Refund policy

Buy Learnomy