Skip to Content
ProvidersGoogle Drive & Docs

Google Drive & Docs

Category: Cloud Storage / Documents
Integration type: Tenant OAuth (Google user account — stored in integrations table)
External API: Google Drive API v3, Google Docs API v1
OAuth Scopes: drive.file, documents


Purpose

The Google Drive integration stores platform-generated deliverables — reports, strategy documents, and content packages — directly in the client’s Google Drive as native Google Docs. Clients can view, comment on, and share documents without logging into the Leadmetrics platform.

Use cases:

  • Monthly performance reports — Report Writer output is pushed as a Google Doc into a designated Drive folder
  • Strategy documents — Strategy Writer output is mirrored into Drive so clients can annotate and share internally
  • Content brief export — Content briefs can be exported to Drive for client sign-off before content production begins
  • Deliverable archive — A structured Drive folder hierarchy organises all deliverables by month and content type

The platform creates documents as view-only shared links by default. Comment access is optionally granted. Edit access is never granted automatically.


Config Structure

Tenant integration (OAuth)

interface GoogleDriveConfig { accessToken: string; // Short-lived OAuth access token refreshToken: string; // Long-lived refresh token rootFolderId: string; // Top-level Drive folder for this client (e.g. "Acme Corp — Leadmetrics") reportFolderId?: string; // Subfolder for reports (created during setup if absent) strategyFolderId?: string; // Subfolder for strategy documents }

Stored in integrations:

provider: 'google-drive' api_key: encrypt(refreshToken) metadata: { rootFolderId: string, reportFolderId: string | null, strategyFolderId: string | null, connectedEmail: string, // Google account email — shown in UI }

Tokens are refreshed automatically before API calls using the Google OAuth2 client.


Integration Pattern

Tool layer (packages/tools/src/google-drive.ts)

import { google } from 'googleapis'; class GoogleDriveTool { private driveClient; private docsClient; constructor(private cfg: GoogleDriveConfig) { const auth = new google.auth.OAuth2(); auth.setCredentials({ access_token: cfg.accessToken, refresh_token: cfg.refreshToken, }); this.driveClient = google.drive({ version: 'v3', auth }); this.docsClient = google.docs({ version: 'v1', auth }); } /** * Create a new Google Doc from Markdown content. * Converts Markdown to Docs batchUpdate requests. */ async createDoc(options: { folderId: string; title: string; markdown: string; // Converted to Docs API batchUpdate requests }): Promise<{ docId: string; url: string }> { // Step 1: Create empty document const createResponse = await this.driveClient.files.create({ requestBody: { name: options.title, mimeType: 'application/vnd.google-apps.document', parents: [options.folderId], }, fields: 'id,webViewLink', }); const docId = createResponse.data.id as string; // Step 2: Populate content via Docs batchUpdate const requests = markdownToDocsRequests(options.markdown); if (requests.length > 0) { await this.docsClient.documents.batchUpdate({ documentId: docId, requestBody: { requests }, }); } return { docId, url: createResponse.data.webViewLink as string, }; } /** * Upload a binary file (PDF, image) to a Drive folder. */ async uploadFile(options: { folderId: string; filename: string; buffer: Buffer; mimeType: string; }): Promise<{ fileId: string; url: string }> { const { Readable } = require('stream'); const stream = Readable.from(options.buffer); const response = await this.driveClient.files.create({ requestBody: { name: options.filename, parents: [options.folderId], }, media: { mimeType: options.mimeType, body: stream, }, fields: 'id,webViewLink', }); return { fileId: response.data.id as string, url: response.data.webViewLink as string, }; } /** * Set sharing permissions on a file or folder. */ async shareFile(options: { fileId: string; role: 'reader' | 'commenter' | 'writer'; type: 'user' | 'anyone'; email?: string; // Required when type = 'user' }): Promise<{ permissionId: string }> { const response = await this.driveClient.permissions.create({ fileId: options.fileId, requestBody: { role: options.role, type: options.type, emailAddress: options.email, }, sendNotificationEmail: false, }); return { permissionId: response.data.id as string }; } /** * Create a subfolder inside an existing folder. */ async createFolder(options: { parentFolderId: string; name: string; }): Promise<{ folderId: string }> { const response = await this.driveClient.files.create({ requestBody: { name: options.name, mimeType: 'application/vnd.google-apps.folder', parents: [options.parentFolderId], }, fields: 'id', }); return { folderId: response.data.id as string }; } async verify(): Promise<void> { await this.driveClient.about.get({ fields: 'user' }); } }

Markdown → Docs API requests helper

The Google Docs API uses batchUpdate requests to insert content. A helper (markdownToDocsRequests) converts Markdown to an array of InsertText, UpdateParagraphStyle, and UpdateTextStyle requests.

function markdownToDocsRequests(markdown: string): object[] { // Uses a lightweight custom parser or a library such as `md-to-google-docs` // to produce Docs API batchUpdate request objects. // Headings → UpdateParagraphStyle (HEADING_1/2/3) // Bold/italic → UpdateTextStyle // Lists → bullet/numbered list InsertText // Tables → InsertTable + InsertText return []; // Placeholder — implement with parser }

Agent Workflow

Report delivery to Drive

Report Writer agent generates monthly performance report (Markdown) ▼ (HITL approval by DM team) Report Publisher worker ├── Resolve tenant's Google Drive integration ├── Determine report folder (create if absent: "Reports / April 2026") ├── Call createDoc() with report title and Markdown content ├── Call shareFile() with role = 'commenter' for client email └── Store docId + url in deliverable_periods.google_doc_id DM Portal shows "Open in Google Docs" link on deliverable period

Folder structure created during setup

📁 [Client Name] — Leadmetrics ← rootFolderId ├── 📁 Reports │ ├── 📄 March 2026 Performance Report │ └── 📄 April 2026 Performance Report └── 📁 Strategy └── 📄 Q2 2026 Marketing Strategy

The folder hierarchy is created during integration setup and subfolders for each month are created by the Report Publisher worker.


OAuth Setup Flow

  1. Tenant clicks “Connect Google Drive” in Dashboard → Settings → Integrations
  2. Platform redirects to Google OAuth with scopes: drive.file, documents, userinfo.email
  3. Google redirects back with auth code → platform exchanges for access_token + refresh_token
  4. Platform creates root folder "[Client Name] — Leadmetrics" in the user’s Drive
  5. Platform creates subfolders: Reports, Strategy
  6. Folder IDs + connected email saved to integrations table

Token Refresh

Access tokens expire after 1 hour. Before each API call, the tool checks token expiry and calls oauth2Client.refreshAccessToken() if within 5 minutes of expiry. The new access_token is written back to integrations.api_key (encrypted).


Test Cases

Unit tests (packages/tools/src/google-drive.test.ts)

TestApproach
createDoc() creates file then calls batchUpdateMock drive and docs clients; assert both called in order
createDoc() skips batchUpdate when no requestsMock markdownToDocsRequests returning []; assert batchUpdate not called
createDoc() returns docId and urlMock files.create returning { id, webViewLink }; assert mapped return
uploadFile() streams buffer to DriveAssert media.body is a Readable stream
shareFile() uses correct role and typeAssert permissions.create called with expected requestBody
createFolder() sets mimeType to folder MIME typeAssert mimeType: 'application/vnd.google-apps.folder'
verify() throws on invalid credentialsMock about.get throws 401; assert propagated

© 2026 Leadmetrics — Internal use only