Small set of Python scripts for managing a Canvas LMS course through its REST API: bulk-change pages/assignments/quizzes/modules from JSON content files, configure late-submission policies, and sanity-check API access.
This repo is a generic tool. It does not include anyone's real course
content, course IDs, or API credentials — you provide your own via a local
.env file (gitignored, never committed).
populate_canvas.py— reads a JSON content file (or directory of them) and creates/updates the matching Pages, Assignments, Quizzes, Discussions, and Modules in a Canvas course. Idempotent: re-running it matches existing items by title/name instead of creating duplicates.set_late_policy.py— configures a course's automatic late/missing submission grading policy.smoke_test_new_quizzes.py— confirms your Canvas token/account has access to the New Quizzes API before relying on it.canvas_client.py/models.py— thinrequests-based Canvas API client and the Pydantic models that define the JSON content schema.
- Create and activate a virtual environment, then install dependencies:
python -m venv .venv .venv\Scripts\Activate.ps1 pip install -r requirements.txt
- Copy
.env.exampleto.envand fill in your own values:Get a token from Canvas under Account > Settings > + New Access Token.CANVAS_BASE_URL=https://yourschool.instructure.com CANVAS_API_TOKEN=your_token_here.envis gitignored — never commit it.
# Preview what would be created, without calling the API
python populate_canvas.py --course-id 12345 --input content/example_content.json --dry-run
# Actually populate a course from a single file or a directory of JSON files
python populate_canvas.py --course-id 12345 --input content/example_content.json
python populate_canvas.py --course-id 12345 --input content/weeks
# Configure a late-submission policy
python set_late_policy.py --course-id 12345
# Confirm New Quizzes API access
python smoke_test_new_quizzes.py --course-id 12345Replace 12345 with your real Canvas course ID.
See content/example_content.json for a
minimal example covering pages, assignments, discussion boards, quizzes, and
modules. Any
pages/assignments/discussions/quizzes entry can use a
body_file / description_file / message_file / instructions_file key
instead of inlining HTML, with the path resolved relative to the JSON file
that references it — see models.py for the full schema.
When --input points at a directory, every *.json file in it is loaded and
merged into one course content set, so module items in one file can
reference pages/assignments/discussions defined in another file in the same
directory.
The real point of keeping course content as plain JSON/HTML files in a repo
(instead of only editing directly in the Canvas web UI) is that an AI coding
agent running in VS Code — GitHub Copilot Chat, Claude Code, or similar — can
read and edit that content for you in natural language, then push the result
to Canvas via populate_canvas.py. Concretely, you can ask the agent things
like:
- "Push back every assignment due date in
content/weeksby 3 days." - "Change Week 5's quiz due date to October 10th at 11:59pm."
- "Add a 1-week extension to the midterm project's
due_atandlock_at." - "Set every discussion board's
due_atto the Sunday of its week."
Because due_at / unlock_at / lock_at (and every other field) are just
JSON values, the agent can find and edit them directly in the week files
under content/weeks/, across as many assignments/quizzes/discussions as
you describe, in one pass — instead of you clicking through each item's
settings in the Canvas UI one at a time. Once the JSON is edited, running:
python populate_canvas.py --course-id 12345 --input content/weekspushes those changes to the live Canvas course through the API. Because
populate_canvas.py matches existing items by title/name, re-running it
after an edit updates the existing assignment/quiz/discussion rather than
creating a duplicate.
launch_course.ps1 supports this workflow directly: picking a course from
courses.json opens VS Code and starts a Copilot Chat session already primed
with that course's ID and content directory, so you can immediately ask the
agent to make changes without re-explaining which course/folder you mean.
Always review what the agent changed (git diff) and run
populate_canvas.py --dry-run first before pushing date/content changes to
a real, live course — the API calls are real and not easily undone (e.g. a
changed due date affects students immediately, and a renamed item can orphan
the old one in Canvas; see the "duplicate on rename" gotcha in the code
comments).
launch_course.ps1 is a small picker for managing several Canvas courses
from one place: copy courses.json.example to courses.json (gitignored)
and list your own courses there, then run the script to open a primed VS
Code Copilot Chat session for whichever course you pick.
- Any real
.env, API tokens, or Canvas base URLs. courses.json— personal course list (seecourses.json.example).- Real course content (
content/weeks/,content/archive/, and the generatedcontent/all_content_bundle.*/content/course_map.txt) — only the genericcontent/example_content.jsonis shared.
All of the above are excluded via .gitignore.
- Never commit
.envor any file containing a real Canvas API token. - Canvas personal access tokens grant full API access as your account — treat them like a password and revoke/rotate them if ever exposed.
- These scripts only make the Canvas API calls you explicitly run; there is no background/scheduled execution.