Skip to main content
RapidDev - Software Development Agency
flutterflow-integrationsFlutterFlow API Call

Google Docs

Connect FlutterFlow to Google Docs by routing all Google API calls through a Firebase Cloud Function backed by a Service Account. FlutterFlow cannot sign Google JWTs or store the Service Account key safely — so the entire Google auth dance happens inside the function, which then returns plain document text to the app. This makes the Cloud Function mandatory, not optional, for any Google Docs integration in a FlutterFlow project.

What you'll learn

  • Why Google Docs auth requires a Service Account and cannot be done inside FlutterFlow
  • How to create a Google Cloud project, enable the Docs API, and create a Service Account
  • How to share a Google Doc with a Service Account email address
  • How to deploy a Cloud Function that handles JWT auth and parses the Docs structural JSON
  • How to bind the returned document text to FlutterFlow widgets
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced15 min read60 minutesProductivityLast updated July 2026RapidDev Engineering Team
TL;DR

Connect FlutterFlow to Google Docs by routing all Google API calls through a Firebase Cloud Function backed by a Service Account. FlutterFlow cannot sign Google JWTs or store the Service Account key safely — so the entire Google auth dance happens inside the function, which then returns plain document text to the app. This makes the Cloud Function mandatory, not optional, for any Google Docs integration in a FlutterFlow project.

Quick facts about this guide
FactValue
ToolGoogle Docs
CategoryProductivity
MethodFlutterFlow API Call
DifficultyAdvanced
Time required60 minutes
Last updatedJuly 2026

The Cloud Function Is Mandatory — Here's Why

Reading or writing a Google Doc from a FlutterFlow app hits a wall immediately: Google's API authentication requires a signed JSON Web Token (JWT) to exchange for an OAuth2 access token. Signing a JWT requires your Service Account's private key — a multi-line RSA key that must never leave a secure server environment. FlutterFlow compiles to Flutter code running on the user's device, which is the least secure place imaginable for a private key. There is no workaround here: the Google auth flow belongs in a Firebase Cloud Function, full stop.

The good news is that Firebase Cloud Functions and Google Cloud share the same infrastructure, which makes this easier than it sounds. Enabling the Google Docs API is a single toggle in the Google Cloud Console for your Firebase project. The googleapis Node.js package (available to Cloud Functions) handles the JWT signing automatically when you provide the Service Account credentials. Your Cloud Function exchanges them for an access token, calls the Docs API, and returns the document content to FlutterFlow — which just makes a simple HTTP call to the function URL.

The Google Docs API is free with a Google Cloud project. The API has limits of 300 read and 60 write requests per minute per user — more than enough for typical app usage. The read path (`documents.get`) returns a JSON structure of paragraph blocks and text runs, not plain text. The Cloud Function must convert this `body.content[]` structural tree to a readable string before returning it to FlutterFlow. For write operations, `documents.batchUpdate` applies changes as a list of Request objects — more complex than a simple PUT, but powerful enough for inserting text, replacing strings, or formatting sections.

Integration method

FlutterFlow API Call

FlutterFlow calls a Firebase Cloud Function that holds a Google Service Account JSON key and handles the JWT-to-OAuth2 token exchange required by the Google Docs API v1. Because FlutterFlow compiles to a client-side Flutter app, it cannot sign JWTs or run the googleapis Node.js package — the entire authentication flow must happen server-side. The function returns flattened plain text from the document's structural JSON tree, which FlutterFlow's widgets can render directly.

Prerequisites

  • A Google account with access to the Google Cloud Console (console.cloud.google.com)
  • A Firebase project (Firebase and Google Cloud share the same project infrastructure)
  • A Google Doc that you want to read from the FlutterFlow app
  • Cloud Functions enabled in your Firebase project (requires the Blaze pay-as-you-go plan)
  • A FlutterFlow project open in your browser

Step-by-step guide

1

Step 1: Enable the Google Docs API and create a Service Account

Open the Google Cloud Console at console.cloud.google.com and select your Firebase project from the project dropdown at the top. In the search bar, type 'Google Docs API' and click on it, then click 'Enable'. This activates the Docs API for your project — it's one click and takes about 30 seconds. Next, create a Service Account: in the left sidebar, navigate to IAM & Admin → Service Accounts → + Create Service Account. Give it a descriptive name like 'flutterflow-docs-reader'. Skip the optional role assignment steps for now (you don't need project-level IAM roles just to read shared Docs). Click Done. On the Service Accounts list, click your new service account, go to the Keys tab, click Add Key → Create new key → JSON → Create. A JSON file downloads to your computer — this is your Service Account key file. It contains the private key needed to sign JWTs. Store this file securely and never commit it to a Git repository or put it in your FlutterFlow project. You'll add it to your Cloud Function as an environment variable or Firebase Functions config. Note the service account's email address — it looks like `flutterflow-docs-reader@your-project.iam.gserviceaccount.com`. You'll need this in the next step to share the Google Doc.

Pro tip: Use the Firebase project's Google Cloud Console (accessible from the Firebase console under Project Settings → Google Cloud → Open Console) rather than a separate Google Cloud account, to keep billing and permissions in one place.

Expected result: The Google Docs API shows as 'Enabled' in your Cloud Console. You have a Service Account with an email address and a downloaded JSON key file.

2

Step 2: Share the Google Doc with the Service Account email

This step is where most developers get stuck — and it causes a 403 'The caller does not have permission' error that looks like an auth failure but is actually a sharing problem. Open the Google Doc you want the app to read. Click the 'Share' button in the top-right corner. In the 'Add people and groups' field, paste your Service Account's email address (e.g., `flutterflow-docs-reader@your-project.iam.gserviceaccount.com`). Set the permission to 'Viewer' for read-only access, or 'Editor' if the app will also write back to the document. Click 'Send' (Google won't actually send an email to a service account, but this is the correct flow). The Google Doc's ID is in its URL: `docs.google.com/document/d/DOCUMENT-ID/edit`. Copy this ID — it's a long string of letters and numbers. You'll pass this to the Cloud Function to specify which document to read. For a terms-of-service or privacy-policy document that should be readable by any user of the app (not per-user auth), this service account sharing approach is the right one. The function reads the document on behalf of the service account, not on behalf of the end user — this is intentional. For user-specific documents (where a logged-in user wants to read their own Google Docs), you'd need OAuth 2.0 user flow, which is significantly more complex and should be a separate architecture conversation.

Pro tip: If you have multiple documents the app needs to read (terms, privacy policy, FAQ), share all of them with the same service account email and store their document IDs in Firestore or as App Values in FlutterFlow.

Expected result: The Google Doc shows the service account email in its Share dialog with Viewer (or Editor) access. The document ID is copied.

3

Step 3: Deploy a Cloud Function that authenticates and parses the document

Your Cloud Function does three things: (1) authenticates with Google using the Service Account JSON key, (2) calls the Docs API to fetch the document, and (3) parses the `body.content[]` structural tree into plain text strings that FlutterFlow widgets can render. Store the Service Account JSON key in Firebase Functions config to keep it out of source code: in the Firebase CLI, run `firebase functions:config:set gdocs.service_account_json='ESCAPED_JSON_HERE'`. Alternatively, for a simpler local setup during development, place the JSON file in your functions/ directory and reference it by path — but always add it to .gitignore. The googleapis Node.js package (available in Cloud Functions by adding `"googleapis": "^120.0.0"` to your functions/package.json) handles the JWT signing and token exchange automatically via `google.auth.GoogleAuth`. You initialize it with the service account credentials and the required scope (`https://www.googleapis.com/auth/documents.readonly`), then call `docs.documents.get` with the document ID. The returned document's `body.content` is an array of structural elements — paragraph, table, inlineObject — each containing textRun elements with the actual text. The parsing function walks this tree and concatenates text strings.

index.js
1// Firebase Cloud Function — Google Docs reader
2// functions/index.js
3const functions = require('firebase-functions');
4const { google } = require('googleapis');
5
6// Parse Docs structural JSON to plain text
7function extractText(content) {
8 let text = '';
9 (content || []).forEach(element => {
10 if (element.paragraph) {
11 element.paragraph.elements.forEach(el => {
12 if (el.textRun) text += el.textRun.content;
13 });
14 }
15 });
16 return text;
17}
18
19exports.getGoogleDoc = functions.https.onRequest(async (req, res) => {
20 res.set('Access-Control-Allow-Origin', '*');
21 res.set('Access-Control-Allow-Methods', 'GET, OPTIONS');
22 res.set('Access-Control-Allow-Headers', 'Content-Type');
23 if (req.method === 'OPTIONS') { res.status(204).send(''); return; }
24
25 const docId = req.query.docId;
26 if (!docId) { res.status(400).json({ error: 'docId query param required' }); return; }
27
28 try {
29 // Auth with Service Account (key stored in functions config)
30 const credentials = JSON.parse(
31 functions.config().gdocs.service_account_json
32 );
33 const auth = new google.auth.GoogleAuth({
34 credentials,
35 scopes: ['https://www.googleapis.com/auth/documents.readonly']
36 });
37
38 const docs = google.docs({ version: 'v1', auth });
39 const response = await docs.documents.get({ documentId: docId });
40 const doc = response.data;
41
42 const plainText = extractText(doc.body.content);
43 const title = doc.title || '';
44 const revisionId = doc.revisionId || '';
45
46 res.status(200).json({ title, text: plainText, revisionId });
47 } catch (err) {
48 res.status(500).json({ error: err.message });
49 }
50});

Pro tip: Add `"googleapis": "^120.0.0"` to your `functions/package.json` dependencies before deploying. FlutterFlow cannot use this package — it only runs in the Cloud Function's Node.js environment.

Expected result: A deployed Cloud Function that returns `{"title": "...", "text": "full document text...", "revisionId": "..."}` when called with `?docId=YOUR_DOCUMENT_ID`.

4

Step 4: Create a FlutterFlow API Group targeting the Cloud Function

In FlutterFlow, click 'API Calls' in the left navigation panel. Click '+ Add' → 'Create API Group'. Name the group 'GoogleDocs'. Set the Base URL to your Cloud Function's HTTPS trigger URL (e.g., `https://us-central1-your-project.cloudfunctions.net/getGoogleDoc`). No group-level headers are needed — the function handles Google auth internally. Click '+ Add API Call'. Name it 'Get Document'. Set the Method to GET. In the Variables tab, click '+ Add Variable' and name it `docId` with a default value set to your primary document's ID. In the API Call URL field, append `?docId={{ docId }}` to pass the document ID as a query parameter — this lets you reuse the same API Call for multiple documents by changing the variable value at runtime. Click 'Response & Test'. Paste a sample response like `{"title": "Terms of Service", "text": "Section 1: Introduction...\nSection 2: ...", "revisionId": "XXXXX"}`. Click 'Generate JSON Paths' — FlutterFlow creates `$.title`, `$.text`, and `$.revisionId` as bindable paths. Click 'Test API Call' with your document ID to verify the live response. For a document selector (where the user picks which doc to open), store document IDs and names in Firestore or an App Value list. Pass the selected document ID as a page parameter to the document viewer screen, which then uses it as the `docId` variable in the API Call.

Pro tip: Use conditional logic in FlutterFlow to show a loading spinner while the API call is in progress and an error state when the call fails — document fetching takes 1-3 seconds on first load.

Expected result: The API Call test returns the document title and full text content. JSON paths $.title and $.text are available for binding in FlutterFlow widgets.

5

Step 5: Render the document content and add write-back (optional)

On your document viewer screen in FlutterFlow, add a Column widget as the page body. At the top, add a Text widget for the document title — in its value binding, select the 'Get Document' API Call's `$.title` path. Below the title, add a Text widget for the main content — bind it to `$.text`. For better readability, configure the content Text widget with `softWrap: true`, a reasonable font size (14-16sp), and generous line spacing (1.5). Wrap the Text in a SingleChildScrollView (use the 'Listview' or a scrollable Column in FlutterFlow) so long documents are scrollable without needing a fixed height. For a backend query setup: in the Page's Backend Query panel, set the query type to 'API Call' and select 'Get Document'. FlutterFlow will load the document when the screen opens. If you passed the docId as a page parameter, set the API Call variable `docId` to reference the page parameter. For write-back — letting users send edits back to the Google Doc — add a second Cloud Function export (or handle PUT requests in the same function). The write function accepts a `requests` array in the POST body and calls `docs.documents.batchUpdate`. In FlutterFlow, add a TextField for the user's edit, a Save button, and an API Call action on the button's On Tap event that sends the text change. Keep write operations in a dedicated button flow rather than autosaving — the 60 writes/minute limit can be hit if you save on every keystroke. For apps where you'd rather not build write-back, a simpler pattern is to let the Service Account read a Doc that admins edit directly in Google Docs — the app always shows the latest saved version, with the editing flow staying entirely in Google's interface.

Pro tip: If you're displaying formatted content (headings, bold text, bullet lists), extend the Cloud Function's text extraction to return a structured array of sections with type labels (heading_1, normal, bullet). Then use a ListView in FlutterFlow to render each section with different Text styles based on the type.

Expected result: The document viewer screen loads and displays the Google Doc's title and full text content. Long documents scroll correctly. For write-enabled builds, tapping Save updates the document and shows a success confirmation.

Common use cases

Legal terms viewer pulling content from a Google Doc

A service app where the terms of service and privacy policy live in Google Docs, edited by the legal team. The FlutterFlow app fetches the current terms on first load and displays them in a scrollable text screen — no app update needed when legal updates the document.

FlutterFlow Prompt

Create a Terms of Service screen that fetches the current text from a specific Google Doc and displays it in a scrollable text widget, with the last-updated date shown at the top.

Copy this prompt to try it in FlutterFlow

Contract template viewer for a field service app

A field service app where technicians view job contracts stored as Google Docs. The app fetches the relevant contract by document ID, displays the service agreement as plain text, and lets the technician collect a signature or check a confirmation box — all without leaving the app.

FlutterFlow Prompt

Build a contract viewer screen that takes a Google Doc ID as a parameter, fetches and displays the document text, and shows a signature confirmation button at the bottom.

Copy this prompt to try it in FlutterFlow

Knowledge base app with Google Docs as the content source

An internal company knowledge base where articles are written in Google Docs and indexed in a Firestore database with their document IDs. The FlutterFlow app lists articles by category and, when a user taps one, fetches the live text from Google Docs and displays it — always showing the latest version without a deployment cycle.

FlutterFlow Prompt

Create a knowledge base article screen that fetches article content from a Google Doc by ID and renders it as formatted text sections, with headings shown in larger bold text.

Copy this prompt to try it in FlutterFlow

Troubleshooting

403 'The caller does not have permission' from the Google Docs API

Cause: The Google Doc has not been shared with the Service Account email address. This is the most common cause of 403 errors in this integration — even a valid Service Account with correct credentials cannot access a document it hasn't been explicitly shared with.

Solution: Open the Google Doc → Share → paste the service account email (e.g., flutterflow-docs-reader@your-project.iam.gserviceaccount.com) → set Viewer access → confirm. If the 403 persists after sharing, double-check that the Docs API is enabled in the Cloud Console for your project (console.cloud.google.com → APIs & Services → Enabled APIs).

Cloud Function returns an empty string or incomplete text for the document

Cause: The `extractText` parsing function only handles `paragraph` elements. Documents with tables, images, headers/footers, or list items use different structural element types that the basic parser skips.

Solution: Extend the parsing function to handle additional element types. Table cells contain nested `tableCells[].content[]` arrays that need recursive parsing. Ordered and unordered lists are marked with a `bullet` property on paragraph elements. For a complete parser, recursively walk all `content` arrays and extract `textRun.content` strings from every element type.

XMLHttpRequest error in FlutterFlow Test Mode — API call blocked in the browser

Cause: CORS error. The Cloud Function is not returning CORS headers. FlutterFlow's Test Mode runs in a browser, which enforces CORS. Native iOS/Android builds are not affected.

Solution: Add `res.set('Access-Control-Allow-Origin', '*')` to the Cloud Function response, and handle OPTIONS preflight requests by returning 204. The example code in Step 3 includes this. Redeploy the function after adding the CORS headers.

SyntaxError or JSON.parse error when loading the Service Account credentials in the Cloud Function

Cause: The Service Account JSON key was not properly escaped when set as a Firebase Functions config value. The JSON contains newlines and special characters that break config serialization if not escaped correctly.

Solution: Escape the JSON correctly before setting it via the Firebase CLI: use `cat service-account.json | python3 -c "import json,sys; print(json.dumps(sys.stdin.read()))"` to escape the entire file contents as a single string. Alternatively, use a Firebase Secret (recommended for production) via the Secret Manager integration — this avoids the escaping problem entirely.

Best practices

  • Never put the Service Account JSON key in FlutterFlow — not in App Values, App State, or API Call variables. Keep it exclusively in the Cloud Function's environment using Firebase Functions config or Google Secret Manager.
  • Share each Google Doc with the Service Account email explicitly — 403 permission errors are almost always caused by missing this share step, not by incorrect credentials.
  • Parse the document's structural JSON tree to plain text inside the Cloud Function, not in FlutterFlow — the `body.content[]` nested structure is too complex for FlutterFlow's JSON-path binding.
  • Enable the Google Docs API in the Cloud Console for your Firebase project before deploying the function — the API must be explicitly enabled even if billing is set up.
  • Use `documents.readonly` OAuth scope for read-only apps to limit blast radius if credentials are compromised — only add write scope when the app genuinely needs to edit documents.
  • Batch multiple text edits into a single `batchUpdate` call to stay within the 60 writes/minute limit — avoid triggering a write on every keystroke.
  • Store document IDs in Firestore (not hardcoded in FlutterFlow App Values) so non-technical team members can update which documents the app reads without a code change.

Alternatives

Frequently asked questions

Can I implement user-level Google OAuth (letting users read their own Docs) in a FlutterFlow app?

Yes, but it's significantly more complex than the Service Account approach. You need a Cloud Function to handle the OAuth 2.0 flow — receiving the authorization code, exchanging it for tokens, and storing the per-user access token securely (e.g., in Firestore keyed to the user's UID). FlutterFlow can open the Google OAuth URL in a WebView or browser, then receive the auth code callback. This is a multi-session architecture that goes beyond what's covered in this guide — consider using Google Sign-In via FlutterFlow's auth providers as a starting point.

Is the Google Docs API free to use?

Yes — the Google Docs API has no per-call charge. You need a Google Cloud project with billing enabled (to run Cloud Functions, which require the Firebase Blaze plan), but the Docs API itself is free. The rate limits are 300 read requests and 60 write requests per minute per user — well within typical app usage for document viewing.

Can I display formatted text (headings, bold, bullet points) from a Google Doc in FlutterFlow?

FlutterFlow's standard Text widget displays plain text only — it doesn't render HTML or Markdown. For formatted display, extend the Cloud Function's parser to extract not just text content but also paragraph types (HEADING_1, HEADING_2, NORMAL_TEXT) and text run styles (bold, italic). Return a structured JSON array of paragraphs with type and content. In FlutterFlow, use a ListView to render each paragraph with conditional widget types — a styled Text widget for headings, a normal Text for body paragraphs, and a Row with a bullet icon for list items.

How do I keep the document up to date without the user manually refreshing?

Google Docs API returns a `revisionId` field with each document fetch. Store the current revisionId in App State. On a timer or on screen focus, call the API again and compare the returned revisionId to the stored one — if different, refresh the displayed content. This lightweight polling approach avoids fetching the full document text on every check. For true real-time updates, Google Docs doesn't have a webhook/push notification system, so polling is the standard approach.

RapidDev

Talk to an Expert

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

Book a free consultation

Integrations are where projects stall

We wire FlutterFlow integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

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.