To integrate Google Docs with Lovable, create Supabase Edge Functions that authenticate using a Google Cloud service account and OAuth2, then proxy Google Docs API calls for document creation, template filling, and batch updates. Store service account credentials in Cloud Secrets to build document generation tools — invoices, reports, and contracts — in Lovable.
Build Document Generation Features in Lovable with Google Docs API
Many business applications need to generate formatted documents — invoices for customers, reports for stakeholders, contracts for clients, or certificates for users. Building document generation directly into a Lovable app by connecting to the Google Docs API lets you create professional, formatted documents on demand, share them automatically with the right people, and store links in your database for easy retrieval — all without any external document service.
The Google Docs API uses a batch update model: you create a document (empty or from a template copy), then apply a list of requests that insert text, apply formatting, replace placeholder tokens, and structure the document. Template-based generation is the most practical pattern — create a Google Doc template with placeholder tokens like {{client_name}}, {{invoice_total}}, and {{due_date}}, then have your Edge Function copy the template and replace the tokens with actual data using the batchUpdate replaceAllText request type. The result is a fully formatted document with real data, available as a Google Docs URL.
Authentication with the Google Docs API requires either OAuth2 user consent (for accessing user-owned files) or a service account with Google Workspace domain delegation (for server-side document creation in a shared Google Drive). For document generation — where your application creates documents on behalf of your service, not reading the user's existing documents — a service account is the right approach. The service account creates documents in a Google Drive folder it has access to, then shares them with specific users if needed.
Integration method
Google Docs integration in Lovable uses Supabase Edge Functions that authenticate via a Google Cloud service account using JWT-based OAuth2 token exchange. The Edge Functions proxy Google Docs API calls for document creation, reading, and batch updates, keeping the service account private key encrypted in Cloud Secrets and accessible only via Deno.env.get(). React components in your Lovable app trigger document generation and receive document URLs without any credential exposure to the browser.
Prerequisites
- A Google Cloud project with the Google Docs API and Google Drive API enabled at console.cloud.google.com
- A Google Cloud service account with a JSON key file downloaded — the service account email needs access to your template documents in Google Drive
- A Google Doc template with placeholder tokens in double curly braces like {{client_name}} that your Edge Function will replace
- The Google Docs template's document ID from its URL (the long alphanumeric string between /d/ and /edit in the URL)
- A Lovable project with Lovable Cloud enabled
Step-by-step guide
Create a Google Cloud service account and enable APIs
Create a Google Cloud service account and enable APIs
Go to console.cloud.google.com and select or create a Google Cloud project. Navigate to APIs & Services → Library and enable two APIs: search for 'Google Docs API' and enable it, then search for 'Google Drive API' and enable it — the Drive API is needed to copy template documents and set sharing permissions. With both APIs enabled, navigate to APIs & Services → Credentials and click 'Create Credentials → Service account'. Name the service account (e.g., 'lovable-docs-generator'), give it a description, and click 'Done'. Do not assign a project-level role unless your Drive files are shared via Google Workspace domain. After creating the service account, click on it in the credentials list, go to the 'Keys' tab, click 'Add Key → Create new key', select JSON format, and download the key file. The JSON file contains a private_key (multi-line RSA private key), client_email (the service account's email address), and project_id. You need both the private_key and client_email for authentication. Now share your Google Doc template with the service account email — open the template document in Google Drive, click 'Share', enter the service account email (it looks like name@project-id.iam.gserviceaccount.com), and give it 'Viewer' or 'Editor' access. The service account can now copy this document and create new documents in drives it has access to.
Pro tip: The service account's private key in the JSON file is a multi-line string with \n newline characters. When storing it in Cloud Secrets, preserve the newline escaping — it is a single-line string with literal \n sequences, not actual newlines.
Expected result: A Google Cloud service account exists with a downloaded JSON key file, and the Google Docs and Drive APIs are enabled for the project.
Store service account credentials in Cloud Secrets
Store service account credentials in Cloud Secrets
In your Lovable project, open the Cloud tab by clicking '+' next to the Preview panel, then navigate to the Secrets section. Add two secrets for the Google service account authentication. Add GOOGLE_SERVICE_ACCOUNT_EMAIL with the service account email address from the JSON key file (the client_email value, which ends with .iam.gserviceaccount.com). Add GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY with the private key value from the JSON file (the private_key value — the entire string including the -----BEGIN RSA PRIVATE KEY----- header and -----END RSA PRIVATE KEY----- footer, with all \n characters preserved as literal backslash-n sequences). Do not add actual newlines — store the key as a single-line string with escaped newlines. Also add GOOGLE_DOCS_TEMPLATE_ID with the document ID of your Google Doc template (the alphanumeric string from the document URL between /d/ and /edit). These three secrets provide everything the Edge Function needs: the service account identity, its signing key for JWT creation, and the template to copy from. Lovable's SOC 2 Type II certified Cloud Secrets store protects the private key — arguably the most sensitive credential in this integration — from any exposure. Never paste the private key into Lovable's chat, which is visible in the prompt history.
Pro tip: To correctly format the private key for storage, copy it from the JSON file and ensure it is the full string with \n characters. In the Secrets panel, paste it as-is — Lovable Cloud Secrets handles the encoding.
Expected result: GOOGLE_SERVICE_ACCOUNT_EMAIL, GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY, and GOOGLE_DOCS_TEMPLATE_ID are stored in Cloud Secrets.
Create the Google Docs API Edge Function with JWT authentication
Create the Google Docs API Edge Function with JWT authentication
Ask Lovable to create a Supabase Edge Function called google-docs-api that implements Google service account authentication using JWT. Service account authentication works by creating a JSON Web Token (JWT) signed with the service account's RSA private key, then exchanging it for an OAuth2 access token at Google's token endpoint. The JWT payload must include iss (the service account email), scope (space-separated list of required API scopes: https://www.googleapis.com/auth/documents and https://www.googleapis.com/auth/drive), aud (https://oauth2.googleapis.com/token), iat (current Unix timestamp), and exp (iat + 3600). Sign the JWT with the private key using RS256 algorithm. Exchange this JWT for an access token by POSTing to https://oauth2.googleapis.com/token with grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer and the JWT as the assertion. Cache the resulting access token for 55 minutes. With the access token, the function can copy a template using POST to https://www.googleapis.com/drive/v3/files/{templateId}/copy and apply batch updates using POST to https://docs.googleapis.com/v1/documents/{docId}:batchUpdate. The batch update with replaceAllText requests replaces placeholder tokens throughout the document with actual values.
Create a Supabase Edge Function called google-docs-api that authenticates with Google using a service account JWT. Read GOOGLE_SERVICE_ACCOUNT_EMAIL and GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY from Deno.env.get(). Build and sign a JWT for Google OAuth2. Exchange it for an access token at https://oauth2.googleapis.com/token. Then accept a command: 'copy_template' copies GOOGLE_DOCS_TEMPLATE_ID with a given title, 'batch_update' applies replaceAllText requests to a document. Return the document ID and URL. Handle JWT signing with the private key using the Web Crypto API.
Paste this in Lovable chat
1import { serve } from "https://deno.land/std@0.168.0/http/server.ts";2import { encode as base64url } from "https://deno.land/std@0.168.0/encoding/base64url.ts";34const corsHeaders = {5 "Access-Control-Allow-Origin": "*",6 "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",7};89let tokenCache: { token: string; expiry: number } | null = null;1011async function getAccessToken(): Promise<string> {12 if (tokenCache && Date.now() < tokenCache.expiry) return tokenCache.token;1314 const email = Deno.env.get("GOOGLE_SERVICE_ACCOUNT_EMAIL")!;15 const rawKey = Deno.env.get("GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY")!;16 const privateKeyPem = rawKey.replace(/\\n/g, "\n");1718 const header = { alg: "RS256", typ: "JWT" };19 const now = Math.floor(Date.now() / 1000);20 const payload = {21 iss: email,22 scope: "https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/drive",23 aud: "https://oauth2.googleapis.com/token",24 iat: now,25 exp: now + 3600,26 };2728 const enc = new TextEncoder();29 const headerB64 = base64url(enc.encode(JSON.stringify(header)));30 const payloadB64 = base64url(enc.encode(JSON.stringify(payload)));31 const signingInput = `${headerB64}.${payloadB64}`;3233 // Import the RSA private key34 const pemBody = privateKeyPem.replace(/-----BEGIN[^-]+-----/g, "").replace(/-----END[^-]+-----/g, "").replace(/\s/g, "");35 const keyBytes = Uint8Array.from(atob(pemBody), c => c.charCodeAt(0));36 const cryptoKey = await crypto.subtle.importKey(37 "pkcs8", keyBytes.buffer,38 { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },39 false, ["sign"]40 );4142 const signature = await crypto.subtle.sign("RSASSA-PKCS1-v1_5", cryptoKey, enc.encode(signingInput));43 const sigB64 = base64url(new Uint8Array(signature));44 const jwt = `${signingInput}.${sigB64}`;4546 const tokenRes = await fetch("https://oauth2.googleapis.com/token", {47 method: "POST",48 headers: { "Content-Type": "application/x-www-form-urlencoded" },49 body: new URLSearchParams({50 grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",51 assertion: jwt,52 }),53 });5455 const tokenData = await tokenRes.json();56 if (!tokenData.access_token) throw new Error(tokenData.error_description || "Token exchange failed");5758 tokenCache = { token: tokenData.access_token, expiry: Date.now() + 55 * 60 * 1000 };59 return tokenCache.token;60}6162serve(async (req) => {63 if (req.method === "OPTIONS") return new Response("ok", { headers: corsHeaders });6465 try {66 const accessToken = await getAccessToken();67 const templateId = Deno.env.get("GOOGLE_DOCS_TEMPLATE_ID");68 const { command, title, documentId, replacements } = await req.json();6970 let result: unknown;7172 if (command === "copy_template") {73 const copyRes = await fetch(74 `https://www.googleapis.com/drive/v3/files/${templateId}/copy`,75 {76 method: "POST",77 headers: { "Authorization": `Bearer ${accessToken}`, "Content-Type": "application/json" },78 body: JSON.stringify({ name: title || "Generated Document" }),79 }80 );81 result = await copyRes.json();82 } else if (command === "batch_update" && documentId && replacements) {83 const requests = Object.entries(replacements as Record<string, string>).map(([key, value]) => ({84 replaceAllText: {85 containsText: { text: `{{${key}}}`, matchCase: false },86 replaceText: value,87 },88 }));89 const updateRes = await fetch(90 `https://docs.googleapis.com/v1/documents/${documentId}:batchUpdate`,91 {92 method: "POST",93 headers: { "Authorization": `Bearer ${accessToken}`, "Content-Type": "application/json" },94 body: JSON.stringify({ requests }),95 }96 );97 result = await updateRes.json();98 } else {99 throw new Error("Unknown command or missing parameters");100 }101102 return new Response(JSON.stringify(result), {103 headers: { ...corsHeaders, "Content-Type": "application/json" },104 });105 } catch (error) {106 return new Response(107 JSON.stringify({ error: error.message }),108 { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }109 );110 }111});Pro tip: Test the Edge Function by calling it with command 'copy_template' and title 'Test Document'. If a new Google Doc appears in the service account's Drive, authentication and document copying are both working.
Expected result: The google-docs-api Edge Function successfully creates a copy of the template document and returns a document ID.
Build the document generation UI and template replacement workflow
Build the document generation UI and template replacement workflow
With the Edge Function working, ask Lovable to build a document generation form. The workflow is: user fills in a form → form submits to the Edge Function → function copies the template → function applies replaceAllText batch updates → function returns the Google Docs URL → application displays the link. The form fields should correspond to the placeholder tokens in your template. For an invoice template with {{client_name}}, {{invoice_date}}, {{total_amount}}, and {{line_items}}, the form needs corresponding inputs. When the form submits, first call the Edge Function with command 'copy_template' to get a new document ID, then immediately call again with command 'batch_update' passing the document ID and a replacements object mapping token names to values. The replacements object format is { client_name: 'Acme Corp', invoice_date: '2026-03-30', total_amount: '$1,500.00', line_items: 'Design services: $1,500.00' }. After successful generation, display the Google Docs URL as a link and save the document metadata to your Supabase table. Add a loading state between form submission and URL display since the document generation involves two API calls and may take a few seconds.
Build a document generation form that generates a Google Doc invoice by calling my google-docs-api Edge Function. The form has fields for: client name, client email, invoice date (default today), line items (textarea with item | price format), and total amount. On submit, first call with command 'copy_template' and title like 'Invoice - {clientName}', then call with command 'batch_update' passing the document ID and replacements object for all placeholder tokens. Show a loading spinner during generation. On success, display the Google Docs URL as a prominent 'Open Document' button and save the record to a Supabase invoices table with columns: id, client_name, client_email, doc_url, created_at.
Paste this in Lovable chat
Pro tip: For line items that span multiple rows in your template, design the template with a table and use separate placeholder tokens for each row position ({{item_1}}, {{item_2}}, etc.), then fill them individually rather than trying to insert dynamic table rows through the Docs API, which is significantly more complex.
Expected result: A document generation form creates real Google Docs from the template with user data, displays the link, and saves the record to Supabase.
Common use cases
Invoice and quote generation from form submissions
When a user submits a quote request or invoice form in your Lovable app, automatically generate a formatted Google Docs invoice by copying a template and replacing placeholder tokens with the client name, line items, totals, and due date. Return the Google Docs link and optionally email it to the client.
Build an invoice generator that, when I submit a form with client name, items, and amounts, calls my google-docs-api Edge Function to copy my invoice template document, replace all {{placeholder}} tokens with the form data ({{client_name}}, {{invoice_date}}, {{total_amount}}, {{line_items}}), and return the Google Docs URL. Show the generated document link in a success state with a 'Open in Google Docs' button. Also save the document URL and client name to my Supabase invoices table.
Copy this prompt to try it in Lovable
Automated report generation from dashboard data
Create weekly or monthly reports by generating a Google Doc from a data snapshot. Pull metrics from your Supabase database, populate a report template with the actual numbers, and create a shareable Google Doc report that stakeholders can access without logging into your application.
Create a report generator that fetches weekly metrics from my Supabase database and calls my google-docs-api Edge Function to create a weekly report Google Doc. Copy the weekly_report template document, replace placeholders for total_users, new_signups, revenue, and top_pages with the real numbers, and share the resulting document with my team email address. Show the generated report link and log the creation to a reports table with date and document URL.
Copy this prompt to try it in Lovable
Contract generation with e-signature preparation
Generate client service agreements or NDAs by populating a contract template with the client's company name, effective date, and specific terms. Produce a clean Google Doc that can be shared with clients for review before signing through a separate e-signature tool.
Build a contract generator that calls my google-docs-api Edge Function when I fill out a client details form with company name, address, service scope, and contract start date. Copy my NDA template document, replace all placeholder tokens, and return the Google Docs URL. Display the link prominently with instructions to review and send to the client. Save the contract URL, client name, and creation date to my contracts table.
Copy this prompt to try it in Lovable
Troubleshooting
JWT token exchange fails with 'invalid_grant' or 'unauthorized_client' error
Cause: The service account's private key is not being correctly parsed — most commonly because the stored key contains literal newline characters instead of escaped \n sequences, or vice versa, causing the RSA key import to fail.
Solution: Verify that GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY contains the key with \n as literal two-character sequences, not actual newlines. In the Edge Function, the line privateKeyPem = rawKey.replace(/\\n/g, '\n') converts the escaped sequences to real newlines before importing. If you stored the key with real newlines, modify the replace pattern or re-store the key with escaped newlines.
1// Handle both escaped and literal newlines in stored key2const privateKeyPem = rawKey.includes('\\n')3 ? rawKey.replace(/\\n/g, '\n')4 : rawKey;The template copy is created but batchUpdate replaceAllText leaves all placeholders unchanged
Cause: The placeholder format in the document does not match what is being searched for. Smart quotes, different curly brace characters, or whitespace inside the braces can cause text matching to fail.
Solution: Open your Google Doc template and manually check the placeholder text. Ensure curly braces are standard ASCII characters {{ and }}, not smart quotes or special Unicode characters. Retype the placeholders manually in the document if they may have been auto-corrected. Also check that matchCase: false is set in the replaceAllText request so case differences do not prevent matching.
Generated documents appear in an unexpected Google Drive location or the service account cannot access them after creation
Cause: Documents copied by the service account are created in the service account's Google Drive, not your personal Drive. The service account needs to share them with your account or move them to a shared folder.
Solution: After copying the template, make a second Drive API call to update the document's permissions: POST to https://www.googleapis.com/drive/v3/files/{docId}/permissions with body { role: 'writer', type: 'user', emailAddress: 'your@email.com' }. Alternatively, place your template in a Google Drive shared folder and grant the service account access — documents copied within that folder will be accessible to all users who have folder access.
Edge Function returns 403 Forbidden when trying to copy the template document
Cause: The service account has not been granted access to the template document in Google Drive.
Solution: Open the template Google Doc, click 'Share', and add the service account email address (from GOOGLE_SERVICE_ACCOUNT_EMAIL) as an Editor or Viewer. The service account must be explicitly granted access to copy the document. If the template is in a Google Drive folder, sharing the folder with the service account grants access to all files inside it.
Best practices
- Store the service account private key in Cloud Secrets as a single-line string with escaped \n newlines — never as a multi-line value, which can cause parsing failures depending on how Deno reads environment variables
- Cache the OAuth2 access token in module-level state with a 55-minute TTL to avoid JWT signing and token exchange on every document generation request
- Design templates with simple placeholder tokens using double curly braces and alphanumeric names only — avoid special characters inside tokens which can cause text search failures
- Share generated documents with specific user emails via the Drive API permissions endpoint rather than making them publicly accessible, protecting document contents from unintended access
- Store generated document URLs in your Supabase database immediately after creation, alongside metadata like document title, generation date, and any relevant IDs — Google Docs URLs do not change but having them in your database prevents re-generation if users lose the link
- Design templates in Google Docs with representative placeholder data that shows the intended formatting — headers, tables, and styled text all look correct in the template before generation, making it easier to verify the output format
- For templates with complex layouts (tables with dynamic row counts), pre-allocate a fixed number of rows in the template and use empty strings to leave unused rows blank, rather than attempting to insert new table rows programmatically which requires more complex batchUpdate requests
Alternatives
Notion is a flexible workspace with database capabilities that works as a lightweight CMS and collaboration tool, while Google Docs excels at creating rich, print-ready formatted documents that integrate with the broader Google Workspace ecosystem.
Confluence is Atlassian's enterprise wiki and knowledge base with deep Jira integration for engineering teams, while Google Docs is a general-purpose document editor better suited for producing client-facing and externally shareable documents.
Quip is Salesforce's collaborative document platform with built-in spreadsheets and live Salesforce data, while Google Docs integrates with Google Workspace and offers more established document formatting tools and broader third-party integrations.
Frequently asked questions
Can I use Google Docs API with a regular Google account instead of a service account?
For generating documents on behalf of your application (server-side generation), a service account is the recommended approach because it does not require user interaction for authorization. OAuth2 with a user account is appropriate when you need to access or modify documents in a specific user's Drive with their consent. For document generation use cases in Lovable apps, service accounts are simpler and more reliable since they do not require storing user refresh tokens.
What happens to documents created by the service account — where do they live in Google Drive?
Documents created by the service account are stored in the service account's private Google Drive space, which is not visible in your regular Google Drive. To make them accessible to your users, share each created document with specific user emails using the Drive API permissions endpoint, or pre-create a shared Google Drive folder, grant the service account access, and specify the folder as the parent when copying templates.
Can I add images or charts to generated Google Docs through the API?
Yes, the Google Docs batchUpdate API supports inserting images via the insertInlineImage request type using a publicly accessible image URL. Charts must be created in Google Sheets and linked to Docs, which requires additional Sheets API calls. For most document generation use cases, pre-placing images in the template and replacing text placeholders is sufficient and much simpler than inserting images programmatically.
How do I generate PDFs from Google Docs through the API?
The Google Drive API supports exporting Google Docs as PDFs using a GET request to https://www.googleapis.com/drive/v3/files/{docId}/export?mimeType=application/pdf. Your Edge Function can make this call after creating and populating the document, then return the PDF binary or store it in Supabase Storage. This lets you offer a 'Download as PDF' button in your Lovable app without any additional PDF generation library.
Is there a limit on how many documents I can generate per day?
Google Docs API has usage limits defined per project in Google Cloud. The default limits are generous for most applications — typically hundreds of requests per minute per user. If you expect high document generation volume, check your project's quota in the Google Cloud Console under APIs & Services → Quotas and request increases if needed. Free tier Google Cloud projects have lower default quotas than projects with billing enabled.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation