Lw lms rest frontend
Skill Lonsdale201/wp-agent-skills/lw-plugins/lw-lms-rest-frontend
A community-maintained collection of agent skills for WordPress plugin and theme development.
npx -y skills add Lonsdale201/wp-agent-skills --skill lw-lms-rest-frontendAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 21 stars21 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Build a custom frontend against LW LMS's headless `/wp-json/lms/v1` REST API in lw-lms v1.6.0. Use for React, Vue, Astro, mobile, or theme code that calls `/courses`, `/courses/{id}`, `/lessons/{id}`, `/progress`, `/progress/course/{id}`, or `/download/{id}` and must handle public course content, lesson/access gating, paid-course products/subscriptions/subscription_variations/memberships, custom paid-access decisions, progress payloads, downloads, and nonce/app-password auth.
SKILL.md
9.6 KB, as published. Nobody here has run it
LW LMS: REST frontend consumer
For frontend developers consuming LW LMS data: course catalog, course detail, lesson player, progress dashboard, and protected downloads. The core lw-lms plugin ships a headless REST API; it does not ship public templates, shortcodes, or blocks.
BETA NOTICE. The plugin README says the plugin is under active development and not recommended for production use. Snapshot the JSON shapes in tests and review
CHANGELOG.mdbefore upgrading. This skill is verified against local lw-lms v1.6.0.
Version deltas that matter
- v1.6.0: no route or JSON-schema change. A server-side
lw_lms_has_course_accesscallback can now alter the final logged-in paid-course access decision after built-in access checks, which affectsaccess.has_access, lesson accessibility, attachments, lesson detail, progress writes, and protected downloads. - v1.5.1: maintenance release, no functional REST changes.
- v1.5.0: paid-course denied
accesspayload can includemembershipswhen WooCommerce Memberships is active and the course has_lw_lms_membership_plan_ids. - v1.4.0: course
contentin/courses/{id}is public marketing/about content. It is no longer gated behindaccess.has_access. Lesson content is still gated by/lessons/{id}. - v1.4.0: open-course lessons remain accessible to guests even if marked as preview.
- v1.3.0: first logged-in access to a free course lazily writes a
source='free'access row server-side; the REST consumer does not need to do anything special. - v1.2.15: denied paid-course
accesspayload can includesubscription_variations.
Misconception this skill corrects
"I should use
course.contentas the access gate."
Wrong for v1.4.0+. CourseTransformer::transform_full() always includes the course content; this is the public course description. Use course.access.has_access and each lesson's accessible flag for gating.
// WRONG: course content is public and is not proof of access.
if (course.content) {
renderLessonPlayer();
}
// RIGHT: access comes from access.has_access and per-lesson accessible.
if (course.access.has_access) {
return <CourseContent html={course.content} progress={course.progress} />;
}
return <PurchaseGate access={course.access} />;
Lesson body content still comes from GET /lms/v1/lessons/{id} and returns 403 forbidden when AccessChecker::has_lesson_access() denies the request.
In v1.6.0, a companion plugin may provide the final logged-in paid-course decision through lw_lms_has_course_access. Frontend code should continue to trust the server response; it must not reproduce membership or entitlement rules in JavaScript. Backend callbacks must be deterministic: the full-course transformer checks course access twice, so a changing result can make access.has_access disagree with lesson/attachment gating in one response.
REST API surface
Namespace: lms/v1, mounted at /wp-json/lms/v1/....
| Method | Path | Auth | Purpose |
|---|---|---|---|
GET | /lms/v1/courses | public | Paginated public course list |
GET | /lms/v1/courses/{id} | public | Single course detail; course content is public, lesson rows carry accessible |
GET | /lms/v1/lessons/{id} | public route, access-gated body | Single lesson; 403 without lesson access |
GET | /lms/v1/progress | logged-in | Current user's all progress rows |
GET | /lms/v1/progress/course/{id} | logged-in | Current user's progress rows for one course plus course summary progress |
POST | /lms/v1/progress | logged-in + lesson access | Upsert lesson status |
GET | /lms/v1/download/{id} | public route, access-gated file | Stream protected attachment binary |
Detailed response contract
Read references/rest-response-contract.md when implementing request parameters, full JSON shapes, nullable fields, lesson errors, progress validation, or binary downloads. The route/auth/access decisions and client safety rules remain in this main skill.
Authentication
| Context | Auth pattern |
|---|---|
| Public catalog and course detail | no auth |
| Logged-in browser UI | WordPress cookie + X-WP-Nonce, usually via wp.apiFetch |
| Headless/mobile | Application Password Basic auth, or a vetted JWT/OAuth layer |
| Downloads | same auth context as the protected course/lesson |
For browser fetch, include both nonce and credentials:
await fetch('/wp-json/lms/v1/progress', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': wpApiSettings.nonce
},
body: JSON.stringify({ course_id: 42, lesson_id: 100, status: 'completed' })
});
Critical rules
- Course
contentis public in v1.4.0+; never use it as the access gate. - A server-side v1.6.0 paid-access filter may change access without creating an enrollment row; the REST response remains the authority for the client.
- Lesson detail and download endpoints are the protected surfaces.
- Iterate both
sectionsandlessons_without_section. - Render locked lessons disabled, not hidden.
access.membershipsis available only when WooCommerce Memberships functions exist and linked plans are configured.- List endpoint access info is intentionally small; build purchase gates from the single-course response.
course_progressusescompleted_lessons, notcompleted_count.completed_atisnullfor non-completed rows.content_rawis editor-only source content; do not render it in public UI.download_urlis already a full REST URL; do not reconstruct it by concatenating/wp-json.- The download endpoint returns binary data; do not call
response.json()on it.
Common mistakes
// WRONG: only iterating sections.
course.sections.map(section => <Section section={section} />);
// RIGHT: include orphan lessons too.
<>
{course.sections.map(section => <Section key={section.id} section={section} />)}
{course.lessons_without_section.length > 0 && (
<LessonGroup lessons={course.lessons_without_section} />
)}
</>
// WRONG: old pre-1.4 assumption.
const canAccess = Boolean(course.content);
// RIGHT.
const canAccess = course.access.has_access;
// WRONG: progress key from stale docs.
<ProgressBar value={data.course_progress.completed_count} />
// RIGHT.
<ProgressBar value={data.course_progress.completed_lessons} />
// WRONG: POST body missing course_id.
fetch('/wp-json/lms/v1/progress', {
method: 'POST',
body: JSON.stringify({ lesson_id: 100, status: 'completed' })
});
// RIGHT.
fetch('/wp-json/lms/v1/progress', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': wpApiSettings.nonce },
credentials: 'same-origin',
body: JSON.stringify({ course_id: 42, lesson_id: 100, status: 'completed' })
});
Cross-references
- Use
lw-lms-backend-extendfor hooks, access/progress repositories, and companion-plugin backend integration. - Use
lw-lms-abilitiesfor admin/agentlw-lms/*Abilities API calls; those are not the learner-facing REST endpoints. - Use
lw-lms-wp-cli-operationsfor operational WP-CLI course, lesson, enrollment, revoke, and force-complete commands. - Use
lw-lms-learndash-migrationfor the one-time LearnDash migration command.
What this skill does NOT cover
- A separate frontend plugin. This skill documents the core
lw-lmsREST contract only. - Payment processing. Link to WooCommerce products/subscriptions/membership join URLs and let WooCommerce handle checkout.
- Custom REST route registration. Use normal WordPress
register_rest_route()patterns in a companion plugin. - Server-rendered theme templates. You can consume REST server-side, but direct CPT/meta queries are usually simpler in PHP templates.
References
- REST namespace and route registration:
includes/Api/RestApi.php. - Course list/detail routes:
includes/Api/Controllers/CoursesController.php. - Lesson route and 403 behavior:
includes/Api/Controllers/LessonsController.php. - Progress routes and cross-validation:
includes/Api/Controllers/ProgressController.php. - Download route and binary response:
includes/Api/Controllers/DownloadController.php. - Course response shape and public
content:includes/Api/Transformers/CourseTransformer.php. - Lesson response shape:
includes/Api/Transformers/LessonTransformer.php. - Progress row shape:
includes/Api/Transformers/ProgressTransformer.php. - Paid access payload including memberships:
includes/Access/AccessChecker.php,includes/Access/MembershipChecker.php. - Official documentation: https://github.com/lwplugins/lw-lms
- Verified source paths:
wp-content/plugins/lw-lms/includes/Access/SubscriptionVariationChecker.phpwp-content/plugins/lw-lms/includes/Access/WooCommerceChecker.phpwp-content/plugins/lw-lms/includes/Meta/VideoParser.phpwp-content/plugins/lw-lms/includes/Progress/ProgressCalculator.phpwp-content/plugins/lw-lms/CHANGELOG.md
Gives 0 of the 12 instructions most design frontend skills give
Counted across 1,170 of the 1,878 authors here whose files we hold, read 2026-08-06
- use css variables for color consistencyin 73 of 1170, across 24 files
- match implementation complexity to the aesthetic visionin 70 of 1170, across 20 files
- commit to one bold aesthetic direction before codingin 70 of 1170, across 25 files
- add atmospheric background effects and texturesin 58 of 1170, across 10 files
- use unexpected spatial compositions and layoutsin 55 of 1170, across 7 files
- implement real working codein 55 of 1170, across 7 files
- vary themes and aesthetics across different designsin 48 of 1170, across 7 files
- launch chromium in headless modein 47 of 1170, across 4 files
- close the browser when donein 47 of 1170, across 4 files
- run provided scripts with help flag firstin 47 of 1170, across 4 files
- use descriptive selectors for elementsin 47 of 1170, across 4 files
- wait for network idle statein 46 of 1170, across 3 files
Said here and by no other author read
- gate access using access.has_access
- use lesson accessible flag for gating
- iterate sections and lessons_without_section
- render locked lessons disabled
- use completed_lessons for progress
- include course_id and lesson_id in progress posts
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once.