TickTick File Sync

unlisted

by mixerabe

Two-way field-level sync between selected TickTick projects and stable Markdown task files.

Updated 24d agoMIT
View on GitHub

TickTick File Sync for Obsidian

TickTick File Sync mirrors selected TickTick projects into an Obsidian folder as one stable Markdown file per task. It accepts edits from either side and reconciles individual fields with a three-way merge instead of treating one side as globally authoritative.

[!WARNING] This repository is implementation-complete for local testing but is still pre-release. Run the official-API characterization probe and the live QA gate before publishing it to the Obsidian community catalog. Checklist editing remains default-off, and completed-history depth/filter behavior remains best-effort until characterized.

What syncs

Two-way in v1:

  • title (frontmatter, managed checkbox line, and filename);
  • body/content;
  • completion and reopen, with recurring completion sent only through TickTick's dedicated completion endpoint;
  • priority, tags, start/due dates, and all-day state;
  • project moves represented by project-folder moves;
  • guarded task creation and deletion;
  • checklist item title/status/order/add/remove when the experimental setting is enabled.

TickTick-to-file only: reminders, recurrence rule, timezone, project/column identity, kind, timestamps, and child ids. Local edits to those keys are reported and restored from TickTick; they never reach the API.

Safety model

  • The first sync is a pull-only transaction plan. Nothing is written until the plan is explicitly confirmed.
  • TickTick writes are separately disabled by default. First enablement saves a timestamped JSON snapshot of all selected project snapshots in the plugin data directory (five retained) and recommends a TickTick-side backup.
  • Every ordinary local push re-reads the task immediately before writing and sends changed fields only.
  • Task create, recurring completion, and checklist updates containing new server-id-less items use a durable prepared → sent journal. A sent operation is never automatically dispatched again after an ambiguous result.
  • Remote deletion needs a successful authoritative project snapshot, task readback, and two consecutive strikes.
  • Local deletion needs two full-root scans. More than 10 missing files or more than 10% of bindings trips a mass-deletion guard.
  • A failed/partial project fetch creates no absence evidence at all.
  • Structural damage, an edited task id, duplicate task identity, and path collisions quarantine or conflict instead of overwriting the file.
  • Plugin writes, renames, and trash operations register provenance so their own vault events do not loop back into remote mutations.
  • Read/comment projects continue mirroring but receive zero writes.

File format

---
title: Fix login retry
priority: medium
tags: [deep-work]
projectId: redacted-project-id
kind: TEXT
id: redacted-task-id
---
<!-- ticktick-sync:start -->
- [ ] **Fix login retry**

Task content. Checkbox-looking lines here remain content.
<!-- ticktick-sync:end -->

Anything below the end marker is user-owned and never synced.

Unknown frontmatter keys and everything below the end marker are preserved. Managed content must not contain the exact reserved marker lines. NOTE-kind tasks omit the status line. CHECKLIST tasks have a separate <!-- ticktick-sync:items --> region and anchored %%tt:<item-id>%% markers.

Filename modes are title-id (default), id, and title with collision fallback. Sanitization is Unicode-NFC, case-fold aware, Windows-reserved-name safe, and intentionally lossy. Frontmatter title is the lossless title channel.

Authentication and setup

Requirements: Obsidian desktop 1.11.4 or later and a TickTick developer application.

  1. Register the exact redirect URI http://localhost:53123/callback (or the port selected in settings).
  2. Add the OAuth client secret to Obsidian SecretStorage with id ticktick-file-sync-client-secret.
  3. Enter the client id and connect. The plugin requests tasks:read tasks:write.
  4. Select projects, show the first-sync plan, inspect collisions/adoption conflicts, and confirm it.
  5. Keep writes disabled while validating the mirror. Enable writes only after reviewing the snapshot disclosure.

The access token is stored only in SecretStorage as ticktick-file-sync-token. It is never placed in plugin data.json or the vault. TickTick tokens have no refresh token and may last roughly six months; the plugin warns at about five months and pauses on HTTP 401 until reconnection.

Every OAuth connection or reconnection clears project selection, disables TickTick writes, and requires a fresh plan plus pre-write snapshot. This deliberately treats a replacement token as a possible account boundary.

The loopback OAuth listener binds only to 127.0.0.1, validates a random state nonce, accepts only /callback, and closes after success or five minutes.

Privacy and network disclosure

The plugin communicates only with TickTick's official Open API and OAuth endpoints. It has no telemetry, analytics, hosted relay, or background service. Project/task data is stored in the Markdown mirror, bounded report history, durable operation intents, and the disclosed pre-write snapshots. OAuth secrets remain in Obsidian SecretStorage.

Multi-device use

Use one active writer. “Sync on this device” is vault-local storage rather than synced plugin configuration. Multiple writers can converge deterministic content, but lifecycle and filesystem races cannot be made transparent; guards, tombstones, and adoption conflicts contain them rather than eliminating them.

Install the public beta

Requires Obsidian desktop 1.11.4 or newer. Until the Community directory review is complete, the simplest installation path is BRAT:

  1. Install and enable BRAT from Community plugins.
  2. Run BRAT: Add a beta plugin for testing from the command palette.
  3. Enter mixerabe/ticktick-file-sync and select Add plugin.
  4. Enable TickTick File Sync under Community plugins.

For a fixed manual installation, download manifest.json, main.js, and styles.css from the latest release, place them in <vault>/.obsidian/plugins/ticktick-file-sync/, reload Obsidian, and enable the plugin.

Install for development

npm ci
npm run check

Copy or symlink manifest.json, main.js, and styles.css into .obsidian/plugins/ticktick-file-sync/, then enable the plugin. The production bundle has zero runtime package dependencies; obsidian and all tooling are development-only dependencies.

Useful commands:

npm run dev       # watched development bundle
npm run typecheck
npm test
npm run build

Official API characterization

The probe is intentionally destructive only inside projects/tasks it creates with the [TTFS v3.2] prefix and ttfs-v3-2 tag. Use a disposable account/corpus.

TICKTICK_TOKEN='…' \
TTFS_ALLOW_WRITES=YES \
npm run probe

The runner records disposable labeled request/response shapes (including runtime ids, but never the token) under ignored probe/output/, tests PATCH behavior, missing/deleted quirks, plain and recurring completion, reopen, checklist semantics, array-shaped moves, documented completed-history filters, speculative fields separately, and prefix-scoped cleanup. A failed cleanup never widens its scope beyond ids created during that run.

The connected MCP corpus was read and a prefix-scoped disposable mutation pass was completed on 2026-07-12. See live corpus findings. In particular, the MCP convenience update replaced checklist item ids, so checklist writes correctly remain default-off pending the official probe. The guarded official probe also creates two identical tasks to characterize ambiguous-create recovery and asserts that a daily recurring completion advances by exactly one day.

Live release gate

Before a public release, exercise the labeled [TTFS v3.2] corpus and record the results:

  1. connect, select Inbox and a kanban project, inspect/confirm the pull-only plan;
  2. pull TickTick-side edits while preserving user frontmatter and the post-marker zone;
  3. validate timed/all-day dates across a DST boundary;
  4. complete/reopen plain and recurring tasks, including commit-then-drop plus restart;
  5. test title/file renames and both-direction project moves;
  6. force same-field and fresh-device adoption conflicts;
  7. edit offline, delete offline, and simulate cloud rename-as-delete/create;
  8. trip and explicitly resolve the mass-deletion guard;
  9. test checklist add/edit/remove/tick/reorder, duplicate titles, concurrent remote add, last-item removal, and metadata retention before considering a default change;
  10. validate token expiry and completed-history filters/depth.

Never mutate an unlabeled account record during QA.

Known limitations

  • Polling and vault events; TickTick exposes no webhooks here.
  • Completed history is best-effort until documented filter behavior and depth are characterized.
  • Project group names are unavailable.
  • Checklist editing is experimental and default-off.
  • Mobile is unsupported because OAuth uses a desktop loopback listener.
  • One account per vault; no attachments or hosted OAuth relay.
  • TickTick has no conditional If-Match update, so a small race remains after the mandatory fresh read.
  • A bodyless or timed-out create has no known id and requires conservative readback/manual resolution; zero candidates never triggers an automatic retry.
  • TickTick exposes Inbox as virtual id inbox but task/create bodies use an account-specific project id. The plugin learns and persists that id from Inbox tasks; if Inbox has never contained a task, create one in TickTick and sync once before creating a task from the Inbox folder.

Recovery

Use Show last sync report or Show sync report history for field values and recovery actions; reports remain in a bounded history until acknowledged. Conflicts offer Take TickTick's/Take mine. Ambiguous checklist-item creates offer a no-replay manual id-mapping dialog. Quarantine offers structure repair (with recovered content) or clean recreation, and both preserve an unbound backup under TickTick/Archive/Repairs/. Unbind current TickTick file detaches a mirror without deleting its remote task. Remote deletion leaves a durable tombstone so a TickTick trash restore can safely recreate the file without being mistaken for a local deletion.

License

MIT

For plugin developers

Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.