A family household management app built for real daily use. Covers chores, meals, shopping, a family calendar, and a rewards system — with full support for kid accounts, PIN login, per-kid permissions, and split-custody scheduling.
- Create chores for the whole household, everyone, or a specific person
- Recurrence options: daily, weekly, fortnightly, monthly, odd weeks, even weeks, or "my week" (custody-aware)
- Kids earn points for completing chores; points feed into the leaderboard and goals
- Set an optional deadline time — useful for "clean your room before 8pm"
- Owner admin panel: mark or unmark any kid's chore for any day of the week
- Kids can only see and toggle their own assigned chores
- Weekly meal planner — plan breakfast, lunch, and dinner for each day
- Recipe library: save your household's go-to meals with meal type, category, notes, ingredients, and a URL
- Add meals directly from the recipe library
- Shared shopping list across the household
- Items have quantity, category, and an optional good price target (green badge shown in-store)
- Added by attribution — shows which member added each item (useful for reviewing kid suggestions)
- Inline editing for quantity, category, and good price
- Sort by: Category / Name / Added by
- Tick items off as you shop; clear checked items in bulk
- Items can be linked to a specific meal
- Planner-native tasks with optional due dates and member assignment
- Recurrence: daily, weekly, monthly — auto-resets at the start of each period
- Due/overdue badges; inline editing (title, due date, assignee, notes, recurrence)
- Visibility: owner sees all; members see unassigned tasks + their own
- Filter tabs: pending / completed / all
- Tasks surface on the calendar grid and in the Today dashboard widget
- Family calendar with manual event creation
- All-day events and timed events supported
- Google Calendar two-way sync — connect once via OAuth; events push/pull automatically
- Incremental sync via Google's
syncToken(full re-sync on 410 Gone)
- Points leaderboard showing all household members ranked by total points
- Weekly points tracker
- Goals: set a target point total with a reward description (e.g. "Pick the Friday night movie")
- Goals can be marked private — only visible to the owner and the specific child
- Owner can mark goals as achieved, edit, or delete them
- Sticky-note style cards with 6 colour choices
- Pin important notes to surface them on the dashboard
- Per-member authorship
- Kids log in with a 4-digit PIN — no email or password required
- On the login screen kids tap "I'm a kid", select their household, and enter their PIN
- Per-page permissions: owner controls what each kid can read or edit
- Kid accounts have their own theme colour
- Personalised greeting with time-of-day awareness
- Progress ring — SVG arc showing today's chore completion (done / total)
- Streak leaderboard — rolling 30-day chore streak per member with medal ranks
- Due tasks widget (overdue + due today, with one-tap complete)
- Upcoming calendar events
- Pinned notes widget
- Activity feed — recent household actions (chore completions, task completions)
- Weekly points summary + all-time total
- Invite family members via a shareable link
- Custody schedule setting (odd/even week) for split-custody households
- Dark mode toggle — persists across sessions, flash-free (reads from localStorage before hydration)
- Themes — choose from a range of colour themes including a dynamic time-of-day theme
- Sync Database button to apply schema updates after app upgrades
- Data export/import — download all household data as JSON; import into a self-hosted instance
- PWA / installable — add to home screen on Android and iOS; supports web push notifications
- The instance admin (set via
ADMIN_EMAILenv var) gets an Admin panel in the nav - Signup control: toggle new registrations on/off
- Household approval queue: require approval before new households can access the app
- Push notification broadcast: send a notification to all subscribed households
| Layer | Technology |
|---|---|
| Frontend | Next.js 16.2.6 (App Router), React 19, TypeScript |
| Styling | Tailwind CSS v4, shadcn/ui component library |
| Backend | PocketBase 0.38 — SQLite database with built-in REST API and auth |
| Infrastructure | Docker Compose |
1. Clone the repo
git clone https://github.com/ben0551/planner.git
cd planner2. Set up environment variables
cp .env.example .envOpen .env and fill in your values — see Environment variables below.
3. Start the stack
docker compose up -dThis starts:
planner-pb— PocketBase on port8090planner-app— Next.js app on port3000
4. Create your PocketBase superuser account
Open http://your-server-ip:8090/_/ and create an admin account. Use the same email and password you put in PB_ADMIN_EMAIL / PB_ADMIN_PASSWORD — these credentials let Planner perform server-side operations.
5. Set up the database schema
PB_EMAIL=admin@example.com PB_PASSWORD=your-admin-password node pb/setup.mjsRun once on a fresh install. After that, use Settings → Sync Database for upgrades.
6. Register your account
Open http://your-server-ip:3000 and click "Create Account". The first user automatically creates a household and becomes the owner.
Prerequisites: Node.js 20+, Docker (for PocketBase), Git
1. Start PocketBase
docker compose up pocketbase -d2. Create the PocketBase admin account
Open http://localhost:8090/_/ and create your superuser.
3. Set up the database schema
PB_EMAIL=admin@example.com PB_PASSWORD=your-admin-password node pb/setup.mjs4. Configure the app environment
cp .env.example app/.env.localEdit app/.env.local with your local values.
5. Install dependencies and start the dev server
cd app
npm install
npm run devOpen http://localhost:3000.
| Variable | Required | Description |
|---|---|---|
PB_INTERNAL_URL |
Yes | URL Next.js uses server-side to reach PocketBase. Docker: http://pocketbase:8090. Local dev: http://localhost:8090 |
PB_ADMIN_EMAIL |
Yes | PocketBase superuser email (the account you created at /_/) |
PB_ADMIN_PASSWORD |
Yes | PocketBase superuser password |
ADMIN_EMAIL |
Recommended | Email of the Planner user who should have the admin panel (signup toggle, approval queue, push broadcasts). This is your regular app login email, not the PocketBase superuser email. Changing this takes effect on container restart — no rebuild needed. |
APP_URL |
For Google Calendar | Public URL of this app, e.g. https://planner.example.com. Used as the Google OAuth redirect URI. |
DOMAIN |
For Traefik | Your domain name, e.g. planner.example.com |
GOOGLE_CLIENT_ID |
Optional | Global Google OAuth client ID — only used as a fallback when there is a single household on the instance. Multi-household deployments should enter credentials per-household in Settings. |
GOOGLE_CLIENT_SECRET |
Optional | Global Google OAuth client secret (same fallback rule as above) |
VAPID_PUBLIC_KEY |
For push notifications | VAPID public key. Generate with: node -e "const wp=require('web-push');const k=wp.generateVAPIDKeys();console.log(k)" |
VAPID_PRIVATE_KEY |
For push notifications | VAPID private key (keep secret, server-side only) |
VAPID_SUBJECT |
For push notifications | mailto: or https: URI, e.g. mailto:you@example.com |
NEXT_PUBLIC_VAPID_PUBLIC_KEY |
For push notifications | Same value as VAPID_PUBLIC_KEY — must be set separately so the browser can subscribe |
Note:
NEXT_PUBLIC_*variables are embedded in the client bundle at build time. All other variables are read at runtime from the running container — no rebuild needed when you change them.
For local development, put these in app/.env.local.
For Docker, put them in .env at the repo root.
Each household owner sets up their own Google OAuth credentials directly in Settings → Google Calendar:
- Go to Google Cloud Console → APIs & Services → Credentials
- Create an OAuth 2.0 Client ID (type: Web application)
- Add an authorised redirect URI:
https://your-domain/api/google-calendar/callback - Copy the Client ID and Client Secret into Settings → Google Calendar → credentials form
- Click Connect Google Calendar and authorise
This keeps credentials isolated per household. The global GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET env vars act as a fallback only when there is exactly one household on the instance.
Push notifications require HTTPS (works on your public domain; localhost also works as a browser exception).
- Generate VAPID keys:
node -e "const wp=require('web-push'); const k=wp.generateVAPIDKeys(); console.log(k);" - Add
VAPID_PUBLIC_KEY,VAPID_PRIVATE_KEY,VAPID_SUBJECT, andNEXT_PUBLIC_VAPID_PUBLIC_KEYto your.env - Deploy and go to Settings → Sync Database to create the
push_subscriptionscollection - Users will be prompted to allow notifications on their next login
- Register at
/register— this creates your household and makes you the owner - Go to Settings to configure your household
- Go to Settings → Members to add family members:
- Invite adults: copy the invite link — they register and join automatically
- Add child account: enter the child's name, set a 4-digit PIN, and configure permissions
- Tap "I'm a kid — find my family" on the login screen
- Select your household
- Enter your 4-digit PIN
After pulling a new version, go to Settings → Sync Database. Safe to run multiple times — only adds what's missing.
planner/
├── app/ # Next.js application
│ ├── src/
│ │ ├── app/
│ │ │ ├── (auth)/ # Login and registration pages
│ │ │ ├── (dashboard)/ # Main app (requires auth)
│ │ │ │ ├── admin/ # Instance admin panel
│ │ │ │ ├── chores/
│ │ │ │ ├── meals/
│ │ │ │ ├── shopping/
│ │ │ │ ├── calendar/
│ │ │ │ ├── rewards/
│ │ │ │ ├── tasks/
│ │ │ │ ├── notes/
│ │ │ │ └── settings/
│ │ │ └── api/
│ │ │ ├── _pb-admin.ts # PocketBase admin auth helper
│ │ │ ├── admin/ # Signup settings, household approvals
│ │ │ ├── google-calendar/ # OAuth flow + sync routes
│ │ │ ├── push/ # Web push subscribe + send
│ │ │ ├── migrate/ # Schema migration endpoint
│ │ │ └── household-lookup/ # Kid login family lookup
│ │ ├── components/
│ │ │ ├── ui/ # shadcn/ui components
│ │ │ ├── nav.tsx # Sidebar + mobile nav
│ │ │ └── pwa.tsx # Service worker registration + push subscribe
│ │ ├── context/auth.tsx # useAuth() hook
│ │ └── lib/
│ │ ├── pocketbase.ts # PocketBase client + TypeScript types
│ │ ├── google-calendar.ts # Google Calendar API helpers
│ │ ├── themes.ts # Theme definitions + dynamic theme
│ │ └── utils.ts
│ ├── public/
│ │ ├── manifest.webmanifest # PWA manifest
│ │ ├── icon.svg # App icon
│ │ └── sw.js # Service worker
│ ├── Dockerfile
│ └── package.json
├── pb/
│ ├── setup.mjs # One-time schema creation
│ └── pb_data/ # PocketBase data (gitignored)
├── .github/ # GitHub Actions CI
├── docker-compose.yml
├── .env.example
└── README.md
On every push to main, GitHub Actions builds and publishes to:
ghcr.io/ben0551/planner/planner-app:latest
| Option | When it appears |
|---|---|
| None (one-off) | Once, on the due date you set |
| Daily | Every day |
| Weekly | Same day each week |
| Fortnightly | Every two weeks |
| Monthly | Same date each month |
| Odd weeks | Weeks with an odd ISO week number |
| Even weeks | Weeks with an even ISO week number |
| My week | Based on the custody schedule — alternates between parents |