org-canvas is an Emacs package that synchronizes course content from Org Mode files to Canvas LMS via its REST API. It enables a “source of truth” workflow where instructors design their entire course in Org Mode and push changes to Canvas.
Tested against the Canvas LMS REST API (/api/v1/) as of 2026-02-07. Canvas does not version its API beyond v1; see the Canvas API Change Log for recent changes.
- Org Mode as Source of Truth: Design your course in plain text with full Org Mode capabilities
- Bidirectional Sync: Push content to Canvas and pull existing courses into Org files
- Conflict Detection: Interactive push/pull/skip resolution when remote changes are detected
- Modular Architecture: Sync individual content types or the entire course at once
- Dependency-Aware: Syncs in proper order respecting cross-references between files
- 16 Content Types: Assignments, classic quizzes, New Quizzes, pages, modules, rubrics, outcomes, discussions, announcements, files, assignment groups, group categories, sections, per-section date overrides, calendar events, and course settings
- Submission Grading: View submissions, post comments, download attachments, and push grades back to Canvas – all from Emacs
- Canvas Migration: Import an entire existing Canvas course into Org files with
org-canvas-pull-all - Orphan Cleanup: Detect and remove Canvas items that no longer have corresponding Org headings
- Resilient Syncing: Pre-sync credential validation, rate limit retry with backoff, actionable error messages for auth failures, graceful handling of missing files
org-canvas is not yet on MELPA. Install from source:
;; Using use-package with straight.el
(use-package org-canvas
:straight (org-canvas :type git :host github :repo "ransomts/org-canvas"
:files ("lisp/*.el")))
;; Using use-package with built-in vc-package (Emacs 29+)
(use-package org-canvas
:vc (:url "https://github.com/ransomts/org-canvas"))The :files directive is not optional. The sources live in lisp/, and a
symlink at the repository root points at lisp/org-canvas.el so that
eldev lint can find the Package-Requires header where package-lint expects
it. straight.el’s default directive matches both *.el and lisp/*.el, sees
the same file twice, and warns:
Warning (straight): Attempted to link ".../build/org-canvas/org-canvas.el" to both ".../repos/org-canvas/org-canvas.el" and ".../repos/org-canvas/lisp/org-canvas.el"
Naming lisp/*.el resolves the ambiguity.
Or clone for development:
git clone https://github.com/ransomts/org-canvas.git
cd org-canvas
eldev prepareThe easiest way to set up a new course is the setup wizard:
M-x org-canvas-initThis prompts for your Canvas URL, API token, and course ID, tests the connection, writes org-canvas-credentials.el, and optionally creates skeleton .org files for all content types.
Or create org-canvas-credentials.el manually in your course directory (not tracked by git):
(setq org-canvas-api-token "your-canvas-api-token")
(setq org-canvas-course-id "12345")
(setq org-canvas-base-url "https://canvas.instructure.com")
(setq org-canvas-directory "/path/to/your/course/")Create Org files for each content type:
my-course/ ├── org-canvas-credentials.el ; Your API credentials (gitignored) ├── settings.org ; Course settings (timezone, homepage, license, navigation tabs) ├── assignments.org ; Course assignments ├── quizzes.org ; Classic quizzes with questions ├── new-quizzes.org ; New Quizzes (Canvas next-gen quiz engine) ├── pages.org ; Wiki pages ├── modules.org ; Course structure/navigation ├── rubrics.org ; Grading rubrics ├── outcomes.org ; Learning outcomes ├── discussions.org ; Discussion boards ├── announcements.org ; Course announcements ├── files.org ; File uploads ├── assignment-groups.org ; Grade weighting categories ├── group-categories.org ; Group sets for group assignments/discussions ├── calendar.org ; Calendar events (office hours, holidays, review sessions) └── sections.org ; Course sections (pulled from Canvas) & date overrides
All commands are accessible through a transient menu:
;; Open the command menu (recommended — shows all commands with keybindings)
M-x org-canvas-dispatchOr call commands directly:
;; Test your connection
M-x org-canvas-test-connection
;; See sync status overview
M-x org-canvas-status
;; Validate all org files (offline, no API calls)
M-x org-canvas-validate
;; Preview what would be synced (no API calls)
M-x org-canvas-sync-dry-run
;; Sync everything (respects dependencies, detects conflicts)
M-x org-canvas-sync
;; Force sync, overwriting any remote changes
M-x org-canvas-force-push
;; Sync individual content types
M-x org-canvas-sync-assignments
M-x org-canvas-sync-quizzes
M-x org-canvas-sync-new-quizzes
M-x org-canvas-sync-pages
M-x org-canvas-sync-settings
M-x org-canvas-sync-group-categories
M-x org-canvas-sync-calendar-events
;; ... etc for all 16 content types
;; Sync a single item at cursor
M-x org-canvas-sync-assignment-at-point
;; Import an existing Canvas course into Org files
M-x org-canvas-pull-all
;; Pull individual content types from Canvas
M-x org-canvas-pull-assignments
M-x org-canvas-pull-pages
;; ... etc for all content types
;; View, comment on, and grade submissions
M-x org-canvas-submissions-view
;; Find and remove orphaned Canvas items
M-x org-canvas-cleanup-orphans
;; Delete all content from Canvas
M-x org-canvas-delete-all* Lab 1: Introduction to Python
:PROPERTIES:
:POINTS: 100
:DUE_AT: <2025-09-15 Mon 23:59>
:SUBMISSION: online_upload
:ALLOWED_EXTENSIONS: py
:GROUP: [[file:assignment-groups.org::*Labs][Labs]]
:RUBRIC_LINK: [[file:rubrics.org::*Standard Lab Rubric][Standard Lab Rubric]]
:END:
Write a Python program that prints "Hello, World!" and demonstrates
basic variable usage.
** Requirements
- Create a file named =hello.py=
- Print a greeting message
- Define at least one variable
After syncing, org-canvas automatically adds:
:CANVAS_ID: 123456
:LAST_SYNCED: [2025-02-02 Sun 10:30]
org-canvas includes a submissions viewer for reviewing student work, posting comments, downloading attachments, and pushing grades – all without leaving Emacs.
M-x org-canvas-submissions-viewThis prompts for an assignment, fetches submissions from Canvas, and opens an interactive buffer with keybindings:
| Key | Action |
|---|---|
v | Toggle between summary table and detail view |
c | Post a comment on the submission at point |
d | Download submission attachments |
S | Push modified grades to Canvas |
g | Refresh submissions from Canvas |
q | Quit |
In detail view, edit the :SCORE: property on any student heading and press S to push grade changes. org-canvas detects which scores changed and confirms before pushing.
- Reference Manual – Complete file format specs, all properties, command reference, architecture
- Workflows Guide – Semester setup, weekly/daily/per-assignment workflows, grading, multi-instructor collaboration
- FAQ & Recipes – Quick answers to “how do I…” questions for assignments, quizzes, modules, grading, and more
- Quick Reference Card – All properties, commands, and keybindings on one page
- Migration Guide – Switching from Canvas web UI to an Org Mode workflow
- Pitfalls & Gotchas – Common mistakes and how to avoid them
- Canvas Coverage Map – What org-canvas manages, what it only reads, and what in Canvas it never touches
- Demo Course – Complete example course (DS 101) showcasing all content types
- Contributing – Development setup, coding conventions, testing practices, PR process
- Emacs 29.1+
- Org Mode 9.6+
- plz - HTTP library
- elog - Logging framework
- transient (0.4+) - Command menu (built into Emacs 29+)
- pandoc (optional) - Required for HTML-to-Org conversion when pulling content from Canvas
# Run tests
eldev test
# Run tests with coverage
eldev test -u "on,text,dontsend"
# Lint
eldev lint
# Compile
eldev compileGPL3 License. See LICENSE for details.