Skip to main content
RapidDev - Software Development Agency
flutterflow-tutorials

How to Build a Custom Task Scheduler in FlutterFlow

Build a recurring task scheduler using a Firestore collection of scheduled_tasks with cron expressions, a Cloud Function dispatcher that runs every minute to check for due tasks, and an admin UI for creating and managing scheduled jobs. Each task execution logs to an execution_log subcollection with success or failure status. Use cases include daily report generation, weekly email digests, hourly data syncs, and monthly cleanup routines.

What you'll learn

  • How to design a Firestore schema for scheduled recurring tasks with cron expressions
  • How to build a Cloud Function dispatcher that checks and executes due tasks
  • How to calculate the next run time from a cron expression
  • How to create an admin UI for managing scheduled tasks and viewing execution logs
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner9 min read25-30 minFlutterFlow Pro+ (Cloud Functions required)March 2026RapidDev Engineering Team
TL;DR

Build a recurring task scheduler using a Firestore collection of scheduled_tasks with cron expressions, a Cloud Function dispatcher that runs every minute to check for due tasks, and an admin UI for creating and managing scheduled jobs. Each task execution logs to an execution_log subcollection with success or failure status. Use cases include daily report generation, weekly email digests, hourly data syncs, and monthly cleanup routines.

Building a Custom Task Scheduler in FlutterFlow

Many apps need recurring background tasks: sending daily digests, syncing external data hourly, generating monthly reports, or cleaning up stale records. This tutorial builds a flexible task scheduler where tasks are defined in Firestore with cron-like schedules, a dispatcher Cloud Function checks for due tasks every minute, and an admin interface lets you create, edit, pause, and monitor scheduled jobs.

Prerequisites

  • A FlutterFlow project on the Pro plan or higher
  • Firebase project on the Blaze plan with Cloud Functions enabled
  • Basic understanding of cron expressions (e.g., '0 8 * * *' means daily at 8 AM)
  • Cloud Functions for each task action already deployed (e.g., sendDailyReport, syncExternalData)

Step-by-step guide

1

Design the Firestore schema for scheduled tasks and execution logs

Create a `scheduled_tasks` collection with fields: name (String), description (String), schedule (String — cron expression like '0 8 * * *'), actionName (String — the Cloud Function to call), parameters (Map — JSON parameters to pass to the function), isActive (bool), lastRunAt (Timestamp), nextRunAt (Timestamp), createdBy (String), and createdAt (Timestamp). Add a subcollection `scheduled_tasks/{taskId}/execution_log` with fields: executedAt (Timestamp), status (String — success or failed), durationMs (int), error (String — null on success), and result (String — summary of what happened).

Expected result: The schema supports flexible task scheduling with full execution history per task.

2

Build the dispatcher Cloud Function that runs every minute

Create a scheduled Cloud Function named taskDispatcher that runs every minute via Cloud Scheduler. The function queries scheduled_tasks where isActive is true and nextRunAt is less than or equal to the current time. For each due task, call the corresponding Cloud Function by name (using a switch statement or dynamic function lookup), pass the task's parameters, record the start time, and wrap the call in a try-catch. On success, log status 'success' to the execution_log subcollection and update lastRunAt. On failure, log the error message. After execution, calculate the next run time from the cron expression and update nextRunAt on the task document.

taskDispatcher.js
1// Cloud Function: taskDispatcher
2const functions = require('firebase-functions');
3const admin = require('firebase-admin');
4admin.initializeApp();
5const db = admin.firestore();
6
7// Import your task action functions
8const actions = require('./taskActions');
9
10exports.taskDispatcher = functions.pubsub
11 .schedule('every 1 minutes')
12 .onRun(async () => {
13 const now = admin.firestore.Timestamp.now();
14 const dueTasks = await db
15 .collection('scheduled_tasks')
16 .where('isActive', '==', true)
17 .where('nextRunAt', '<=', now)
18 .get();
19
20 for (const doc of dueTasks.docs) {
21 const task = doc.data();
22 const start = Date.now();
23
24 try {
25 const actionFn = actions[task.actionName];
26 if (!actionFn) throw new Error(
27 `Unknown action: ${task.actionName}`);
28
29 const result = await actionFn(task.parameters);
30
31 await doc.ref.collection('execution_log').add({
32 executedAt: admin.firestore.FieldValue
33 .serverTimestamp(),
34 status: 'success',
35 durationMs: Date.now() - start,
36 result: result || 'Completed',
37 });
38 } catch (err) {
39 await doc.ref.collection('execution_log').add({
40 executedAt: admin.firestore.FieldValue
41 .serverTimestamp(),
42 status: 'failed',
43 durationMs: Date.now() - start,
44 error: err.message,
45 });
46 }
47
48 // Update lastRunAt and calculate nextRunAt
49 const nextRun = getNextCronTime(task.schedule);
50 await doc.ref.update({
51 lastRunAt: admin.firestore.FieldValue
52 .serverTimestamp(),
53 nextRunAt: admin.firestore.Timestamp
54 .fromDate(nextRun),
55 });
56 }
57 });

Expected result: Every minute, the dispatcher finds due tasks, executes them, logs the result, and schedules the next run.

3

Implement cron expression parsing for next run calculation

Add the cron-parser npm package to your Cloud Functions. Create a helper function getNextCronTime that takes a cron expression string and returns the next Date when the task should run. Use the cron-parser library's parseExpression method with the currentDate set to now. Call interval.next().toDate() to get the next occurrence. Handle invalid cron expressions with a try-catch that defaults to 24 hours from now and logs a warning. This function is called by the dispatcher after each execution to set the nextRunAt field.

cronHelper.js
1// Helper: getNextCronTime
2const cronParser = require('cron-parser');
3
4function getNextCronTime(cronExpression) {
5 try {
6 const interval = cronParser.parseExpression(
7 cronExpression,
8 { currentDate: new Date() }
9 );
10 return interval.next().toDate();
11 } catch (err) {
12 console.warn(
13 `Invalid cron: ${cronExpression}`, err
14 );
15 // Default: 24 hours from now
16 const fallback = new Date();
17 fallback.setHours(fallback.getHours() + 24);
18 return fallback;
19 }
20}

Expected result: The helper correctly calculates the next run time from any valid cron expression and handles invalid expressions gracefully.

4

Build the admin UI for creating and managing scheduled tasks

Create a TaskScheduler admin page gated by role check. At the top, add a 'Create Task' Button that opens a BottomSheet with a form: name TextField, description TextField, schedule input (use a Row of DropDowns for frequency: every X minutes/hours/days or specific time, which generates the cron expression), actionName DropDown (populated from a config document listing available action functions), and a parameters TextField (JSON format). On submit, create the scheduled_tasks document with isActive true and nextRunAt calculated from the cron expression using a Custom Function. Below the form, display a ListView of all tasks from a Backend Query ordered by name. Each task shows name, schedule description, last run status, next run time, and an isActive Switch toggle.

Expected result: Admins can create new scheduled tasks using a user-friendly form, view all tasks, and toggle them active or inactive.

5

Add execution log viewing and task monitoring

When an admin taps a task in the list, navigate to a TaskDetail page passing the taskId. Display the task configuration at the top, then a ListView of the execution_log subcollection ordered by executedAt descending. Each log entry shows the execution timestamp, status badge (green for success, red for failed), duration in milliseconds, and the result or error message. Add summary statistics at the top: total executions, success rate percentage, average duration, and last failure timestamp. Add a 'Run Now' Button that updates the task's nextRunAt to now, causing the dispatcher to pick it up on its next run. Add a 'Delete Task' Button with a confirmation dialog.

Expected result: Admins see detailed execution history per task with success rates, can trigger immediate execution, and can delete tasks.

Complete working example

Task Scheduler Cloud Functions
1// Cloud Functions: Task Scheduler System
2const functions = require('firebase-functions');
3const admin = require('firebase-admin');
4const cronParser = require('cron-parser');
5admin.initializeApp();
6
7const db = admin.firestore();
8
9// Task action registry — add your functions here
10const taskActions = {
11 sendDailyReport: async (params) => {
12 // Your daily report logic
13 return 'Report sent to ' + (params.email || 'admin');
14 },
15 syncExternalData: async (params) => {
16 // Your sync logic
17 return 'Synced ' + (params.source || 'default');
18 },
19 cleanupStaleData: async (params) => {
20 const cutoff = new Date();
21 cutoff.setDate(cutoff.getDate() - (params.days || 30));
22 // Delete old documents logic
23 return 'Cleaned up records older than ' + params.days + ' days';
24 },
25};
26
27function getNextCronTime(expression) {
28 try {
29 const interval = cronParser.parseExpression(
30 expression, { currentDate: new Date() }
31 );
32 return interval.next().toDate();
33 } catch (err) {
34 const fallback = new Date();
35 fallback.setHours(fallback.getHours() + 24);
36 return fallback;
37 }
38}
39
40// Dispatcher: runs every minute
41exports.taskDispatcher = functions.pubsub
42 .schedule('every 1 minutes')
43 .onRun(async () => {
44 const now = admin.firestore.Timestamp.now();
45 const due = await db
46 .collection('scheduled_tasks')
47 .where('isActive', '==', true)
48 .where('nextRunAt', '<=', now)
49 .get();
50
51 const promises = due.docs.map(async (doc) => {
52 const task = doc.data();
53 const start = Date.now();
54 let status = 'success';
55 let result = '';
56 let error = null;
57
58 try {
59 const fn = taskActions[task.actionName];
60 if (!fn) throw new Error('Unknown action');
61 result = await fn(task.parameters || {});
62 } catch (e) {
63 status = 'failed';
64 error = e.message;
65 }
66
67 const logEntry = {
68 executedAt: admin.firestore.FieldValue
69 .serverTimestamp(),
70 status,
71 durationMs: Date.now() - start,
72 ...(result && { result }),
73 ...(error && { error }),
74 };
75
76 const nextRun = getNextCronTime(task.schedule);
77
78 await Promise.all([
79 doc.ref.collection('execution_log')
80 .add(logEntry),
81 doc.ref.update({
82 lastRunAt: admin.firestore.FieldValue
83 .serverTimestamp(),
84 nextRunAt: admin.firestore.Timestamp
85 .fromDate(nextRun),
86 }),
87 ]);
88 });
89
90 await Promise.all(promises);
91 });

Common mistakes when building a Custom Task Scheduler in FlutterFlow

Why it's a problem: Running the dispatcher every minute with many simultaneous tasks causing cold start delays

How to avoid: For critical tasks that must run exactly on time, create dedicated Cloud Scheduler jobs (one per task) instead of routing through a single dispatcher. Reserve the dispatcher for non-time-critical tasks.

Why it's a problem: Storing the cron expression without calculating and storing nextRunAt

How to avoid: Calculate nextRunAt whenever a task is created, edited, or executed. The dispatcher simply queries for tasks where nextRunAt <= now, which is a fast indexed Firestore query.

Why it's a problem: Not handling task execution failures with retry logic

How to avoid: Add a retryCount field to each task. On failure, if retryCount is below a max (e.g., 3), set nextRunAt to 5 minutes from now instead of the next cron interval. Reset retryCount to 0 on success.

Best practices

  • Pre-calculate and store nextRunAt as a Firestore Timestamp for efficient querying instead of parsing cron expressions on every dispatcher run
  • Log every execution with status, duration, and result or error message for complete observability
  • Use the cron-parser npm package instead of writing custom cron parsing logic — it handles edge cases like DST transitions
  • Process due tasks in parallel with Promise.all to minimize the dispatcher function execution time
  • Add a 'Run Now' feature that sets nextRunAt to the current time for on-demand task execution
  • Keep task action functions idempotent so that accidental double-execution (from dispatcher retries) does not cause problems
  • Monitor the dispatcher function itself for failures — if the dispatcher goes down, no tasks run

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I'm building a custom task scheduler in FlutterFlow. I need a Firestore schema for scheduled tasks with cron expressions, a Cloud Function dispatcher that runs every minute and executes due tasks, cron parsing to calculate next run times, and execution logging. Show me the complete Cloud Functions code and Firestore schema.

FlutterFlow Prompt

Create an admin page for managing scheduled tasks. Show a list of tasks with name, cron schedule, last run status, and next run time. Add a form to create new tasks with name, description, frequency selector, and action name. Each task should have a switch to enable or disable it.

Frequently asked questions

What is a cron expression and how do I write one?

A cron expression defines a schedule using five fields: minute, hour, day-of-month, month, day-of-week. For example, '0 8 * * *' means every day at 8:00 AM, '*/30 * * * *' means every 30 minutes, and '0 0 * * 1' means every Monday at midnight.

Can the dispatcher handle hundreds of tasks?

The dispatcher Cloud Function has a 9-minute timeout. For hundreds of tasks, process them in parallel with Promise.all. If you have thousands of tasks, split them across multiple dispatcher instances or use Google Cloud Tasks for fan-out.

How do I add a new task action without redeploying Cloud Functions?

You cannot avoid redeployment for new Cloud Function code. However, you can make actions configurable by creating generic actions like 'httpCall' that send HTTP requests to configurable endpoints. New tasks only need a new URL, not new code.

What happens if the dispatcher itself fails?

Cloud Scheduler retries the dispatcher on failure. Additionally, tasks that were due but not executed will still have nextRunAt in the past, so the next successful dispatcher run will pick them up. No tasks are permanently lost.

Can I schedule tasks to run at specific times in different time zones?

Yes. Store a timeZone field on each task (e.g., 'America/New_York'). Pass the timezone option to cron-parser when calculating the next run time. This ensures tasks run at the correct local time regardless of DST changes.

Can RapidDev help build an enterprise-grade task scheduling system?

Yes. RapidDev can implement distributed task scheduling with priority queues, dependency chains, retry policies, dead letter queues, and real-time monitoring dashboards.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.