Connect Bubble to Google Docs using two separate API Connector groups — one for the Google Docs API and one for the Google Drive API. The primary use case is document generation: a Bubble form triggers a Backend Workflow that duplicates a template Doc via Drive, then fills placeholders using the Docs batchUpdate endpoint. Both API groups use a service account Bearer token marked Private.
| Fact | Value |
|---|---|
| Tool | Google Docs |
| Category | Productivity |
| Method | Bubble API Connector |
| Difficulty | Advanced |
| Time required | 90–120 minutes |
| Last updated | July 2026 |
Automated document generation: the most valuable Bubble + Google Docs pattern
Virtually every Bubble + Google Docs integration follows the same template-replacement pattern: create a well-formatted Google Doc with placeholder text like {{client_name}}, {{project_title}}, {{date}}, then from Bubble, (1) duplicate the template via the Drive API to create a fresh document, (2) call the Docs batchUpdate endpoint with replaceAllText operations to swap every placeholder with real data. The result is a fully formatted document with a shareable Google Drive URL that Bubble can display or email to the user. Two key technical requirements make this pattern Bubble-specific: the Drive and Docs APIs have different base URLs and must be separate API Connector groups, and the sequential two-call pattern (copy then fill) requires a Bubble Backend Workflow for reliable execution — available on paid Bubble plans only.
Integration method
Two separate Bubble API Connector groups: one for the Google Docs REST API v1 and one for the Google Drive REST API v3, both authenticated via a service account Bearer token marked Private.
Prerequisites
- A Google account and access to Google Cloud Console (console.cloud.google.com) to create a project and service account
- A template Google Doc with placeholder text in the format {{placeholder_name}} — avoid special characters or smart quotes in placeholders
- A paid Bubble plan (Starter or above) — the two-call sequential pattern requires Backend Workflows, which are not available on the Free plan
- The API Connector plugin installed in your Bubble app (Plugins tab → Add plugins → search 'API Connector' by Bubble)
Step-by-step guide
Enable the Google Docs and Drive APIs and create a service account
Open Google Cloud Console at console.cloud.google.com. Create a new project (or use an existing one). In the left menu, go to 'APIs & Services' → 'Library'. Search for 'Google Docs API' and click Enable. Then search for 'Google Drive API' and click Enable — you need both. Next, go to 'APIs & Services' → 'Credentials'. Click 'Create Credentials' → 'Service Account'. Give it a name like 'Bubble Docs Integration'. Click Create and Continue. For the role, select 'Editor' at the project level, or leave the role blank if you plan to share files individually (recommended for tighter security). Click Done. Find the service account in the list, click on it, go to the Keys tab, click 'Add Key' → 'Create new key' → select JSON → Create. A JSON file downloads to your computer — save it securely. The service account has an email address that looks like something@yourproject.iam.gserviceaccount.com — copy this email, you will need it in the next step.
Pro tip: The downloaded JSON key file contains the service account credentials. Store it securely and never commit it to version control. You will extract the private key and client_email fields from this file.
Expected result: Both Google Docs API and Google Drive API show as Enabled in your Cloud Console. You have a service account JSON key file and the service account email address.
Share the template Doc with the service account and get the template ID
Open the Google Doc template you want to use in your browser. Click 'Share' in the top right corner. In the sharing dialog, paste the service account email address (something@yourproject.iam.gserviceaccount.com) into the 'Add people and groups' field. Set the permission level to 'Editor' — Viewer is not sufficient because the service account needs to copy the file. Click 'Send' or 'Share'. Now look at the URL in your browser while viewing the template — it looks like: https://docs.google.com/document/d/DOCUMENT_ID/edit. Copy the DOCUMENT_ID (the long string between /d/ and /edit). This is your template document ID that you will use in the Drive API copy call. Also note the Google Drive folder where you want generated documents to be saved — share that folder with the service account email as well (Editor permission) if you want organized file management.
Pro tip: A common issue: if you forget to share the template with the service account, the Drive API copy call returns 404 'File not found' — even though you can see the file when logged in as yourself.
Expected result: The template Google Doc is shared with the service account email. You have the template document ID copied. When the service account copies the file, it will have Editor access to the original.
Configure two separate API Connector groups for Drive and Docs
In Bubble, open Plugins → API Connector. You need two separate API groups because the Google Drive and Google Docs APIs have different base URLs — if you mix them under one base URL, all calls fail. Click 'Add another API'. Name the first group 'Google Drive API'. Add one shared header: key = Authorization, value = Bearer YOUR_ACCESS_TOKEN — mark it Private. Note: obtaining the access_token from a service account normally requires exchanging the JSON key for a short-lived token via Google's OAuth2 token endpoint. For simplicity in Bubble, the most practical approach is to use a service account with domain-wide delegation OR to generate a long-lived access token using a script or service like Token Service, then store it in Bubble as an App Data value and reference it in the header. See the troubleshooting section for the service account token flow. Click 'Add another API' again. Name the second group 'Google Docs API'. Add the same Authorization header (Bearer token, Private). Leave the base URL at the group level empty for both — you will enter full URLs in each call.
1Google Drive API group:2 Shared header: Authorization: Bearer <your_access_token> [Private]3 Base URL: (leave empty)45Google Docs API group:6 Shared header: Authorization: Bearer <your_access_token> [Private]7 Base URL: (leave empty)89Note: Both groups use the same token but different API endpoint base URLs in each call.Pro tip: Service account access tokens expire after 1 hour. For a production Bubble app, implement a Backend Workflow that refreshes the token using the JWT grant flow and stores the new token in Bubble's App Data — then reference App Data's token in the shared header value.
Expected result: Two separate API Connector groups exist in your Bubble app: 'Google Drive API' and 'Google Docs API', each with an Authorization header marked Private.
Add the Drive copy call to duplicate the template
In the 'Google Drive API' group, click 'Add another call'. Name it 'Copy Template'. Set Method to POST. Set the URL to: https://www.googleapis.com/drive/v3/files/YOUR_TEMPLATE_ID/copy — replace YOUR_TEMPLATE_ID with the document ID you copied in Step 2. Set 'Use as' to 'Action' since this call is triggered from a workflow. In the Body section, check 'Send arbitrary data' and provide the JSON body shown below — this sets the name of the newly created document. To make the document name dynamic (e.g., include the client's name), define a parameter in the body with a placeholder like <new_doc_name> that Bubble will prompt you to fill when using this call in a workflow. Click 'Initialize call'. Bubble will make the actual API call and return the new file's metadata including its id — this id is what you will pass to the Docs batchUpdate call in the next step. After initialization, confirm that the 'id' field appears in Bubble's detected response fields.
1POST https://www.googleapis.com/drive/v3/files/YOUR_TEMPLATE_ID/copy23Headers (shared):4 Authorization: Bearer <token> [Private]56Body:7{8 "name": "<dynamic: new_doc_name>"9}Pro tip: After the copy call initializes, verify that Bubble detected 'id' in the response. This is the new document's ID that you will pipe into the Docs batchUpdate call. Without this field being detected, you cannot chain the calls.
Expected result: The Initialize call succeeds and Bubble detects an 'id' field in the response (the new document's Google Drive file ID). A duplicate of the template appears in your Google Drive.
Add the Docs batchUpdate call for placeholder replacement
In the 'Google Docs API' group, click 'Add another call'. Name it 'Fill Placeholders'. Set Method to POST. Set the URL to: https://docs.googleapis.com/v1/documents/DOCUMENT_ID:batchUpdate — where DOCUMENT_ID will be a dynamic parameter filled from the previous step's result. In the API Connector, configure DOCUMENT_ID as a parameter by writing it as <new_doc_id> in the URL path so Bubble prompts you to fill it in the workflow. Set 'Use as' to 'Action'. In the Body, check 'Send arbitrary data' and provide the replaceAllText request array. Each placeholder replacement is one object in the requests array. Add dynamic parameters for each value you want to fill in (e.g., <client_name>, <project_title>). Initialize the call with real example values to confirm it works before wiring into the workflow. Note: replaceAllText is case-sensitive and requires the placeholder text to match exactly — including the double curly braces if you used them in the template.
1POST https://docs.googleapis.com/v1/documents/<new_doc_id>:batchUpdate23Headers (shared):4 Authorization: Bearer <token> [Private]56Body:7{8 "requests": [9 {10 "replaceAllText": {11 "containsText": {12 "text": "{{client_name}}",13 "matchCase": true14 },15 "replaceText": "<dynamic: client_name>"16 }17 },18 {19 "replaceAllText": {20 "containsText": {21 "text": "{{project_title}}",22 "matchCase": true23 },24 "replaceText": "<dynamic: project_title>"25 }26 },27 {28 "replaceAllText": {29 "containsText": {30 "text": "{{date}}",31 "matchCase": true32 },33 "replaceText": "<dynamic: document_date>"34 }35 }36 ]37}Pro tip: The batchUpdate returns 200 OK even if no replacements were made — it does not tell you how many replacements occurred. If you open the generated document and see unfilled placeholders, check the tip in Troubleshooting about smart quotes and split textRun elements.
Expected result: The Initialize call succeeds and returns 200. When you open the test document in Google Drive, you see the placeholder values replaced with the example data you provided during initialization.
Wire a Backend Workflow to chain copy and fill in sequence
In Bubble, go to the Backend Workflows section (Settings → API → enable 'This app exposes a Workflow API', then open the Backend Workflows tab). Create a new API Workflow named something like 'Generate Document'. Add the parameters you need: client_name (text), project_title (text), date (date), and any other fields your template uses. In the workflow steps: Step 1 — Plugins → Google Drive API → Copy Template, set the new_doc_name parameter to a dynamic value combining your inputs (e.g., 'Proposal - ' + client_name). Step 2 — Plugins → Google Docs API → Fill Placeholders, set new_doc_id = Result of Step 1's id, and map client_name, project_title, and date from the workflow parameters. Step 3 — Make changes to a Thing: create or update a Bubble data type record to store the new document URL (construct it as 'https://docs.google.com/document/d/' + Step 1's id). To trigger this Backend Workflow from a regular page workflow, add a 'Schedule API Workflow' step in your form submit button's workflow and pass the form field values as the parameters. Backend Workflows on paid Bubble plans run reliably in sequence, ensuring the copy completes before the fill starts. RapidDev's team has built document generation flows like this across dozens of Bubble apps — book a free scoping call at rapidevelopers.com/contact if you get stuck.
1Backend Workflow: Generate Document2Parameters: client_name (text), project_title (text), document_date (date)34Step 1: [API] Copy Template5 → new_doc_name: "Proposal - " + client_name6 → returns: id (new document ID)78Step 2: [API] Fill Placeholders9 → new_doc_id: Result of Step 1's id10 → client_name: This workflow's client_name11 → project_title: This workflow's project_title12 → document_date: This workflow's document_date :formatted as MM/DD/YYYY1314Step 3: Make changes to Thing (Order)15 → document_url: "https://docs.google.com/document/d/" + Step 1's idPro tip: Backend Workflows require a paid Bubble plan. If you try to build this as a standard page workflow with two chained API calls, the second call may fire before the first completes — resulting in a 'File not found' error because the copied document doesn't exist yet.
Expected result: Clicking the form submit button triggers the Backend Workflow, which creates a new Google Doc with all placeholders filled and stores the document URL in Bubble's database. The whole process completes within a few seconds.
Common use cases
Proposal and contract generation
A Bubble form collects project details, pricing, and client information. On submit, a Backend Workflow duplicates the proposal template, fills in the client's data via replaceAllText, and stores the new Google Doc URL in Bubble's database. The client receives an email with the shareable link.
When the 'Generate Proposal' button is clicked, duplicate the proposal template Doc, replace {{client_name}}, {{project_budget}}, and {{delivery_date}} with the form values, then display the new Google Doc link in a popup.
Copy this prompt to try it in Bubble
Invoice and receipt creation
After a Stripe payment is processed, a Bubble Backend Workflow creates a Google Doc invoice populated with the customer name, line items, and payment amount. The invoice URL is saved to the customer's record and sent via email.
After a successful payment, generate a PDF-ready Google Doc invoice with the customer's billing details and payment amount, and store the document URL in the user's order record.
Copy this prompt to try it in Bubble
Personalized onboarding documents
New users fill out an onboarding form in Bubble. The workflow generates a personalized welcome packet, project brief, or terms document from a template, filling in the user's name, company, and selected service options.
When a new client completes onboarding, create a personalized project brief Google Doc with their company name, selected services, and assigned team member, then share it with their email address.
Copy this prompt to try it in Bubble
Troubleshooting
Drive API copy call returns 404 'File not found'
Cause: The service account email has not been granted access to the template document. Google Drive requires explicit sharing — the service account cannot see files you own unless you share them.
Solution: Open the template Google Doc, click Share, add the service account email (format: name@project.iam.gserviceaccount.com), set permission to Editor, and click Share. Then retry the copy call.
batchUpdate returns 200 OK but placeholders in the document remain unfilled
Cause: The most common cause is that the placeholder text in the template was auto-corrected to smart quotes (curly quotes like {{client_name}} instead of {{client_name}}), or that Google Docs internally split the placeholder across multiple textRun elements during editing. The API's replaceAllText performs exact string matching and cannot find placeholders that are typographically different from what it was given.
Solution: Open the template Google Doc, select the placeholder text, delete it, and retype it manually without using autocorrect. Alternatively, turn off smart quotes in Tools → Preferences → uncheck 'Use smart quotes'. Then check the template by pasting it into a plain text editor to confirm the characters are standard ASCII. If the placeholder was split across runs (happens when you edit part of it), delete and retype the entire placeholder in one action.
'There was an issue setting up your call' when initializing the Drive or Docs API call
Cause: The access token is expired or invalid. Service account tokens are short-lived (1 hour) and must be refreshed via Google's OAuth2 token endpoint using a JWT assertion.
Solution: For testing, generate a fresh access token using the Google OAuth Playground (oauth2.googleapis.com/tokeninfo) or a curl command with your service account JSON. For production, implement a Bubble Backend Workflow that periodically calls Google's token endpoint and updates the token stored in Bubble's App Data. Reference the App Data value in the API Connector shared header.
The Docs API call fails with 'Request had insufficient authentication scopes'
Cause: The service account access token was generated with insufficient OAuth2 scopes. The Docs API requires the scope https://www.googleapis.com/auth/documents, and the Drive API requires https://www.googleapis.com/auth/drive.
Solution: When generating the access token, include both scopes in the JWT claim: 'scope': 'https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/drive'. Regenerate the token and update it in your Bubble API Connector headers.
The second API call in the workflow fires before the first completes
Cause: Standard Bubble page workflows do not wait for API calls to finish before moving to the next step. Chaining a Drive copy call and a Docs batchUpdate call in a standard workflow creates a race condition.
Solution: Move the two-call sequence into a Bubble Backend Workflow (requires paid plan). Backend Workflows execute steps sequentially and guarantee that Step 1 completes (and its result is available) before Step 2 begins.
Best practices
- Mark the Authorization header as Private in both API Connector groups — this keeps the service account token completely server-side and never exposes it in the browser's network requests.
- Add Privacy rules in Bubble's Data tab for any Things that store generated document URLs — without Privacy rules, any logged-in Bubble user can query all document records via Bubble's API.
- Type placeholder text manually in your Google Docs template and disable smart quotes (Tools → Preferences) to prevent the auto-correction that silently breaks replaceAllText matches.
- Use a Backend Workflow for the two-call copy + fill sequence — it guarantees sequential execution and provides reliable error handling via Bubble's Logs tab.
- Store the generated Google Doc URL in a Bubble data type record immediately after creation. This gives you a link to share or display, and a record to reference if you need to update the document later.
- Implement periodic token refresh for production apps: Google service account access tokens expire after 1 hour. A Bubble Backend Workflow scheduled every 50 minutes can call Google's token endpoint and update the token in App Data.
- Test your template with all placeholder types (text, dates, numbers) before wiring it into Bubble — the batchUpdate succeeds silently even if no replacements match, making it hard to detect configuration errors without visual inspection.
Alternatives
Notion is a database CMS pattern (query rows, display in Repeating Groups) while Google Docs is a document generation pattern (duplicate template, fill placeholders, download). Use Google Docs when you need formatted downloadable documents like contracts or proposals. Use Notion when you need to display and edit structured tabular content in your Bubble app.
Airtable stores structured data like Google Sheets and can be used as a Bubble database backend. It does not generate formatted documents. Use Google Docs for document output; use Airtable to manage the underlying data that feeds into those documents.
Slack is a communication tool, not a document generator. However, the Slack API in Bubble follows a similar private-header pattern and is commonly paired with Google Docs workflows — Bubble sends a Slack notification when a new document is generated.
Frequently asked questions
Can I use a regular Google account instead of a service account?
Technically yes, using OAuth 2.0 user authorization, but it is significantly more complex in Bubble. OAuth requires a redirect flow to obtain a refresh token, and Bubble does not have a native OAuth callback handler. A service account with direct API access is much simpler to set up and maintain in Bubble — it is the recommended approach for Bubble apps that generate documents on behalf of the app owner rather than on behalf of individual users.
Why do I need two separate API Connector groups?
Because the Google Drive API and Google Docs API have different base URLs — Drive uses googleapis.com/drive/v3 and Docs uses docs.googleapis.com/v1. Bubble's API Connector applies the base URL to all calls in a group, so mixing them under one base URL causes all calls to go to the wrong endpoint. Separating them into two groups ensures each call reaches the correct API.
How do I get the final document URL to display or share with the user?
After the Backend Workflow's copy step completes, the Drive API returns the new file's id. Construct the Google Docs URL as: https://docs.google.com/document/d/ + the id value. You can store this in a Bubble data field and display it as a link, or use a 'Send email' step to email the link to the user. The document inherits the sharing settings of the destination Drive folder — if the folder is private, the generated document is private and requires sign-in to view.
Can I generate a PDF instead of a Google Doc?
Yes. After generating the Doc with filled placeholders, add a third call to the Drive API: GET https://www.googleapis.com/drive/v3/files/DOCUMENT_ID/export?mimeType=application/pdf. This returns the PDF binary. However, handling binary data in Bubble is complex — you would need to store the export URL and let users click it directly, which prompts a browser download, rather than processing the PDF within Bubble.
What happens if a placeholder in my template doesn't get replaced?
The batchUpdate endpoint returns HTTP 200 OK regardless of whether replacements were made — it does not report zero-match replacements as errors. If you see unfilled placeholders in the generated document, check: (1) the placeholder text in the template uses standard ASCII quotes (not smart/curly quotes), (2) the placeholder was not accidentally split across multiple text runs in Google Docs internal formatting, (3) the matchCase setting in replaceAllText matches the actual capitalization of your placeholder.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation