
1vibecoders/google-classroom-bulk-exporter
Developer toolsA Chrome extension that downloads the relevant content from an entire Google Classroom class into an organized local archive.
Overview
A Chrome extension (Manifest V3) that exports an entire Google Classroom class into one organized ZIP archive: every assignment, material, question and (optionally) announcement, with its instructions, metadata and attached files, kept together in one folder per item. does the same for every active (not archived) class of your Google account, into a single ZIP that is easy to hand to Claude's /academic-schedule skill. It runs entirely in your browser with your existing Google sign-in. Nothing is uploaded anywhere. puts every class in its own folder with the same layout; only the manifest moves up, to the top-level folder: Chrome 116 or newer (or another Chromium browser: Edge, Brave, …) is required. 1. Extract the ZIP into a folder you will keep; Chrome loads the extension from it. 2. Open chrome://extensions (edge://extensions, brave://extensions, …) and turn on (top right). 3. Click and select the extracted folder (the one containing manifest.json). 4.
README
Google Classroom Bulk Exporter
A Chrome extension (Manifest V3) that exports an entire Google Classroom class
into one organized ZIP archive: every assignment, material, question and
(optionally) announcement, with its instructions, metadata and attached files,
kept together in one folder per item. Export all classes does the same for
every active (not archived) class of your Google account, into a single ZIP
that is easy to hand to Claude’s /academic-schedule skill.
It runs entirely in your browser with your existing Google sign-in. Nothing is uploaded anywhere.
English 10 - Period 3/
├── index.html ← offline table of contents
├── class-info.json
├── class-description.txt
├── export-report.txt ← what was downloaded, what failed and why
├── export-report.json
├── export-manifest.json ← machine-readable summary (see below)
├── Assignments/
│ ├── Macbeth Act 1 Questions/
│ │ ├── description.txt
│ │ ├── metadata.json
│ │ └── Attachments/
│ │ ├── Macbeth.pdf
│ │ ├── Act 1 notes.docx (Google Doc, exported)
│ │ └── Act 1 slides.pptx (Google Slides, exported)
│ └── Essay - Unit 1/
│ ├── description.txt
│ ├── metadata.json
│ └── Attachments/
│ └── Self assessment.url (Google Form, saved as a link)
├── Materials/
│ ├── Poetry Terms/
│ └── Poetry Terms (2)/ (two materials with the same title)
├── Questions/
├── Announcements/
└── Other Coursework/ (items whose type could not be determined)
Export all classes puts every class in its own folder with the same layout; only the manifest moves up, to the top-level folder:
Google Classroom Export - 2026-10-07/
├── export-manifest.json ← every class: folder, status, counts
├── index.html ← offline table of contents linking to each class
├── export-report.txt ← overall summary, then each class's report
├── English 10 - Period 3/ ← the layout above
│ ├── class-info.json
│ ├── Assignments/ …
│ └── …
├── Biology - Period 1/
└── Biology - Period 1 (2)/ (two classes with the same name and section)
Install
Chrome 116 or newer (or another Chromium browser: Edge, Brave, …) is required.
From a release (recommended)
Each release has three files:
| File | What it is |
|---|---|
google-classroom-bulk-exporter-.zip |
The extension’s files with manifest.json at the root. Extract it and Load unpacked (below), upload it to the Chrome Web Store, or pack it yourself. |
google-classroom-bulk-exporter-.crx |
The packed, signed extension (its extension ID is in the release notes). |
SHA256SUMS.txt |
Checksums: sha256sum -c SHA256SUMS.txt. |
Load unpacked (ZIP, works everywhere):
- Extract the ZIP into a folder you will keep; Chrome loads the extension from it.
- Open
chrome://extensions(edge://extensions,brave://extensions, …) and turn on Developer mode (top right). - Click Load unpacked and select the extracted folder (the one containing
manifest.json). - Pin the extension (puzzle-piece icon → pin) so its button is easy to reach.
To update, extract the new ZIP over the same folder and click the reload icon on the extension card.
Packed (CRX): Chrome only accepts .crx files from outside the Chrome Web
Store in some setups. On Linux, open chrome://extensions with Developer
mode on and drag the .crx onto the page. On managed devices (any OS),
deploy it with the ExtensionInstallForcelist policy and an update manifest
pointing at the .crx. On Windows and macOS, regular Chrome disables
extensions installed from a .crx that are not in the Web Store, so use the
ZIP there.
From source
- Download or clone this repository.
- In
chrome://extensions, turn on Developer mode, click Load unpacked and select the repository folder.
No build step is needed.
Use
- Open a class at classroom.google.com (its Stream or Classwork page).
- Click the extension button. The popup shows the detected class.
- Optionally click Scan only to see how many assignments, materials and attachments were found without downloading anything.
- Click Export Class. Leave the Classroom tab open while the popup says scanning class: the exporter opens the Classwork page (and the Stream, if announcements are included), scrolls through it and expands each item. It takes the tab back to where you were when scanning finishes.
- Files then download in the background (you can use the tab normally and
close or reopen the popup). The ZIP is saved to
Downloads/Classroom Exports/ - .zip; an existing file is never overwritten (Chrome adds(1)). - When finished the popup shows how many items were processed, files downloaded, files that could not be downloaded and links saved. Show Details lists every failure with its reason; Save report saves the report as a text file; Show in folder opens the archive.
Options (in the popup; they apply to both a single class and Export all classes):
| Option | Default | Effect |
|---|---|---|
| Include announcements from the Stream | on | Also exports Stream announcements and their attachments. |
| Open each item’s page for complete instructions | on | Reads each item’s own page; finds attachments and full text the Classwork list may hide. Turn off for a faster, list-only export. |
| Google Docs, Sheets and Slides as | Office files | Export Google files as .docx/.xlsx/.pptx (Drawings as .png) or as PDF. |
| Ask where to save the ZIP file | off | Shows Chrome’s Save as dialog. |
Export all classes
- Open any page of classroom.google.com with
the Google account you want to export: the class list (home page) or any
class. With several accounts signed in, the account is the one in the
address (
/u/0/,/u/1/, …). - Click the extension button, then Export all classes. The exporter opens
the Classroom home page and lists the classes on it (archived classes are
not on it, so they are not exported). The redesigned home page shows one
role view at a time (for example Enrolled at
/u/1/h/st, or Teaching), so the classes that only the navigation menu lists — those of your other view — are added after the home page’s cards. The popup shows N classes found with each class’s name, section and teacher (the teacher is only known for classes with a card on the page). - Click Export N classes. The classes are exported one after the other, in the same tab and with the same options as a single class; the popup shows Class k of N: name and the progress of that class. Leave the Classroom tab alone until the last class has been scanned (also while the files of earlier classes download); it is then taken back to where you started. Cancel stops the export at any point. If the list is more than 15 minutes old, or the tab has moved to another account since, the classes are listed again for you to confirm.
- A class that cannot be scanned (its page does not load, the tab is taken elsewhere within Classroom, …) is marked as not exported, with the reason, and the export goes on with the next class. The popup lists each class with ✓ (exported), ! (partially exported, e.g. some files could not be downloaded) or ✗ (not exported).
- The ZIP is saved as
Downloads/Classroom Exports/All classes - .zip(an existing file is never overwritten).
What is exported
| Resource | Result |
|---|---|
| Files stored in Google Drive (PDF, Word, images, video, …) | Downloaded with their original file name. |
| Google Docs / Sheets / Slides / Drawings | Exported (Office formats or PDF, see options). |
| Links in the instructions to Drive files or Google Docs | Downloaded too, marked as description-link in metadata.json. |
| Google Forms, Drive folders, YouTube videos, Google Sites, other websites, Classroom add-ons | Saved as .url shortcuts (open in a browser) and listed in the report. They are not files. |
| Files you cannot open in Classroom, or whose owner disabled downloading | Not downloaded; listed in the report with the reason. |
| Your own submitted work, class comments, private comments, grades, other students’ work | Not exported. |
Each item folder has:
description.txt– title, type, topic, due date, points, posting date, Classroom link, the full instructions/text and where each attachment went.metadata.json– the same as structured data, with one entry per attachment: title, type, original URL, Drive file id, status (downloaded,failedorlink), saved file path, original file name, size, content type and the error for failures.
Class-level files: class-info.json (class id, name, section, counts, topics,
options, the list of items and their folders), class-description.txt,
export-report.txt/.json and index.html.
export-manifest.json
Every archive has an export-manifest.json in its top-level folder, so a
program (or Claude) can read both kinds of export the same way:
| Field | Meaning |
|---|---|
kind |
"account" (Export all classes) or "class" (one class). |
formatVersion |
1. It changes only if the format changes incompatibly. |
exportedAt |
Time of the export (ISO 8601, UTC). Classroom shows dates such as Oct 10 without the year; this tells which year they belong to. |
accountIndex |
The N of /u/N/: which signed-in Google account was exported. |
options |
includeAnnouncements, readItemPages, googleFilesExportedAs (office or pdf). |
classes |
One entry per class, in the order of the Classroom home page (see below). |
totals |
classes, exported, partial, failed, items, filesDownloaded, filesFailed, linksSaved, bytesDownloaded. |
extension |
name and version of the exporter. |
Each entry of classes has courseId, name, section, teacher (as shown
on the class card; null when the card shows none, e.g. for classes you
teach), url, folder, status, counts and, when something went wrong,
error:
folderis the class folder relative to the manifest:"."in a single-class archive, the class’s folder name in an account archive, andnullfor a class that was not exported.statusisexported(complete),partial(the folder is there, but some files could not be downloaded, which are listed in itsexport-report.txt; or writing the folder stopped with an error, whicherrorgives, and the folder may be incomplete) orfailed(not exported;errorsays why).countshasitems,assignment,material,question,announcement,other,filesDownloaded,filesFailed,linksSaved,bytesDownloadedandwarnings(nullfor a class that was not exported).
To find the classes without the manifest (for example in an archive made by an
older version), look for every class-info.json: each one marks a class folder
(a partial folder with an error may lack it; the manifest still lists it).
Using an export with Claude (/academic-schedule)
Export all classes is the convenient way to give Claude everything at
once, for example to build a study or homework schedule with the
/academic-schedule skill. The skill lives in this repository in
skills/academic-schedule/; its documentation is
academic-scheduler/SCHEDULE_SKILL.md.
- Export all classes (above) and wait for Export complete.
- In Claude, attach
All classes - .zip(or the extracted folder) and run/academic-schedule.
Everything Claude needs is in the archive: export-manifest.json lists the
classes and their folders; in each class folder, class-info.json lists the
items and, per item, metadata.json has the type, topic, due date, points and
posting date, and description.txt the full instructions. Attachments are
there too. If a class shows as failed, export it again on its own and attach
that ZIP as well.
Dates are kept as Classroom displays them (e.g. Oct 10, 11:59 PM), because
Classroom does not show years for the current year or time zones.
Sending classes to the Academic Scheduler website
After you scan or export a class, the popup shows Send to Academic
Scheduler with every class scanned in this browser session. Type the
website’s address (e.g. https://your-site.vercel.app) and click Send:
Chrome asks once to let the extension open that one website, the website opens
on its Import page and shows the changes to review. Nothing changes in your
schedule until you click Import there. What is sent: class names, topics,
assignments and questions (title, instructions, due date and points as
displayed), materials and announcements, and attachment links, plus the
attached documents themselves (Google Docs and Slides, PDFs, Word and PowerPoint
files; at most 40, 10 MB each and 25 MB in all) so the website can read them.
They are downloaded the same way the ZIP export downloads them.
Permissions
| Permission | Why it is needed |
|---|---|
https://classroom.google.com/* |
Read the class you are viewing (and, for Export all classes, the class list of your Classroom home page): inject the exporter into the Classroom tab when you open the popup, and open item pages with your session. |
https://drive.google.com/*, https://drive.usercontent.google.com/* |
Download Drive files (the same download endpoints the Drive Download button uses). |
https://docs.google.com/* |
Export Google Docs, Sheets, Slides and Drawings (the File → Download endpoints). |
https://*.googleusercontent.com/* |
Google serves some downloads and exports from these hosts after a redirect. |
scripting |
Inject the exporter into the Classroom tab only when you open the popup (nothing runs on pages otherwise). |
downloads |
Save the finished ZIP file to your Downloads folder and show it in its folder. |
offscreen |
A hidden extension page that downloads the files and builds the ZIP; unlike the service worker it is not stopped while a long export runs. |
storage |
Remember your options and the progress of the current export (session storage, cleared when the browser closes). |
| optional: the Academic Scheduler’s address | Only when you click Send to Academic Scheduler: Chrome asks to allow that one website, so the extension can open it and hand it your scanned classes. Declared as optional https://*/* (plus localhost) because the address is yours to choose; nothing is granted until you allow a specific site. |
The extension does not request tabs, cookies, webRequest or
``, and no non-Google site unless you allow your Academic Scheduler
address. External links are saved as shortcuts
instead of being fetched, so no broad host permission is needed. Export all
classes needs no additional permission: it uses the same Classroom tab and
the same hosts.
Privacy
- Everything happens locally in your browser. There is no server, analytics or telemetry. Classroom content, attachments and the archive never leave your computer except as the ZIP saved to your Downloads folder, or when you click Send to Academic Scheduler (then the scanned classes and their attached documents go to the website address you typed).
- Requests go only to Google, as you, with your existing session. The exporter can only see and download what you can already open in Classroom; it does not bypass sharing settings, “disable download” settings, sign-in or DRM.
- The archive does not include your e-mail address, other people’s comments, grades, the class code or Meet links.
How it works
popup ──commands──▶ background service worker ──inject / messages──▶ content scripts (Classroom tab)
▲ (job state machine, ▲ discovery: Classwork list,
│ chrome.storage.session) │ Stream, item pages
│ │ └─ progress / results ─┘
└── storage.onChanged ─────┤
├──snapshot──▶ offscreen document: download engine + ZIP writer
│◀─ progress / blob URL ─┘
└──▶ chrome.downloads (saves the ZIP)
| Component | Files | Responsibility |
|---|---|---|
| Popup | src/popup/ |
Shows the detected class (or the account’s class list), counts, progress, results and details. Stateless: renders the job stored by the background, so it can be closed and reopened at any time. |
| Background service worker | src/background/ |
Event-driven job state machine (preparing → discovering → downloading → zipping → saving → complete/failed/cancelled) persisted in chrome.storage.session, so the worker can be suspended between events. An account export keeps its class list and the index of the current class in the same job and runs the discovering → downloading steps once per class. Injects content scripts on demand, navigates the tab between discovery steps, detects navigation away and tab closure, manages the offscreen document and saves the ZIP with chrome.downloads. |
| Content scripts (discovery) | src/content/ |
All Classroom DOM knowledge: URL model, link classifier, item extractor, class list of the home page, Classwork and Stream scanners, item-page reader, class detection. Produces a JSON snapshot of the class. |
| Export engine | src/engine/ |
Download planning per resource type, authenticated downloads with retries, file naming and de-duplication, generated text/JSON files, report, manifest, and a streaming ZIP writer. Pure ES modules, unit-tested in Node. |
| Offscreen document | src/offscreen/ |
Hosts the engine; holds downloaded files as Blobs and hands the finished archive to the background as an object URL. During an account export it keeps the archive being built while the next class is scanned. |
| Protocol | src/shared/protocol.js |
Message names, phases and defaults shared by every context. |
Discovery strategy (and why)
Google Classroom is a dynamic single-page app with obfuscated, frequently changing CSS class names. The exporter therefore never uses CSS class names and relies on, in order of trust:
- URLs and ids. Items carry
data-stream-item-id; item pages live at/c////details(assignment, material, question) and/c//p/(announcement); topics at/w//tc/. Attachments are recognised by their link target (Drive, Docs, YouTube, …). Course/item ids may appear in decimal or as base64 of the decimal; both are normalised. - ARIA and semantic structure.
[aria-expanded]accordion toggles, headings,[data-drive-id]attachment containers, form controls (comment boxes),role=progressbar/aria-busyloading indicators. - Layout-free heuristics. An attachment card is a resource link with an icon/thumbnail or an aria-label; the description is the largest text block that is not the title or a card, contains no controls or images and comes before the comments.
- English UI text, only as a last resort, e.g. “Due …”, “100 points”,
“Class comments”, “Your work”. Item pages are fetched with
hl=enso these patterns apply regardless of your Classroom language.
Steps (an account export first lists the classes, then runs steps 1–3 for each class in turn and adds it to the archive before scanning the next):
- Class list (account export,
/u//h): the home page shows a card per active class. A class is a link to its stream (/c/) in the page content (the navigation drawer is only a fallback); name and section are the first two lines of that link and the teacher the first line next to it. The list is scrolled until no more cards appear and de-duplicated by course id, in page order. - Classwork list (
/w//t/all): wait until items render (or the page settles empty), scroll until no new items appear, map items to topics, then expand each collapsed row, wait for its content to settle, extract it and collapse it again. - Stream (
/c/, optional): scroll to load older posts; extract announcements; “new assignment” notices are used to cross-check the Classwork list. - Item pages: each item’s own page is read for the complete text and all
attachments. The first items are used to pick a strategy: a same-origin
fetchwith your session (fast, when Classroom server-renders the page), else a hidden same-origin frame that waits for Classroom to render it, else list-only (with a warning). Results from the list and the item page are merged; attachments are united, never dropped.
Google Classroom behaviours that needed special handling
- Lazy loading / infinite scroll on Classwork and Stream: the scanner scrolls and watches the DOM (MutationObserver) until the item count stops growing and no loading indicator is visible, instead of fixed delays.
- Accordion rows: Classwork rows only render instructions and attachments after being expanded; rows are re-located by id after every click because Classroom re-renders them. If a click opens the item page instead of expanding, the scanner goes back and relies on item pages.
- Truncated lists: the Classwork list may show only some attachments (“+N more”) or shortened text; the item page fills the gap.
- Duplicate and nested DOM: the same
data-stream-item-idappears on nested widgets (comment areas) and sometimes twice on a page; items are de-duplicated by id and only the outermost node is used. - Comments and “Your work”: links in class comments, private comments and your own submissions are excluded so only teacher-provided content is exported.
- Multiple Google accounts:
/u//in the Classroom URL selects the account; every Drive/Docs download uses the sameauthuserso files are fetched as the account that is viewing the class. Link-shared Drive files keep theirresourcekey. - Drive’s virus-scan warning for large files: the “Download anyway” confirmation link is followed, as a user would click it.
- Google-native files cannot be downloaded directly and are exported through the Docs/Sheets/Slides export endpoints.
- HTML instead of a file: sign-in pages, “you need access” pages and quota pages are recognised and reported as such.
- Navigation during the scan (the user clicks elsewhere, reloads or closes the tab) stops the export with a clear message instead of exporting a partial class silently. Once files are downloading the tab is no longer needed. In an account export, a class whose page does not load or is left within Classroom is recorded as not exported and the next class starts; until the last class has been scanned (also while earlier classes download), leaving the account’s Classroom, signing out or closing the tab stops the whole export, and the tab is not taken back.
- Late messages between classes: an account export tags every scan with the class it belongs to, so a result or error that arrives after the exporter has moved on to the next class is ignored.
- Background suspended mid-export: Chrome may stop the service worker at any moment. A class’s outcome and the move to the next class are saved together, and while an account export runs the offscreen document wakes the worker every 30 seconds; a job that has gone quiet for 90 seconds is picked up where the tab or the archive builder is (a class whose scan was lost is scanned again once, a class or the archive’s own files are handed to the builder again, a lost result is read back from it).
Known limitations
- Built and tested against a simulation of Classroom. The extraction logic follows Classroom’s current URL and DOM conventions and is covered by tests against several representative page structures, but this repository’s tests cannot log in to a real Classroom. Please run the testing checklist on a real class and report anything missing (the export report lists every item and warning).
- Classroom’s UI changes without notice. If Google changes the page structure,
items may be missed; the exporter reports this (“No classwork was found…”,
“Item pages could not be read…”) rather than failing silently. The selectors
live in
src/content/item-extractor.js,classwork-scanner.jsandstream-scanner.js. - Metadata parsing (due date, points, posting date) understands English text. Item pages are read in English to make this reliable; if item pages cannot be read, metadata in other UI languages may be missing (titles, instructions and attachments are still exported).
- Dates are exported as displayed by Classroom (no year for the current year, no time zone).
- Drive folders, Google Forms, YouTube videos, Google Sites, Classroom add-ons and other websites are saved as links, not downloaded.
- Files are downloaded only if you can download them in Classroom. Files whose owner disabled downloading, files not shared with you, and Google files too large for Google to export appear in the report as failures.
- Your own submissions, comments, rubrics shown only inside Classroom, grades and student work (for teachers) are not exported.
- Very large classes: the archive is assembled from Blobs (Chrome keeps large ones on disk), and the final ZIP is a single file. ZIP64 is used automatically above 4 GB. Exports of many GB need matching free disk space.
- Paths are kept short (≤ 50 characters for class/item folders, ≤ 90 for file
names) so the archive extracts on Windows; longer names are shortened, and
the original name is kept in
metadata.json. An account export adds its top-level folder (Google Classroom Export -); on Windows, extract it into a short path such asC:\Exportsif Explorer reports names that are too long. - Only one export runs at a time. Export all classes exports one Google account (the one of the open Classroom tab); classes are scanned one after another in that tab, so a large account takes a while.
- Export all classes exports the classes shown on the Classroom home page. Archived classes are not exported (export one on its own from its page in Archived classes if you need it).
Troubleshooting
| Message | What to do |
|---|---|
| No Google Classroom class detected | Open a class on classroom.google.com (not the class list) and click the extension again. Export all classes works from the class list too. |
| No classes were found on the Classroom home page… | The account has no active classes, or the home page had not finished loading. Open the home page of the right account (/u//), wait for the class cards and try again. |
A class is marked ✗ / failed in an account export |
The reason is shown in the popup, in export-report.txt and in export-manifest.json. Open that class and export it on its own. |
| The page is still loading… | Wait for Classroom to finish loading, then reopen the popup. |
| Export stopped because the Classroom tab navigated away… | Keep the tab on the class until the popup leaves the scanning class stage, then export again. |
| Many files fail with Google asked to sign in | You are signed in to several Google accounts: open the class from the account that has access (the /u// part of the URL) and export again. |
| Access denied (HTTP 403) for a file | The file is not shared with you or its owner disabled downloads; open it from Classroom to check. |
| Download quota exceeded | Google limits downloads of popular files; export again later. |
Development
Requirements: Node.js 22+, Python 3 and unzip (used by tests to verify
archives), and Chromium for the browser tests (Playwright’s Chromium or set
CHROMIUM_PATH).
npm install # installs playwright-core (test-only dependency)
npm run check # manifest references, syntax, no remote code / eval
npm test # unit tests (Node): URL model, link classifier, file names,
# ZIP writer (verified with Python zipfile + unzip -t),
# downloader error handling/retries, full export engine,
# account archive (class folders, manifest, combined
# report, a class that fails), CRX packing (signatures
# checked with OpenSSL)
npm run test:dom # browser tests: discovery against the mock Classroom
# (lazy loading, accordions, frame fallback, navigation,
# empty class, the home page's class list), fixture page
# structures, and the CRX compared with one packed by Chromium
npm run test:e2e # loads the real unpacked extension in Chromium, maps the
# Google hosts to a local HTTPS mock and drives the popup
# through scan → export → saved ZIP, and Export all classes
# (one class failing, cancel) (needs port 443)
npm run package # build dist/google-classroom-bulk-exporter-.zip
npm run pack:crx -- --key crx-key.pem
# build the signed dist/...-.crx (see Releasing)
Layout:
manifest.json
icons/ extension icons (scripts/make-icons.py regenerates them)
src/
shared/protocol.js message names, phases, defaults (all contexts)
background/ service worker: job state machine, tab + offscreen bridges, storage
content/ classic scripts injected in order (see tab-bridge.js CONTENT_FILES)
url-model.js Classroom URL parsing/building, id normalisation
resource-classifier.js link → resource type (Drive, Docs, Forms, YouTube, …)
item-extractor.js title, type, description, attachments, metadata of one item
classwork-scanner.js Classwork list: loading, topics, expansion
stream-scanner.js Stream announcements and coursework notices
detail-loader.js item pages via fetch or hidden frame
page-loader.js waiting for dynamic lists, infinite scroll
class-list.js the account's classes on the home page (Export all classes)
class-info.js class name/section detection
discovery.js steps + merge into the snapshot
content-main.js message handling
engine/ ES modules run in the offscreen document
archive-content.js generated files of a class folder (descriptions, metadata, report, index)
account-content.js export-manifest.json and the account archive's index and report
offscreen/ offscreen document
popup/ popup UI
tests/
unit/ dom/ e2e/ test suites (node:test)
mock/classroom-mock.mjs simulated Classroom/Drive/Docs (one class, or an account
with several) used by unit, dom and e2e tests
fixtures/classroom/ page-structure variants for extractor tests
scripts/ check, package, CRX packing, release notes, icon generation
.github/workflows/release.yml builds and publishes a release when a version tag is pushed
Content scripts are plain scripts (not modules) that publish their API on
globalThis.GCX; the service worker, offscreen document and popup are ES
modules. The background injects the content scripts with
chrome.scripting.executeScript only when the popup is opened on a Classroom
tab.
Releasing
Releases are built by GitHub Actions (.github/workflows/release.yml):
- Bump
versionin bothmanifest.jsonandpackage.json(for example to1.1.0) and merge it. - Either push a matching tag (
git tag v1.1.0 && git push origin v1.1.0), or open Actions → Release → Run workflow onmainand enterv1.1.0(the tag is created on that commit). - The workflow checks that the tag matches the version, runs
npm run checkand the unit tests, builds the ZIP and the CRX, and publishes a release with both files,SHA256SUMS.txtand installation notes. Running the workflow again for an existing tag rebuilds that tag and replaces its files.
CRX signing key. The CRX is signed with an RSA private key, and that key
determines the extension ID. Generate one once and keep it private (never
commit it; *.pem is git-ignored):
node scripts/pack-crx.mjs --generate-key crx-key.pem # also prints the extension ID
Then add the contents of crx-key.pem as a repository secret named
CRX_PRIVATE_KEY (Settings → Secrets and variables → Actions). Every release
signed with it keeps the same extension ID, so installed copies can be
updated. If the secret is missing, the workflow signs the CRX with a one-time
key and the release notes say so; the ZIP is unaffected. You can also pack
locally with Chrome’s own Pack extension button and the same key: the ID
is identical.
scripts/lib/crx.mjs writes and verifies the CRX3 format with Node’s crypto.
The unit tests check signatures with OpenSSL, and a browser test checks the
key, ID and header layout against a CRX packed by Chromium itself.
When Classroom changes
- Run Scan only on a real class and compare the counts with what
Classroom shows; check
export-report.txtwarnings. - Save the page (DevTools → Elements → copy outer HTML of a Classwork row,
an expanded row, an item page and a Stream post), remove personal data, add
it to
tests/fixtures/classroom/and write a test intests/dom/extractor-fixtures.test.mjs. - Adjust the heuristics (prefer URLs, ids and ARIA attributes over classes) until the fixture passes, and keep the other fixtures passing.
Testing checklist
Automated (npm run check && npm test && npm run test:dom && npm run test:e2e)
covers the mechanics. On a real Google Classroom account, check:
- [ ] Popup on a non-Classroom tab says no class is detected; on the class list it asks to open a class.
- [ ] Popup on a class’s Stream and Classwork pages shows the right class name.
- [ ] Scan only counts match the number of assignments, materials and questions on the Classwork page (including topics further down the page and items in “No topic”).
- [ ] Export from the Stream page: the tab moves to Classwork (and Stream), then returns to where you started.
- [ ] Every item has its own folder in the right type folder; duplicate titles get
(2). - [ ]
description.txtcontains the full instructions (compare a long one with Classroom); due date, points and topic are right. - [ ] Attachments: a PDF, a Word file, an image, a Google Doc, Sheet and Slides each download and open; names match Classroom.
- [ ] An item with more attachments than the Classwork row shows exports all of them.
- [ ] A Drive link typed into the instructions is downloaded too.
- [ ] Google Form, YouTube, Drive folder and website attachments appear as
.urlfiles and in the report. - [ ] A file you cannot access (or with downloads disabled) is listed under Failed with a reason; the export still completes.
- [ ] Comments, private comments and your own submitted files are not in the archive.
- [ ] Announcements with attachments are exported (option on) and skipped (option off).
- [ ] With two Google accounts signed in, exporting a class of the second account (
/u/1/) downloads its files. - [ ] A large video attachment (Drive “can’t scan for viruses” warning) downloads.
- [ ] Turning off network during an export: affected files are retried and then reported as failed; the export completes.
- [ ] Navigating away or closing the tab while scanning class stops the export with a message.
- [ ] Closing and reopening the popup during an export shows live progress; Cancel stops it.
- [ ] Exporting twice creates
... (1).zipinstead of overwriting. - [ ] The ZIP opens with Windows Explorer, macOS Finder/Archive Utility and
unzipon Linux, including non-English file names. - [ ] Teacher account: drafts/scheduled items are exported and marked in
metadata.json; student submissions are not downloaded. - [ ] Export all classes from the class list and from a class page: the popup lists exactly the classes on the home page (names, sections, teachers), not the archived ones.
- [ ] Every class gets its own folder (two classes with the same name and section get
(2)),export-manifest.jsonlists them in home-page order andindex.htmllinks to each. - [ ] With two Google accounts signed in, Export all classes from
/u/1/exports that account’s classes. - [ ] Closing and reopening the popup during Export all classes shows Class k of N; Cancel stops it and takes the tab back.
Recommended Tools
Try a different keyword or remove a filter.
Install
npx skillfish add 1vibecoders/google-classroom-bulk-exporter