Feature spec
IntermediateCategory
personalization-ux
Build with AI
4–7 hours with Lovable or V0
Custom build
1–2 weeks custom dev
Running cost
$0/mo on free tier · $25–50/mo at 10K users
Works on
Everything it takes to ship Personalized Goal Setting and Tracking — parts, prompts, and real costs.
A goal-setting feature needs four things: a SMART goal creation form with a numeric target and deadline, a progress ring that shows percentage completion, a milestone list for sub-goals, and a weekly check-in scheduler. With Lovable or V0 you can ship the core in 4–7 hours. Running cost is $0/month on the free tier — Supabase Pro ($25/mo) is the first cost you hit at around 1K users.
What a Goal-Setting and Tracking Feature Actually Is
A goal-setting and tracking feature lets users define a measurable target — 'Save $5,000 by December', 'Read 24 books this year', 'Run a 5K under 30 minutes' — and log incremental progress toward it. The product decisions that matter most are: whether goals are numeric (target + current value) or binary (done / not done); whether milestones break the goal into stages; how progress is visualized (ring, bar, sparkline); and whether the app proactively nudges users to log updates via a check-in cadence. AI tools generate the CRUD form and Recharts radial chart reliably in the first prompt. The projected completion date calculation, the milestone parent-child relationship, and the weekly check-in email scheduler are where builds need follow-up prompting.
What users consider table stakes in 2026
- SMART goal form with a required numeric target, unit label (lbs, pages, $, km, %), start date, and deadline
- Progress ring or radial bar showing percentage to target, updating instantly on every log entry
- Milestone checklist within each goal — completing all milestones auto-marks the parent goal
- Goal status color coding: green (on-track), yellow (at-risk, less than 50% progress with less than half the time remaining), red (overdue)
- Celebration animation when goal reaches 100% — confetti or Lottie on web, flutter_animate on mobile
- Weekly check-in prompt at a user-configurable cadence reminding them to log progress
Anatomy of the Feature
Eight components. The form, chart, and log table are what AI tools produce confidently. The milestone parent-child logic, projected completion date RPC, and check-in scheduler are the three pieces that need explicit prompting or a second pass.
Goal Creation Form
UIA multi-step form built with react-hook-form and zod on web, or a Flutter Stepper widget. Fields: goal title, description, category (tag), target_value (numeric), target_unit (lbs, pages, $, %, km, or custom text), start_date, deadline, and check_in_cadence (daily, weekly, monthly). shadcn/ui's Stepper or Tabs component separates the steps without a multi-page navigation.
Note: Validate that deadline is after start_date at the zod schema level, not in a useEffect — schema-level validation gives the correct error message before form submission.
Progress Input
UIA quick-log modal that appears in one tap or click from the goal card. The user enters their current value (or an increment to add to it), optionally adds a note, and saves. The modal auto-calculates progress_pct = Math.min(Math.round((current_value / target_value) * 100), 100). Supports both cumulative tracking (total savings $X so far) and replacement tracking (current weight X lbs, not additive).
Note: Decide up front whether the logged_value replaces current_value or is added to it — this determines the Supabase UPDATE logic and the way history is interpreted.
Progress Visualization
UIA Recharts RadialBarChart on web (or fl_chart RadialBarChart on Flutter) for the circular progress ring. A Recharts LineChart or AreaChart for the sparkline history below the ring, showing logged_value over time from goal_progress_logs. react-confetti fires on first render when progress_pct reaches 100; a has_celebrated boolean on the goal prevents it from re-firing on every subsequent load.
Note: Memoize the chart components with React.memo — they should only re-render when their data changes, not on every parent state update.
Milestone Manager
UIAn ordered list of sub-goals within a goal, each with its own title and target_value. Milestones are draggable via @dnd-kit/sortable and have a completion checkbox. Checking off the last milestone triggers a status update on the parent goal to 'completed'. The milestone list is stored in the goal_milestones table with sort_order and a FK to goals.
Note: When a user creates a milestone, check whether the milestone target_value exceeds the parent goal target_value and show a warning — common input error.
Check-In Scheduler
BackendA Supabase Edge Function triggered by pg_cron at the user's check_in_cadence. The function sends a check-in email via Resend using a template that shows the goal title, current progress percentage, and a one-click log link. For mobile: a flutter_local_notifications alarm scheduled at the user's preferred check-in time.
Note: Group check-in emails by user (one email per user with all their active goals) rather than sending one email per goal per cadence — users with 5 active goals should not receive 5 separate emails.
Goals Table
DataThe primary Supabase table: id, user_id, title, description, category, target_value (numeric), target_unit, current_value (numeric), start_date, deadline, status (active/completed/paused/abandoned), check_in_cadence, and created_at. RLS restricts all operations to auth.uid() = user_id.
Progress Logs Table
DataRecords every update event: goal_id, user_id, logged_value, notes, and logged_at. The sparkline history chart reads from this table. An index on (goal_id, logged_at) keeps history queries fast when a goal accumulates hundreds of log entries over months.
Note: Store logged_value as the absolute value at the time of logging (not the delta). This makes the sparkline chart straightforward and allows corrections without complex delta math.
Goal Insights
BackendA Supabase RPC function that calculates the projected completion date based on average daily progress over the last 7–14 log entries. Also returns on_track / at_risk / behind status by comparing (current_value / target_value) to (days_elapsed / total_days). The RPC is called once per goal card render and cached in React Query with a 5-minute stale time.
Note: Return 'Insufficient data' when fewer than 3 log entries exist — the projection is meaningless with sparse data and can mislead users early in the goal.
The data model
Three tables with RLS locked to the authenticated user. Run this in the Supabase SQL editor — it creates all tables, enables RLS, defines policies, and adds the performance indexes in one pass.
1create table public.goals (2 id uuid primary key default gen_random_uuid(),3 user_id uuid references auth.users(id) on delete cascade not null,4 title text not null,5 description text,6 category text,7 target_value numeric not null,8 target_unit text not null default 'units',9 current_value numeric not null default 0,10 start_date date not null default current_date,11 deadline date,12 status text not null default 'active',13 check_in_cadence text not null default 'weekly',14 has_celebrated boolean not null default false,15 created_at timestamptz not null default now()16);1718alter table public.goals enable row level security;1920create policy "Users can manage own goals"21 on public.goals22 using (auth.uid() = user_id)23 with check (auth.uid() = user_id);2425create table public.goal_milestones (26 id uuid primary key default gen_random_uuid(),27 goal_id uuid references public.goals(id) on delete cascade not null,28 user_id uuid references auth.users(id) on delete cascade not null,29 title text not null,30 target_value numeric,31 is_completed boolean not null default false,32 sort_order int not null default 033);3435alter table public.goal_milestones enable row level security;3637create policy "Users can manage own milestones"38 on public.goal_milestones39 using (auth.uid() = user_id)40 with check (auth.uid() = user_id);4142create table public.goal_progress_logs (43 id uuid primary key default gen_random_uuid(),44 goal_id uuid references public.goals(id) on delete cascade not null,45 user_id uuid references auth.users(id) on delete cascade not null,46 logged_value numeric not null,47 notes text,48 logged_at timestamptz not null default now()49);5051alter table public.goal_progress_logs enable row level security;5253create policy "Users can manage own progress logs"54 on public.goal_progress_logs55 using (auth.uid() = user_id)56 with check (auth.uid() = user_id);5758create index goal_progress_logs_goal_time_idx59 on public.goal_progress_logs (goal_id, logged_at desc);6061create index goals_user_status_idx62 on public.goals (user_id, status);Heads up: The has_celebrated column prevents the completion confetti from re-firing every time the goal page loads after hitting 100%. Set it to true on the first 100% detection and persist it with a Supabase UPDATE.
Build it — pick your path
Each tool fits this feature differently. Switch tabs to compare fit, time, the exact steps, and a copy-paste prompt.
The strongest path for a goal dashboard inside a Next.js app — V0 generates beautiful Recharts radial charts, the shadcn/ui goal card grid, and Server Actions for log insertion with high visual quality.
Step by step
- 1Prompt V0 with the spec below to generate the GoalCard, GoalGrid, ProgressRing, LogModal, and MilestoneList components
- 2Add your Supabase credentials in V0's Vars panel (NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY)
- 3Run the SQL schema from this page in the Supabase SQL editor
- 4Follow up with a second prompt: 'Add a getGoalInsights(goalId) Server Action that queries the last 14 goal_progress_logs for the goal, computes the average daily progress rate, and returns projected_completion_date and status (on_track / at_risk / behind / insufficient_data). Use date-fns for date math.'
- 5For check-in emails: 'Add a Resend email sent by a Supabase Edge Function triggered weekly by pg_cron. The email lists all active goals for the user with current progress percentage and a direct link to the goal detail page.' — set this up in Supabase after deploying to Vercel
Build a goal setting and tracking feature for a Next.js App Router project backed by Supabase. Server Components fetch data; client components handle interaction. GoalGrid — server component that fetches all goals for the authenticated user (status = 'active') ordered by deadline ascending. GoalCard — client component showing: title, category badge, RadialBarChart from Recharts displaying progress_pct (Math.min(Math.round((current / target) * 100), 100)), days-remaining badge (green > 7, yellow 1–7, red 0 or overdue), a quick-log button. QuickLogModal — shadcn/ui Dialog client component with a controlled numeric input for logged_value and a textarea for notes; on submit calls a logProgress Server Action that inserts into goal_progress_logs and updates goals.current_value; if the update brings current_value >= target_value, also set has_celebrated = false on the first log (so confetti fires once). ProgressPage (/goals/[id]) — server component layout with: full-page RadialBarChart, Recharts LineChart of logged_value history from goal_progress_logs ordered by logged_at, MilestoneList client component (checkboxes backed by Supabase UPDATEs on goal_milestones.is_completed; when all milestones are checked, update parent goals.status to 'completed'), and a LogHistory list. GoalForm — shadcn/ui Dialog with react-hook-form + zod; fields: title, description, category (text), target_value (number > 0), target_unit, start_date, deadline (must be after start_date), check_in_cadence (daily/weekly/monthly). Celebrate on 100%: client component checks if progress_pct >= 100 and has_celebrated = false; fires react-confetti; calls a Server Action to set has_celebrated = true. Empty state: when no goals exist show a centered illustration and 'Set your first goal' CTA. Skeleton loading states for GoalGrid and ProgressPage.
Where this path bites
- V0 does not provision Supabase — run the SQL schema manually and configure env vars in the Vercel dashboard after deploying
- The Supabase RPC for projected completion date and the pg_cron check-in scheduler need explicit follow-up prompts after the initial UI build
- V0's sandbox cannot run Server Actions with live Supabase data — deploy to Vercel to test the full flow with real database reads and writes
Third-party services you'll need
The core feature runs entirely on Supabase. The check-in email is the only optional paid service:
| Service | What it does | Free tier | Paid from |
|---|---|---|---|
| Supabase | goals, goal_milestones, and goal_progress_logs tables; RLS; Auth; Edge Functions for check-in email scheduling via pg_cron | Free tier: 2 projects, 500MB DB, 50K MAU | $25/mo (Pro) |
| Resend | Weekly check-in email listing active goals with progress percentages; triggered from Supabase Edge Function | 3,000 emails/month free | $20/mo (Pro, 50K emails/mo) |
| Recharts | RadialBarChart for progress ring and LineChart for log history on web | Open-source, zero cost | Free |
| fl_chart | RadialBarChart and LineChart for progress visualization on Flutter | Open-source, zero cost | Free |
| react-confetti | Goal completion celebration animation on web | Open-source, zero cost | Free |
Swipe the table sideways to see pricing.
What it costs to run
Drag through the tiers to see how your monthly bill scales with users — no surprises later.
Estimated monthly running cost
Supabase free tier covers storage and auth. Resend free tier easily handles 100 users at weekly check-in cadence (400 emails/month). All charting libraries are free.
Estimates use verified 2026 pricing and assume typical usage per user. Your real bill depends on activity, storage, and third-party plans.
What breaks when AI tools build this
The failures people actually hit on their first build — the symptom, why it happens, and the exact fix.
Progress bar jumps to 100% prematurely due to floating-point comparison
Symptom: When progress_pct is computed as (current_value / target_value) * 100, floating-point arithmetic can produce values like 99.99999... or 100.00001. A simple >= 100 check may trigger or miss the completion state incorrectly. Lovable and V0 both generate this pattern without the epsilon guard by default.
Fix: Use Math.min(Math.round((current_value / target_value) * 100), 100) for display. For the completion trigger, compare with a small epsilon tolerance: Math.abs(current_value - target_value) < 0.001. Cap displayed percentage at 100 even when current_value exceeds target_value — over-achievement is common and shouldn't break the UI.
Projected completion date shows a past date while the goal is still active
Symptom: The projection function averages all log entries from the goal's start date. Early slow-start periods (the first week with only 1–2 small log entries) drag the average daily progress rate down to near zero, producing a projected date far in the past or effectively infinity. Including correction entries (where a user logs a lower value than a previous log) makes this worse.
Fix: Use only the last 7–14 log entries for the rolling average: SELECT AVG(daily_delta) FROM (SELECT logged_value - LAG(logged_value) OVER (ORDER BY logged_at) AS daily_delta FROM goal_progress_logs WHERE goal_id = $1 ORDER BY logged_at DESC LIMIT 14). Return 'Insufficient data' if fewer than 3 log entries exist. Filter out negative deltas if using replacement-value tracking.
Completion confetti fires on every goal page load after hitting 100%
Symptom: react-confetti is typically triggered whenever progress_pct >= 100 in a useEffect. Without a guard, every navigation to the goal detail page fires the animation, which goes from delightful to annoying after the third visit. Lovable frequently generates this pattern without the celebration guard.
Fix: Add a has_celebrated boolean column to the goals table. Set it to false on goal creation. When the completion condition first triggers: fire react-confetti, then immediately call a Server Action or Supabase UPDATE to set has_celebrated = true. In the useEffect condition, check both progress_pct >= 100 AND !goal.has_celebrated before firing the animation.
Milestone completion doesn't update the parent goal status
Symptom: In FlutterFlow, tapping a milestone checkbox triggers a Supabase UPDATE on goal_milestones.is_completed but no subsequent check runs to see if all milestones are now done. The parent goal stays at status = 'active' even after every milestone is checked off, which confuses users who expect the goal to auto-complete.
Fix: After every milestone UPDATE, run a follow-up query: SELECT COUNT(*) FROM goal_milestones WHERE goal_id = target_goal_id AND is_completed = false. If the result is 0, execute a second UPDATE: goals SET status = 'completed' WHERE id = target_goal_id. In Lovable and V0, include this conditional logic explicitly in the prompt.
Weekly check-in emails sent at wrong local time
Symptom: A pg_cron job scheduled at '0 9 * * 1' fires at 9am UTC for all users simultaneously. A user who configured their check-in for Monday morning receives it at 9am UTC — which is 2am in New York or 5pm in Singapore. The mismatch makes the email feel impersonal and reduces the open rate significantly.
Fix: Store the user's IANA timezone string in the profiles table. In the Edge Function, loop over users grouped by their UTC offset and send emails at the correct UTC time per group. Alternatively: use a pg_cron job that runs every hour, and the Edge Function filters users whose local time matches their preferred check-in hour. Use Luxon or date-fns-tz for timezone arithmetic inside the Edge Function.
Best practices
Enforce numeric targets from the start — 'lose weight' is untrackable; 'lose 10 lbs by September 1' is measurable. Use a required target_value field with a unit picker in the creation form
Store logged_value as the absolute current value (not the delta) — this makes the history sparkline straightforward and allows users to make corrections without complex undo logic
Show projected completion date on every goal card, not just the detail page — users make different decisions when they see they are 3 weeks behind vs 3 weeks ahead
Use a 7–14 day rolling average for the projection rate, not all-time average — early slow starts skew the projection pessimistically and discourage users
Send check-in reminders as a single consolidated email per user covering all active goals, not one email per goal — inbox noise from goal apps is the top reason users unsubscribe
Cap displayed progress at 100% and allow current_value to exceed target_value — over-achievement is common (e.g., saving $6,000 toward a $5,000 goal) and should show 100% not 120%, which looks broken
Include an 'abandon' status alongside 'completed' and 'paused' — users who can cleanly remove goals they no longer care about stay engaged with their remaining ones longer than users who must look at stale goals
Persist the has_celebrated flag immediately on the first 100% detection — do not wait for a separate user action — so the confetti never repeats on reload
When You Need Custom Development
AI tools handle solo goal tracking with Supabase reliably. These scenarios require architecture beyond what a prompted build can sustain:
- AI-powered goal decomposition: the user types 'I want to run a marathon' and an LLM generates a 16-week training plan with SMART weekly milestones, realistic target values, and a built-in check-in cadence
- Automatic progress logging from third-party APIs: Strava for fitness goals, Plaid for savings goals, Goodreads for reading goals — each requires a separate OAuth flow, webhook ingestion, and normalization into the goal_progress_logs schema
- Social accountability layer: users assign an accountability partner who receives push notifications if the goal owner misses a weekly check-in — requires goal_shares table, per-partner RLS, and partner notification flows
- Enterprise OKR platform: company and team-level goals cascade to individual goals with manager approval workflows, cross-team progress visibility, and integration with HRIS data for performance review alignment
RapidDev builds these features for production
Real auth, database, and payments — fixed price, delivered in weeks. You own 100% of the code.
Frequently asked questions
What makes a goal 'SMART' and how do I enforce that in the UI?
SMART stands for Specific, Measurable, Achievable, Relevant, Time-bound. The measurable and time-bound parts are enforceable in the form: require a numeric target_value with a unit, and require a deadline date that is after the start date. Validate both at the zod schema level so the error appears before submission. You cannot enforce 'achievable' or 'relevant' programmatically — those are judgment calls — but you can show a warning if the projected daily progress rate required to hit the target by the deadline seems unusually high.
Can I track progress in non-numeric goals like habit formation?
Yes — use binary goals where the target_value is 1 and each log entry sets current_value to 1 (done). Or use a count-based goal (e.g., 'meditate 30 times this month' with target_value = 30 and each log increments by 1). The same table structure handles both. For pure habit tracking with a streak counter, the personalized-daily-routines feature is a better fit.
How do I add sub-goals or milestones?
The goal_milestones table handles this: each milestone has a goal_id FK, a title, an optional target_value (useful for numeric checkpoints like 'hit 50% of savings target'), and an is_completed boolean. Display them as a checklist on the goal detail page using @dnd-kit/sortable for reordering. When all milestones are checked, run a query to count incomplete ones and update the parent goal status to 'completed' if zero remain.
How do I get notified to update my progress?
Set the check_in_cadence field (daily, weekly, monthly) when creating the goal. A Supabase Edge Function triggered by pg_cron reads the cadence for each user and sends a Resend email at the scheduled time. The email includes current progress percentage and a direct link to the log modal. For mobile apps, flutter_local_notifications schedules an on-device alarm that works even when the app is closed.
What's the difference between this and a to-do list?
A to-do list tracks discrete tasks (done or not done). A goal-tracking feature tracks continuous progress toward a numeric target over time. The key distinction is the logged history: goal tracking records every intermediate data point so you can see trajectory and project completion dates. A to-do list does not. Build a to-do list when your users need task management; build goal tracking when they need to see how far they've come.
How do I archive completed goals without deleting them?
Use the status column — 'completed' and 'abandoned' goals remain in the database but are excluded from the active goals list view by default. Add a toggle to the UI ('Show archived goals') that re-runs the query without the status = 'active' filter. This preserves the full history log for completed goals, which users often want to look back on.
Can I import goals from another app?
The simplest approach: build a CSV import flow. The CSV template has columns for title, target_value, target_unit, deadline, and category. Parse the file client-side using PapaParse, preview the parsed goals in a table, then bulk-insert into Supabase with a single Server Action. Full-file format imports from apps like Notion or Habitica require custom parsers specific to their export formats.
Can I share my goals publicly or with friends?
Add an is_public boolean column to goals and update the RLS SELECT policy: allow reads when is_public = true in addition to the existing auth.uid() = user_id rule. Public goals get a shareable URL like /goals/{goal_id} that shows progress without requiring login. For a private accountability partner, add a goal_shares table with (goal_id, shared_with_user_id) and extend the RLS policy to allow reads for users in this table.
Need this feature production-ready?
RapidDev builds personalized goal setting and tracking into real apps — auth, database, payments — at $13K–$25K.
Book a free consultation30-min call. No commitment.