Create an AI-powered fitness planner by collecting user stats and goals in an assessment form, sending that data to an AI API via a Cloud Function with a personal trainer system prompt, and displaying the returned plan in a TabBar with one tab per day. Add workout logging and a regeneration button for progressive overload. Always include a medical disclaimer.
What You Are Building and Why
A generic workout plan app is easy to build. A personalized one that adapts to each user's body, goals, and available equipment is genuinely useful — and a strong differentiator in the fitness app market. FlutterFlow lets you build the entire UI visually, while a Firebase Cloud Function handles the AI API call so your API key never touches the client. The result is a polished app that feels custom-built for each user, with workout logs that feed back into the next plan generation cycle to keep the training progressive. This tutorial builds the full loop: assess → generate → log → regenerate.
Prerequisites
- A FlutterFlow project connected to Firebase (Blaze plan for Cloud Functions)
- An AI API key (OpenAI GPT-4o or Anthropic Claude) stored as a Firebase Function environment secret
- Firestore collections: users, fitness_plans, and workout_logs
- Basic knowledge of FlutterFlow forms and Page State variables
Step-by-step guide
Build the fitness assessment form
Build the fitness assessment form
Create a new page called FitnessAssessmentPage. Add a ScrollView containing a Column with the following input widgets: a Slider for age (16–75), two TextFields for height (cm) and weight (kg), a DropdownButton for primary goal (options: Lose Weight, Build Muscle, Improve Endurance, Maintain Fitness), a Multi-select chip widget listing available equipment (Bodyweight Only, Dumbbells, Barbell, Resistance Bands, Full Gym), and a ToggleButtons row for days per week (3, 4, 5, or 6). Bind each widget to a Page State variable of the appropriate type. Add a prominent medical disclaimer Text widget at the bottom of the form in red: 'Consult your doctor before starting any new exercise program, especially if you have a medical condition, injury, or are new to exercise.' Add a Checkbox that must be ticked to enable the 'Generate My Plan' button.
Expected result: A complete scrollable assessment form with all fields, the medical disclaimer, the consent checkbox, and a disabled 'Generate My Plan' button that enables only when the checkbox is ticked.
Set up the Cloud Function with AI API call
Set up the Cloud Function with AI API call
In your Firebase project, create a Cloud Function called generateFitnessPlan. It accepts a JSON body with fields: age, heightCm, weightKg, goal, equipment (array), daysPerWeek, and optionally previousPlanSummary and recentLogs for progressive regeneration. Build a detailed system prompt that positions the AI as a certified personal trainer. Construct a user message summarising the assessment data. Call your AI API (OpenAI or Anthropic) with the system prompt and user message, requesting a structured JSON response with an array of day objects, each containing dayName, focus, and an exercises array with name, sets, reps, rest, and notes. Return the parsed JSON to the Flutter client. Store your API key using Firebase Functions config or Secret Manager — never hardcode it.
1// functions/index.js2const functions = require('firebase-functions');3const admin = require('firebase-admin');4const axios = require('axios');5admin.initializeApp();67exports.generateFitnessPlan = functions.https.onCall(async (data, context) => {8 if (!context.auth) throw new functions.https.HttpsError('unauthenticated', 'Login required');910 const { age, heightCm, weightKg, goal, equipment, daysPerWeek, previousPlanSummary } = data;11 const apiKey = process.env.OPENAI_API_KEY;1213 const systemPrompt = `You are a certified personal trainer with 10 years of experience.14Create safe, progressive workout plans tailored to the individual.15Always prioritize proper form and injury prevention.16Return ONLY valid JSON matching this schema:17{"days": [{"dayName": string, "focus": string, "exercises": [{"name": string, "sets": number, "reps": string, "rest": string, "notes": string}]}]}`;1819 const userMessage = `Create a ${daysPerWeek}-day/week workout plan for:20- Age: ${age}, Height: ${heightCm}cm, Weight: ${weightKg}kg21- Goal: ${goal}22- Available equipment: ${equipment.join(', ')}23${previousPlanSummary ? '- Previous plan: ' + previousPlanSummary + ' (increase intensity by 5-10%)' : ''}`;2425 const response = await axios.post('https://api.openai.com/v1/chat/completions', {26 model: 'gpt-4o',27 messages: [{ role: 'system', content: systemPrompt }, { role: 'user', content: userMessage }],28 response_format: { type: 'json_object' },29 }, { headers: { Authorization: `Bearer ${apiKey}` } });3031 const plan = JSON.parse(response.data.choices[0].message.content);32 return plan;33});Expected result: Deploying the function succeeds. Calling it from the Firebase console returns a valid JSON fitness plan with the correct number of day objects.
Call the Cloud Function from FlutterFlow and store the plan
Call the Cloud Function from FlutterFlow and store the plan
Create a Custom Action called callGenerateFitnessPlan. It reads all Page State variables from the assessment form and calls FirebaseFunctions.instance.httpsCallable('generateFitnessPlan') with them as a map. On success, save the returned plan JSON to a new Firestore document in the fitness_plans collection with the current user's UID, a createdAt timestamp, and a planVersion integer starting at 1. Also save the plan JSON to an App State variable called currentPlan so you can display it immediately without a Firestore read. Navigate to the PlanViewPage. Wire this action to the 'Generate My Plan' button. Show a full-screen loading overlay with an animated logo while the AI generates the plan — this typically takes 3–8 seconds.
Expected result: Tapping 'Generate My Plan' shows a loading screen, then navigates to PlanViewPage with the generated plan displayed.
Display the plan in a day-by-day TabBar
Display the plan in a day-by-day TabBar
Create PlanViewPage. Retrieve the currentPlan from App State. The plan's days array length determines the number of tabs — use a Custom Widget or FlutterFlow's Tab Bar widget with dynamic tab count. Each tab label is the dayName field (e.g., 'Monday - Push', 'Wednesday - Pull'). Inside each tab, add a Text widget for the focus description, then a ListView of exercise cards. Each card shows the exercise name in a bold Text, and a Row with chips for sets x reps, rest time, and a small info icon that opens a Popup showing the notes field. Add a 'Mark All Complete' button at the bottom of each tab that logs the day's workout to Firestore.
Expected result: PlanViewPage shows a TabBar with one tab per training day. Each tab displays the exercises for that day in a scrollable card list.
Implement workout logging and progressive overload regeneration
Implement workout logging and progressive overload regeneration
When the user taps 'Mark All Complete' on a day tab, create a Firestore document in the workout_logs collection with fields: userId, planId, dayName, completedAt, and exercisesCompleted (the full exercise list). After logging, check if all days in the current plan have been completed within the last 7 days. If so, show a Congratulations Dialog with two buttons: 'Keep This Plan' and 'Generate Progressive Plan'. The 'Generate Progressive Plan' button calls callGenerateFitnessPlan again but this time passes previousPlanSummary — a string summarising the completed plan's exercises and volumes. The Cloud Function uses this to increase intensity by 5–10% in the new plan. Increment planVersion in Firestore so users can browse their plan history.
Expected result: Completing all workout days unlocks a 'Generate Progressive Plan' button. The new plan is visibly harder (more sets, heavier weights, reduced rest times) than the previous one.
Complete working example
1import 'package:cloud_functions/cloud_functions.dart';2import 'package:cloud_firestore/cloud_firestore.dart';3import 'package:firebase_auth/firebase_auth.dart';45Future<Map<String, dynamic>?> callGenerateFitnessPlan({6 required int age,7 required double heightCm,8 required double weightKg,9 required String goal,10 required List<String> equipment,11 required int daysPerWeek,12 String? previousPlanSummary,13}) async {14 final user = FirebaseAuth.instance.currentUser;15 if (user == null) return null;1617 // Call the Cloud Function18 final callable = FirebaseFunctions.instance.httpsCallable(19 'generateFitnessPlan',20 options: HttpsCallableOptions(timeout: const Duration(seconds: 60)),21 );2223 final result = await callable.call({24 'age': age,25 'heightCm': heightCm,26 'weightKg': weightKg,27 'goal': goal,28 'equipment': equipment,29 'daysPerWeek': daysPerWeek,30 if (previousPlanSummary != null) 'previousPlanSummary': previousPlanSummary,31 });3233 final planData = Map<String, dynamic>.from(result.data as Map);3435 // Persist to Firestore36 final planRef = await FirebaseFirestore.instance37 .collection('fitness_plans')38 .add({39 'userId': user.uid,40 'plan': planData,41 'goal': goal,42 'daysPerWeek': daysPerWeek,43 'createdAt': FieldValue.serverTimestamp(),44 'planVersion': 1,45 'completedDays': [],46 });4748 // Return plan with its Firestore ID49 planData['planId'] = planRef.id;50 return planData;51}5253// Log a completed workout day54Future<void> logWorkoutDay({55 required String planId,56 required String dayName,57 required List<dynamic> exercises,58}) async {59 final user = FirebaseAuth.instance.currentUser;60 if (user == null) return;6162 await FirebaseFirestore.instance.collection('workout_logs').add({63 'userId': user.uid,64 'planId': planId,65 'dayName': dayName,66 'exercisesCompleted': exercises,67 'completedAt': FieldValue.serverTimestamp(),68 });6970 // Mark day complete on the plan document71 await FirebaseFirestore.instance72 .collection('fitness_plans')73 .doc(planId)74 .update({75 'completedDays': FieldValue.arrayUnion([dayName]),76 });77}Common mistakes when building a Personalized Fitness Plan Generator in FlutterFlow
Why it's a problem: Generating a fitness plan with no medical disclaimer or consent gate
How to avoid: Add a visible medical disclaimer and a mandatory checkbox consent gate before the user can generate a plan. Log that consent with a timestamp to Firestore alongside the plan document.
Why it's a problem: Calling the AI API directly from the Flutter client instead of via a Cloud Function
How to avoid: Always proxy AI API calls through a server-side Cloud Function. Store the API key in Firebase Secret Manager or Functions environment config, never in client-side code or Firestore.
Why it's a problem: Not requesting JSON format from the AI API
How to avoid: Always pass response_format: { type: 'json_object' } for OpenAI, or include explicit JSON schema in your system prompt for other providers. Add a try-catch around JSON.parse() and return a helpful error message if parsing fails.
Best practices
- Always include a medical disclaimer and require checkbox consent before generating any fitness plan
- Store AI API keys exclusively in server-side environment variables, never in Flutter code or Firestore
- Set a Cloud Function timeout of at least 60 seconds — AI generation can occasionally take 20–30 seconds
- Archive all previous plan versions in Firestore so users can compare their progression over time
- Include exercise notes explaining correct form — reducing injury risk improves retention
- Validate the AI response schema before rendering — use a fallback message if a field is missing
- Add a difficulty rating widget on each exercise so users can give feedback for the next regeneration
- Cache the current plan locally in App State to avoid reloading from Firestore every time the user opens the plan
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I am building a fitness plan generator in FlutterFlow. Write a Firebase Cloud Function in Node.js that accepts user stats (age, weight, height, goal, equipment, days per week) and calls the OpenAI GPT-4o API with a personal trainer system prompt. Return a structured JSON plan with one object per training day, each containing exercises with sets, reps, rest, and form notes.
In my FlutterFlow project, add a Custom Action called callGenerateFitnessPlan that takes age (int), heightCm (double), weightKg (double), goal (String), equipment (List<String>), and daysPerWeek (int) as parameters, calls a Firebase HTTPS Callable Function named generateFitnessPlan, saves the result to Firestore under fitness_plans, and returns the plan as a JSON map.
Frequently asked questions
Which AI API works best for generating fitness plans in FlutterFlow?
OpenAI's GPT-4o is the most reliable for structured JSON output because it supports the response_format parameter natively. Anthropic Claude 3.5 Sonnet is an excellent alternative and often produces more nuanced exercise progressions. Both work well in a Firebase Cloud Function proxy pattern.
Can I generate the plan without a Cloud Function by calling the AI API from Flutter directly?
Technically yes, but you must never do this in production. Calling an AI API from Flutter code means your API key is embedded in the app binary and can be stolen. Always use a server-side proxy such as a Firebase Cloud Function.
How do I handle the 3-8 second AI generation delay without losing users?
Show a full-screen loading overlay with an animated progress indicator and rotating motivational text. Users tolerate longer waits for personalised content, but they need visual feedback that something is happening. A 'Your plan is being built' message with an animation keeps engagement high.
What happens if the AI returns a plan that is unsafe for a user's medical condition?
Your app cannot medically screen users, so the medical disclaimer and consent checkbox are essential. You should also include low-intensity modification notes in the system prompt and recommend consulting a doctor. Consider adding a 'I have an injury or medical condition' checkbox that adjusts the prompt to request a gentler, rehabilitation-focused plan.
How do I implement progressive overload automatically?
After the user completes the full plan cycle, pass a previousPlanSummary string to the Cloud Function containing the exercises, sets, and reps from the last plan. Include an instruction in the system prompt such as 'increase volume by 5-10% from the previous plan'. The AI will generate slightly more demanding exercises, replicating what a real trainer would do.
Can I add video demonstrations for each exercise?
Yes. The simplest approach is to maintain a Firestore collection of exercises with a videoUrl field. When rendering each exercise card, look up the exercise name in that collection and display a video thumbnail. Tapping the thumbnail opens an in-app video player. You can pre-populate this collection with links to public YouTube exercise demonstration videos.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation