Preface
Google's document APIs are powerful but can be intimidating to work with directly. Between authentication flows, service accounts, and API quirks, you end up writing a lot of boilerplate just to read or write documents. I built this package after getting tired of copying the same Google Docs integration code across projects. It wraps the complexity behind simple functions while handling authentication and common operations cleanly. This guide walks through the complete setup process and implementation, including the Google Cloud configuration that trips up most developers.
Google Cloud Setup
Start by creating a fresh Google Cloud project. Head to the Google Cloud Console and click "Create or select a project", then "New project". Give it a name and hit create.
Next, enable the APIs you'll need. Navigate to "APIs & Services" from the hamburger menu, then click "Enable APIs and services". Search for and enable both "Google Drive API" and "Google Docs API".
Now create a service account for authentication. Go to "IAM & Admin" → "Service Accounts" → "Create Service Account". Fill in a name like "google-docs-service" and add a description. For permissions, select "Owner" role to keep things simple during development.
After creating the service account, click on it and go to the "Keys" tab. Click "Add Key" → "Create new key" and choose JSON format. This downloads your credential file - keep it secure since it can't be recovered if lost.
Save this JSON file in your project and you're ready to start coding.
Package Implementation
Our Google Docs package wraps both the Docs and Drive APIs into a single clean interface. The core struct DocsPkg holds authenticated clients for both services, which we need since document operations span both APIs.
package google
import (
"context"
"fmt"
"golang.org/x/oauth2/google"
"google.golang.org/api/docs/v1"
"google.golang.org/api/drive/v3"
"google.golang.org/api/option"
"io"
"os"
)
// PlaceholderData contains all placeholder values to be replaced
type PlaceholderData map[string]string
// DocsPkg wraps the Google Docs and Drive services
type DocsPkg struct {
ctx context.Context
docsClient *docs.Service
driveClient *drive.Service
}
// NewDocsService creates a new service with Google API authentication
func NewDocsService(ctx context.Context, credentialsFile string) (*DocsPkg, error) {
// Read credentials file
credBytes, err := os.ReadFile(credentialsFile)
if err != nil {
return nil, fmt.Errorf("failed to read credentials file: %w", err)
}
// Create config from credentials
config, err := google.JWTConfigFromJSON(credBytes,
docs.DocumentsScope,
drive.DriveScope)
if err != nil {
return nil, fmt.Errorf("failed to create JWT config: %w", err)
}
// Create HTTP client with the config
client := config.Client(ctx)
// Initialize the Docs service
docsService, err := docs.NewService(ctx, option.WithHTTPClient(client))
if err != nil {
return nil, fmt.Errorf("failed to create Docs service: %w", err)
}
// Initialize the Drive service
driveService, err := drive.NewService(ctx, option.WithHTTPClient(client))
if err != nil {
return nil, fmt.Errorf("failed to create Drive service: %w", err)
}
return &DocsPkg{
ctx: ctx,
docsClient: docsService,
driveClient: driveService,
}, nil
}
// CopyTemplate creates a copy of the template document
func (s *DocsPkg) CopyTemplate(templateID string, documentName string) (string, error) {
// Create a copy of the template
file := &drive.File{
Name: documentName,
}
// Execute the copy operation
copiedFile, err := s.driveClient.Files.Copy(templateID, file).Do()
if err != nil {
return "", fmt.Errorf("failed to copy template: %w", err)
}
return copiedFile.Id, nil
}
// ReplacePlaceholders replaces all placeholders in the document
func (s *DocsPkg) ReplacePlaceholders(documentID string, data PlaceholderData) error {
// Create a batch update request
var requests []*docs.Request
// Add a replace text request for each placeholder
for placeholder, value := range data {
requests = append(requests, &docs.Request{
ReplaceAllText: &docs.ReplaceAllTextRequest{
ContainsText: &docs.SubstringMatchCriteria{
Text: "{{" + placeholder + "}}",
MatchCase: true,
},
ReplaceText: value,
},
})
}
// Execute the batch update
_, err := s.docsClient.Documents.BatchUpdate(documentID, &docs.BatchUpdateDocumentRequest{
Requests: requests,
}).Do()
if err != nil {
return fmt.Errorf("failed to replace placeholders: %w", err)
}
return nil
}
// ExportToPDF exports the document as PDF
func (s *DocsPkg) ExportToPDF(documentID string) ([]byte, error) {
// Export the file as PDF
resp, err := s.driveClient.Files.Export(documentID, "application/pdf").Download()
if err != nil {
return nil, fmt.Errorf("failed to export as PDF: %w", err)
}
defer resp.Body.Close()
// Read the response
pdfBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read PDF content: %w", err)
}
return pdfBytes, nil
}
The NewDocsService function handles the messy authentication setup. It reads your JSON credentials, creates JWT config with proper scopes, and initializes both API clients. This gives you everything needed for document operations in one go.
Testing the Package
Here's a simple test to verify our Google Docs package works:
func TestGoogleDocsPackage(t *testing.T) {
ctx := context.Background()
// Initialize the service with your credentials
docsService, err := google.NewDocsService(ctx, "path/to/your/credentials.json")
if err != nil {
t.Fatalf("Failed to create docs service: %v", err)
}
// Template document ID (create a test document in Google Drive first)
templateID := "your-template-document-id"
// Copy the template
newDocID, err := docsService.CopyTemplate(templateID, "Test Document Copy")
if err != nil {
t.Fatalf("Failed to copy template: %v", err)
}
t.Logf("Created document: %s", newDocID)
// Replace placeholders
placeholders := google.PlaceholderData{
"NAME": "John Doe",
"DATE": "June 15, 2025",
"COMPANY": "Test Corp",
}
err = docsService.ReplacePlaceholders(newDocID, placeholders)
if err != nil {
t.Fatalf("Failed to replace placeholders: %v", err)
}
// Export to PDF
pdfBytes, err := docsService.ExportToPDF(newDocID)
if err != nil {
t.Fatalf("Failed to export PDF: %v", err)
}
if len(pdfBytes) == 0 {
t.Fatal("PDF export returned empty data")
}
t.Logf("PDF exported successfully, size: %d bytes", len(pdfBytes))
}
This test demonstrates the complete workflow: copying a template document, replacing placeholder text with actual values, and exporting the result as PDF. Create a test document in Google Drive with placeholders like {{NAME}} and {{DATE}} to see the replacement in action.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.