A self-hosted task manager with deep AI integration, a native iOS app, and a full REST API.
Single container. SQLite. Your data, your server.
Try the Demo Β· Documentation Β· API Reference Β· Roadmap
π Web (PWA) Β· π± iOS (native) Β· β Apple Watch Β· π REST API Β· OpenAPI 3.1
Note
Early release β Please bear with the rough edges. π· Expect occasional bugs and other issues as things get ironed out. Please open an issue if you run into any problems.
OpenTask is a self-hosted task manager I built for personal use. I've been using it daily, and it's reached a point where I'm excited to open it up.
It runs as a single Docker container with SQLite β no Postgres, no Redis, no external services to manage. Your data lives in one file on your server.
- Mobile-first PWA β Installable on iOS and Android. Designed for phones first, looks great on desktop too.
- Snooze everything β Bulk snooze all overdue tasks in one tap. Individual snooze with presets, fine-grained adjustments, or custom times.
- Native iOS app β Real push notifications with interactive snooze actions, including Apple Watch support. (Source included β not on the App Store.)
- Full undo/redo β Every action is logged and reversible.
- Trash & archive β Deleted tasks go to trash, completed tasks go to archive. Nothing is permanently lost until you say so.
- Single container β SQLite with WAL mode. No external database, no message queue, no cache layer. Back up your data by copying one file.
- Reverse proxy auth β Works with Authelia, Authentik, and other auth proxies out of the box.
- Data export β JSON and CSV export of all your tasks, projects, and completions. Nothing is locked in.
- Full REST API β Every UI operation available over HTTP. Bearer token auth, interactive API docs, and curl-friendly. Script it, automate it, pipe it into Apple Shortcuts.
- Webhooks β HTTP callbacks on task events with HMAC-SHA256 signing. Integrate with n8n, Home Assistant, or anything that accepts webhooks.
Every task gets AI-generated commentary and an attention score. AI sees your full task list and writes a one-liner β what it means, why it matters, what you might be forgetting.
- Commentary + scoring β Each task gets a contextual one-liner and an attention score. Filter by Stale, Act Soon, Quick Win, or Misprioritized.
- Natural language input β Type tasks in plain English. AI extracts the title, due date, recurrence, priority, project, and notes.
- What's Next β Recommendations that surface overlooked or forgotten tasks.
- Entirely optional β Disabled by default. Turn it off and every trace of AI disappears from the UI. Works with Claude, GPT-4.1-mini, Grok, DeepSeek, Ollama, and others.
# Create a directory for OpenTask
mkdir opentask && cd opentask
# Download the compose file
curl -O https://raw.githubusercontent.com/trentmcnitt/opentask/main/docker-compose.yml
# Generate a secret key and start the app
cat > .env <<EOF
AUTH_SECRET=$(openssl rand -base64 32)
OPENTASK_INIT_USERNAME=admin
OPENTASK_INIT_PASSWORD=changeme
OPENTASK_INIT_TIMEZONE=America/New_York
EOF
docker compose up -dOpen http://localhost:3000 and log in with the username and password you set above. The initial user and database are created automatically on first start.
Tip
Run docker compose logs -f to see startup output and troubleshoot any issues.
docker compose pull
docker compose up -dYour data is stored in ./data/ and persists across updates. Store this directory on a local filesystem β network mounts (NFS, CIFS) are not compatible with SQLite's file locking.
SQLite makes backups simple β use the built-in .backup command for a safe, consistent copy:
# While running (recommended)
docker compose exec opentask sqlite3 /app/data/tasks.db '.backup /app/data/backup.db'
cp data/backup.db /path/to/your/backups/tasks-$(date +%F).db
# Or stop first, then copy directly
docker compose stop
cp data/tasks.db /path/to/your/backups/
docker compose startTip
SQLite uses WAL (write-ahead logging), so tasks.db-wal and tasks.db-shm files may exist alongside the main database. A plain cp tasks.db while the app is running could produce an inconsistent backup. The sqlite3 .backup command handles this correctly.
Create more users from the command line:
# Docker
docker compose exec opentask tsx scripts/create-user.ts <username> <password>
# Bare metal
npx tsx scripts/create-user.ts <username> <password> <email> <timezone>Create a Bearer token for API access and automation:
# Docker
docker compose exec opentask tsx scripts/create-token.ts <username> [token-name]
# Bare metal
npm run db:create-token -- <username> [token-name]Requires Node.js 20+ and npm.
git clone https://github.com/trentmcnitt/opentask.git
cd opentask
npm install
# Configure
cp .env.example .env.local
# Edit .env.local β at minimum, set AUTH_SECRET (openssl rand -base64 32)
# Create your user
npx tsx scripts/create-user.ts admin changeme admin@localhost America/New_York
# Build and start
npm run build
npm run startFor development: npm run dev starts a hot-reloading server on port 3000.
See .env.example for all options. The essentials:
| Variable | Required | Description |
|---|---|---|
AUTH_SECRET |
Yes | Secret key for sessions (openssl rand -base64 32) |
AUTH_URL |
No | Public URL when behind a reverse proxy |
OPENTASK_DB_PATH |
No | SQLite database path (default: ./data/tasks.db) |
OPENTASK_AI_ENABLED |
No | Enable AI features (default: false) |
Login is username-based, not email-based.
OpenTask runs on port 3000 by default. Set AUTH_URL to your public URL when using a reverse proxy.
Caddy
tasks.example.com {
reverse_proxy localhost:3000
}
Nginx
server {
server_name tasks.example.com;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Required for Server-Sent Events (SSE)
proxy_buffering off;
proxy_cache off;
}
}Note
OpenTask uses Server-Sent Events for real-time updates. Caddy handles this automatically. Nginx needs proxy_buffering off.
If you run an auth proxy like Authelia, Authentik, or Caddy's forward_auth, OpenTask can trust the authenticated username from a request header β no separate login required.
# docker-compose.yml
environment:
OPENTASK_PROXY_AUTH_HEADER: Remote-UserThe header value must match an existing OpenTask username (case-insensitive). Users are not auto-created.
Warning
Your reverse proxy must strip this header from external requests. If external clients can set this header directly, they can authenticate as any user.
- Web Push β Generate VAPID keys with
npx web-push generate-vapid-keysand set them in your environment. Works on all platforms including iOS Safari. - iOS Native (APNs) β Requires the iOS companion app and an Apple Developer Program membership. See
.env.examplefor configuration.
AI is entirely optional β disabled by default. See AI-native task management above for what it provides. Add OPENTASK_AI_ENABLED=true and a provider key to your .env file β see .env.example for setup with Claude, GPT-4.1-mini, Grok, DeepSeek, and Ollama.
Three auth methods, checked in order: Bearer token β Proxy header β Session cookie
Key endpoints:
| Endpoint | Method | Description |
|---|---|---|
/api/tasks |
GET | List tasks with filters |
/api/tasks |
POST | Create a task |
/api/tasks/:id |
PATCH | Update task fields |
/api/tasks/:id |
DELETE | Soft delete to trash |
/api/tasks/:id/done |
POST | Mark done (advances recurring) |
/api/tasks/:id/snooze |
POST | Snooze to future time |
/api/tasks/bulk/snooze-overdue |
POST | One-tap snooze all overdue tasks |
/api/tasks/bulk/done |
POST | Bulk mark done |
/api/tasks/bulk/snooze |
POST | Bulk snooze by task IDs |
/api/undo |
POST | Undo last action |
/api/redo |
POST | Redo last undone action |
/api/projects |
GET/POST | List/create projects |
/api/export |
GET | Export data (JSON or CSV) |
/api/webhooks |
GET/POST | List/create webhooks |
/api/openapi |
GET | OpenAPI 3.1 spec (no auth) |
Full API reference with curl examples, webhook setup, and Apple Shortcuts integration: Documentation β
| Layer | Technology |
|---|---|
| Runtime | Next.js 16 (App Router) + React 19 + TypeScript |
| Database | SQLite with WAL mode (better-sqlite3) |
| Auth | NextAuth/Auth.js (credentials, JWT sessions) |
| Styling | Tailwind CSS 4 + Shadcn UI |
| Testing | Vitest (behavioral + integration) + Playwright (E2E) |
src/
βββ app/ # Next.js pages and API routes
βββ components/ # React components
βββ core/ # Business logic (auth, db, tasks, recurrence, undo, ai)
βββ hooks/ # Custom React hooks
βββ lib/ # Utilities
βββ types/ # TypeScript types
ios/ # Native iOS companion app (SwiftUI + WKWebView)
tests/ # Behavioral, integration, E2E, and AI quality tests
docs/ # Product spec, design rationale, API guide, roadmap
Contributions are welcome! See CONTRIBUTING.md for the full guide.
git clone https://github.com/trentmcnitt/opentask.git
cd opentask
npm install
cp .env.example .env.local
echo 'AUTH_SECRET=dev-secret-change-me' >> .env.local
npm run db:seed-dev
npm run dev
# Open http://localhost:3000 β login: dev / devCLAUDE.md has detailed development conventions for AI-assisted development.
Built by Trent McNitt β AI implementation consultant specializing in chatbots, workflow automation, and custom AI applications.




