ServicesProductsDocsBlogConsultingAboutContactBook a free call
Back to BlogAn event planner at night catching a freshly made contract as it slides out of a steel press, a messy pile of hand-edited old contracts on the desk beside her.
14 min readMageSheet Team

Auto-Generate Contracts in Google Docs + eSignature

Google Apps ScriptGoogle DocseSignatureDocument AutomationGoogle Workspace

You can generate contracts from Google Sheets with a short Apps Script: each deal row fills a Google Docs template, optional clauses are kept or removed by rule, the finished contract is filed in Drive, and its status is tracked back in the sheet. For the signature, Google Docs now has built-in eSignature on Workspace Individual and on most Business, Enterprise and Education Plus plans: up to 10 signers per request, an audit trail page, and signers who don't need Gmail. What we couldn't find, as of October 2026, is a documented API for it, so a person still clicks "Request signature". If you need that step automated too, the script hands off to an eSignature provider's API. This guide covers both routes, what each costs, and when you have outgrown all of it.

This article explains tooling, not law. Nothing here is legal advice. Have a lawyer review your contract template and confirm which signature type your contracts need.

Why automate contracts at all

The problem with contracts isn't writing them. It's the copy-paste. A typical small-business flow looks like this: open last month's contract, "Save as", change the client name in four places, forget the fifth, update the fee, delete the deposit clause that doesn't apply, export a PDF, email it, then chase it. Every step is a chance to send a client someone else's name or last year's price.

A script removes the copy-paste completely. The deal data is entered once, in the sheet, and everything downstream reads from it. Preparing a contract becomes one menu click instead of a session of editing and proofreading, and, more importantly, a whole class of errors disappears: last month's client name can't survive in clause 7 when nobody edits clause 7 by hand.

If you've already automated invoices this way, this is the same engine with more logic on top. Our automated invoices with Google Docs walkthrough covers the basic copy-template, replace-placeholders, export-PDF mechanics. This post focuses on what contracts add: optional clauses, contract numbering, signature routing and status tracking.

Step 1: Build the template with placeholders

Start from your lawyer-approved contract in Google Docs and replace every variable part with a placeholder in double curly braces, such as {{CLIENT_NAME}}, {{START_DATE}} or {{FEE}}. Keep the names in capitals with underscores so they can't clash with real contract text.

For clauses that only apply to some deals, put each optional clause in its own paragraph and start it with a tag, for example [[DEPOSIT]] or [[NDA]]. The script keeps the paragraph (and strips the tag) when the rule applies, and deletes it when it doesn't.

Three rules that save pain later:

  • Make the template read-only for everyone except one owner. The template is now the contract. Edits go through whoever owns the legal wording, not whoever happens to have the file open.
  • Use one row per contract and one column per placeholder. The column header should match the placeholder name, so adding a field later means adding a column and a placeholder, not changing code.
  • Keep a template version in the footer, such as Template v2026-11. When a dispute comes up a year later, you'll want to know which wording was sent.

Step 2: Turn a row into a contract

The script below reads one row from a Deals sheet, assigns a contract number, copies the template into a contracts folder, resolves the optional clauses, fills the placeholders, and refuses to finish if any placeholder is left unfilled.

const TEMPLATE_ID = 'YOUR_TEMPLATE_DOC_ID';
const FOLDER_ID = 'YOUR_CONTRACTS_FOLDER_ID';

function generateContract(rowNumber) {
  const sheet = SpreadsheetApp.getActive().getSheetByName('Deals');
  const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
  const values = sheet.getRange(rowNumber, 1, 1, headers.length).getValues()[0];
  const deal = Object.fromEntries(headers.map((h, i) => [h, values[i]]));

  if (deal.STATUS !== 'Ready') throw new Error(`Row ${rowNumber} is not marked Ready`);

  const id = deal.CONTRACT_ID || nextContractId_();
  const name = `${id} - ${deal.CLIENT_NAME} - Services Agreement`;
  const file = DriveApp.getFileById(TEMPLATE_ID)
    .makeCopy(name, DriveApp.getFolderById(FOLDER_ID));
  const doc = DocumentApp.openById(file.getId());
  const body = doc.getBody();

  // 1. Optional clauses: paragraphs starting with [[TAG]]
  const include = { DEPOSIT: Number(deal.DEPOSIT) > 0, NDA: deal.NDA === true };
  body.getParagraphs().forEach(p => {
    const m = p.getText().match(/^\[\[(\w+)\]\]\s*/);
    if (!m) return;
    if (include[m[1]]) p.editAsText().deleteText(0, m[0].length - 1);
    else p.removeFromParent();
  });

  // 2. Placeholders: every column header can be used as {{HEADER}}
  const tz = Session.getScriptTimeZone();
  Object.entries({ ...deal, CONTRACT_ID: id }).forEach(([key, value]) => {
    const text = value instanceof Date
      ? Utilities.formatDate(value, tz, 'd MMMM yyyy')
      : String(value ?? '');
    body.replaceText(`\\{\\{${key}\\}\\}`, text);
  });

  // 3. Never send a contract with a placeholder left in it
  const leftover = body.findText('\\{\\{[A-Z_]+\\}\\}');
  if (leftover) {
    throw new Error('Unfilled placeholder: ' + leftover.getElement().asText().getText());
  }
  doc.saveAndClose();

  // 4. Write the result back to the sheet
  const col = h => headers.indexOf(h) + 1;
  sheet.getRange(rowNumber, col('CONTRACT_ID')).setValue(id);
  sheet.getRange(rowNumber, col('DOC_URL')).setValue(file.getUrl());
  sheet.getRange(rowNumber, col('STATUS')).setValue('Draft generated');
  sheet.getRange(rowNumber, col('UPDATED_AT')).setValue(new Date());
}

function nextContractId_() {
  const lock = LockService.getScriptLock();
  lock.waitLock(10000);
  try {
    const props = PropertiesService.getScriptProperties();
    const n = Number(props.getProperty('LAST_CONTRACT') || 0) + 1;
    props.setProperty('LAST_CONTRACT', String(n));
    return `C-${new Date().getFullYear()}-${String(n).padStart(4, '0')}`;
  } finally {
    lock.releaseLock();
  }
}

A few details that matter in production:

  • replaceText takes a regular expression as a string, not a literal. Google's reference says it uses the RE2 library and that backslashes must be escaped, which is why the braces above are written \\{\\{. Skip that and placeholders with special characters fail silently or throw.
  • The leftover-placeholder check is the most valuable line in the script. A contract that reaches a client with a placeholder still in it does more damage than one that never goes out.
  • The contract number comes from a locked counter, so two people generating contracts at the same moment can't get the same number.
  • Don't put an optional clause in the very last paragraph of the document. Removing the final paragraph of a document body is a commonly reported error in Apps Script, because a Docs body always has to end with a paragraph. A closing signature block underneath avoids the problem.
  • Quotas: a script can create 250 documents a day on a personal Gmail account and 1,500 on Workspace, and each run is capped at 6 minutes. For contracts that is rarely a constraint, but bulk renewals should run in batches.

Add a custom menu ("Contracts, then Generate for selected row") and the person who closes the deal never has to open the editor.

Step 3: Signature routing, two realistic routes

Once the contract exists, it has to be signed. There are two routes that hold up, and they differ mainly in how much is automated.

Route A: Google Docs eSignatureRoute B: eSignature provider API
Who sends the requestA person, from Tools, then eSignature, in the DocThe script, through the provider's API
Plans / costIncluded on eligible Workspace plans (below)The provider's subscription or per-envelope pricing
Signers per requestUp to 10Depends on the provider's plan
Signer needs GmailNoNo
Audit trailFinal PDF ends with an audit trail pageProvider's certificate of completion
Status back into the sheetManual, or a best-effort folder checkAutomatic, through the provider's webhooks
Stronger identity checks (SMS, ID)Not offeredAvailable on many providers

Route A, Google's built-in eSignature, is the right default for a small business already on an eligible plan. From Google's help pages, as of October 2026:

  • It is available on Workspace Individual, and on Business Standard and Plus, Enterprise Starter, Standard and Plus, Enterprise Essentials and Essentials Plus, and Education Plus when an administrator enables it. Business Starter isn't listed.
  • It works on Google Docs, on PDFs stored in Drive, and on Word files opened in Docs.
  • A request can have up to 10 signers and up to 200 fields: signature, initials, name, text and date signed.
  • Sending locks a PDF copy, so the signed version can't drift from what was sent. To change the terms, you cancel and resend.
  • Optional reminders go out every 3 days, for up to 9 days.
  • When everyone has signed, the final PDF lands in the same folder as the original (or in the sender's My Drive if folder permissions get in the way), and its last page is an audit trail with timestamps and the requester's and signers' names and emails.
  • Signers don't need a Gmail address. Google listed non-Gmail signers among the features when eSignature went generally available in December 2023. Send one test request to an outside address before you rely on it.
  • Check the cross-border rule. Google's eSignature terms for Workspace Individual say you may only use it with counterparties in the same legal jurisdiction as you. If you sign contracts with clients in other countries, check the terms for your plan before relying on Route A, or use a dedicated provider (Route B).

The limit for automation: as of October 2026, we found no documented Apps Script or API method for sending a Google eSignature request. So in Route A the script prepares everything, then a person opens the generated Doc and clicks "Request signature". That's about 30 seconds of human work per contract, and for most teams sending a handful of contracts a week it's a fair trade for paying nothing extra.

Route B, a provider API, makes sense when contracts are high-volume, need signing order or stronger identity checks, or must go out without anyone touching them. The script exports the generated Doc to PDF (file.getAs(MimeType.PDF)), posts it to the provider's API with UrlFetchApp along with signer emails and field positions, and stores the provider's envelope ID in the row. When the provider calls back to an Apps Script web app on "completed", the script marks the row signed and files the signed PDF. Our webhooks guide shows how to receive those callbacks safely.

Step 4: Track status in the sheet

A contract generator without status tracking only moves the chaos one step down the line. Give every row a STATUS column with a fixed list (data validation), and let only the script or a defined person move it forward:

StatusSet byWhat happens next
ReadySalespersonScript can generate the contract
Draft generatedScriptInternal review, if your process needs one
Sent for signaturePerson (Route A) or script (Route B)Reminders run; the date sent is logged
SignedWebhook (Route B), or person or folder check (Route A)Signed PDF linked in the row; follow-on tasks start
Rejected / CancelledPerson or webhookReason recorded; row reopened or closed

Then add one daily trigger that emails the owner a list of contracts that have been "Sent for signature" for more than five days. That single report often does more than a reminder tool, because a person follows up.

With Route A, Google doesn't expose signature status to scripts, so you have two choices. The dependable one is a menu item, "Mark selected row as signed", that also asks for the signed PDF link. The best-effort one is a timed script that looks in the contract's folder for a new PDF whose name contains the contract number. That works only if people keep the contract number in the PDF title when sending, so test it on your own account before relying on it.

Once a contract is signed, the same row can trigger the rest: create the client's Drive folder, add the start date to Calendar, and raise the first invoice from your Google Sheets invoice template. That chain is where the time savings compound.

What DocuSign and PandaDoc cost by comparison

Dedicated tools do more, and they charge for it every month. DocuSign's published eSignature prices on an annual plan, as of October 2026:

DocuSign planPrice (annual plan)Envelope allowance
Personal$11/month5 envelopes per month
Standard$30/user/month100 envelopes per user per year
Business Pro$45/user/month100 envelopes per user per year, plus bulk send
EnhancedContact salesCustom

PandaDoc also charges per seat, with document-building and CRM features on its higher tiers. Check its pricing page for current figures, since third-party summaries don't agree with each other.

For three people sending contracts on DocuSign Standard, that's $1,080 a year. The setup in this post runs on a Workspace plan you may already be paying for, plus a one-off build. Our guide to replacing SaaS with Google Workspace runs this comparison across other tool categories.

To be fair to the dedicated tools, they earn their price when you need signing order, SMS or ID verification, qualified signatures, in-document negotiation, payment collection at signing, or hundreds of envelopes a month. If those are on your list, use one and let Apps Script feed it the data (Route B).

When you need a real contract management system

A sheet plus a template stops being enough when:

  • Contracts are negotiated, not just signed. Redlines, counter-proposals and clause approval by legal need version comparison and a review workflow. Docs comments don't scale to that.
  • You manage obligations after signature. Renewal notice periods, price escalations and SLA commitments across hundreds of live contracts need a database with alerts, not a status column.
  • You're in a regulated sector where a regulator or auditor expects a specific signature level, retention rules or access logs.
  • Many outside parties need their own logins to view and sign their contracts, rather than an email link.

That's contract lifecycle management (CLM) territory: a dedicated product, or a custom system built around your process. Until then, a well-built template, a script and a status column cover more than most businesses expect.

Electronic signatures are widely accepted, but not universally. In the US, the ESIGN Act says a contract or signature may not be denied legal effect solely because it is electronic, but it excludes areas such as wills, testamentary trusts and family-law matters. In the EU, Article 25 of the eIDAS Regulation says an electronic signature can't be denied legal effect or admissibility as evidence solely because it is electronic or isn't a qualified electronic signature, and gives qualified electronic signatures the equivalent legal effect of a handwritten one. Beyond that, how much weight a simpler signature carries depends on national law and the facts of the case. Google's signer terms say Google isn't a party to signed documents, can't guarantee their enforceability in your jurisdiction, and that not every document is suitable for electronic signing.

Automation doesn't change any of that. It only makes sure the right wording reaches the right person. Have a lawyer approve the template and the signature method once, and let the script apply that decision consistently every time.

Want this built around your contracts?

If your team still builds contracts by copy-paste, the fix is usually a small, fixed-scope build, not a new subscription.

  • Book a free 30-minute discovery call. Walk us through how a deal becomes a signed contract today. We'll tell you which steps a script can take over, whether Google's built-in eSignature is enough, and what a fixed-scope build would involve.
  • Prefer to write? Send us a message with a redacted copy of your template and a description of your current flow, and we'll reply with how we'd approach it.

See how we scope this kind of work on our Google Apps Script development service page.

Frequently Asked Questions

Are contracts signed with Google Docs eSignature legally binding?

In many places electronic signatures are recognised, but whether your contract is binding depends on where you are and what you are signing. This is not legal advice. In the US, the ESIGN Act says a signature or contract may not be denied legal effect solely because it is electronic, but it excludes matters such as wills, testamentary trusts and family law. In the EU, the eIDAS Regulation says an electronic signature can't be denied legal effect or admissibility as evidence solely because it is electronic, and gives a qualified electronic signature the equivalent legal effect of a handwritten one. Google's own signer terms say Google is not a party to signed documents, cannot guarantee their validity or enforceability in your jurisdiction, and that not every document is suitable for electronic signing. Google's Workspace Individual eSignature terms also limit its use to counterparties in the same legal jurisdiction as you, so cross-border contracts may need a different tool. For anything high-value or regulated, ask a lawyer which signature level you need.

Which Google Workspace plans include eSignature?

According to Google's help pages, eSignature is available on Workspace Individual, and on Business Standard, Business Plus, Enterprise Starter, Enterprise Standard, Enterprise Plus, Enterprise Essentials, Enterprise Essentials Plus and Education Plus when an administrator enables it. Business Starter and free personal Gmail accounts are not on the list; Google points users without an eligible plan to Workspace Individual. If the option is missing on an eligible plan, your Workspace administrator has to switch it on.

Does the signer need a Google account?

They don't need Gmail. When Google made eSignature generally available for Workspace Individual in December 2023, the announcement listed the ability to request a signature from non-Gmail users, and the signer works from an email link. Google's help pages don't spell out every step an external signer may see, so send a test request to an outside address before rolling it out. Bear in mind that the email address is the identity check: Google's signer terms point out that anyone with access to an email account may be able to use it to sign. If you need stronger identity verification, such as SMS codes or ID checks, use a dedicated eSignature provider.

Can Apps Script send the Google eSignature request automatically?

Not that we can find. Google documents eSignature as something you start from Tools, then eSignature, in Docs, or from the PDF menu in Drive, and we found no documented Apps Script or API method to send a request as of October 2026. So with the built-in feature, a script prepares the contract and a person clicks Request signature. If you need the whole flow hands-free, the script can call an eSignature provider's API instead, at that provider's price.

How many contracts can Apps Script generate per day?

Google's quotas allow a script to create 250 documents a day on a personal Gmail account and 1,500 on a Google Workspace account, and each run stops at 6 minutes. For most businesses that is far more than needed. If you generate hundreds at once, process them in batches so a single run doesn't hit the time limit.

Stay Updated

Get the latest insights on AI, e-commerce, and Magento delivered to your inbox.

We value your privacy

We use essential cookies to make our site work. With your consent, we may also use non-essential cookies to improve user experience and analyze website traffic. By clicking "Accept", you agree to our website's cookie use as described in our Cookie Policy.