Keeping one master contract or proposal template and duplicating it for every new client beats copy-pasting text into a fresh document each time and hoping nothing gets left out.
This script uses DriveApp.getFileById on a template document, calls makeCopy to duplicate it into a target folder, then opens the copy with DocumentApp to fill in a handful of placeholder values.
Separating the copy step from the edit step matters because makeCopy operates on the file at the Drive level, while the placeholder replacement needs the richer DocumentApp API to touch the document body.
The result is a new, uniquely named document for each client that already contains their name, date, and any other detail you choose to inject at copy time.
Need this built? Hire a Google Apps Script developer →
Sheet / project setup
| Resource | Name / value | Purpose |
|---|---|---|
| Template Doc | TEMPLATE_DOC_ID | Source makeCopy target |
| Dest folder | DEST_FOLDER_ID | Where copies land |
| Placeholders | {{ClientName}}, {{Date}} | Tokens replaced after copy |
What It Does
The script duplicates a template document into a destination folder using makeCopy, renames the copy to include the client's name, then opens the new file and replaces placeholder tokens with real values.
Because makeCopy returns a DriveApp File object rather than a Doc, the script has to reopen the new file's ID with DocumentApp.openById before it can touch the document's body text.
Prerequisites
Build your template document with clearly bracketed placeholders like {{ClientName}} and {{StartDate}} so the replacement step can target exact strings without accidentally matching unrelated text.
Create or choose a destination folder in Drive for finished copies and note its ID, since makeCopy accepts a folder as an optional second argument.
Walkthrough
Set TEMPLATE_ID to your template document's ID and DEST_FOLDER_ID to the destination folder, then call createClientDoc with an object containing the client's name and start date.
Run createClientDoc({ ClientName: 'Acme Co', StartDate: '2026-08-01' }) from the editor and confirm a new document appears in the destination folder with both placeholders replaced correctly.
Open the generated document and manually verify formatting around the replaced text still looks correct, since replaceText can occasionally affect surrounding character styles in edge cases.
Edge Cases
If a placeholder appears in the template but is missing from the data object passed to the function, that placeholder is left in the document unchanged rather than being replaced with the word undefined.
Placeholders that appear inside a table cell or a header are still replaced correctly, since body.replaceText searches the entire document body regardless of which structural element contains the text.
Testing
Run the function twice with two different client names and confirm two separate documents exist in the destination folder, each with the correct name substituted and no cross-contamination between the two.
Deliberately omit one placeholder value and confirm the unmatched token remains visibly in the output document so a missed field is easy to spot before sending it to a client.
Hardening
Validate that every required key exists in the incoming data object before calling makeCopy, so the script fails fast with a clear error instead of producing a half-filled document.
Log the new document's ID and URL to a tracking sheet immediately after creation so there is a record connecting each generated document back to the client it was created for.
Variations
Chain this script with the mail-merge tutorial so each generated document is automatically emailed to the corresponding client as soon as it is created.
Convert the finished document to PDF with getAs('application/pdf') before handing it off if the destination system expects a static file rather than an editable Google Doc.
createClientDoc.gs
createClientDoc duplicates a template document into a destination folder, reopens the copy, and replaces every bracketed key from the supplied data object with its value.
// Copy a template Doc and fill in client-specific placeholders
function createClientDoc(data) {
var TEMPLATE_ID = 'REPLACE_WITH_TEMPLATE_ID';
var DEST_FOLDER_ID = 'REPLACE_WITH_DEST_FOLDER_ID';
var template = DriveApp.getFileById(TEMPLATE_ID);
var destFolder = DriveApp.getFolderById(DEST_FOLDER_ID);
var copy = template.makeCopy(data.ClientName + ' - Agreement', destFolder);
var doc = DocumentApp.openById(copy.getId());
var body = doc.getBody();
for (var key in data) {
var token = '{{' + key + '}}';
body.replaceText(token, data[key]);
}
doc.saveAndClose();
Logger.log('Created document: ' + copy.getUrl());
return copy.getId();
}- Line 8: makeCopy takes the new file's name and destination folder together, so the duplicate lands in the right place with a useful name in one call.
- Line 10: openById reopens the freshly created copy as a Doc, since makeCopy itself only returns a generic Drive File object.
- Line 13: Looping over the keys already present in the data object means only placeholders you actually supplied a value for get touched.
- Line 15: replaceText runs once per key, substituting every occurrence of that bracketed token throughout the whole document body.
- Line 18: saveAndClose flushes the pending edits, which matters because DocumentApp changes are not guaranteed to be visible elsewhere until the document is closed.
Deploy this example
- 01
Open Apps Script
In the bound spreadsheet: Extensions → Apps Script. For standalone projects, create one at script.google.com and link your Sheet by ID.
- 02
Paste and save
Add a .gs file, paste the code below, rename constants at the top (sheet names, column letters, API property keys), then save.
- 03
Authorize once
Run the main function from the editor. Accept OAuth scopes when prompted — triggers cannot run until authorization succeeds once.
- 04
Add the trigger
Triggers → Add trigger → choose the handler function and event (time-driven, on edit, or on form submit). Delete test triggers before production.
Before you run: copy template doc
- 1Template document built with clearly bracketed placeholders
- 2Destination folder created for finished copies
- 3TEMPLATE_ID and DEST_FOLDER_ID filled in at the top of the script
- 4Function run with sample client data
- 5Generated document reviewed for correct placeholder replacement
- 6Second client tested to confirm no cross-contamination between documents
Frequently asked questions
It is left in the document exactly as written rather than being replaced with the word undefined, since the loop only iterates over keys actually present in the object.
Yes, the create-slides-from-template tutorial follows the identical makeCopy pattern, just swapping DocumentApp for SlidesApp and replaceText for replaceAllText.
Generally yes, replaceText substitutes the matched string while leaving surrounding character formatting intact.
Yes, call getAs('application/pdf') on the finished document right after saveAndClose and save that blob instead of, or alongside, the editable Doc.
DriveApp.getFileById throws immediately with a clear error, which is a fast way to confirm you copied the correct ID from the template's URL.
Yes, body.replaceText searches the whole document body including table cells and headers.