Skip to main content
RapidDev - Software Development Agency
flutterflow-integrationsFlutterFlow API Call

Moodle

Connect FlutterFlow to Moodle using the API Calls panel to build a custom mobile LMS front-end. Create one API Group targeting Moodle's Web Services REST endpoint, then switch behavior using the `wsfunction` query parameter for each call. Because the `wstoken` is an identity credential, route it through a Firebase Cloud Function proxy — never embed it in Dart.

What you'll learn

  • How Moodle's function-based single-URL API works and why it differs from standard REST
  • How to create a 'Moodle WS' API Group in FlutterFlow with shared base params
  • How to route the wstoken through a Firebase Cloud Function proxy
  • How to parse Moodle's JSON responses using JSON Paths and bind them to ListViews
  • How to detect Moodle's silent HTTP-200 error responses using Action Flow conditionals
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate15 min read45 minutesEducationLast updated July 2026RapidDev Engineering Team
TL;DR

Connect FlutterFlow to Moodle using the API Calls panel to build a custom mobile LMS front-end. Create one API Group targeting Moodle's Web Services REST endpoint, then switch behavior using the `wsfunction` query parameter for each call. Because the `wstoken` is an identity credential, route it through a Firebase Cloud Function proxy — never embed it in Dart.

Quick facts about this guide
FactValue
ToolMoodle
CategoryEducation
MethodFlutterFlow API Call
DifficultyIntermediate
Time required45 minutes
Last updatedJuly 2026

Building a Custom Moodle Mobile App with FlutterFlow

Moodle's Web Services API surprises developers who expect standard REST. Instead of separate resource URLs like `/courses` or `/users`, every single call goes to one endpoint — `/webservice/rest/server.php` — and you specify what you want with a `wsfunction` query parameter such as `core_course_get_courses`, `core_user_get_users`, or `gradereport_user_get_grade_items`. Add `moodlewsrestformat=json` as a shared param and every response comes back as JSON instead of XML. This design maps elegantly to FlutterFlow's API Group model: one group, many calls, shared base URL.

The authentication model is equally distinctive. Moodle issues a per-user Web Services token (wstoken) from Site administration → Server → Web services. This token acts as the user's full identity — it can read grades, user data, and course content depending on which functions are added to its External service. Because the token is so powerful, it must never travel inside your compiled Flutter app. Anyone who extracts the APK could extract the token. The solution is a Firebase Cloud Function (or Supabase Edge Function) that holds the token as a server-side secret and proxies every Moodle API call on behalf of the app.

Moodle is open-source and free to self-host; Moodle Cloud (hosted) is available on paid plans. Since it is self-hosted by each institution, the base URL, rate limits, and available Web Service functions all vary per installation. Always confirm Web services and the REST protocol are enabled in Site administration before building, and verify that each `wsfunction` you plan to use has been added to the token's External service — otherwise every call returns `accessexception` regardless of token validity.

Integration method

FlutterFlow API Call

Moodle exposes all functionality through a single Web Services REST endpoint at `/webservice/rest/server.php`. Every call hits the same URL; the `wsfunction` query parameter determines the operation (e.g., `core_course_get_courses` or `gradereport_user_get_grade_items`). In FlutterFlow you model this as one API Group with a shared base URL and per-call `wsfunction` variables. Because the `wstoken` authenticates the user, it must never live in client Dart — proxy every request through a Firebase Cloud Function or Supabase Edge Function that injects the token server-side.

Prerequisites

  • A running Moodle installation (self-hosted or Moodle Cloud) with admin access
  • Web services and the REST protocol enabled in Site administration → Server → Web services
  • A Moodle Web Services token with the needed functions added to its External service
  • A Firebase project (for Cloud Functions) or a Supabase project (for Edge Functions) to act as the proxy
  • A FlutterFlow project with Firebase connected (or Supabase) for the proxy backend

Step-by-step guide

1

Enable Web Services and create a token in Moodle

Before FlutterFlow can talk to Moodle, the Web Services system must be switched on inside your Moodle administration panel. Log in to your Moodle site as an administrator and go to Site administration → Server → Web services → Overview. Follow the checklist: enable Web services (step 1), enable the REST protocol (step 2), and create or designate an External service that lists every `wsfunction` your app will call — for example `core_course_get_courses`, `core_user_get_users`, `gradereport_user_get_grade_items`, and `mod_quiz_get_quizzes_by_courses`. Skipping the External service step means every API call returns `accessexception` even with a valid token. Once the External service is configured, go to Site administration → Server → Web services → Manage tokens and create a new token. Associate it with a Moodle user that has the appropriate role (a dedicated service account is best practice — not a real teacher's account). Copy the token string and store it immediately in your Firebase Cloud Function's environment configuration or Supabase secrets vault — NOT in a text file, not in FlutterFlow's App Values, and never in Dart code. This token grants access to everything in the External service; treat it like a database password.

Pro tip: Use a dedicated Moodle service account user rather than an admin account for the token. Scope its role to only what the app needs.

Expected result: Your Moodle admin panel shows Web services and REST protocol enabled, and you have a token string saved securely outside of FlutterFlow.

2

Deploy a Cloud Function proxy to hold the token

Since the Moodle wstoken cannot be placed in FlutterFlow (it would compile into the app binary), you need a server-side proxy. The simplest path is a Firebase Cloud Function that accepts a `wsfunction` name and any additional parameters from your Flutter app, then injects the `wstoken` and the Moodle base URL before forwarding the request to Moodle and returning the JSON response. Open the Firebase Console, navigate to Functions, and create a new function called `moodleProxy`. Set the `MOODLE_BASE_URL` and `MOODLE_TOKEN` as environment configuration values using the Firebase CLI (`firebase functions:config:set moodle.url='https://your-site/webservice/rest/server.php' moodle.token='your-token'`). The function receives GET or POST requests from your Flutter app, appends the token and `moodlewsrestformat=json` server-side, proxies the call to Moodle, and returns the response. Make sure to authenticate the Cloud Function call using Firebase Auth ID tokens — verify the caller's token in the function before forwarding to Moodle, otherwise your Moodle token is still exposed to unauthenticated requests.

index.js
1// Firebase Cloud Function: functions/index.js
2const functions = require('firebase-functions');
3const axios = require('axios');
4
5exports.moodleProxy = functions.https.onCall(async (data, context) => {
6 // Require Firebase Auth
7 if (!context.auth) {
8 throw new functions.https.HttpsError('unauthenticated', 'Login required.');
9 }
10
11 const { wsfunction, params = {} } = data;
12 const moodleUrl = functions.config().moodle.url;
13 const wstoken = functions.config().moodle.token;
14
15 const queryParams = new URLSearchParams({
16 wstoken,
17 wsfunction,
18 moodlewsrestformat: 'json',
19 ...params,
20 });
21
22 try {
23 const response = await axios.get(`${moodleUrl}?${queryParams.toString()}`);
24 // Moodle returns HTTP 200 even on errors — check the body
25 if (response.data && response.data.exception) {
26 throw new functions.https.HttpsError('internal', response.data.message);
27 }
28 return response.data;
29 } catch (err) {
30 throw new functions.https.HttpsError('internal', err.message);
31 }
32});
33

Pro tip: Always call `functions.config().moodle.token` — never hardcode the token string in the function source file, which is checked into version control.

Expected result: The `moodleProxy` Cloud Function is deployed and returns Moodle API data when called with a valid Firebase Auth token and a `wsfunction` name.

3

Create the 'Moodle WS' API Group in FlutterFlow

Now configure FlutterFlow to call your Cloud Function proxy. Click API Calls in the left navigation panel, then click the + Add button and choose Create API Group. Name it 'Moodle WS' — or 'Moodle Proxy' if you prefer. Set the Base URL to your Firebase Cloud Function's callable URL, which looks like `https://us-central1-YOUR_PROJECT.cloudfunctions.net/moodleProxy`. If you are using Firebase callable functions (onCall), you will call them differently — see the tip below. Alternatively, if you convert the Cloud Function to an HTTP endpoint (`onRequest`), the base URL becomes the function's HTTPS URL and you send the `wsfunction` as a query parameter. In the API Group, add a shared header for the Firebase Auth ID token: `Authorization: Bearer {{ idToken }}` — you will supply this from the logged-in user's app state. Next, add a shared query parameter: `moodlewsrestformat=json`. This ensures every call in the group gets JSON back without repeating the parameter in each individual API Call configuration. Click Save on the API Group before adding individual calls. The group acts as a container; individual API Calls within it inherit the base URL and shared headers/params, so you only change the `wsfunction` parameter per call.

typescript
1// API Group config (conceptual)
2{
3 "name": "Moodle WS",
4 "base_url": "https://us-central1-YOUR_PROJECT.cloudfunctions.net/moodleProxy",
5 "headers": {
6 "Authorization": "Bearer {{ idToken }}"
7 },
8 "query_params": {
9 "moodlewsrestformat": "json"
10 }
11}
12

Pro tip: Firebase onCall functions use a special JSON body format and require the Firebase SDK. To keep FlutterFlow's API Calls simple, convert to an onRequest HTTP endpoint and pass the Auth token in the Authorization header.

Expected result: The 'Moodle WS' API Group appears in the left nav API Calls panel with the proxy base URL and shared auth header configured.

4

Add individual API Calls for each wsfunction

Inside the 'Moodle WS' API Group, click + Add → Create API Call for each Moodle function you need. The pattern is always the same: Method = GET, endpoint = empty (the base URL is the full path), and add a query parameter `wsfunction` with the function name as either a hard-coded value or a variable if you want to make the call reusable. For the courses call: name it 'GetCourses', set Method to GET, add a query parameter `wsfunction` = `core_course_get_courses`. Click the Variables tab and add any additional Moodle parameters you need — for example `userid` as a variable so you can fetch courses for a specific user. In the Response & Test tab, paste a sample Moodle JSON response and click Generate JSON Paths. FlutterFlow will auto-detect paths like `$[0].id`, `$[0].fullname`, `$[0].summary`, and `$[0].courseimage`. Bind these to your ListView or GridView widgets via a Backend Query or Action. Repeat for every function your app needs: `GetUsers` using `core_user_get_users`, `GetGrades` using `gradereport_user_get_grade_items`. Give each call a descriptive name. The key insight is that FlutterFlow's API Group is a perfect match for Moodle's single-URL API — the group holds the URL, each call only specifies the `wsfunction` and its extra params.

typescript
1// Example API Call config for GetCourses
2{
3 "name": "GetCourses",
4 "method": "GET",
5 "endpoint": "",
6 "query_params": {
7 "wsfunction": "core_course_get_courses"
8 },
9 "variables": [
10 { "name": "userid", "type": "integer", "required": false }
11 ]
12}
13
14// JSON Paths to bind after generating from response:
15// Course ID: $[*].id
16// Course name: $[*].fullname
17// Summary: $[*].summary
18// Image URL: $[*].courseimage
19

Pro tip: Always test each API Call inside FlutterFlow's Response & Test tab with a real Moodle token in the test headers before wiring to widgets. This confirms your JSON Paths are correct before you start binding.

Expected result: Each Moodle function (GetCourses, GetUsers, GetGrades) appears as a named API Call inside the Moodle WS group, with JSON Paths auto-generated from a real Moodle response.

5

Bind Moodle data to widgets and handle error responses

With the API Calls configured, open your FlutterFlow canvas and add a ListView (or GridView) widget for the course catalog. Select the widget, open the right-side panel, go to the Backend Query section, and choose 'API Call' → select 'GetCourses' from the 'Moodle WS' group. Pass the logged-in user's ID as the `userid` variable using the 'Authenticated User → UID' app state value (or a Firestore document field that stores the Moodle user ID). For each ListTile inside the ListView, bind Text widgets to the JSON Paths you generated: `$[*].fullname` for the course title and `$[*].summary` for the description. Add a NetworkImage widget bound to `$[*].courseimage` for the course thumbnail. Critically: Moodle returns HTTP 200 even when an error occurs — the body contains `{"exception":"...","errorcode":"accessexception","message":"..."}` instead of course data. FlutterFlow won't flag this as a failure. In your Action Flow (the + button under Actions for the page load or button tap), add a Condition action after the API call: check whether the JSON path `$.errorcode` is not empty. If it has a value, show an error banner widget using conditional visibility rather than rendering the empty ListView. This defensive check prevents a blank white screen from confusing your users when a Moodle token issue silently returns an error.

Pro tip: Add a conditional visibility rule to a red error banner widget: make it visible only when the API response JSON path `$.exception` is not null/empty. This surfaces Moodle's silent HTTP-200 errors to the user.

Expected result: The ListView populates with real Moodle course names and thumbnails. A conditional error banner appears if Moodle returns an errorcode instead of course data.

6

Test on a real device and handle CORS for web builds

FlutterFlow's Run mode preview in the browser makes direct HTTP calls from the browser's JavaScript context. If your Firebase Cloud Function does not return correct CORS headers (`Access-Control-Allow-Origin: *` or your specific domain), the browser will block the response and you will see 'XMLHttpRequest error' in the Flutter web console — even though the exact same call works on a native iOS or Android build. To fix this, add CORS headers to your Cloud Function response. If using `onRequest`, set `res.set('Access-Control-Allow-Origin', '*')` before sending the response, and handle the OPTIONS preflight. Firebase provides a `cors` npm package that simplifies this. Once CORS is handled, test the full flow: sign in with Firebase Auth in FlutterFlow's Run mode, confirm the ID token is passed correctly to the Cloud Function, and verify courses appear in the ListView. For the definitive test, build and install a debug APK on an Android device — native builds bypass CORS entirely and give the cleanest signal about whether the Moodle API calls are working. If you encounter 'accessexception' responses, return to Moodle's admin panel and verify the wsfunction is listed in the External service associated with your token.

index.js
1// Add CORS to Firebase Cloud Function (index.js)
2const cors = require('cors')({ origin: true });
3
4exports.moodleProxy = functions.https.onRequest((req, res) => {
5 cors(req, res, async () => {
6 // ... rest of proxy logic
7 });
8});
9

Pro tip: If RapidDev's team builds FlutterFlow integrations like this every week — if you'd rather skip the Cloud Function setup, book a free scoping call at rapidevelopers.com/contact.

Expected result: The app loads and displays real Moodle courses in both the web Run mode preview (with CORS resolved) and on a native device build.

Common use cases

University mobile course browser for students

A Flutter app that shows enrolled students their active courses, upcoming assignments, and recent grades fetched live from the institution's Moodle instance. Students log in via FlutterFlow's Firebase Auth and the backend maps their email to a Moodle user ID for grade queries.

FlutterFlow Prompt

Build a mobile app where university students can see their enrolled Moodle courses, view assignment due dates, and check their grade for each course — all in a clean card-based UI.

Copy this prompt to try it in FlutterFlow

Corporate LMS companion app with progress dashboard

A branded companion app for a company running Moodle for internal training. HR managers can see completion rates per course using the `gradereport_user_get_grade_items` function, while employees track their own module progress in a visual progress ring widget.

FlutterFlow Prompt

Create a FlutterFlow app where employees see their training course list, mark modules complete, and HR admins see a dashboard of team-wide completion percentages pulled from Moodle grades.

Copy this prompt to try it in FlutterFlow

School quiz and assessment delivery app

A focused quiz-delivery app for a K-12 school that surfaces Moodle quiz attempts using `mod_quiz_get_quizzes_by_courses` and presents them in a timed card-flip UI, syncing results back to Moodle via the quiz submission functions.

FlutterFlow Prompt

Build a Flutter quiz app that fetches available quizzes from Moodle for the logged-in student, presents questions one at a time with a countdown timer, and submits answers back to Moodle on completion.

Copy this prompt to try it in FlutterFlow

Troubleshooting

Every API call returns {"exception":"accessexception","errorcode":"accessexception"}

Cause: The wsfunction you are calling has not been added to the External service associated with the wstoken. Moodle's access control works at the function level, not just the token level.

Solution: Go to Site administration → Server → Web services → External services, find your service, click Functions, and add every `wsfunction` your app calls (e.g., `core_course_get_courses`, `gradereport_user_get_grade_items`). Regenerate the token if it was created before the functions were added.

XMLHttpRequest error in FlutterFlow Run mode (web preview) but the same call works on a device

Cause: Your Firebase Cloud Function is not returning CORS headers, so the browser blocks the response. Native iOS/Android builds do not enforce CORS, which is why they work fine.

Solution: Add the `cors` npm package to your Cloud Function and wrap the handler with `cors(req, res, () => { ... })`. Set `origin: true` to reflect the request origin, or restrict to your specific domain in production.

typescript
1const cors = require('cors')({ origin: true });
2exports.moodleProxy = functions.https.onRequest((req, res) => {
3 cors(req, res, async () => { /* proxy logic */ });
4});

FlutterFlow shows an empty ListView — no error, just blank — when fetching courses

Cause: Moodle returned an error body with HTTP status 200. FlutterFlow treated the call as successful and tried to bind the error JSON (which has no course array) to the ListView, resulting in zero items.

Solution: In your Action Flow, add a Condition step after the API call that checks the JSON path `$.exception` (or `$.errorcode`). If it is not empty, set a page state variable `hasError = true` and use conditional visibility to show an error message widget instead of the course list.

403 or 401 response from the Firebase Cloud Function

Cause: The Firebase Auth ID token is missing, expired, or not being passed correctly in the Authorization header from FlutterFlow.

Solution: In FlutterFlow's API Calls panel, verify the 'Moodle WS' API Group has the `Authorization` header set to `Bearer {{ idToken }}` and that the `idToken` variable is being populated from the logged-in user's Firebase Auth token. Use the Response & Test tab to manually enter a valid token and confirm the Cloud Function responds correctly.

Best practices

  • Use a dedicated Moodle service account for the wstoken, scoped to the minimum functions the app needs — never use an admin account token.
  • Store the wstoken exclusively in Firebase Functions config or Supabase vault; never in FlutterFlow App Values, Dart code, or any client-side storage.
  • Always check `$.exception` and `$.errorcode` JSON paths after every Moodle API call — HTTP 200 does not mean success in Moodle's Web Services API.
  • Add `moodlewsrestformat=json` as a shared query parameter on the API Group level so it applies to every call automatically.
  • Enable only the specific wsfunctions your app needs in the External service — a minimal scope limits damage if the token is ever compromised.
  • Handle CORS in your Cloud Function from day one so the FlutterFlow web preview works correctly alongside native builds.
  • Cache frequently-requested data (like the course list) in Firestore or Supabase rather than calling Moodle on every widget render — self-hosted servers can be slow.
  • Test each new wsfunction in FlutterFlow's Response & Test tab with a real token before wiring to widgets to confirm the JSON structure.

Alternatives

Frequently asked questions

Can I put the Moodle wstoken directly in a FlutterFlow API Call header?

No. FlutterFlow compiles to a client app that runs on users' devices. Any token placed in an API Call header or App Value will be present in the compiled APK or IPA file and can be extracted. The wstoken grants access to all the Moodle functions in its External service, so it must live in a Firebase Cloud Function or Supabase Edge Function secret and be injected server-side.

Why does Moodle return HTTP 200 when there is an error?

Moodle's Web Services API always returns HTTP 200 and puts error information in the JSON body as `exception`, `errorcode`, and `message` fields. This is a design quirk of the Moodle WS layer. FlutterFlow's API Call will not flag these as failures — you must check the `$.errorcode` JSON path yourself in an Action Flow condition after every call.

Does this integration work with Moodle Cloud, or only self-hosted Moodle?

Both. The Web Services REST API is available on self-hosted Moodle installations and on Moodle Cloud (paid plans). The base URL will differ — for Moodle Cloud it is your school's subdomain. Confirm with your Moodle Cloud admin that Web services and the REST protocol are enabled, as plan-level restrictions may apply.

How do I map a FlutterFlow Firebase Auth user to a Moodle user?

Moodle user records contain an `email` field that usually matches the user's Google or institution email. In your Cloud Function, after verifying the Firebase Auth ID token, use `context.auth.token.email` to look up the Moodle user via `core_user_get_users` with the email as a filter. Store the returned Moodle `userid` in Firestore under the Firebase UID for fast lookups on subsequent requests.

Can the FlutterFlow app write data back to Moodle, like submitting assignment grades?

Yes, Moodle's Web Services API includes write functions such as `core_grades_update_grades` and quiz submission functions. Follow the same proxy pattern — the Cloud Function accepts the submission data from the app, validates the Firebase Auth token, then calls the appropriate Moodle wsfunction server-side. Never call write functions directly from client Dart.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Integrations are where projects stall

We wire FlutterFlow integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.