# How to Integrate Lovable with Moodle

- Tool: Lovable
- Difficulty: Intermediate
- Time required: 45 minutes
- Last updated: March 2026

## TL;DR

Integrate Moodle with Lovable by creating an Edge Function that calls Moodle's Web Services REST API using a token stored in Cloud → Secrets. Moodle exposes courses, users, grades, assignments, and completion data through a function-based API system. You can build custom learning portals, grade dashboards, and enrollment management tools on top of any self-hosted or cloud Moodle instance.

## Build custom Moodle frontends and dashboards in Lovable

Moodle is the most widely deployed open-source LMS in the world, powering universities, K-12 schools, and corporate training programs across more than 240 countries. Unlike SaaS platforms, Moodle is self-hosted — your institution runs its own Moodle instance on its own servers. This means the integration points to your specific Moodle installation URL, not a shared API endpoint. The trade-off is complete data ownership and fine-grained configuration control.

Moodle's Web Services API uses a distinctive calling pattern: instead of different URL paths for different resources (GET /courses, GET /grades), all requests go to a single endpoint — typically yoursite.edu/webservice/rest/server.php — with the specific operation specified as a wsfunction query parameter. For example, to get all courses a user is enrolled in, you call ?wstoken={token}&wsfunction=core_enrol_get_users_courses&userid={id}&moodlewsrestformat=json. Learning this pattern is the key to working with Moodle's API.

For Lovable developers, this means a single generic Edge Function can handle all Moodle API operations by accepting the wsfunction name and parameters as inputs. Your frontend components call the Edge Function with the specific Moodle function they need, and the Edge Function constructs the correct Moodle API request, injects the token, and returns the data. This architecture is both secure (token never leaves the server) and flexible (works with any Moodle web service function your instance has enabled).

## Before you start

- A Lovable account with at least one project created and deployed
- A running Moodle instance (self-hosted or Moodle Cloud) with admin access
- Moodle version 2.2 or later (Web Services are available in all modern versions)
- The URL of your Moodle installation (e.g., https://learn.myschool.edu)
- Moodle admin or manager role to enable Web Services and create tokens

## Step-by-step guide

### 1. Enable Moodle Web Services and create an API token

Before storing credentials in Lovable, you need to enable Moodle's Web Services feature and create a token for API access. This is done entirely in your Moodle admin panel.

Log in to your Moodle site as an administrator. Go to Site Administration (the cog icon or Administration menu). Navigate to Advanced Features. Find the 'Enable web services' toggle and turn it on. Click 'Save changes'.

Next, enable the REST protocol. Go to Site Administration → Plugins → Web services → Manage protocols. Find 'REST protocol' in the list and click the eye icon to enable it. This activates the /webservice/rest/server.php endpoint.

Now create a web service. Go to Site Administration → Plugins → Web services → External services. Click 'Add'. Name it (e.g., 'Lovable Integration'). Enable it. Under 'Functions', click the service you just created, then 'Add functions'. Add the functions your app needs — common ones include: core_course_get_courses, core_enrol_get_users_courses, core_enrol_get_enrolled_users, gradereport_user_get_grade_items, mod_assign_get_assignments, mod_assign_get_submissions, core_user_get_users. Save.

Finally, create a token. Go to Site Administration → Plugins → Web services → Manage tokens. Click 'Add'. Select the user whose permissions the API calls will use (typically an admin account or a dedicated service user). Select the web service you just created. Optionally set an expiry date. Click 'Save changes'. The token is now shown in the token list — copy it.

Note: The token inherits the permissions of the user you selected. Use a dedicated service account user with only the permissions needed for your integration — not your personal admin account.

**Expected result:** Moodle Web Services are enabled. The REST protocol is active. A web service with the required functions is created. A token is generated and ready to store in Lovable's Secrets panel.

### 2. Store Moodle credentials in Cloud → Secrets

With your Moodle token and site URL ready, store them in Lovable's Cloud → Secrets panel. These encrypted environment variables are accessible only from Edge Functions and never from client-side code.

In Lovable, click the '+' icon at the top of the editor to open the Cloud panel. Click the Secrets tab. Click 'Add new secret' and add the following two secrets:

- Name: MOODLE_TOKEN — Value: the web service token you generated in Step 1 (a long alphanumeric string)
- Name: MOODLE_URL — Value: the base URL of your Moodle installation, without trailing slash (e.g., https://learn.myschool.edu)

The MOODLE_URL should be the root URL of your Moodle site — not the API endpoint path. The Edge Function will construct the full API URL by appending /webservice/rest/server.php.

Verify the MOODLE_URL is accessible from the public internet. If your Moodle site is behind a firewall or VPN, the Edge Function (which runs on Deno Deploy's infrastructure) will not be able to reach it. For internal Moodle instances, you may need to whitelist Supabase/Deno Deploy IP ranges in your firewall, or expose a specific API proxy endpoint publicly.

Lovable's security system actively prevents approximately 1,200 API keys from being hardcoded in application code per day. Using Cloud → Secrets ensures your Moodle token is encrypted and isolated — it is only readable by Edge Functions via Deno.env.get(), not from any frontend code or GitHub repository.

**Expected result:** MOODLE_TOKEN and MOODLE_URL are stored in Cloud → Secrets with masked values. Your Edge Function will read them via Deno.env.get('MOODLE_TOKEN') and Deno.env.get('MOODLE_URL').

### 3. Create the Moodle Web Services proxy Edge Function

Now create the Edge Function that proxies Moodle Web Services requests. Moodle's API uses GET requests with query parameters rather than a JSON body — each call includes wstoken (the API token), wsfunction (the Moodle function to call), and moodlewsrestformat=json (to get JSON responses instead of XML).

Paste this prompt into Lovable's chat to generate the Edge Function:

'Create a Supabase Edge Function at supabase/functions/moodle-api/index.ts. It should accept a POST request with JSON body { wsfunction: string, params: object }. The function should read MOODLE_TOKEN and MOODLE_URL from Deno.env. Construct a GET request to {MOODLE_URL}/webservice/rest/server.php with query parameters: wstoken={token}, wsfunction={wsfunction}, moodlewsrestformat=json, and all entries from params as additional query parameters. Return the Moodle API response as JSON with CORS headers. Handle Moodle exception responses (which have errorcode field instead of normal data) by returning them as 4xx HTTP responses with the Moodle error message.'

Moodle API error responses look like: { exception: 'dml_missing_record_exception', errorcode: 'invalidrecord', message: 'Can not find data record...' }. These still return HTTP 200 from Moodle's server — your Edge Function should check for the exception field and return an appropriate error HTTP status (400 or 404) with the error details.

Common Moodle functions and their required parameters:
- core_course_get_courses — no required params (returns all courses)
- core_enrol_get_users_courses — userid (integer)
- core_enrol_get_enrolled_users — courseid (integer)
- gradereport_user_get_grade_items — courseid (integer), userid (integer)
- mod_assign_get_assignments — courseids[0], courseids[1], etc.
- core_user_get_users — criteria[0][key]='email', criteria[0][value]='student@school.edu'

```
// supabase/functions/moodle-api/index.ts
const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};

Deno.serve(async (req) => {
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders });
  }

  try {
    const moodleToken = Deno.env.get('MOODLE_TOKEN');
    const moodleUrl = Deno.env.get('MOODLE_URL');

    if (!moodleToken || !moodleUrl) {
      return new Response(
        JSON.stringify({ error: 'Moodle credentials not configured' }),
        { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
      );
    }

    const { wsfunction, params = {} } = await req.json();

    if (!wsfunction) {
      return new Response(
        JSON.stringify({ error: 'wsfunction is required' }),
        { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
      );
    }

    const queryParams = new URLSearchParams({
      wstoken: moodleToken,
      wsfunction,
      moodlewsrestformat: 'json',
    });

    // Add all additional params
    for (const [key, value] of Object.entries(params)) {
      queryParams.set(key, String(value));
    }

    const apiUrl = `${moodleUrl}/webservice/rest/server.php?${queryParams.toString()}`;

    const response = await fetch(apiUrl, {
      headers: { 'User-Agent': 'LovableApp/1.0' },
    });

    const data = await response.json();

    // Moodle returns errors as HTTP 200 with exception field
    if (data && data.exception) {
      return new Response(
        JSON.stringify({ error: data.message, errorcode: data.errorcode }),
        { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
      );
    }

    return new Response(JSON.stringify(data), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    });
  } catch (error) {
    return new Response(
      JSON.stringify({ error: error.message }),
      { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
    );
  }
});
```

**Expected result:** The Moodle proxy Edge Function is deployed and visible in the Cloud tab. When called with a valid wsfunction and params, it returns data from your Moodle instance. Moodle error responses are converted to HTTP error status codes.

### 4. Build course and grade display components

With the Edge Function deployed, build the React components that display Moodle data in your Lovable app. The architecture is the same for all Moodle data: call the Edge Function with the desired wsfunction, handle loading and error states, and render the response data.

Paste this prompt into Lovable's chat:

'Create a MoodleDashboard React component with two sections: (1) My Courses: call the moodle-api Edge Function with wsfunction core_enrol_get_users_courses and params { userid: currentMoodleUserId } to fetch enrolled courses. Display each course as a card with fullname, shortname, category, and a progress percentage if available from wsfunction core_completion_get_course_completion_status. (2) Recent Grades: call the moodle-api Edge Function with wsfunction gradereport_user_get_grade_items and params { courseid: selectedCourseId, userid: currentMoodleUserId } for the currently selected course. Show grades in a table with item name, grade value, max grade, and percentage. Highlight failing grades (below 50%) in red.'

You will need to map your Lovable app's logged-in user to a Moodle user ID. The cleanest way is to store the Moodle user ID in your Supabase user profile table. When a user first connects, call wsfunction core_user_get_users with criteria[0][key]=email and criteria[0][value]={userEmail} to look up their Moodle ID and store it in your profiles table.

For the grade display, Moodle returns grades as a graderaw field (raw numeric value), grademin, grademax, and gradeformatted (display string). Use gradeformatted for display as it includes Moodle's configured grade formatting (letter grades, percentages, etc.).

For complex Moodle integrations across multiple sites or with custom Moodle plugins, RapidDev's team can help configure the web service functions and data mapping for your specific institutional setup.

**Expected result:** A Moodle dashboard appears in your app showing the user's enrolled courses. Selecting a course loads the grade table with real data from your Moodle instance. The display correctly formats grades and highlights any failing marks.

### 5. Add assignment tracking and completion monitoring

Extend the dashboard with assignment tracking — showing upcoming assignments with due dates, submission status, and completion data across courses. This requires calling several Moodle functions in sequence.

Paste this prompt into Lovable's chat:

'Add an Upcoming Assignments section to the MoodleDashboard. For each enrolled course, call the moodle-api Edge Function with wsfunction mod_assign_get_assignments and params { courseids[0]: courseId } to get assignments. Filter assignments with duedate greater than the current timestamp. Show them in a timeline view sorted by duedate: assignment name, course shortname, due date, and a status badge showing SUBMITTED, DUE SOON (within 3 days), or UPCOMING. Get submission status by calling wsfunction mod_assign_get_submission_status with params { assignid: assignmentId } for each assignment.'

Moodle assignment due dates are Unix timestamps (seconds, not milliseconds). Convert them with new Date(duedate * 1000) before displaying or comparing with the current date.

For completion tracking at the course level (useful for showing overall progress), use wsfunction core_completion_get_activities_completion_status with params { courseid: courseId, userid: userId }. This returns a list of all activities in the course and their completion status for the given user.

Be mindful of Moodle API call volume. Loading assignments for 10 courses requires 10+ separate API calls. Consider batching course IDs in mod_assign_get_assignments (it accepts multiple courseids) rather than calling it once per course. The function accepts courseids[0], courseids[1], etc. — you can pass up to 10-20 courses in a single call.

**Expected result:** The dashboard shows upcoming assignments across all enrolled courses with due dates and status indicators. Overdue assignments are highlighted in red. The data reflects the actual assignment state in Moodle including any already-submitted items.

## Best practices

- Enable only the Moodle web service functions your app actually needs — each additional function expands the security surface area, so add only what is required
- Use a dedicated Moodle service user with limited permissions rather than an admin account token — this limits the damage if the token is ever compromised
- Cache frequently accessed Moodle data (course lists, user profiles) in your Supabase database with a refresh interval rather than calling Moodle on every page load
- Handle Moodle's Unix timestamp format (seconds) consistently — always multiply by 1000 when creating JavaScript Date objects from Moodle date fields
- Check that your Moodle token is not set to expire (or set a generous expiry) before deploying to production — expired tokens silently fail with invalidtoken errors
- Add the moodlewsrestformat=json parameter to all API calls — without it, Moodle returns XML by default which is harder to parse in TypeScript
- For high-traffic dashboards, implement Supabase-based caching with a last_synced_at column and only refresh data older than 5-10 minutes to avoid overwhelming your Moodle instance
- Test all wsfunction calls against your specific Moodle version — function availability and parameter formats vary between Moodle 3.x and 4.x versions

## Use cases

### Student course and grade portal

Build a personalized student portal where students see their enrolled courses, current grades, upcoming assignments, and completion progress — a cleaner, faster interface than Moodle's default UI.

Prompt example:

```
Create a student portal that calls my moodle-api Edge Function with wsfunction core_enrol_get_users_courses to get the logged-in student's enrolled courses. Show courses as cards with name, category, and progress percentage (using wsfunction core_completion_get_course_completion_status). Clicking a course shows grades from gradereport_user_get_grade_items. Display an upcoming assignments widget using wsfunction mod_assign_get_assignments filtered to assignments with duedate in the next 7 days.
```

### Teacher grade book dashboard

Build a teacher-facing dashboard that aggregates grade data across all their courses, shows submission rates for each assignment, and highlights students at risk of failing based on low grades or missing submissions.

Prompt example:

```
Build a grade book dashboard for teachers. Fetch the teacher's courses with wsfunction core_course_get_courses. For each course, get all enrolled students with wsfunction core_enrol_get_enrolled_users and their grades with gradereport_user_get_grades_table. Flag students with an average grade below 60% in red. Show submission counts for each assignment using wsfunction mod_assign_get_submissions. Export the grade data to CSV when the teacher clicks Export.
```

### Enrollment management admin panel

Build an administrative interface for managing student enrollments — bulk enrolling students into courses, unenrolling them, and tracking enrollment history across the institution.

Prompt example:

```
Create an enrollment management panel that lists all courses from wsfunction core_course_get_courses. Admins can search students by name or email using wsfunction core_user_get_users, see their current enrollments, and enroll or unenroll them from courses using wsfunction enrol_manual_enrol_users and wsfunction enrol_manual_unenrol_users. Show a confirmation modal before bulk changes and log all enrollment actions to a Supabase audit table.
```

## Troubleshooting

### Edge Function returns 'invalidtoken' error from Moodle

Cause: The MOODLE_TOKEN secret is incorrect, has been revoked in Moodle, or the web service associated with the token was disabled.

Solution: Go to your Moodle admin panel → Site Administration → Plugins → Web Services → Manage tokens. Verify the token still exists and is not expired. If it was revoked, create a new token and update the MOODLE_TOKEN secret in Lovable's Cloud → Secrets. Also check that the web service associated with the token is still enabled in Site Administration → Plugins → Web Services → External services.

### The Edge Function returns 'accessexception: Access control exception' from Moodle

Cause: The web service token user does not have permission to call the requested wsfunction, or the function was not added to the web service in Moodle's admin panel.

Solution: In Moodle admin, go to Site Administration → Plugins → Web Services → External services. Click on your service and then 'Functions'. Verify the function you are calling (e.g., gradereport_user_get_grade_items) appears in the list. If not, click 'Add functions' and add it. Also verify the token user has the appropriate Moodle role to access the data — a student token cannot read other students' grades.

### API calls time out or return network errors in the Edge Function

Cause: The Moodle instance is behind a firewall, on a private network, or uses a self-signed SSL certificate that Deno's fetch does not accept.

Solution: Verify your Moodle URL is accessible from the public internet by pasting it in a browser outside your school network. If Moodle is internal-only, you need to either expose the /webservice/rest/server.php endpoint publicly (via firewall rule or reverse proxy) or set up a VPN connection — which is not possible in Lovable's Edge Function environment. For self-signed SSL, you cannot disable certificate validation in Deno without custom flags, so the best solution is to install a proper Let's Encrypt certificate on your Moodle server.

### grade data returns null or empty even though the student has grades in Moodle

Cause: The gradereport_user_get_grade_items function requires both courseid and userid parameters. Calling it without a valid userid returns empty results.

Solution: Verify you are passing the correct Moodle user ID (integer) as the userid parameter, not a UUID or email address. If you are looking up users by email first with core_user_get_users, check that the user exists in Moodle and that your token user has permission to read user data. Also verify that grades are visible to the token user's role — Moodle's grade visibility settings can hide grades from non-teacher accounts even via the API.

## Frequently asked questions

### Does Moodle have a native Lovable connector?

No. Moodle is not one of Lovable's 17 shared connectors as of March 2026. You integrate it manually using Moodle's Web Services REST API with a token stored in Cloud → Secrets and a Deno Edge Function as a proxy. This tutorial covers the complete setup from enabling Moodle Web Services to building dashboard components.

### Do I need to be a Moodle admin to use the API?

You need admin access to enable Web Services and create the initial web service configuration and token in the Moodle admin panel. Once set up, the token can be used with any Moodle user account's permissions. For read operations like fetching course lists and grades, a teacher or manager account is often sufficient. For enrollment management, an admin or manager account with enrolment permissions is required.

### Does this integration work with Moodle Cloud (the hosted version)?

Yes. Moodle Cloud (Moodle's own hosted offering) supports Web Services with the same configuration steps. Log in to your Moodle Cloud site as admin, follow the same Web Services setup process, and use your Moodle Cloud site URL (e.g., https://yourschool.moodlecloud.com) as MOODLE_URL. Third-party Moodle hosting providers like Bitnami, Pantheon, or school-hosted instances also work the same way.

### How do I handle Moodle's complex array parameters in API calls?

Moodle uses PHP array notation for multi-value parameters: courseids[0]=123&courseids[1]=456. In your Edge Function, when you need to pass an array, expand it into indexed URL parameters before adding to URLSearchParams. For example, for an array of course IDs: courseIds.forEach((id, i) => queryParams.set(`courseids[${i}]`, String(id))). This matches Moodle's expected parameter format.

### What Moodle version is required for the Web Services API?

Moodle Web Services are available from Moodle 2.2 onwards. All modern Moodle installations (3.x and 4.x) fully support them. If you are on a very old Moodle installation, upgrading to at least Moodle 3.9 is recommended for API stability and security. Some functions like core_completion_get_course_completion_status require Moodle 3.6 or later.

---

Source: https://www.rapidevelopers.com/lovable-integration/moodle
© RapidDev — https://www.rapidevelopers.com/lovable-integration/moodle
