Export every field of your Google Tasks to JSON β including the hidden metadata the Tasks UI never shows.
CI / CD Β Β
|
|
Β Β Β Β Quality Β Β
|
|
Security Β Β
|
|
Β Β Β Β Community Β Β
|
|
πΒ Installation
|
πΒ GoogleΒ Setup
|
β‘Β QuickΒ Start
|
πΒ Usage
|
ποΈΒ OutputΒ Format
|
πΒ Security
|
π§―Β Troubleshooting
|
π§ͺΒ ExamplesΒ &Β Tests
|
π οΈΒ CodeΒ Standards
|
π€Β AIΒ Usage
|
π€Β Contributing
|
π₯Β Maintainers
|
βοΈΒ License
|
||
gtask-extractor (binary: gtask-extractor) is a small, local, read-only CLI that snapshots a Google
Tasks list to JSON β capturing not just titles and notes but updated (which doubles as the
creation time), position, completed, hidden, deleted, parent, links, and
assignment info from Docs/Chat. Fields the raw API drops (false, "", null, []) are all
preserved.
- β
Read-only β requests only the
tasks.readonlyscope; it can never modify or delete tasks. - β Captures every field β its own JSON envelope preserves values the Google client would drop.
- β
Two modes β a friendly interactive picker, or a scriptable
gtask-extractor export --list <id> / --all. - β Runs locally β your data never leaves your machine; there is no server.
$ gtask-extractor
Your task lists:
# TITLE UPDATED ID
1 My Tasks 2026-08-10 MTIzNDU2Nzg5MDEyMzQ1Njc4OTA
2 Work 2026-08-11 Nzg5MDEyMzQ1Njc4OTAxMjM0NTY
3 Sandbox 2026-08-12 OTAxMjM0NTY3ODkwMTIzNDU2Nzg
Select a list to export [1-3] (q to quit): 3
β Exported 7 tasks from "Sandbox" β output/sandbox-OTAxMjM0NTY3ODkwMTIzNDU2Nzg-2026-08-12.json
active: 5 completed: 2 hidden: 1 deleted: 1 assigned: 2
Requires Go 1.26+ (the tool uses crypto/rand.Text, errors.AsType, and
reflect.Type.Fields).
Once a release is tagged, cross-platform archives are published via GoReleaser
on the releases page. Install the latest into
~/.local/bin (user-writable, no sudo) with a single copy-paste β gtask-extractor update can
self-update it in place afterward:
# Install the latest gtask-extractor release into ~/.local/bin
VER=$(curl -fsSL https://api.github.com/repos/mrz1836/gtask-extractor/releases/latest | grep '"tag_name"' | cut -d'"' -f4 | tr -d v)
OS=$(uname -s | tr '[:upper:]' '[:lower:]'); ARCH=$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')
mkdir -p ~/.local/bin
curl -fsSL "https://github.com/mrz1836/gtask-extractor/releases/download/v${VER}/gtask-extractor_${VER}_${OS}_${ARCH}.tar.gz" | tar -xzf - -C ~/.local/bin gtask-extractor
gtask-extractor --versionIf gtask-extractor isn't found afterward, add ~/.local/bin to your PATH
(export PATH="$HOME/.local/bin:$PATH" in your ~/.zshrc or ~/.bashrc).
Binaries installed from a release archive self-update:
gtask-extractor update # install the latest release (verifies the SHA-256 checksum)
gtask-extractor update --check # only report whether a newer version exists
gtask-extractor update --force # reinstall even if already currentIt installs only from the project's GitHub release archives and refuses to overwrite a binary
managed by go install or Homebrew. gtask-extractor also prints a one-line notice when a newer
version is available β opt out with GTASK_EXTRACTOR_NO_UPDATE_CHECK=1, NO_UPDATE_CHECK=1, or by
running in CI.
Build from source (contributors)
git clone https://github.com/mrz1836/gtask-extractor.git
cd gtask-extractor
go build -o bin/gtask-extractor ./cmd/gtask-extractor
./bin/gtask-extractor versionThis project uses MAGE-X for builds β there is no Makefile.
Install it once with go install github.com/mrz1836/mage-x/cmd/magex@latest, then magex build
(the binary lands at ./cmd/gtask-extractor/gtask-extractor). Run magex help for every target.
Your tasks live in a personal Google (Gmail) account. Service accounts and domain-wide
delegation only apply to Google Workspace orgs β they cannot read a personal account's tasks.
The only supported path is the OAuth 2.0 installed-app flow (loopback redirect + PKCE), so you
need a credentials.json describing an OAuth Desktop app client. This is a one-time, ~5-minute
setup in the Google Cloud Console.
β οΈ Common misconception: access is not granted throughmyactivity.google.com, a Google Search Services Settings page, or any "Connect Google Workspace" / "search services" screen. The only place you configure access is the Google Cloud Console, and the only "allow" you ever click is the browser consent screen this tool opens for you.
- Create (or select) a project at https://console.cloud.google.com/projectcreate.
- Enable the Google Tasks API: https://console.cloud.google.com/apis/library/tasks.googleapis.com β Enable.
- Configure the OAuth consent screen (APIs & Services β OAuth consent screen, newer UI:
Google Auth Platform):
- User type / Audience: External β Create.
- App name (anything), your email for support + developer contact. Save.
- Add yourself as a Test user β the step that trips people up. Under Test users (newer
UI: Google Auth Platform β Audience β Test users) add the exact Gmail you'll sign in
with. In Testing mode only listed testers can authorize; anyone else gets "Access blocked β¦
has not completed the Google verification process" (Error 403:
access_denied).
- Create the OAuth client ID (Credentials β Create credentials β OAuth client ID, newer UI: Clients β Create client) β Application type Desktop app β Create.
- Download the JSON and save it as
credentials.jsonin the directory where you'll rungtask-extractor.
credentials.json identifies the app; it is not a password, but it is still a secret β it's
git-ignored by this repo.
gtask-extractor # opens the browser once for read-only consent
# β pick a list from the numbered menu; JSON lands in ./output/Examples call the binary as
gtask-extractor(on yourPATH). From a source build use the full path, e.g../bin/gtask-extractor.
On the first run a browser opens to Google's consent screen. Sign in with the Test-user
account β "Google hasn't verified this app" β Advanced β Go to <your app name> (unsafe)
β approve "View your tasks". The token is cached to token.json (0600), so later runs are
silent β no browser. If the browser can't open (e.g. over SSH), the URL is printed to paste
anywhere.
gtask-extractor has two modes: a friendly interactive picker (default) and a scriptable export
subcommand.
gtask-extractorPrints a numbered table of your task lists (with IDs); type a number to export one (or q to
quit), then it offers to export another. Needs a terminal β if stdin/stdout aren't a TTY it exits
2 (use export below for scripts).
gtask-extractor export --list <id> # one list
gtask-extractor export --list <id> --list <id> # several (repeat --list)
gtask-extractor export --all # every task list
# Nightly snapshot of everything into a dated folder
gtask-extractor export --all --output-dir "backups/$(date +%F)"--list and --all are mutually exclusive; an unknown ID exits 2. The first run still opens the
browser once; afterward the cached token is reused, so export runs unattended.
Global (apply to every command):
| Flag | Default | Meaning |
|---|---|---|
--creds |
credentials.json |
Path to the OAuth client credentials file |
--token |
token.json |
Path to the cached OAuth token |
--output-dir |
output |
Directory for exported JSON files |
--verbose, -v |
off | Print extra diagnostic lines (to stderr) |
--version |
Print the version and exit | |
--help, -h |
Show help (works on subcommands too) |
Subcommands: gtask-extractor export β¦, gtask-extractor update (self-update; alias upgrade),
gtask-extractor version.
The machine-facing result (β Exported N tasks β¦ β <path>) goes to stdout; prompts, progress,
and the counts breakdown go to stderr β so gtask-extractor export --list <id> >paths.txt captures just
the file paths.
Exit codes: 0 success Β· 2 usage / not a TTY / bad flags Β· 3 credentials or consent
problem Β· 4 API/network failure Β· 5 failed to write output Β· 130 interrupted (Ctrl-C).
Each export is a single JSON document. Every field is always present β a task that was never
completed still has "completed": null, a top-level task still has "parent": "", and so on. The
raw Google API omits these; capturing them is the whole point.
Field reference
| Field | Type | Notes |
|---|---|---|
kind |
string | Always tasks#task / tasks#taskList. |
id |
string | Stable task/list identifier. |
etag |
string | Version tag of the resource. |
title |
string | The task/list title. |
notes |
string | Free-text notes. Empty string if none. |
status |
string | needsAction or completed. |
updated |
string (RFC 3339) | Last modification time. If a task was never edited, this is effectively its creation time β the closest thing the API exposes to "created at". |
selfLink |
string | API URL of the resource. |
parent |
string | Parent task id; "" for a top-level task. parent + position reconstruct the subtask hierarchy. |
position |
string | Lexicographic manual sort key among siblings. |
due |
string (RFC 3339) | Scheduled day. Date-only β the time is always 00:00:00Z; the API cannot store a due time. |
completed |
string | null | Completion timestamp, or null if not completed. |
deleted |
bool | true if the task is in the trash. |
hidden |
bool | true if it was hidden by "clear completed" on the list. |
links |
array | Related links (type, description, link); [] if none. |
webViewLink |
string | Link to the task in the Tasks web UI. |
assignmentInfo |
object | null | Present for tasks assigned from Google Docs or Chat Spaces; null otherwise. Contains linkToTask, surfaceType, and either driveResourceInfo (Docs) or spaceInfo (Chat). |
The document also includes an export block (generation time, tool version, scope, list id/title,
and a counts breakdown) and a list block with the six task-list fields.
Sample (redacted)
{
"schemaVersion": "1.0",
"export": {
"generatedAt": "2026-08-12T15:04:05Z",
"tool": "gtask-extractor",
"toolVersion": "1.0.0",
"scope": "https://www.googleapis.com/auth/tasks.readonly",
"listId": "MDEyMzQ1",
"listTitle": "Sandbox",
"counts": { "total": 7, "needsAction": 5, "completed": 2, "deleted": 1, "hidden": 1, "assigned": 2, "subtasks": 1, "topLevel": 6 }
},
"list": {
"kind": "tasks#taskList",
"id": "MDEyMzQ1",
"etag": "\"etag-list\"",
"title": "Sandbox",
"updated": "2026-08-01T12:00:00.000Z",
"selfLink": "https://tasks.googleapis.com/tasks/v1/users/@me/lists/MDEyMzQ1"
},
"tasks": [
{
"kind": "tasks#task",
"id": "t1",
"etag": "\"etag1\"",
"title": "Buy groceries",
"notes": "milk & eggs",
"status": "needsAction",
"updated": "2026-08-02T10:00:00.000Z",
"selfLink": "https://tasks.googleapis.com/tasks/v1/lists/MDEyMzQ1/tasks/t1",
"parent": "",
"position": "00000000000000000000",
"due": "2026-08-10T00:00:00.000Z",
"completed": null,
"deleted": false,
"hidden": false,
"links": [],
"webViewLink": "https://tasks.google.com/task/t1",
"assignmentInfo": null
}
]
}Note how completed, parent, deleted, hidden, and links are all present even when
"empty" β that's the point.
- Read-only. Requests only
https://www.googleapis.com/auth/tasks.readonly. It cannot create, edit, or delete tasks. - Local only. All processing happens on your machine; nothing is uploaded anywhere.
- Secrets stay put.
credentials.jsonandtoken.jsonare git-ignored; the token cache the tool writes (token.json) is created atomically with0600permissions. Exported data underoutput/is git-ignored too. - Revoke any time at https://myaccount.google.com/permissions. To reset locally, delete
token.json. - Minimal, trusted dependencies β the official Google API client, the Go team's
golang.org/x/oauth2andgolang.org/x/term, and Cobra. No heavyweight TUI or browser-launching third-party packages.
Read the full security policy.
| Symptom | Cause & fix |
|---|---|
"Access blocked β¦ has not completed the Google verification process" / access_denied (Error 403) after sign-in |
The account you signed in with isn't an approved Test user. Add that exact Gmail under Google Cloud Console β Google Auth Platform β Audience β Test users and re-run. A new test-user entry can take a couple of minutes to propagate. Not a tool bug. |
| "Google hasn't verified this app" | Expected in Testing mode. Advanced β Go to <your app name> (unsafe). |
invalid_grant on a later run |
Cached token is stale/expired. Delete token.json and re-run. |
| Auth stops working after ~a week | In Testing mode Google expires refresh tokens after ~7 days. Delete token.json and re-consent (or publish the app to Production to stop the expiry). |
redirect_uri_mismatch |
Your OAuth client is the wrong type β it must be Desktop app. |
403 / "Tasks API has not been usedβ¦" |
Enable the Tasks API: https://console.cloud.google.com/apis/library/tasks.googleapis.com. |
credentials.json not found |
Download the OAuth Desktop app JSON and save it as credentials.json (or pass --creds <path>). |
nothing to export / task list not found |
Pass --list <id> (repeatable) or --all; get IDs from the interactive picker or gtask-extractor export --all. |
| "must be run in a terminal" | Interactive mode needs a TTY β use gtask-extractor export --list <id> / --all for scripts and pipes. |
All unit tests run via GitHub Actions on
Go 1.26.x and make zero network requests β the Google API is exercised through an in-process
httptest server, the OAuth flow through a mocked browser + fake token endpoint, and the exporter
through a fake task lister.
magex test # run all tests (fast)
magex test:race # run tests with the race detector
magex test:coverage # run tests with coverage
magex lint # golangci-lint v2 (.golangci.json) + go vetCoverage reports are uploaded to Codecov on every commit.
Build Commands
View all build commands:
magex helpCommon commands:
magex buildβ build thegtask-extractorbinarymagex testβ run the test suitemagex lintβ run all lintersmagex deps:updateβ update dependencies
GitHub Workflows
This project uses the Fortress workflow system for CI/CD:
- fortress-test-suite.yml β full test suite across Go versions
- fortress-code-quality.yml β gofmt / golangci-lint / staticcheck
- fortress-security-scans.yml β vulnerability scanning
- fortress-coverage.yml β coverage reporting to Codecov
- fortress-release.yml β automated binary releases via GoReleaser
See all workflows in .github/workflows/.
Repository Layout
.
βββ cmd/gtask-extractor/ # thin entrypoint: ldflags version vars β cli.Execute
βββ internal/
βββ cli/ # Cobra tree: interactive root, `export`, `version`, self-`update`; exit-code mapping
βββ auth/ # OAuth loopback + PKCE, token cache, persisting refresh
βββ tasksclient/ # paginating wrapper around the Tasks API (generic paginate; interface)
βββ export/ # JSON envelope, converters, atomic writer, filename slug
βββ ui/ # numbered list table + stdin picker (stdlib only)
Version metadata (version/commit/buildDate) is injected into cmd/gtask-extractor via
-ldflags β see .goreleaser.yml and .mage.yaml.
A reflection test (TestNoDroppedFields) walks the JSON tags of every tasks.* type the exporter
mirrors and fails if a field is unmapped β so a future Google client bump that adds a task field
makes the tests fail until the new field is captured.
Read more about this Go project's code standards.
Read the AI Usage & Assistant Guidelines for details on how AI is used in this project and how to interact with AI assistants.
View the contributing guidelines and please follow the code of conduct.
All kinds of contributions are welcome π! The most basic way to show your support is to star π the project, or to raise issues π¬. You can also support this project by becoming a sponsor on GitHub π or by making a bitcoin donation to ensure this journey continues indefinitely! π
| MrZ |
This project is licensed under the terms of the MIT license. See LICENSE.