Getting Started with CanvasManager
Run a Canvas course from a folder of markdown files: set it up in ten minutes, see the semester on a calendar, and publish with one click.
What CanvasManager is
CanvasManager is a web app you run on your own machine that turns a folder of markdown files into a Canvas course. Every assignment, quiz, page and lecture is a plain text file. You edit the files (in the app or in any editor), the app shows them on a semester calendar, and one click publishes an item to Canvas with its dates, rubric, module placement and formatting.
How your course lives on disk
You point the app at one storage folder. Inside it, each course is a folder with a settings.yml and one folder per module. Most people keep one folder per class and a subfolder per semester.
faculty/ ← the storage folder (mount this)
globalSettings.yml ← which course folders to show in the UI
4620_Distributed/
2026-fall/
Modules/
settings.yml ← dates, class days, defaults, grading categories
00 - lectures/
week-01/
1-Monday.md
3-Wednesday.md
01 Chaos KV/
assignments/
W01 Single-Node KV Store.md
quizzes/
W03 Reading Quiz.md
pages/
KV Wire Protocol Spec.md
02 Async Messaging/
...
globalSettings.yml lists the course folders you want to see right now, so old semesters can stay on disk without cluttering the app:
courses:
- path: ./4620_Distributed/2026-fall/Modules/
name: Distributed
- path: ./3630_Mobile/2026-fall/Modules/
name: Mobile
The app edits this file for you when you add or import a course. You can also edit it by hand.
Setting it up
You need Docker and a Canvas API token. Everything runs in one container.
Canvas token
- In Canvas, open Account › Settings and scroll to Approved Integrations.
- Click New Access Token, give it a purpose like “CanvasManager”, and copy the token. Canvas shows it only once.
- Create a file named
.envnext to your compose file:
CANVAS_TOKEN=paste-your-token-here
# optional, only for the Classroom 50 (GitHub Classroom) buttons,
# and for pulling and pushing a course repo on GitHub:
GH_TOKEN=ghp_...
# optional, only for the "Explain with AI" button on the error list;
# any OpenAI-compatible endpoint (OpenAI, OpenRouter, a local Ollama...):
AI_BASE_URL=https://api.openai.com/v1
AI_API_KEY=sk-...
AI_MODEL=gpt-4o-mini
The repository’s .env.example has these lines with more endpoint examples.
The token acts as you in Canvas. Keep .env out of git.
Docker Compose
Save this as docker-compose.yml and adjust the three volume paths to your machine:
services:
canvas_manager:
image: snowcollege/canvas_management:4
user: "1000:1000"
container_name: canvas-manager
ports:
- 3000:3000
env_file:
- .env
environment:
- storageDirectory=/app/storage
- TZ=America/Denver
- NEXT_PUBLIC_ENABLE_FILE_SYNC=true
volumes:
- ./globalSettings.yml:/app/globalSettings.yml
- ~/projects/faculty:/app/storage
- ~/projects/facultyFiles:/app/public/images/facultyFiles
| Line | Why |
|---|---|
user: "1000:1000" | Files the app writes are owned by you, not root. Use your own uid if it differs (id -u). |
TZ | Due times are interpreted in this zone. Set it to your campus. |
globalSettings.yml | Create it first with just courses: []. |
/app/storage | Your course folder. Make it a git repo; the course page can then commit, pull and push it (see Git). |
facultyFiles | Optional. Images you reference from markdown; see Images and files. |
First run
echo "courses: []" > globalSettings.yml
docker compose pull
docker compose up -d
Open http://localhost:3000. An empty course list with two buttons is what success looks like.
When a newer image is on Docker Hub the home page shows a banner with the commits it contains. Nothing updates on its own; run docker compose pull && docker compose up -d when you want it.
Adding a course
- Add New Course creates a folder and
settings.yml, and asks which Canvas course it maps to. You can also choose an existing course to import content from; assignments, quizzes, pages and lectures are copied and shifted onto the new semester's dates. - Import Existing Course adds a folder that already has a
settings.yml(a colleague's course, or a semester you removed from the list) back intoglobalSettings.yml. - Delete Mode removes a course from the list. Files on disk are not touched.
Course settings
Open a course and click Course Settings in the top right. Set these before you start publishing.
| Setting | What it does |
|---|---|
| Days of week | Click a day to cycle it through three states: class day (shaded on the calendar and offered by the date picker), no class, and hidden, a day without class that the calendar narrows to a strip so the class days get the room. See the calendar. |
| Semester start and end | Bounds the calendar and the “next class day” helper. |
| Classroom 50 | Optional GitHub Classroom integration. Skip it unless you use Classroom 50. |
| Default submission types and upload types | What new assignments start with. |
| Default due time and lock offset | The time of day dragged items get, and how many hours after due an assignment locks. |
| Assignment groups | Canvas grading categories and their weights. Update Assignment Groups In Canvas pushes them; do that once at the start of a semester. |
| Students and Groups | Read-only views of the Canvas roster and group sets, used by the editor. See the Group Assignments & Student Schedules guide. |
| Holidays | Days with no class. They are labeled on the calendar and struck through in the date picker. |
The calendar
The course page is a calendar of the whole semester with the module list beside it. Each item is a pill on its due day, and the border tells you its Canvas state:
- Green: published and matches Canvas.
- Red: in Canvas but different (dates, description, rubric, points, group set or assignment group) or not published yet. Hover to read every difference, with a link to the compare page.
- Grey: only on disk, not in Canvas yet.
The icon at the right of each pill tells the type apart: a blue pencil for an assignment, an amber question mark for a quiz, a violet page for a page. The same icon sits before the item’s name at the top of its editor.
Things you can do from the calendar:
- Hover a published item to preview its content; hover a red one to see everything that differs.
- Drag an item to another day. Due, lock and unlock dates move together, keeping their spacing. Lectures can be dragged too.
- Right-click an item for a menu: publish, view, compare, update or delete in Canvas, duplicate, rename. Publish turns into a green Published once students can see it.
- Click the + on a day (hover it for Add assignment / quiz / page) to create a new item due that day, or click the day's title to open that day's lecture. On a day with no lecture notes yet, hovering the title fades in a grey Add lecture notes.
- Reload Canvas Data refreshes the Canvas side of the comparison; View in Canvas opens the course. The git button beside them is described under Git.
- The modules you expand, and how far you have scrolled the calendar and the module list, are remembered for each course, so they are where you left them when you come back.
Hiding days. Days your class never uses, often Saturday and Sunday, can be hidden in course settings: click a day until it is struck through. The calendar then draws that day as a narrow strip with just the date, so the days you teach get the width. Anything due on a hidden day still shows, as a small type icon in the strip that opens the item, and you can still drag items onto the strip.
Editing content
Every editor has the same shape: the file on the left, a live preview on the right, and a footer that says how the file stands against Canvas. Changes save to disk as you type, and the preview shows parse errors at the top in red. The file is also watched, so editing it in another program updates the app immediately.
A file that can’t be read (say a quiz answer written a. instead of a), edited outside the app) is not dropped silently. Its module shows a red ⚠ count, and the file is listed at the top of the module in red under Can’t be read, click to fix. If the file’s date line still reads, it is also on the calendar on that day, as a red dashed pill with a warning icon; hover it to read the parse error. Clicking either one opens the file as plain text with the syntax help beside it and the parse error on the right. Every change is saved as you type, and the regular editor opens by itself as soon as the file parses. File history at the bottom goes back to a version that worked, if the file is in git.
Assignments
An assignment file is a settings block, a --- line, the description in markdown, and a ## Rubric heading followed by the criteria.
UnlockAt:
LockAt: 09/04/2026 23:59:00
DueAt: 09/04/2026 23:59:00
AssignmentGroupName: Assignments
Classroom50Slug:
SubmissionTypes:
- online_text_entry
- online_upload
AllowedFileUploadExtensions:
- pdf
---
Next week you'll implement primary-backup replication...
## Rubric
- 10pts: Formatting
- 10pts: proper margins, font size, spacing, and headings
- 7pts: margins and font size correct but missing heading styles
- 5pts: Student response to reading
- 2pts: (extra credit) Went beyond the prompt
- Dates are
MM/DD/YYYY HH:MM:SS.UnlockAtandLockAtmay be blank. - Each rubric line is
- Npts: label(ptfor a single point; decimals work). Indented lines under a criterion become ratings the grader can pick from. Labels starting with (extra credit), and negative rows used as deductions, do not count toward points possible. - Markdown, tables, Mermaid diagrams, LaTeX math and images all render in Canvas.
- Link to another page, assignment or quiz by its relative path, like
[spec](<../pages/Wire Protocol Spec.md>). The preview opens it in the app, and publishing rewrites it to the Canvas url. - The ? button at the bottom left opens a searchable reference with copyable examples for every setting and syntax.
Quizzes
A quiz file has its own settings block, then one question per section separated by --- lines. The preview renders each question the way Canvas will.
Points: Non a question's first line sets its points; without it the question is worth 1.- The question text is markdown, then the answers. The last lines decide the question type:
| Type | How to write it |
|---|---|
| Multiple choice | a) ..., b) ...; a * in front marks the correct one |
| Multiple answers | [*] right and [ ] wrong checkboxes |
| Essay | a last line of essay |
| Short answer | a last line of short_answer (graded by hand), or starred answers followed by short_answer= to auto-grade |
| Numerical | = 2.5 for an exact answer, = [2.4, 2.6] for a range |
| Matching | ^ left - right lines; ^ - extra adds a distractor |
| Multiple dropdowns | matching lines split into groups by blank lines; each prompt gets its own dropdown of its group's answers |
| Fill in multiple blanks | [ans1] placeholders in the question, then [ans1] = answer for each blank; more accepted answers for the same blank go on the next lines as = answer |
- Lines beginning
+and-before the answers are feedback for correct and incorrect answers;...is shown either way. - Code blocks work in questions and answers, and answer markers inside a code block are ignored.
- The quiz help (the ? button) has a copyable example of every type.
Pages
Pages are plain markdown with one setting, DueDateForOrdering, which only decides where the page sits on the calendar and in the module.
Link to a page the way Canvas will resolve it, by its title in lowercase with dashes: [Belt Tour](../pages/belt-tour), from any page, assignment or quiz. The preview follows such a link to the page in the app, keeping anything after a #.
Lectures
Each class day has one lecture file with a name, a date and markdown notes. Click a day's title on the calendar to open it. preview opens a full-width view for projecting.
Editor helpers
The settings block has autocomplete. A blank line offers the keys the file is missing; a key like AssignmentGroupName offers its valid values; a - under a list offers the list's options. Date fields open a date and time picker that knows your class days and holidays.
The help panel is searchable: type a word like rubric, numerical or schedule to filter it to the topics that mention it. Expand all and Collapse all sit beside the search box, and every example has a Copy button.
Publishing to Canvas
Each editor’s footer says where the item stands and shows the one button you need next:
- Not in Canvas yet: Add to Canvas creates the item in Canvas, unpublished, with its rendered description, dates, rubric and module entry. A module that does not exist yet is created.
- 3 differences from Canvas (in amber): Update in Canvas pushes the current file over the existing Canvas item. Submissions and grades are untouched. Click the amber text to open Compare with Canvas.
- In sync · students can’t see it yet: Publish in Canvas makes it visible to students. If the item’s module is still unpublished in Canvas, the app says so and links to the modules page; it does not publish the module for you.
- In sync · Published: nothing to do; View in Canvas opens it.
- For a quiz, Update in Canvas also replaces the questions with the file’s when any of them differ. If students have already started, it asks first: their attempts keep the questions they got and are not regraded. It refuses a change to the quiz’s point total, since their scores are out of the total they were given.
- Not being published yet is not counted as a difference from the file, since publishing is your call. An unpublished item still has a red border on the calendar, but its footer and the compare page say in sync when it matches, and the compare page notes not published next to its Canvas id.
- You can add or update several items in a row, or a quiz with many questions, without waiting. Every request to Canvas goes through one queue that sends a few at a time, slows down when Canvas reports its rate limit running low, and when Canvas says too many requests waits and tries again instead of failing.
- A page is compared on its content, so its footer works the same way: In sync, or the number of differences with Update in Canvas as its button.
Everything else is behind the arrow on the right edge of that button: View in Canvas, Compare with Canvas, Update in Canvas anyway, File history, Classroom 50, and, at the bottom in red, Delete from Canvas (removes it there and leaves your file alone) or Delete locally for an item that is only on disk. At the left are the help button, previous and next item, and Back to calendar. On a phone all of these are in the ☰ menu.
Published quiz? Click “Save it now”. After updating the questions of a published quiz, students keep getting the old questions until you open the quiz in Canvas and click Save it now. The Canvas API cannot do that step. The app reminds you with a message that links to the quiz.
The calendar border and the right-click menu do the same jobs from the course page, so a typical week is: write the files, glance at the calendar for grey and red pills, publish or update from the menu.
Publishing uses your Canvas token, so it can do anything you can. The app never deletes a Canvas item unless you ask it to.
Compare with Canvas
When an item’s border is red, Compare with Canvas (the amber differences from Canvas text in the editor footer, the menu on the footer’s main button, the right-click menu, and the red pill’s hover card) opens a page that lists every difference between your file and Canvas:
- A settings table with the file’s value and Canvas’s side by side: dates, assignment group, group set, points and each rubric row, and for a quiz its password, attempts and other options.
- For a quiz, a row for every question, compared on type, points, text, answers (correct ones marked ✓) and feedback. Questions are lined up by what they say, not by number, so deleting or adding one doesn’t make every later question look changed: a question you deleted from the file shows as one Canvas question N row, one you added as a row with nothing on the Canvas side, and one you moved as a position difference. Every question shows in full on both sides, answers included, and ≠ marks the lines of each part that differs. The quiz editor’s footer counts these differences too. The calendar checks questions for this week’s quizzes and the next three weeks’, fetching them in the background one quiz at a time (this week first) so Canvas doesn’t throttle you; a quiz outside those four weeks gets its red border from settings and description only.
- For a quiz students have started, a What students got section for each quiz version they took, comparing the file with the questions from one of its attempts (ignoring each attempt’s own answer shuffle). Canvas’s current questions can differ from these until Save it now, and this is the only way to see what students actually got.
- The description (a page’s content) side by side, as changes only (a word-level diff), or as the normalized HTML when you need to see why two descriptions that look the same count as different.
- An Update Canvas button at the bottom that pushes the file, with a note saying exactly what it will change. The top of the page says when it last read Canvas, and refresh reads it again.
Images and files
Mount a folder of images at /app/public/images/facultyFiles (see the compose file) and reference them from markdown in an assignment, quiz or page:

- When you add or update the item in Canvas, each image is uploaded to the course’s Canvas files and the link is rewritten to the Canvas copy. Nothing is uploaded while you type.
- An image is uploaded once. The mapping, with a fingerprint of the file, is remembered in
settings.yml, so an image you replace with a new version is uploaded again the next time you publish. - If an image can’t be found, publishing stops with a message naming the image and where the app looked, instead of sending Canvas a link that only works on your computer.
- Images on the web, Mermaid diagrams included, are left as links; Canvas shows them from where they are.
Keep images in a separate repo from your course text so the text repo stays small.
Git: commit, sync, history
When your course folder is in a git repository, the course page’s header has a git button beside Reload Canvas Data. It reads Committed, or 3 uncommitted with an amber dot when files in this course have changed, with ↑2 for commits waiting to be pushed and ↓1 for commits waiting to be pulled.
- Commit commits only this course’s folder, never anything else in the repository. If git doesn’t know your name and email yet, it asks for them first.
- Pull and push gets the remote’s new commits, then pushes yours. If you and someone else both committed, the two are merged automatically when they changed different lines. When they changed the same lines nothing is merged, your files are left exactly as they were, and the message names the files to sort out in a terminal (
git pull). - Commit automatically after adding or updating something in Canvas makes a commit each time you publish, with a message saying what was published, so your history matches what students saw.
- The app checks the remote for new commits every ten minutes while a course page is open.
To pull and push a GitHub repository from the container, set GH_TOKEN in .env to a token that can write to the repo; a remote written as git@github.com:... is reached over https with that token. With your own ssh keys mounted in the container, those are used instead.
File history. Every assignment, quiz and page has a File history item in the menu on its editor’s footer. It lists the commits that changed the file, renames included. Pick one to see what changed between that version and the file now, and Restore this version to write it back over the file. Restoring doesn’t commit anything; if the file has changes that were never committed, it warns you first, because those are not in git to get back.
When something goes wrong
Every error the app runs into while it is running, a file that doesn’t parse, a failed Canvas request, a pull that couldn’t merge, is kept in a list. While any of them are unfixed, a red pill at the bottom center of the screen counts them; click it to see the list.
- Explain with AI asks an AI model what the error means and how to fix it. For a file that doesn’t parse it sends the file and a working file from the same folder for comparison, so the answer can point at the line to change. Use any OpenAI-compatible endpoint by setting
AI_BASE_URL,AI_MODELand, if it needs one,AI_API_KEYin.env(see Canvas token); without them the button is greyed out. Course files are sent, never student names, since those are not in the files. - Open goes to the item the error is about; Dismiss and Clear all take entries off the list.
- The list lives in the app’s memory and starts empty each time the app starts.
Tips
- Make the storage folder a git repo and commit as you go, from the git button on the course page. The files are small and diff well. No student names are written to them.
- Edit anywhere. The app watches the folder, so a change made in your own editor shows up in the app within a second.
- Start a semester from the previous one. Add New Course, choose the old course to import from, and everything lands on the new dates ready to review.
- Push assignment groups once, at the start of the semester, then check the “Weight final grade based on assignment groups” box in Canvas.
- Read the red pills. Hovering an out-of-sync item tells you exactly what differs, which is faster than opening Canvas to compare.
- Error messages stay until you close them, so you will not miss one, and the same error repeated shows once instead of stacking. Closed ones are still in the error list.
- Group projects and per-student due dates have their own guide: Group Assignments & Student Schedules.
Recent changes
| When | What changed |
|---|---|
| Oct 2026 | Opening a course whose term has ended no longer shows a roster.groupSets … 403 error: Canvas won’t list group sets for a finished course, so it shows none. |
| Oct 2026 | Compare with Canvas checks a quiz’s password, allowed attempts, shuffle answers, show correct answers and one question at a time, so changing only one of them no longer shows the quiz as in sync. |
| Oct 2026 | Compare with Canvas compares short-answer answers as plain text, so an answer like + or a*b*c no longer shows as different when it matches. |
| Oct 2026 | Update in Canvas refuses to change a quiz’s point total once students have started, since their scores are out of the total they were given. |
| Oct 2026 | Compare with Canvas shows what students got on each quiz version they took, next to the file. |
| Oct 2026 | Compare with Canvas shows every quiz question in full, answers included, marking the parts that differ, and says when it last read Canvas. |
| Oct 2026 | Update in Canvas leaves a quiz’s questions alone when they already match the file, so a settings-only change to a published quiz no longer needs Save it now. |
| Oct 2026 | Pages are compared with Canvas on their content: an edited page gets a red border, a difference count in its footer, and a Compare with Canvas page. |
| Oct 2026 | Page links written for Canvas, like ../pages/belt-tour, open the page in the preview instead of a missing file. |
| Oct 2026 | Classroom 50 links send students to the assignment’s accept page, and setup recognizes the gh-teacher extension when the Docker image copies it in. |
| Oct 2026 | On the calendar, hovering a day’s title fades in Add lecture notes when the day has none, and the + button has an Add assignment / quiz / page tooltip. |
| Oct 2026 | Compare with Canvas lines quiz questions up by content, so deleting, adding or moving one question shows as one difference instead of marking every question after it as changed. |
| Oct 2026 | Requests to Canvas are queued and retried when Canvas rate-limits them, so adding several items in a row or a long quiz no longer loses requests (or quiz questions) to 429 Too Many Requests. |
| Oct 2026 | Not being published is no longer counted as a difference: the compare page drops its Published row and says in sync, while the calendar border stays red until you publish. |
| Oct 2026 | Git from the course page: commit a course’s changes with a message written for you, pull and push (merging when there is no conflict), optionally commit after each publish, and see or restore any earlier version of a file. |
| Oct 2026 | Error list: a pill counts this session’s errors and opens a list of them, each with an Explain with AI button that uses any OpenAI-compatible endpoint you configure. |
| Oct 2026 | A file that doesn’t parse is listed in red in its module, shown on the calendar on its date when that line still reads, and opens as plain text to fix, instead of disappearing. |
| Oct 2026 | Hidden days: a day without class can be hidden in course settings, and the calendar narrows it to a strip. |
| Oct 2026 | Images are uploaded when you publish an assignment, quiz or page, not while you type; a changed image is uploaded again, a missing one stops the publish, and web images and Mermaid diagrams are no longer copied into Canvas files. |
| Oct 2026 | Expanded modules and the module list’s scroll position are remembered per course. |
| Oct 2026 | Publish in Canvas button in the editors and the right-click menu, showing Published once students can see the item. |
| Oct 2026 | Fill in multiple blanks quiz questions, with several accepted answers per blank. |
| Oct 2026 | Distinct calendar icons for assignments, quizzes and pages. |
| Oct 2026 | Calendar titles wrap under the type icon instead of beside it, and the editors show the same icon before the item’s name at the top. |
| Oct 2026 | Compare with Canvas and the quiz editor’s footer compare every quiz question too: type, points, text, answers and feedback. The calendar does the same for the next four weeks of quizzes. |
| Oct 2026 | The editor footer is one row: it says whether the file is in sync with Canvas and shows the one button you need next, with the other actions behind the arrow on that button. |
| Oct 2026 | Renaming an assignment, quiz or page no longer spins forever on a name with a character a file name cannot hold (like / or :); the rename box says which character to remove. |
| Oct 2026 | The editor help is searchable and split into collapsible topics with copyable examples. It now covers every quiz setting and question type, numerical questions, rubric deductions, and links between course files. |
| Oct 2026 | Multiple dropdowns quiz questions. |
| Oct 2026 | Update in Canvas replaces a quiz’s questions too, warns first if students have started, and reminds you to click Save it now on a published quiz. |
| Oct 2026 | Repeated error messages show once, and errors stay on screen until closed. |
| Sep 2026 | Compare with Canvas page; the calendar lists every difference instead of the first one, and quiz settings can be updated in place. |
| Sep 2026 | Group assignments, per-student schedules, the date picker and settings autocomplete. See Group Assignments & Student Schedules. |