Skip to content

Goal Templates — Curated Library

A curated library of 28 hand-built goal templates across fitness, learning, financial, habit/wellness, career, productivity, and relationships domains. Users can select a template when creating a goal to prefill the form with realistic milestones, timelines, and example structure — eliminating the blank-slate problem for new goal creators.

Overview

Goal Templates ship as a read-only, curator-authored feature that reduces friction at goal creation. Users tap "Start from a template" in GoalCreate.vue, browse 28 curated templates grouped by theme, select one, and see their goal form prefilled with template name, category, estimated duration, and milestone suggestions. All fields remain editable — templates are starting points, not constraints. The goal + milestones create atomically in a single round-trip via the extended addGoal mutation.

For users: Templates make it easier to get started when motivation is high but direction is uncertain. "Run Your First 5K" is more actionable than a blank form.

For the product: PostHog telemetry captures template adoption. The has_created_from_template user property enables cohort analysis: users who start with templates show higher engagement metrics than legacy blank-form creators.

GraphQL Surface

Queries

goalTemplates

graphql
query GetGoalTemplates {
  goalTemplates {
    publicId
    name
    description
    theme  # One of: fitness, learning, financial, habit_wellness, career, productivity, relationships
    category { name }
    estimatedDurationDays
    imageUrl  # Nullable; bespoke template hero or null for category-default stock photo
    displayOrder
    milestones {
      name
      order
      daysOffsetFromStart
    }
  }
}

Unauthenticated-accessible query. Returns all 28 non-deleted templates ordered by (theme, display_order) (alphabetical by theme, then curator-ordered).

Mutations

addGoal (extended)

The standard addGoal mutation accepts two new optional arguments:

graphql
mutation CreateGoalFromTemplate(
  $name: String!
  $description: String
  $targetDate: String
  $fromTemplateId: ID
  $milestones: [MilestoneInput!]
) {
  addGoal(
    name: $name
    description: $description
    targetDate: $targetDate
    fromTemplateId: $fromTemplateId
    milestones: $milestones
  ) {
    goal {
      publicId
      name
      fromTemplate { name }  # Nullable; used for badge rendering
    }
    errors
  }
}
  • fromTemplateId (ID, optional): The template's public_id if creating from a template. Stored in goals.from_template_id (string column).
  • milestones (array of MilestoneInput, optional): Template milestone list (or user-customized). Array of { name: String!, order: Int!, daysOffsetFromStart: Int! }.

Pre-v1.21 calls (no fromTemplateId, no milestones array) work unchanged — full backward compatibility.

Goal Type fields

graphql
type GoalType {
  fromTemplate: GoalTemplateType  # Nullable
  # ... existing fields
}

type GoalTemplateType {
  publicId: ID!
  name: String!
  description: String!
  theme: String!  # One of: fitness, learning, financial, habit_wellness, career, productivity, relationships
  category: GoalCategoryType
  estimatedDurationDays: Int!
  imageUrl: String  # Nullable
  displayOrder: Int!
  milestones: [GoalTemplateMilestoneType!]!
}

type GoalTemplateMilestoneType {
  name: String!
  order: Int!
  daysOffsetFromStart: Int!
}

Telemetry Events

All events fire from documented call sites. Verified by Phase 85 Vitest assertions on posthog.capture mocks.

EventFire-sitePayloadNotes
template_browsedionic_frontend/src/components/TemplatePickerModal.vue:357 (watch isOpen with { immediate: true }){ source: 'goal_create' }Fires each time isOpen transitions from false → true. { immediate: true } fires the watcher on mount; if modal is closed at mount (typical), only the reset branch runs.
template_selectedionic_frontend/src/components/TemplatePickerModal.vue:388 (confirmTemplate() method; posthog.capture call at line 390){ templateId: string, templateName: string, theme: string }Fires on "Use this template" confirmation, before emitting the select event up to parent.
goal_created_from_templateionic_frontend/src/composables/useGoalForm.ts:237 (on addGoal mutation success when fromTemplateId is present){ templateId: string, templateName: string, goalPublicId: string }Fires only when fromTemplateId is present in the mutation input; legacy addGoal calls (no fromTemplateId) do not fire this event.
has_created_from_template (user property)ionic_frontend/src/composables/useClerkSync.ts (setPostHogUser 4th param) + rails_api/app/graphql/types/user_type.rb (resolver)true if user has ≥1 goal with from_template_id setSet at app boot via the hasCreatedFromTemplate field (computed from goals.where.not(from_template_id: nil).exists?); also set immediately at addGoal success via posthog.people.set({ has_created_from_template: true }). Mirrors the v1.18 has_enneagram_assessment pattern.

Feature Scope — v1.21

In scope:

  • 28 curated templates (4 each across fitness, learning, financial, habit/wellness, career, productivity, relationships)
  • Template picker modal from GoalCreate.vue
  • Form prefill on selection (name, category, target date computed from estimated duration, milestone list)
  • Atomic goal + milestone creation via extended addGoal mutation
  • "From template: [name]" badge on Goal.vue detail page
  • Three PostHog capture events + has_created_from_template user property

Out of scope (deferred to future milestones):

  • User-generated templates or template marketplace
  • Template ratings, usage analytics, or popularity sort
  • Template editing by users (templates are read-only curator-owned content)
  • Separate goal_template_milestones table (JSONB array on template model)
  • Separate CreateGoalFromTemplate mutation (extend addGoal instead)
  • Top-level "Templates" discovery surface (picker accessible only from goal creation)

Brand Voice & PBC Alignment

Template copy is action-forward and coaches-as-friend tone. No paywall, no Supporter-only templates, no FOMO. Templates are free-tier core feature.

Example templates (all brand-voice approved):

  • Fitness: "Run Your First 5K", "Build a 90-Day Strength Foundation", "Train for a Half Marathon"
  • Learning: "Learn to Code in 90 Days", "Reach Conversational Fluency in a New Language", "Finish an Online Course in 30 Days"
  • Financial: "Build a 6-Month Emergency Fund", "Pay Off Credit Card Debt", "Start Investing with $1,000"
  • Habit/Wellness: "Build a Daily Meditation Practice", "Complete a 30-Day Alcohol-Free Reset", "Establish a Consistent Morning Routine"

Milestone names lead with action verbs. No clinical language, no generic motivation ("You can do it!"). Specific, encouraging, achievable.

Technical Notes

  • Model: GoalTemplate inherits from PublicRecord, acts_as_paranoid. Columns: public_id, name, description, theme (indexed), category_id, estimated_duration_days, image_url (nullable), milestones (JSONB, array of {name, order, days_offset_from_start}), display_order, deleted_at, timestamps.
  • Data layer: Phase 82 (PR #671) delivered model, migration, GraphQL type, and placeholder seeds.
  • Picker UI: Phase 84 (PR #682) delivered modal, card component, and form integration.
  • Telemetry + content: Phase 85 (PR #698) delivered all three PostHog events, has_created_from_template user property, and the canonical template set (now 28 templates across 7 themes).

Last updated: 2026-07-06

Loading…