Most people think building an online academy means picking a theme, installing TutorLMS, and calling it a day. What actually happens is messier — broken redirects, students landing on wp-admin, and a subscription wall that lets paid content slip through to guests. I ran into every single one of these problems while building Talweeh Arabic, a fully Arabic-language online learning platform. The fix was not a premium theme upgrade.
A WordPress Academy Website built to production standard requires surgical PHP customizations layered on top of TutorLMS, Elementor, and WooCommerce. In this post I document five micro-plugins I wrote for Talweeh Arabic, explaining the exact problem each one solves, the logic behind the code, and how those pieces fit together into a locked-down, conversion-optimized LMS. If you want the same system built for your academy, you can hire me directly at hasnainayaz.com.
A WordPress Academy Website is a self-hosted e-learning platform built on WordPress, combining an LMS plugin such as TutorLMS or LearnDash with a page builder like Elementor, a payment layer, and custom PHP logic to gate content, manage student sessions, and deliver a branded learning experience.
Before diving into the plugins, here is the exact stack this entire system runs on. Every plugin below is written to extend this foundation, not replace it.
wp_tutor_subscriptions)One reason this stack works so well is that Elementor exposes global CSS variables like --e-global-color-primary and --e-global-typography-primary-font-family. Every custom UI element described below reads those variables at runtime, so any rebrand in Elementor automatically cascades into the custom overlays and modals without touching PHP.
The problem: Logged-in students who had not yet purchased a subscription could freely browse premium pages like /courses/module-1/ through /courses/module-5/, /resources/, and /start-learning/. TutorLMS only gates individual course lessons, not arbitrary WordPress pages.
The solution: Talweeh_Membership_Overlay hooks into template_redirect (priority 1) and checks three conditions in sequence before deciding to render the gate:
status = 'active' in wp_tutor_subscriptions?If all three conditions pass, a full-viewport overlay is injected into wp_footer at priority 999 — after all theme content has rendered. The overlay uses backdrop-filter: blur(16px) to obscure the page behind it, preventing content scraping via inspect-element, and locks scroll with a single JS class toggle (body.talweeh-no-scroll).
The card UI reads Elementor's global color and typography tokens (--e-global-color-primary, --e-global-color-accent) so it matches the site brand automatically.
<?php
/**
* Plugin Name: Talweeh Membership Overlay
* Description: Shows a premium overlay on specific pages for students
* without an active TutorLMS subscription.
* Version: 1.1.0
*/
defined('ABSPATH') || exit;
if (!defined('TALWEEH_MEMBERSHIP_URL')) {
define('TALWEEH_MEMBERSHIP_URL', '/membership/');
}
final class Talweeh_Membership_Overlay {
private $show_overlay = false;
public function __construct() {
add_action('template_redirect', [ $this, 'maybe_flag_overlay' ], 1);
add_action('wp_footer', [ $this, 'render_overlay' ], 999);
}
private function get_target_paths() {
return apply_filters('talweeh_overlay_target_paths', [
'/start-learning/',
'/help-info/',
'/resources/',
'/courses/module-1/',
'/courses/module-2/',
'/courses/module-3/',
'/courses/module-4/',
'/courses/module-5/',
]);
}
public function maybe_flag_overlay() {
if (is_admin() || !is_user_logged_in()) return;
$user = wp_get_current_user();
if (in_array('administrator', (array) $user->roles, true)) return;
if (in_array('tutor_instructor', (array) $user->roles, true)) return;
$request_path = trailingslashit(wp_parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
$is_target = false;
foreach ($this->get_target_paths() as $path) {
if (trailingslashit($path) === $request_path) { $is_target = true; break; }
}
if (!$is_target) return;
if ($this->user_has_active_subscription($user->ID)) return;
$this->show_overlay = true;
}
private function user_has_active_subscription($user_id) {
global $wpdb;
$table = $wpdb->prefix . 'tutor_subscriptions';
if (!$wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table))) return false;
return (int) $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$table} WHERE user_id = %d AND status = %s",
$user_id, 'active'
)) > 0;
}
public function render_overlay() {
if (!$this->show_overlay) return;
$membership_url = esc_url(apply_filters('talweeh_membership_url', TALWEEH_MEMBERSHIP_URL));
// Full overlay HTML/CSS/JS injected here (see full source)
}
}
new Talweeh_Membership_Overlay();
For deep dives into TutorLMS subscription table schema, the official documentation is a good starting point: TutorLMS Developer Docs
The problem: Logged-in students who visited /student-login/ or /student-registration/ were shown the login and registration forms again — a confusing dead end that hurt UX and produced duplicate session warnings.
The solution: A single template_redirect action that checks is_user_logged_in() first, then is_page() against a slug array. If both match, it fires wp_redirect() to /start-learning/ and exits. Administrators are not excluded here because they rarely visit those pages from the front-end — but you could add a role check identical to Plugin 1 if needed.
add_action('template_redirect', 'talweeh_redirect_logged_in_access');
function talweeh_redirect_logged_in_access() {
if (is_user_logged_in()) {
$restricted_pages = ['student-login', 'student-registration'];
if (is_page($restricted_pages)) {
wp_redirect('https://talweeharabic.com/start-learning/');
exit;
}
}
}
Why this matters for SEO and UX: Serving a login form to an already-authenticated user inflates bounce rate and can confuse crawlers that follow internal links. A hard 302 redirect is the cleanest fix.
The problem: Every logged-in user, including students and instructors, was seeing the WordPress admin bar at the top of every front-end page. This exposed WordPress internals, broke the Elementor-designed header layout, and looked deeply unprofessional.
The solution: A show_admin_bar filter returns false for any user whose role appears in the $student_roles array. The check for tutor_utils() existence ensures the code degrades gracefully if TutorLMS is deactivated. Administrators always keep their admin bar.
/**
* Plugin Name: Talweeh Hide Admin Bar for Students
* Description: Hides the WP admin bar for subscriber, student, and instructor roles.
* Version: 1.0.0
*/
defined('ABSPATH') || exit;
add_filter('show_admin_bar', function ($show) {
if (!is_user_logged_in()) return $show;
$user = wp_get_current_user();
$student_roles = ['subscriber', 'tutor_instructor', 'student'];
if (function_exists('tutor_utils')) {
$student_roles[] = 'tutor_student';
}
foreach ($student_roles as $role) {
if (in_array($role, (array) $user->roles, true)) {
return false;
}
}
return $show;
}, 999);
This is one of those small changes that makes a site feel 10x more polished. Elementor's sticky header designs always break when #wpadminbar pushes the page down 32px. Removing it entirely eliminates the offset without needing body { margin-top: 0 !important; } hacks in the CSS.
The problem: TutorLMS creates a /dashboard/enrolled-courses/ page by default. For Talweeh Arabic, the learning hub lives at /start-learning/ — a custom Elementor-built page with module cards, progress indicators, and resource links. Having two separate "student home" URLs was confusing and split internal link equity.
The solution: Two hooks in one file. The template_redirect action catches any visit to the exact path /dashboard/enrolled-courses/ and issues a 302 to /start-learning/. The tutor_dashboard/nav_items filter renames the sidebar link from "Enrolled Courses" to "Student Portal" so the sidebar label stays accurate after the redirect.
/**
* Plugin Name: Talweeh Enrolled Courses Redirect
* Description: Redirects the TutorLMS Enrolled Courses page to Start Learning.
* Version: 1.0.0
*/
defined('ABSPATH') || exit;
add_action('template_redirect', function () {
$path = trailingslashit(wp_parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
if ($path === '/dashboard/enrolled-courses/') {
wp_redirect('https://talweeharabic.com/start-learning/', 302);
exit;
}
});
add_filter('tutor_dashboard/nav_items', function ($items) {
if (isset($items['enrolled-courses'])) {
$items['enrolled-courses']['title'] = __('Student Portal', 'talweeh-overlay');
}
return $items;
});
This is a clean demonstration of how to use TutorLMS's own filter system to reshape the student dashboard without touching template files. The TutorLMS hooks reference documents dozens of similar filters for further customization.
The problem: After login or registration, students were landing on /dashboard/ — the default TutorLMS dashboard — instead of /start-learning/. This happened via five distinct authentication paths: standard WordPress login, WordPress registration, TutorLMS native login, WooCommerce login, and WooCommerce registration. Each has its own filter. Patching one and missing another meant some students still landed on the wrong page.
The solution: One plugin file with five targeted filters plus a template_redirect fallback that catches any residual /dashboard/ redirect immediately post-login. The helper talweeh_login_target_url() centralizes the destination URL so it is changed in exactly one place.
/**
* Plugin Name: Talweeh Login & Registration Redirect
* Description: Sends all users to /start-learning/ after login or registration.
* Version: 1.1.0
*/
defined('ABSPATH') || exit;
function talweeh_login_target_url() {
return home_url('/start-learning/');
}
// 1. Standard WP login
add_filter('login_redirect', function ($redirect_to, $requested, $user) {
if (isset($user->roles) && in_array('administrator', (array) $user->roles, true)) return $redirect_to;
return talweeh_login_target_url();
}, 9999, 3);
// 2. Standard WP registration
add_filter('registration_redirect', fn() => talweeh_login_target_url(), 9999);
// 3. TutorLMS login
add_filter('tutor_after_login_redirect_url', function () {
$user = wp_get_current_user();
if ($user && in_array('administrator', (array) $user->roles, true)) {
return tutor_utils()->tutor_dashboard_url();
}
return talweeh_login_target_url();
}, 9999);
// 4. WooCommerce login
add_filter('woocommerce_login_redirect', function ($redirect, $user) {
if (in_array('administrator', (array) $user->roles, true)) return $redirect;
return talweeh_login_target_url();
}, 9999, 2);
// 5. WooCommerce registration
add_filter('woocommerce_registration_redirect', fn() => talweeh_login_target_url(), 9999);
// Fallback: catch /dashboard/ immediately post-login
add_action('template_redirect', function () {
if (!is_user_logged_in()) return;
$user = wp_get_current_user();
if (in_array('administrator', (array) $user->roles, true)) return;
$path = trailingslashit(wp_parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
$referer = $_SERVER['HTTP_REFERER'] ?? '';
if ($path === '/dashboard/' && (
strpos($referer, 'wp-login.php') !== false ||
strpos($referer, '/login/') !== false ||
strpos($referer, 'action=login') !== false ||
strpos($referer, '/register/') !== false
)) {
wp_redirect(talweeh_login_target_url(), 302);
exit;
}
}, 1);
The 9999 priority ensures these filters always fire after any TutorLMS or WooCommerce internal redirects that might set their own destination earlier in the hook chain.
Here is the complete student journey on a fully configured Talweeh Arabic installation and which plugin handles each step:
/student-registration/ and registers. Plugin 5 fires on registration and sends them to /start-learning/./student-login/. Plugin 2 checks they are already logged in and redirects immediately to /start-learning/./courses/module-2/. Plugin 1 queries wp_tutor_subscriptions, finds no active row, and renders the membership overlay over the page content./start-learning/./courses/module-2/. Plugin 1 queries wp_tutor_subscriptions again, finds the active row, and shows nothing — the page renders normally./start-learning/.To keep the WordPress Academy Website content-gating maintainable as the course library grows, follow these conventions when adding new paths to Plugin 1:
/courses/module-6/ not courses/module-6talweeh_overlay_target_paths filter from your child theme's functions.php, not by editing the plugin file directlyTALWEEH_MEMBERSHIP_URL constant in wp-config.php so it survives plugin updates// Add new protected paths from functions.php
add_filter('talweeh_overlay_target_paths', function ($paths) {
$paths[] = '/courses/module-6/';
$paths[] = '/bonus-content/';
return $paths;
});
A production-grade WordPress Academy Website is not just a TutorLMS installation with a page builder theme slapped on top. It is a coordinated system of micro-plugins, each solving a specific UX or security gap that out-of-the-box tools leave open. The five plugins documented here — membership overlay, auth-page redirect, admin-bar removal, dashboard redirect, and unified login routing — collectively cover the entire student session from first registration to active learning, and every piece of code is filter-extensible so it grows with the platform.
Talweeh Arabic is live proof this stack works at scale for a real multilingual learning community. If you are building an academy and want this level of custom engineering applied to your project, I would love to work with you.
Ready to build your WordPress Academy Website? Visit hasnainayaz.com to see my portfolio, read case studies, and get in touch. Drop a comment below if you have questions about any of the plugins above — I read every reply.