Moodle

Connect Moodle, an open-source learning management system, with Webflow to sync course catalogs to CMS collections, automate student enrollment from form submissions, and display completion credentials on marketing pages.

Install app
View website
View lesson
A record settings
CNAME record settings
Moodle

Pair a pixel-perfect marketing site with a full-featured LMS — without rebuilding either one. The visual canvas handles your catalog design, landing pages, and brand experience, while Moodle powers quizzes, graded assignments, progress tracking, SCORM delivery, and certificates on the back end.

This split-site architecture fits EdTech startups, universities backed by institutional LMS infrastructure, corporate L&D teams, and freelancers building education sites that need both design flexibility and serious learning tools.

How to integrate Moodle with Webflow

What is Moodle? Moodle is a free, open-source learning management system (LMS) licensed under GPLv3+. It supports course authoring with quizzes, assignments, forums, and H5P interactive content, along with eLearning standards including SCORM, xAPI, and LTI. You can run Moodle as a self-hosted installation, through Certified Partner hosting, or as a fully managed service via MoodleCloud.

Use Moodle alongside your site when you need a marketing presence with full design control and an LMS for the features the visual canvas can't replicate: exams, graded assignments, learner progress tracking, and completion certificates. Most teams place the marketing site at www.example.com and Moodle at learn.example.com, with data syncing between them through automation or API calls.

You can connect Moodle to your site in 3 ways:

  • Embed elements and direct links let you display Moodle course content or H5P activities inside your pages without writing back-end code.
  • Zapier supports triggers and actions for automation workflows without custom development.
  • The Data API and Moodle REST API give you full control over course syncing, enrollment logic, grade retrieval, and event-driven updates, but require server-side development.

Most implementations combine two or more of these methods depending on the complexity of the setup.

Embed Moodle content with Code Embed elements

Code Embed elements let you display Moodle course pages, H5P interactive activities, and other LMS content directly inside a page using iframes. This keeps learners on the marketing site while they interact with Moodle content. You need a paid site plan because Code Embed elements aren't available on the free Starter plan. Before any iframe will load, your Moodle administrator may also need to change the default server security settings that block cross-origin embedding.

To embed a Moodle course page:

  1. In Moodle, go to Site Administration > Security > HTTP Security, enable Allow frame embedding, and add your domain to the Frame ancestor allowlist.
  2. In the Designer, drag a Code Embed element onto the canvas from the Add panel.
  3. Paste the iframe code, replacing the URL with your Moodle course address:
<iframe
  src="https://yourmoodlesite.com/course/view.php?id=123"
  width="100%"
  height="600"
  frameborder="0">
</iframe>
  1. Save, close the code editor, and publish the site.

For H5P interactive content like quizzes or interactive videos, use a two-part embed that includes Moodle's resizer script:

<iframe
  src="https://yourmoodlesite/mod/hvp/embed.php?id=596"
  width="1077"
  height="424"
  frameborder="0"
  allowfullscreen="allowfullscreen">
</iframe>
<script
  src="https://yourmoodlesite/mod/hvp/library/js/h5p-resizer.js"
  charset="UTF-8">
</script>

You can also inject Moodle-related scripts at the page or site level using custom code in head and body tags, which is useful when you need to load scripts globally rather than on a single section.

Keep these iframe limitations in mind:

  • Moodle blocks iframe embedding by default. Without the admin configuration described above, browsers may refuse to render embedded Moodle content because of X-Frame-Options: SAMEORIGIN or related frame restrictions.
  • Users must already be logged into Moodle separately. Unauthenticated visitors will see a Moodle login prompt, not course content.
  • Safari blocks Moodle session cookies inside cross-origin iframes as part of its Intelligent Tracking Prevention. Users on Safari may see the login page even when they're authenticated on Moodle directly. Chrome is moving toward similar restrictions.
  • Iframe height doesn't auto-adjust. You'll need to set fixed dimensions and test across content types. The H5P resizer script helps with H5P content specifically, though results vary.
  • Your web server (Apache or Nginx) may independently set X-Frame-Options headers that override Moodle's application-level setting, so you may need a server configuration change.

For simpler setups where visual embedding isn't necessary, a standard hyperlink to your Moodle course page (https://yourmoodle.com/course/view.php?id=123) works on any site plan, including free, and sidesteps all iframe-related issues.

Use CMS fields for dynamic course embeds

If you manage a course catalog in the CMS, create a "Courses" Collection with a plain-text field containing each course's Moodle URL. On CMS template pages, a Code Embed element can reference that field value as the iframe src attribute, outputting a different Moodle course on each page automatically.

To set this up:

  1. Add a plain-text field called "Moodle URL" to your Courses Collection.
  2. Populate each CMS item with the corresponding Moodle course URL.
  3. On the Collection template page, add a Code Embed element and insert iframe code that references the CMS field using the dynamic embed syntax.

This scales well when you have many courses, since adding a new CMS item with the correct URL automatically generates a new page with the embedded course content. Each site plan includes its own CMS item limits, so check the current pricing page for the specific cap that applies to your plan.

Connect Moodle to your site with Zapier

Zapier supports triggers such as New Form Submission and New Order, along with actions such as Create Item and Update Item, so teams without developers can wire things up. Moodle is classified as a Premium app on Zapier, so you'll need a paid Zapier plan.

To set up a form-to-Moodle enrollment Zap:

  1. In Moodle, enable Web Services at Site Administration > Advanced Features, enable the REST protocol at Site Administration > Plugins > Web services > Manage protocols, and generate an API token at Site Administration > Plugins > Web services > Manage tokens.
  2. In Zapier, create a new Zap with Webflow: New Form Submission as the trigger.
  3. Add a Moodle: Find User step using core_user_get_users to check whether the student already exists (this prevents duplicate account errors).
  4. Add a Moodle: Create User step using core_user_create_users for new students only.
  5. Add a Moodle: Enroll User step using enrol_manual_enrol_users with roleid=5 (the default student role).

Explicitly named triggers and actions on Zapier include:

  • Webflow: New Form Submission
  • Webflow: New Order
  • Webflow: Create Item
  • Webflow: Update Item

These are the core touchpoints you'll use for no-code enrollment and catalog-sync workflows with Moodle.

For non-technical teams, Zapier is the recommended path — see the integration documentation. Store your Moodle API token in the automation platform, never in client-side code on your site.

Build with the Data API and Moodle API

For full control over course catalog syncing, enrollment workflows, grade retrieval, and event-driven updates, build a custom integration using both platform APIs. This path needs server-side development because the hosting environment can't execute server-side code or securely store API credentials. All Moodle API calls must go through middleware (Vercel, Cloudflare Workers, AWS Lambda, or a similar service) that holds credentials in environment variables and relays requests between the two systems.

This integration uses:

  • Moodle's Web Services REST API for user management, course retrieval, enrollment, grades, and completion data
  • The Data API v2 for CMS collections, collection items, form submissions, and user management
  • Site webhooks for real-time events like form submissions and e-commerce orders that your middleware receives and processes

Moodle's REST API uses form-encoded POST parameters for all requests (it doesn't accept JSON request bodies). The moodlewsrestformat=json parameter controls only the response format. Authentication uses a wstoken parameter passed with every request, not an HTTP Authorization header.

Sync the Moodle course catalog to the CMS

Course catalog syncing pulls course data from Moodle and writes it into a CMS Collection, producing SEO-friendly course pages that update automatically. A scheduled function (running hourly or daily via cron) fetches all courses from Moodle, compares them against existing CMS items, and creates or updates items as needed.

To implement course catalog syncing:

  1. Call Moodle's core_course_get_courses function to retrieve all course data (titles, descriptions, dates, visibility).
  2. Fetch existing items from your CMS Courses Collection via GET /v2/collections/{collection_id}/items and build a lookup map of Moodle course IDs to CMS item IDs.
  3. For new courses, batch-create items with POST /v2/collections/{collection_id}/items/bulk (up to 100 items per request). For existing courses, batch-update with PATCH /v2/collections/{collection_id}/items.
  4. Publish staged items with POST /v2/collections/{collection_id}/items/publish, or use the /live suffix endpoints to create and publish in a single call.

Map Moodle course fields to CMS fieldData keys:

  • fullname maps to name (plain text, required)
  • shortname maps to slug (sanitize to lowercase with hyphens)
  • summary maps to description (rich text)
  • startdate maps to start-date (convert Unix timestamp to ISO 8601)
  • visible maps to is-published (convert 0/1 to false/true)

Here's an example serverless function for scheduled syncing:

async function syncCourseCatalog() {
  // Fetch all courses from Moodle
  const moodleParams = new URLSearchParams({
    wstoken: process.env.MOODLE_TOKEN,
    wsfunction: 'core_course_get_courses',
    moodlewsrestformat: 'json',
  });
  const courses = await fetch(
    `${process.env.MOODLE_URL}/webservice/rest/server.php`,
    { method: 'POST', body: moodleParams }
  ).then(r => r.json());

  // Fetch existing Webflow items
  const { items: existingItems } = await fetch(
    `https://api.webflow.com/v2/collections/${process.env.WF_COURSES_COLLECTION}/items`,
    { headers: { 'Authorization': `Bearer ${process.env.WEBFLOW_TOKEN}` } }
  ).then(r => r.json());

  const existingMap = Object.fromEntries(
    existingItems.map(i => [i.fieldData['moodle-course-id'], i.id])
  );

  const toCreate = [];
  const toUpdate = [];

  for (const course of courses) {
    const fieldData = {
      name: course.fullname,
      slug: course.shortname.toLowerCase().replace(/\s+/g, '-'),
      'moodle-course-id': String(course.id),
      description: course.summary,
      'start-date': new Date(course.startdate * 1000).toISOString(),
      'is-published': course.visible === 1,
    };
    if (existingMap[String(course.id)]) {
      toUpdate.push({ id: existingMap[String(course.id)], fieldData });
    } else {
      toCreate.push({ fieldData });
    }
  }

  // Batch create (max 100 per request)
  for (let i = 0; i < toCreate.length; i += 100) {
    await fetch(
      `https://api.webflow.com/v2/collections/${process.env.WF_COURSES_COLLECTION}/items/bulk`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.WEBFLOW_TOKEN}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ items: toCreate.slice(i, i + 100) }),
      }
    );
  }

  // Batch update (max 100 per request)
  for (let i = 0; i < toUpdate.length; i += 100) {
    await fetch(
      `https://api.webflow.com/v2/collections/${process.env.WF_COURSES_COLLECTION}/items`,
      {
        method: 'PATCH',
        headers: {
          'Authorization': `Bearer ${process.env.WEBFLOW_TOKEN}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ items: toUpdate.slice(i, i + 100) }),
      }
    );
  }
}

Items created or updated via the CMS API default to a staged (draft) state unless you use the /live endpoints. You'll need a separate publish call to make staged items visible on the live site.

Automate enrollment from form submissions

When a prospective student submits a registration form on your site, a webhook can trigger your middleware to create a Moodle user account and enroll them in the correct course. This removes manual processing from the enrollment workflow entirely.

To implement webhook-based enrollment:

  1. Register a webhook at Site Settings > Apps & integrations > Webhooks, or via the API with POST /v2/sites/{site_id}/webhooks, using form_submission as the trigger type.
  2. In your middleware, receive the webhook payload containing form field data (email, name, course selection). Validate the x-webflow-signature header before processing.
  3. Call Moodle's core_user_get_users with criteria[0][key]=email to check for an existing account. If the user exists, skip creation. If not, call core_user_create_users with the student's details.
  4. Call enrol_manual_enrol_users with enrolments[0][roleid]=5 (student), the user ID from step 3, and the course ID from the form's hidden field.

Here's an example webhook receiver:

app.post('/webhook/webflow-enrollment', async (req, res) => {
  const { triggerType, payload } = req.body;
  if (triggerType !== 'form_submission') return res.sendStatus(200);

  const email = payload.data['email'];
  const firstName = payload.data['First Name'];
  const lastName = payload.data['Last Name'];
  const courseId = payload.data['course-id'];

  // Check for existing user
  const searchParams = new URLSearchParams({
    wstoken: process.env.MOODLE_TOKEN,
    wsfunction: 'core_user_get_users',
    moodlewsrestformat: 'json',
    'criteria[0][key]': 'email',
    'criteria[0][value]': email,
  });
  const userData = await fetch(
    `${process.env.MOODLE_URL}/webservice/rest/server.php`,
    { method: 'POST', body: searchParams }
  ).then(r => r.json());

  let moodleUserId;
  if (userData.users && userData.users.length > 0) {
    moodleUserId = userData.users[0].id;
  } else {
    const createParams = new URLSearchParams({
      wstoken: process.env.MOODLE_TOKEN,
      wsfunction: 'core_user_create_users',
      moodlewsrestformat: 'json',
      'users[0][username]': email.split('@')[0] + Date.now(),
      'users[0][email]': email,
      'users[0][firstname]': firstName,
      'users[0][lastname]': lastName,
      'users[0][password]': 'Temp@' + Math.random().toString(36).slice(2, 10),
    });
    const created = await fetch(
      `${process.env.MOODLE_URL}/webservice/rest/server.php`,
      { method: 'POST', body: createParams }
    ).then(r => r.json());
    moodleUserId = created[0].id;
  }

  // Enroll in course
  const enrollParams = new URLSearchParams({
    wstoken: process.env.MOODLE_TOKEN,
    wsfunction: 'enrol_manual_enrol_users',
    moodlewsrestformat: 'json',
    'enrolments[0][roleid]': 5,
    'enrolments[0][userid]': moodleUserId,
    'enrolments[0][courseid]': courseId,
  });
  await fetch(
    `${process.env.MOODLE_URL}/webservice/rest/server.php`,
    { method: 'POST', body: enrollParams }
  );

  res.sendStatus(200);
});

If you're selling courses through Ecommerce, trigger enrollment from a confirmed Stripe payment webhook rather than from the form submission webhook alone. See webflow.com/integrations/form-payments — form webhooks don't include payment confirmation status, so triggering enrollment directly from a form submission could grant access before payment completes.

Receive Moodle events via webhooks

For real-time data flow from Moodle to your site, install the local_webhooks plugin on your Moodle instance. The plugin intercepts Moodle's internal events and POSTs them as JSON to your middleware endpoint, which can then update CMS items accordingly.

Relevant Moodle events for CMS syncing include:

  • \core\event\course_created fires when a new course is added. Your middleware creates a new CMS item.
  • \core\event\course_updated fires when course settings change. Your middleware patches the corresponding CMS item.
  • \core\event\user_enrolment_created fires when a user enrolls. Your middleware can update an enrollment-tracking Collection.
  • \core\event\course_completed fires when a student meets all completion criteria. Your middleware can flag the completion in a CMS record or trigger a certificate workflow.
  • \core\event\badge_awarded fires when a badge is issued to a student. Your middleware can add the badge to a CMS-based credential display.

Every Moodle event payload includes standard fields: eventname, objectid, userid, courseid, relateduserid, and timecreated. The user_graded event fires frequently (on every quiz attempt), so consider using Moodle's adhoc_task system to defer high-frequency HTTP calls rather than blocking the request cycle.

The local_webhooks plugin requires a self-hosted Moodle installation. MoodleCloud doesn't allow arbitrary plugin installation, which limits event-driven architectures if you're on MoodleCloud.

What can you build with the Moodle and Webflow integration?

Pairing Moodle with your site lets you run a branded education business or training program without maintaining course data manually across two platforms.

  • Auto-updating course catalog: Sync Moodle course titles, descriptions, dates, and categories into a CMS Collection. A Collection List on your marketing site renders each course with full design control, and new Moodle courses appear automatically through scheduled API syncing or Zapier automation using CMS actions such as Create Item and Update Item.
  • Self-service enrollment from a registration form: A form on your site captures student name, email, and course selection. Zapier or a webhook-triggered middleware function creates the Moodle user account and enrolls them in the correct course within seconds, eliminating manual admin processing.
  • Completion-driven credential display: When a learner finishes a Moodle course, a webhook updates a "Certificates" CMS Collection with the student's name, course title, and completion date. The certificate record appears on a verification page at a URL like /certificates/{certificate-code}.
  • E-commerce course sales with automatic access: A learner purchases a course through Ecommerce checkout. A confirmed Stripe payment webhook triggers middleware that calls Moodle's enrol_manual_enrol_users endpoint, granting immediate course access without manual intervention.

If you need more control over grade display, progress dashboards, or multi-course learning paths, the API integration path covers those cases with full flexibility.

Frequently asked questions

  • Yes, but it requires configuration on both sides. In Moodle, an administrator must enable Allow frame embedding at Site Administration > Security > HTTP Security and add the Webflow domain to the allowlist. In Webflow, you paste iframe code into a Code Embed element on a paid site plan. Without the Moodle-side change, browsers block the embedded content because Moodle sends X-Frame-Options: SAMEORIGIN headers by default. Safari users may still see a login prompt due to third-party cookie restrictions that block Moodle session cookies inside cross-origin iframes.

  • No. All Moodle-Webflow integration work requires manual setup through embed elements, Zapier, or API-based middleware.

  • No. CORS will block the request, and exposing your Moodle API token in client-side JavaScript creates a security risk. Per Webflow's integration documentation, all Moodle API calls must go through server-side middleware that stores credentials securely. Deploy a proxy function on Vercel, Cloudflare Workers, AWS Lambda, or a similar service. That function holds the Moodle API token in an environment variable and relays requests between Webflow and Moodle.

  • Self-hosted Moodle (or a Certified Partner-hosted instance) is strongly preferred for any integration beyond simple links. Current MoodleCloud documentation describes restrictions on plugin installation and server-level configuration, which limits the workarounds needed for iframe embedding, outgoing webhooks (the local_webhooks plugin), and some SSO approaches. If your integration plan includes any of these features, self-hosted Moodle gives you the configuration access you need.

  • Webflow has no built-in SSO bridge to Moodle. Webflow's native SSO feature applies only to team member workspace access, not visitor authentication. To create a unified login for learners, implement a shared identity provider (Auth0, Descope, or Okta) that both platforms trust. On the Moodle side, install the SAML2 authentication plugin or configure Moodle's built-in OAuth2 support. On the Webflow side, add the identity provider's JavaScript SDK via custom code. Users authenticate through the shared provider and gain access to both platforms without logging in twice.

Moodle
Moodle
Joined in

Description

Moodle adds LMS capabilities to Webflow through embedded course content via iframes, Zapier for no-code enrollment workflows, or the Moodle REST API and Webflow Data API for catalog syncing, automated enrollment, and event-driven grade and completion tracking.

Install app

This integration page is provided for informational and convenience purposes only.


Other Memberships and user login integrations

Other Memberships and user login integrations

Owwlish

Owwlish

Connect Owwlish, a learning management system for course creators, with Webflow to embed course players, process payments through Stripe, and track student progress on your existing site.

Memberships and user login
Learn more
Softr

Softr

Connect Softr with Webflow to build business applications, client portals, and internal tools.

Memberships and user login
Learn more
Whop

Whop

Connect Whop, a platform for selling digital products and memberships, with Webflow to add checkout, subscription billing, and product delivery to any page without building a custom commerce backend.

Memberships and user login
Learn more
Thinkific

Thinkific

Connect Thinkific with Webflow to deliver online courses through custom marketing pages while managing course delivery separately.

Memberships and user login
Learn more
LearnDash

LearnDash

Connect LearnDash with Webflow to sync course data, manage enrollments, and display learning progress on your marketing site.

Memberships and user login
Learn more
Circle

Circle

Connect Circle, an all-in-one community platform, with Webflow to embed discussion spaces and courses on pages, sync member data to CMS collections, and build community-driven experiences under one branded domain.

Memberships and user login
Learn more
Patreon

Patreon

Connect Patreon with Webflow to add membership widgets, sync patron data to your CMS, and build tier-based content access on your site.

Memberships and user login
Learn more
Supabase

Supabase

Connect Supabase, an open-source backend platform, with Webflow to add PostgreSQL database storage, user authentication, file uploads, and real-time subscriptions to any page.

Memberships and user login
Learn more
Outseta

Outseta

Connect Outseta, an all-in-one membership platform, with Webflow to add subscription billing, user authentication, content gating, and CRM management without a backend developer.

Memberships and user login
Learn more

Related integrations

No items found.

Get started for free

Try Webflow for as long as you like with our free Starter plan. Purchase a paid Site plan to publish, host, and unlock additional features.

Get started — it’s free