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

How to Implement Data Export Features in FlutterFlow

Implement data export in FlutterFlow using two approaches: client-side CSV with the share_plus package for small datasets under 1,000 rows, and Cloud Functions for large-scale CSV, PDF, and Excel generation. Cloud Functions query Firestore server-side, generate the file using pdfkit or exceljs, upload to Firebase Storage, and return a signed download URL that the app opens with Launch URL.

What you'll learn

  • Generate and share small CSV files client-side using share_plus in a Custom Action
  • Create a Cloud Function that generates PDF reports using pdfkit and uploads to Firebase Storage
  • Build an Excel export with multiple sheets using exceljs in a Cloud Function
  • Display export progress and download the generated file using Launch URL
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner10 min read40-55 minFlutterFlow Pro+ (code export required for Cloud Function setup)March 2026RapidDev Engineering Team
TL;DR

Implement data export in FlutterFlow using two approaches: client-side CSV with the share_plus package for small datasets under 1,000 rows, and Cloud Functions for large-scale CSV, PDF, and Excel generation. Cloud Functions query Firestore server-side, generate the file using pdfkit or exceljs, upload to Firebase Storage, and return a signed download URL that the app opens with Launch URL.

Scalable Data Export for CSV, PDF, and Excel

Data export is one of the most requested features in business FlutterFlow apps. This tutorial covers two export approaches based on dataset size. For small datasets under 1,000 rows, a client-side Custom Action builds the CSV string in memory and shares it using share_plus — no server needed. For large datasets or formatted documents like PDF reports and Excel files, a Firebase Cloud Function handles the heavy lifting: it queries Firestore with full pagination support, formats the data, generates the file using battle-tested Node.js libraries, uploads it to Firebase Storage, and returns a 15-minute signed URL. The app opens this URL with the Launch URL action, triggering the device's file manager or browser to handle the download.

Prerequisites

  • FlutterFlow Pro account with Firebase connected
  • Firebase Cloud Functions enabled in your project
  • Firebase Storage enabled with a bucket configured
  • Basic familiarity with FlutterFlow's Custom Actions panel

Step-by-step guide

1

Build a Client-Side CSV Export for Small Datasets

In FlutterFlow, create a Custom Action named exportToCSV. Add the share_plus: ^7.0.0 dependency in the Custom Action's pubspec section. The action accepts a parameter documents (List of dynamic — your Firestore query result) and a filename (String). It iterates over the documents, extracts the fields you want to export, builds a CSV string with a header row, writes it to a temporary file using dart:io's File class and path_provider to get the temp directory, then calls ShareXFiles with the file. Wire this Custom Action to an Export CSV button on your data screen. Pass the Backend Query's documents list as the documents parameter.

export_to_csv.dart
1// Custom Action: exportToCSV
2// pubspec: share_plus: ^7.0.0, path_provider: ^2.1.0
3import 'dart:io';
4import 'package:path_provider/path_provider.dart';
5import 'package:share_plus/share_plus.dart';
6
7Future exportToCSV(List<dynamic> documents, String filename) async {
8 final buffer = StringBuffer();
9 // Header row
10 buffer.writeln('Name,Email,Date,Amount');
11 // Data rows
12 for (final doc in documents) {
13 final data = doc.data() as Map<String, dynamic>? ?? {};
14 final name = (data['name'] ?? '').toString().replaceAll(',', ' ');
15 final email = (data['email'] ?? '').toString();
16 final date = (data['createdAt']?.toDate()?.toIso8601String() ?? '');
17 final amount = (data['amount'] ?? 0).toString();
18 buffer.writeln('$name,$email,$date,$amount');
19 }
20 final dir = await getTemporaryDirectory();
21 final file = File('${dir.path}/$filename.csv');
22 await file.writeAsString(buffer.toString());
23 await Share.shareXFiles([XFile(file.path)], text: 'Exported data');
24}

Expected result: Tapping Export CSV opens the device's native share sheet with the CSV file ready to save or send.

2

Create a Cloud Function for Large-Scale CSV Export

In the Firebase Console, create a Cloud Function named exportUserDataCSV. The function authenticates the caller, queries the target Firestore collection with pagination (using startAfter to page through all results without hitting memory limits), builds the CSV in a stream, uploads it to Firebase Storage, and returns a signed URL. In FlutterFlow's API Calls panel, create a new Cloud Function API call targeting the function's HTTPS endpoint. Pass currentUser.uid and any filter parameters (date range, project ID). On the Export button's action, call this API, then use Launch URL with the returned URL.

index.js
1// Cloud Function: exportUserDataCSV (index.js)
2const functions = require('firebase-functions');
3const admin = require('firebase-admin');
4admin.initializeApp();
5
6exports.exportUserDataCSV = functions.https.onCall(async (data, context) => {
7 if (!context.auth) {
8 throw new functions.https.HttpsError('unauthenticated', 'Login required');
9 }
10 const userId = context.auth.uid;
11 const db = admin.firestore();
12 const rows = ['Name,Email,Date,Amount'];
13 let query = db.collection('orders').where('userId', '==', userId).orderBy('createdAt', 'desc');
14 if (data.startDate) query = query.where('createdAt', '>=', new Date(data.startDate));
15
16 // Paginate through all results
17 let lastDoc = null;
18 let hasMore = true;
19 while (hasMore) {
20 let pageQuery = lastDoc ? query.startAfter(lastDoc).limit(500) : query.limit(500);
21 const snap = await pageQuery.get();
22 snap.forEach(doc => {
23 const d = doc.data();
24 const name = (d.name || '').replace(/,/g, ' ');
25 const email = d.email || '';
26 const date = d.createdAt?.toDate()?.toISOString() || '';
27 const amount = d.amount || 0;
28 rows.push(`${name},${email},${date},${amount}`);
29 });
30 if (snap.size < 500) hasMore = false;
31 else lastDoc = snap.docs[snap.docs.length - 1];
32 }
33
34 const csv = rows.join('\n');
35 const bucket = admin.storage().bucket();
36 const filePath = `exports/${userId}/data_${Date.now()}.csv`;
37 const file = bucket.file(filePath);
38 await file.save(csv, { contentType: 'text/csv' });
39 const [url] = await file.getSignedUrl({ action: 'read', expires: Date.now() + 900000 });
40 return { url, rowCount: rows.length - 1 };
41});

Expected result: The Cloud Function generates a CSV from all matching Firestore documents regardless of size and returns a download URL.

3

Add PDF Report Generation with pdfkit

Create a second Cloud Function named exportPDFReport. Add pdfkit as a dependency in package.json. The function creates a PDFDocument, adds a styled header with your app name and report date, iterates over the Firestore query results, and adds formatted rows with alternating background colors. It pipes the PDF stream to a buffer, then uploads the buffer to Firebase Storage. More complex PDF reports can include totals rows, charts embedded as base64 images, and multi-page support with automatic page breaks. In FlutterFlow, add an Export PDF button alongside the CSV button with a separate API call to this function.

Expected result: Tapping Export PDF triggers the Cloud Function. The device opens the PDF via Launch URL in the system PDF viewer.

4

Create an Excel Export with Multiple Sheets Using exceljs

Create a third Cloud Function named exportExcelReport. Add exceljs to package.json. The function creates a new Workbook, adds a Summary sheet with aggregate totals, then adds a Detail sheet with all individual records. Apply cell formatting: bold headers, number format for currency columns, date format for timestamp columns. ExcelJS supports column width auto-fit, frozen header rows, and column filters — add all three for a professional output. Upload the .xlsx buffer to Firebase Storage and return a signed URL. In FlutterFlow, add an Export Excel button that calls this function.

Expected result: The exported Excel file opens with two sheets, formatted headers, and proper column widths.

5

Show Export Progress and Handle Errors

Large exports can take 10-30 seconds. Add a proper loading state to prevent the user from tapping the button multiple times and to set expectations. Create a Page State variable isExporting (Boolean, default false). When the Export button is tapped, set isExporting to true and show a CircularProgressIndicator with a Text widget saying 'Generating your export...' using Conditional Visibility. After the API call returns (whether success or error), set isExporting back to false. On success, call Launch URL with the returned URL. On error, show a Snack Bar with the error message. Disable the Export button using Conditional Interactivity tied to isExporting.

Expected result: A loading spinner appears during export. The button is disabled to prevent double-taps. Success opens the file; errors show a clear message.

Complete working example

index.js
1// Cloud Functions: exportUserDataCSV + exportPDFReport
2// package.json dependencies: pdfkit, firebase-admin, firebase-functions
3
4const functions = require('firebase-functions');
5const admin = require('firebase-admin');
6const PDFDocument = require('pdfkit');
7admin.initializeApp();
8
9// ---- CSV EXPORT ----
10exports.exportUserDataCSV = functions.https.onCall(async (data, context) => {
11 if (!context.auth) throw new functions.https.HttpsError('unauthenticated', 'Login required');
12 const userId = context.auth.uid;
13 const db = admin.firestore();
14 const rows = ['Name,Email,Date,Amount'];
15 let query = db.collection('orders').where('userId', '==', userId).orderBy('createdAt', 'desc');
16 let lastDoc = null;
17 let hasMore = true;
18 while (hasMore) {
19 const pageQ = lastDoc ? query.startAfter(lastDoc).limit(500) : query.limit(500);
20 const snap = await pageQ.get();
21 snap.forEach(doc => {
22 const d = doc.data();
23 rows.push([
24 (d.name || '').replace(/,/g, ' '),
25 d.email || '',
26 d.createdAt?.toDate()?.toISOString() || '',
27 d.amount || 0
28 ].join(','));
29 });
30 hasMore = snap.size === 500;
31 if (hasMore) lastDoc = snap.docs[snap.docs.length - 1];
32 }
33 const csv = rows.join('\n');
34 const bucket = admin.storage().bucket();
35 const filePath = `exports/${userId}/data_${Date.now()}.csv`;
36 await bucket.file(filePath).save(csv, { contentType: 'text/csv' });
37 const [url] = await bucket.file(filePath).getSignedUrl({ action: 'read', expires: Date.now() + 900000 });
38 return { url, rowCount: rows.length - 1 };
39});
40
41// ---- PDF EXPORT ----
42exports.exportPDFReport = functions.https.onCall(async (data, context) => {
43 if (!context.auth) throw new functions.https.HttpsError('unauthenticated', 'Login required');
44 const userId = context.auth.uid;
45 const db = admin.firestore();
46 const snap = await db.collection('orders').where('userId', '==', userId).limit(200).get();
47 return new Promise(async (resolve, reject) => {
48 const doc = new PDFDocument({ margin: 40 });
49 const buffers = [];
50 doc.on('data', chunk => buffers.push(chunk));
51 doc.on('end', async () => {
52 const pdfBuffer = Buffer.concat(buffers);
53 const filePath = `exports/${userId}/report_${Date.now()}.pdf`;
54 const bucket = admin.storage().bucket();
55 await bucket.file(filePath).save(pdfBuffer, { contentType: 'application/pdf' });
56 const [url] = await bucket.file(filePath).getSignedUrl({ action: 'read', expires: Date.now() + 900000 });
57 resolve({ url });
58 });
59 doc.on('error', reject);
60 doc.fontSize(18).font('Helvetica-Bold').text('Orders Report', { align: 'center' });
61 doc.moveDown();
62 doc.fontSize(10).font('Helvetica');
63 snap.forEach(d => {
64 const row = d.data();
65 doc.text(`${row.name || 'N/A'} — $${row.amount || 0} — ${row.createdAt?.toDate()?.toLocaleDateString() || ''}`);
66 });
67 doc.end();
68 });
69});

Common mistakes

Why it's a problem: Building the entire CSV or PDF in a Custom Action on the client for large datasets

How to avoid: Use a Cloud Function for any export exceeding 1,000 rows. The function runs server-side with no memory constraint, uses Firestore Admin SDK (no read quotas), and uploads directly to Firebase Storage without touching the client device.

Why it's a problem: Using a non-paginated Firestore query in the Cloud Function (e.g., .get() with no limit)

How to avoid: Use pagination with .limit(500) and .startAfter() in a while loop, as shown in the Cloud Function code above. This processes the data in batches and completes well within the timeout.

Why it's a problem: Hardcoding column names in the export that differ from what users see in the app UI

How to avoid: Use human-readable headers in the CSV/Excel header row: 'Order Date' not 'createdAt', 'Customer Name' not 'name'. Map your Firestore field names to display names in the export logic.

Best practices

  • Use client-side CSV for datasets under 1,000 rows and Cloud Functions for anything larger.
  • Always paginate Firestore queries in Cloud Functions using limit() and startAfter() — never query without a limit.
  • Generate signed URLs with a 15-minute expiry so the download link is available long enough to use but does not stay valid indefinitely.
  • Clean up old export files from Firebase Storage using a Cloud Storage lifecycle rule that deletes files older than 24 hours.
  • Escape CSV values that contain commas or newlines by wrapping them in double quotes and doubling any internal double quotes.
  • Show a row count in the success message so users know the export captured all expected records.
  • For scheduled exports (e.g., weekly reports), use a Cloud Scheduler trigger instead of on-demand Cloud Function calls.

Still stuck?

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

ChatGPT Prompt

I need to export Firestore data as a CSV file from a FlutterFlow app. The dataset can have up to 50,000 rows. I want to use a Firebase Cloud Function that paginates through all documents, builds a CSV, uploads it to Firebase Storage, and returns a signed download URL. Write the complete Cloud Function code in Node.js including pagination, CSV formatting with proper escaping, and a 15-minute signed URL.

FlutterFlow Prompt

Create a Custom Action in FlutterFlow named exportToCSV that takes a list of Firestore documents and a filename string. Use the share_plus and path_provider packages to write the CSV to a temp file and open the share sheet. Include proper CSV escaping for fields that contain commas.

Frequently asked questions

Can I export data as Excel (.xlsx) instead of CSV?

Yes. Use the exceljs npm package in a Cloud Function. It supports multiple sheets, cell formatting, number formats, and column widths. The generated .xlsx file is uploaded to Firebase Storage and returned as a signed URL, exactly like the CSV and PDF exports.

How do I add images or charts to a PDF export?

PDFKit supports embedding images from URLs or buffers using doc.image(buffer, options). For charts, generate the chart as a PNG on the server using chart.js with the canvas npm package, then embed the PNG buffer in the PDF.

The Cloud Function times out for very large exports — how do I fix this?

Increase the Cloud Function timeout to 540 seconds (9 minutes) in the Firebase Console under Functions settings. Also increase the memory allocation to 1GB for large file generation. For exports over 100,000 rows, consider generating the file asynchronously and emailing the download link when done.

How do I let users pick a date range for their export?

Add two DateTime Picker widgets to the export dialog — Start Date and End Date. Pass these as parameters to the Cloud Function API call. In the Cloud Function, add .where('createdAt', '>=', startDate).where('createdAt', '<=', endDate) to the Firestore query.

Can users download the export on a desktop browser?

Yes. The signed Firebase Storage URL works in any browser — desktop or mobile. On desktop, Launch URL opens the URL in the default browser which downloads the file. On mobile, the system file manager or appropriate app handles the file type.

How do I prevent users from exporting other users' data?

The Cloud Function must use context.auth.uid (the authenticated user's ID) as the userId filter — never trust a userId parameter passed by the client. Since Cloud Functions verify the Firebase Auth token, a malicious user cannot impersonate another user's UID.

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.