1G

1vibecoders/google-classroom-bulk-exporter

Developer tools
0 stars Quality 55 Trend 20

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

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

  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 Developer mode (top right).
  3. Click Load unpacked and select the extracted folder (the one containing manifest.json).
  4. 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

  1. Download or clone this repository.
  2. In chrome://extensions, turn on Developer mode, click Load unpacked and select the repository folder.

No build step is needed.

Use

  1. Open a class at classroom.google.com (its Stream or Classwork page).
  2. Click the extension button. The popup shows the detected class.
  3. Optionally click Scan only to see how many assignments, materials and attachments were found without downloading anything.
  4. 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.
  5. 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)).
  6. 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

  1. 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/, …).
  2. 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).
  3. 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.
  4. 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).
  5. 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, failed or link), 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:

  • folder is the class folder relative to the manifest: "." in a single-class archive, the class’s folder name in an account archive, and null for a class that was not exported.
  • status is exported (complete), partial (the folder is there, but some files could not be downloaded, which are listed in its export-report.txt; or writing the folder stopped with an error, which error gives, and the folder may be incomplete) or failed (not exported; error says why).
  • counts has items, assignment, material, question, announcement, other, filesDownloaded, filesFailed, linksSaved, bytesDownloaded and warnings (null for 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.

  1. Export all classes (above) and wait for Export complete.
  2. 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:

  1. 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.
  2. ARIA and semantic structure. [aria-expanded] accordion toggles, headings, [data-drive-id] attachment containers, form controls (comment boxes), role=progressbar / aria-busy loading indicators.
  3. 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.
  4. English UI text, only as a last resort, e.g. “Due …”, “100 points”, “Class comments”, “Your work”. Item pages are fetched with hl=en so 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):

  1. 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.
  2. 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.
  3. Stream (/c/, optional): scroll to load older posts; extract announcements; “new assignment” notices are used to cross-check the Classwork list.
  4. 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 fetch with 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-id appears 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 same authuser so files are fetched as the account that is viewing the class. Link-shared Drive files keep their resourcekey.
  • 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.js and stream-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 as C:\Exports if 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):

  1. Bump version in both manifest.json and package.json (for example to 1.1.0) and merge it.
  2. Either push a matching tag (git tag v1.1.0 && git push origin v1.1.0), or open Actions → Release → Run workflow on main and enter v1.1.0 (the tag is created on that commit).
  3. The workflow checks that the tag matches the version, runs npm run check and the unit tests, builds the ZIP and the CRX, and publishes a release with both files, SHA256SUMS.txt and 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

  1. Run Scan only on a real class and compare the counts with what Classroom shows; check export-report.txt warnings.
  2. 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 in tests/dom/extractor-fixtures.test.mjs.
  3. 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.txt contains 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 .url files 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).zip instead of overwriting.
  • [ ] The ZIP opens with Windows Explorer, macOS Finder/Archive Utility and unzip on 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.json lists them in home-page order and index.html links 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.
View this README on GitHub

Recommended Tools

Try a different keyword or remove a filter.

Install

npx skillfish add 1vibecoders/google-classroom-bulk-exporter