Skip to content

Repository files navigation

πŸ“‹Β Β gtask-extractor

Export every field of your Google Tasks to JSON β€” including the hidden metadata the Tasks UI never shows.


Release Go Version License


CI / CD Β Β  Build Last Commit Β Β Β Β  Quality Β Β  Coverage
Security Β Β  Scorecard Security Β Β Β Β  Community Β Β  Contributors Bitcoin


Project Navigation

πŸš€Β 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.readonly scope; 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

πŸš€ Installation

Requires Go 1.26+ (the tool uses crypto/rand.Text, errors.AsType, and reflect.Type.Fields).

Prebuilt binaries (recommended)

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 --version

If gtask-extractor isn't found afterward, add ~/.local/bin to your PATH (export PATH="$HOME/.local/bin:$PATH" in your ~/.zshrc or ~/.bashrc).

Keeping it up to date

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 current

It 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 version

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


πŸ”‘ Google Cloud Setup (one-time)

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 through myactivity.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.

  1. Create (or select) a project at https://console.cloud.google.com/projectcreate.
  2. Enable the Google Tasks API: https://console.cloud.google.com/apis/library/tasks.googleapis.com β†’ Enable.
  3. 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).
  4. Create the OAuth client ID (Credentials β†’ Create credentials β†’ OAuth client ID, newer UI: Clients β†’ Create client) β†’ Application type Desktop app β†’ Create.
  5. Download the JSON and save it as credentials.json in the directory where you'll run gtask-extractor.

credentials.json identifies the app; it is not a password, but it is still a secret β€” it's git-ignored by this repo.


⚑ Quick Start

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 your PATH). 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.


πŸ“š Usage

gtask-extractor has two modes: a friendly interactive picker (default) and a scriptable export subcommand.


Interactive (default)

gtask-extractor

Prints 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).


Non-interactive (export) β€” scripts, cron, re-pulls

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.


Flags

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


πŸ—‚οΈ Output Format

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.


πŸ” Security

  • 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.json and token.json are git-ignored; the token cache the tool writes (token.json) is created atomically with 0600 permissions. Exported data under output/ 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/oauth2 and golang.org/x/term, and Cobra. No heavyweight TUI or browser-launching third-party packages.

Read the full security policy.


🧯 Troubleshooting

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.

πŸ§ͺ Examples & Tests

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 vet

Coverage reports are uploaded to Codecov on every commit.

Build Commands

View all build commands:

magex help

Common commands:

  • magex build β€” build the gtask-extractor binary
  • magex test β€” run the test suite
  • magex lint β€” run all linters
  • magex 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.


πŸ› οΈ Code Standards

Read more about this Go project's code standards.


πŸ€– AI Usage & Assistant Guidelines

Read the AI Usage & Assistant Guidelines for details on how AI is used in this project and how to interact with AI assistants.


🀝 Contributing

View the contributing guidelines and please follow the code of conduct.

How can I help?

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! πŸš€

Stars


πŸ‘₯ Maintainers

MrZ
MrZ

βš–οΈ License

License

This project is licensed under the terms of the MIT license. See LICENSE.

About

πŸ“‹ Read-only CLI that exports every field of your Google Tasks to JSON

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages