moodlekit
Your Moodle, programmable. Python API · CLI · AI agents, with the login you already have.
Use Moodle from Python, the command line, or an AI agent, with the login you already have in your browser. No API token needed, so it works even where the university uses single sign-on (Microsoft, Google, SAML) and has disabled Moodle's mobile/web-service token.
$ moodle courses
61802 MA22038 MA22038: Probabilistic modelling / Probability 2
...
$ moodle ls MA22038
## Problem Sheets
resource Problem Sheet 1 (pdf)
resource Sheet 1 solutions to remaining questions (pdf) [not available yet]
| Site | Moodle | Login | Status |
|---|---|---|---|
| University of Bath | 4.5 | Microsoft SSO | ✅ Tested: courses, contents, files, folders, pages, HTML notes to PDF, CLI, MCP (Firefox and Chrome) |
| yours? | open an issue or fork |
What it can do
- List your courses and everything on a course page (files, folders, assignments, forums, pages...), including items that are listed but not released yet.
- Download files and folders, and turn HTML lecture notes into one PDF.
- Read pages as plain text (assignment briefs, forum posts, course diaries).
- Upcoming deadlines across your courses.
- Everything is available as a Python library, a
moodlecommand (with--jsonfor scripts and agents), and an optional MCP server.
Install
pip install pymoodlekit # library + `moodle` command
pip install "pymoodlekit[pdf]" # + HTML-notes-to-PDF (then: playwright install chromium)
pip install "pymoodlekit[mcp]" # + MCP server for AI agents
Requires Python 3.11+.
Logging in
moodlekit doesn't handle your password or your SSO login. You log in to Moodle normally in your browser, and moodlekit reads that browser's session cookie (using browser-cookie3).
moodle check # "Logged in to https://moodle.bath.ac.uk with firefox, 15 courses visible."
That's it. By default moodlekit uses the University of Bath's Moodle and your system's default browser (if it's one of the supported ones below, otherwise Firefox). If you're not logged in, it opens Moodle in that browser, waits while you log in, and then carries on with your command.
The cookie is read fresh every time and never written to disk by moodlekit. When the session expires, moodlekit re-reads the browser first, so staying logged in there keeps it working.
To change the defaults (flags win over environment variables):
| flag | environment | default | |
|---|---|---|---|
| site | --url moodle.example.ac.uk |
MOODLE_URL |
https://moodle.bath.ac.uk |
| browser | --browser chrome |
MOODLE_BROWSER |
your default browser |
| raw cookie instead | --cookie |
MOODLE_COOKIE |
none |
Supported browsers: Firefox, Chrome, Safari, Edge, Brave, Arc, Chromium, Opera, Vivaldi, LibreWolf.
Firefox tip: Firefox is the most reliable choice on macOS. Chrome-based browsers encrypt cookies with a key in the system keychain, so you may get a keychain prompt.
Manual cookie: open your Moodle in any browser, then DevTools → Application/Storage → Cookies. Copy the MoodleSession… cookie and export MOODLE_COOKIE='MoodleSessionXYZ=abc123' (or just the value).
Command line
moodle check # is the login working?
moodle courses [inprogress|past|future|all]
moodle ls MA22038 # sections + activities (id, shortname or part of the name)
moodle files <activity-url> # files behind a resource/folder/page
moodle get <url> -o notes/ # download them (never overwrites: "x (1).pdf")
moodle get <url> --pdf notes.pdf # HTML notes -> one PDF (needs [pdf])
moodle read <url> # page text + file links
moodle deadlines --days 14
moodle mcp # MCP server (needs [mcp])
Add --json before the command for machine-readable output, e.g. moodle --json ls MA22038.
Python
from moodlekit import Moodle
m = Moodle() # Bath + your default browser
# m = Moodle("moodle.example.ac.uk", browser="chrome")
m.login() # optional: opens the browser if you're not logged in
for course in m.courses():
print(course.id, course.shortname, course.name)
for act in m.activities("MA22038"): # id, shortname, or part of the name
if act.type == "resource" and act.available:
m.download(act, "downloads/") # -> [Path(...)]
m.page("https://moodle.bath.ac.uk/mod/page/view.php?id=123").text
m.deadlines(days=14)
# Escape hatches for anything not wrapped yet:
m.call("core_course_get_enrolled_courses_by_timeline_classification", classification="all",
offset=0, limit=0, sort="fullname") # any AJAX-enabled web service function
m.get("/course/view.php?id=61802").text # any page, with your session
Results are plain dataclasses (Course, Activity, File, Event, Page). Utils.to_dict(result) turns any of them into JSON-ready data. Errors are MoodleError, and NotLoggedIn when the session is missing or expired.
HTML to PDF: PdfRenderer(m).render(activity_or_url, "notes.pdf").
MCP server (AI agents)
pip install "pymoodlekit[mcp]"
claude mcp add moodle -- moodle mcp
Tools: list_courses, list_activities, list_files, read_page, download, upcoming_deadlines. If you're not logged in, the first tool call opens Moodle in your browser and waits for you.
Agents can also just use the CLI with --json, which is often simpler.
How it works
Moodle's official web-service API needs a per-user token. Many SSO universities don't let students create one, and disable the mobile-app login that would hand one out. But Moodle's own web pages talk to an internal AJAX endpoint (/lib/ajax/service.php), authenticated by the normal session cookie and a sesskey embedded in every page. moodlekit does the same:
| Feature | How | Fragility |
|---|---|---|
| Courses, deadlines | AJAX web-service functions | Low: stable Moodle APIs |
| Course contents | AJAX core_courseformat_get_state (Moodle 4.0+), HTML fallback for older sites |
Low / high for the fallback |
| Files | Follows resource redirects to pluginfile.php (and on to cloud storage such as S3) |
Medium |
| Folders, embedded files, page text | HTML scraping (#region-main) |
High: theme-dependent |
| HTML notes to PDF | Headless Chromium with your session | Medium |
Only AJAX-enabled functions can be called this way, which is why some features need scraping. See ROADMAP.md for what's next.
Security and fair use
- A Moodle session cookie is full access to your account. moodlekit only sends it to your Moodle site, never logs or stores it, and has no telemetry. Don't paste your cookie into issues or chats.
- Course material is your university's copyright. Download it for your own study, and don't redistribute it (including by committing it to a public repo).
- Be polite to your university's servers. moodlekit makes about one request per item, so don't loop it every minute. Check your university's IT acceptable-use policy.
- This is an unofficial project, not affiliated with Moodle HQ or any university.
Development
uv sync --all-extras
uv run pytest # offline tests with mocked HTTP; no real Moodle needed
uv run ruff check src tests
See CONTRIBUTING.md.
License
Metadata
Release files for pymoodlekit 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pymoodlekit-0.1.1.tar.gz | 105.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pymoodlekit-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 126.0 kB
Release files / pymoodlekit-0.1.1.tar.gz
| Download URL | pymoodlekit-0.1.1.tar.gz |
|---|---|
| Size | 105.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a5d77f4ae44936f0ad31d51903f6ad0795c246ff8ebb2ae86c5c4de7acbd7523
|
|
BLAKE2b-256 checksum How to use checksums |
da7d7996a5983ab998c3de7a72f65c8915202616ebfb053474f579e02b27b944
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency logRelease files / pymoodlekit-0.1.1-py3-none-any.whl
| Download URL | pymoodlekit-0.1.1-py3-none-any.whl |
|---|---|
| Size | 20.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e692f9bb97f46864f006b55c34c4457eaa78c8b02ef7423c9fa12967f441fed9
|
|
BLAKE2b-256 checksum How to use checksums |
897c2002d08ccaebec972ea0bc94dfe2f5ddfd8681fa9dc4af7597bf82e4d133
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency log