Skip to content

Repository files navigation

Canvas Course Builder

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).

Features

  • 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 — thin requests-based Canvas API client and the Pydantic models that define the JSON content schema.

Setup

  1. Create and activate a virtual environment, then install dependencies:
    python -m venv .venv
    .venv\Scripts\Activate.ps1
    pip install -r requirements.txt
  2. Copy .env.example to .env and fill in your own values:
    CANVAS_BASE_URL=https://yourschool.instructure.com
    CANVAS_API_TOKEN=your_token_here
    
    Get a token from Canvas under Account > Settings > + New Access Token. .env is gitignored — never commit it.

Usage

# 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 12345

Replace 12345 with your real Canvas course ID.

Content JSON schema

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.

Editing your course with an AI coding agent

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/weeks by 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_at and lock_at."
  • "Set every discussion board's due_at to 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/weeks

pushes 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).

Multiple courses (optional)

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.

What's intentionally NOT in this repo

  • Any real .env, API tokens, or Canvas base URLs.
  • courses.json — personal course list (see courses.json.example).
  • Real course content (content/weeks/, content/archive/, and the generated content/all_content_bundle.* / content/course_map.txt) — only the generic content/example_content.json is shared.

All of the above are excluded via .gitignore.

Security notes

  • Never commit .env or 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.

Releases

Packages

Contributors

Languages