Bỏ qua để đến nội dung chính
Điền NguyễnAZ

dịch vụ

Tự động tạo hợp đồng Word từ Excel, Google Sheets: 3 cách dễ đến khó

27/10/2026· Điền Nguyễn AZ
Tự động tạo hợp đồng Word từ Excel, Google Sheets: 3 cách dễ đến khó

Muốn hết cảnh mở hợp đồng cũ, gõ lại tên, số CCCD, địa chỉ, số tiền cho từng khách? Có ba cách, xếp từ dễ đến khó: Mail Merge có sẵn trong Word, không cần code; macro VBA trong Excel để mỗi hợp đồng ra một file Word/PDF riêng; và Apps Script nếu dữ liệu nằm trên Google Sheets.

Lợi ích lớn nhất: mỗi thông tin (tên, CCCD, số tiền) chỉ nhập một lần ở bảng dữ liệu, mọi chỗ trong hợp đồng lấy từ đó nên không còn cảnh sót tên khách cũ hay số tiền bằng số và bằng chữ lệch nhau. Bài này đi qua phần chuẩn bị chung, rồi từng cách kèm mã mẫu chạy được.

Bước chuẩn bị chung: dữ liệu nguồn và file mẫu

Bảng dữ liệu: 1 dòng = 1 hợp đồng

Tạo sheet tên DuLieu, dòng 1 là tiêu đề cột, từ dòng 2 trở đi mỗi dòng là một hợp đồng. Ví dụ:

SoHD TenKhach CCCD DiaChi SoTien SoTienChu NgayKy
15/2026/HĐMB Nguyễn Văn An 040095001234 Xóm 3, Hưng Tân, Hưng Nguyên 12.500.000 Mười hai triệu năm trăm nghìn đồng 27/10/2026
16/2026/HĐMB Trần Thị Bình 040190004567 12 Lê Lợi, TP Vinh 250.000.000 Hai trăm năm mươi triệu đồng 28/10/2026

Một số quy tắc giúp tránh lỗi về sau:

  1. Tiêu đề cột viết liền, không dấu, không khoảng trắng (TenKhach, không phải "Tên khách"). Tên này sẽ dùng làm placeholder trong file mẫu.
  2. Không gộp ô, không để dòng trống xen giữa dữ liệu.
  3. Cột CCCD phải định dạng Text trước khi nhập (chọn cột → Home → ô Number Format → Text). CCCD 12 số thường bắt đầu bằng 0; nếu để dạng số, Excel sẽ tự xóa số 0 đầu (040095001234 thành 40095001234).
  4. Ngày ký nhập đúng kiểu ngày và định dạng hiển thị dd/mm/yyyy, để không bị đảo ngày–tháng khi đưa sang Word.

File mẫu Word: đánh dấu chỗ cần điền

Mở hợp đồng chuẩn, xóa thông tin khách cũ. Với cách 2 và cách 3, gõ placeholder trùng tên cột, đặt trong hai cặp ngoặc nhọn:

Bên B (Bên mua): {{TenKhach}}
Số CCCD: {{CCCD}}          Địa chỉ: {{DiaChi}}
Giá trị hợp đồng: {{SoTien}} đồng (Bằng chữ: {{SoTienChu}})
Hợp đồng số: {{SoHD}}, ký ngày {{NgayKy}}

Định dạng chữ (đậm, nghiêng, font) đặt sẵn trên placeholder sẽ được giữ cho giá trị điền vào. Nếu bạn chưa có file mẫu gọn gàng, xem thêm cách tạo template hợp đồng tái sử dụng trong Word.

Với cách 1 (Mail Merge), không gõ {{...}} mà chèn Merge Field như hướng dẫn bên dưới.

Cách 1 — Mail Merge: không cần viết code

Phù hợp khi bạn cần in nhanh vài chục hợp đồng và chấp nhận kết quả là một file Word chứa tất cả, mỗi hợp đồng bắt đầu ở trang mới.

  1. Lưu và đóng file Excel dữ liệu.
  2. Mở file mẫu Word → tab Mailings → Start Mail Merge → Letters.
  3. Select Recipients → Use an Existing List... → chọn file Excel → chọn sheet DuLieu$.
  4. Đặt con trỏ vào chỗ cần điền → Insert Merge Field → chọn cột tương ứng. Word chèn ký hiệu dạng «TenKhach».
  5. Bấm Preview Results, dùng nút mũi tên để xem từng hợp đồng.
  6. Finish & Merge → Edit Individual Documents → All → OK. Word tạo file mới chứa toàn bộ hợp đồng; lưu lại hoặc File → Save As → chọn PDF.

Sửa định dạng số tiền và ngày tháng bằng mã trường

Lỗi kinh điển: số tiền 12500000 hiện không có dấu phân cách, ngày 27/10/2026 thành 10/27/2026. Cách sửa là thêm "switch" vào mã trường:

  1. Nhấn Alt + F9 để hiện mã trường (hiện cho cả văn bản).
  2. Sửa trường số tiền và ngày như sau (giữ nguyên dấu ngoặc nhọn của Word, không gõ tay ngoặc nhọn mới):
{ MERGEFIELD SoTien \# "#,##0" }
{ MERGEFIELD NgayKy \@ "dd/MM/yyyy" }
  1. Nhấn Alt + F9 lần nữa để ẩn mã, rồi Preview Results kiểm tra lại.

Giải thích: \# là định dạng số, "#,##0" nghĩa là số nguyên có phân cách hàng nghìn. \@ là định dạng ngày; chú ý MM viết hoa là tháng, mm viết thường là phút. Dấu phân cách hiển thị (chấm hay phẩy) có thể theo cài đặt vùng của Windows; nếu vẫn chưa đúng ý, tạo một cột văn bản trong Excel đã định dạng sẵn rồi trộn cột đó.

Chi tiết từng bước Mail Merge có ở bài Mail Merge trong Word: gửi thư hàng loạt tự động.

Giới hạn của Mail Merge: không tự lưu mỗi hợp đồng thành file riêng đặt tên theo khách, và không xử lý được phụ lục có số dòng hàng hóa khác nhau cho từng hợp đồng. Khi cần những điều đó, chuyển sang cách 2 hoặc 3.

Cách 2 — Macro VBA trong Excel: mỗi hợp đồng một file Word + PDF

Macro dưới đây đọc từng dòng sheet DuLieu, mở một bản mới từ file mẫu, thay mọi {{TenCot}} bằng giá trị tương ứng (kể cả trong header, footer, text box), rồi lưu thành .docx và .pdf đặt tên theo số hợp đồng và tên khách.

Cách cài: lưu file Excel dạng Excel Macro-Enabled Workbook (*.xlsm) → nhấn Alt + F11 → Insert → Module → dán mã → sửa 2 đường dẫn ở đầu → nhấn F5 để chạy. Không cần bật thư viện Word vì mã dùng CreateObject.

Option Explicit

Sub TaoHopDongTuExcel()
    Const MAU As String = "D:\HopDong\Mau_HopDong.docx"   ' file mau co {{...}}
    Const THU_MUC As String = "D:\HopDong\DaTao\"         ' thu muc luu ket qua
    Const XUAT_PDF As Boolean = True

    Dim ws As Worksheet, wdApp As Object, doc As Object
    Dim lastRow As Long, lastCol As Long, r As Long, c As Long, dem As Long
    Dim tenFile As String

    Set ws = ThisWorkbook.Worksheets("DuLieu")
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
    lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column

    Set wdApp = CreateObject("Word.Application")
    wdApp.Visible = False

    For r = 2 To lastRow
        If ws.Cells(r, 1).Text <> "" Then
            Set doc = wdApp.Documents.Add(Template:=MAU)
            For c = 1 To lastCol
                ThayTheMoiNoi doc, "{{" & ws.Cells(1, c).Text & "}}", ws.Cells(r, c).Text
            Next c
            tenFile = THU_MUC & LamSachTen(ws.Cells(r, 1).Text & "_" & ws.Cells(r, 2).Text)
            doc.SaveAs2 Filename:=tenFile & ".docx", FileFormat:=16      ' 16 = .docx
            If XUAT_PDF Then
                doc.ExportAsFixedFormat OutputFileName:=tenFile & ".pdf", ExportFormat:=17 ' 17 = PDF
            End If
            doc.Close SaveChanges:=False
            dem = dem + 1
        End If
    Next r

    wdApp.Quit
    Set wdApp = Nothing
    MsgBox "Da tao xong " & dem & " hop dong trong " & THU_MUC
End Sub

' Thay the trong than van ban, header, footer, text box...
Sub ThayTheMoiNoi(doc As Object, tim As String, thay As String)
    Dim rng As Object
    For Each rng In doc.StoryRanges
        Do
            With rng.Find
                .ClearFormatting
                .Replacement.ClearFormatting
                .Text = tim
                .Replacement.Text = thay
                .Forward = True
                .Wrap = 1               ' wdFindContinue
                .MatchWildcards = False
                .Execute Replace:=2     ' wdReplaceAll
            End With
            Set rng = rng.NextStoryRange
        Loop Until rng Is Nothing
    Next rng
End Sub

' Bo ky tu khong duoc phep trong ten file (vd dau / trong so hop dong)
Function LamSachTen(s As String) As String
    Dim k As Variant
    For Each k In Array("\", "/", ":", "*", "?", """", "<", ">", "|")
        s = Replace(s, k, "-")
    Next k
    LamSachTen = Trim(s)
End Function

Giải thích các phần quan trọng:

  • Documents.Add(Template:=MAU) tạo tài liệu mới dựa trên file mẫu, nên file mẫu gốc không bao giờ bị ghi đè.
  • ws.Cells(r, c).Text lấy đúng chữ đang hiển thị trong ô: số tiền có dấu chấm, ngày dạng 27/10/2026, CCCD giữ số 0. Đây là lý do định dạng cẩn thận ở bước chuẩn bị.
  • Vòng lặp StoryRanges + NextStoryRange đi qua mọi "vùng" của tài liệu. Nếu chỉ dùng doc.Content.Find, placeholder nằm trong header hay text box sẽ bị bỏ sót.
  • LamSachTen thay dấu / trong 15/2026/HĐMB bằng -, vì Windows không cho dùng / trong tên file. Kết quả: 15-2026-HĐMB_Nguyễn Văn An.pdf.

Hai lưu ý ít bài nào nói:

  1. Trình soạn thảo VBA không hiển thị được tiếng Việt có dấu trong chuỗi gõ trực tiếp, nên chú thích và MsgBox trong mã viết không dấu. Dữ liệu tiếng Việt lấy từ ô Excel vẫn được điền vào Word bình thường.
  2. Tính năng Find/Replace của Word giới hạn 255 ký tự cho chuỗi thay thế. Placeholder cho đoạn dài (ví dụ điều khoản riêng) cần tách thành nhiều cột ngắn.

Cách 3 — Google Sheets + Apps Script + Google Docs

Phù hợp khi nhân viên nhập dữ liệu trên Google Sheets, kể cả từ điện thoại, và muốn hợp đồng PDF nằm sẵn trên Google Drive để gửi link cho khách.

Chuẩn bị:

  1. Tạo file mẫu bằng Google Docs với placeholder {{TenCot}} như trên. Lấy ID của file: phần chuỗi dài giữa /d/ và /edit trên thanh địa chỉ.
  2. Tạo một thư mục trên Drive để chứa kết quả, lấy ID thư mục (phần sau /folders/).
  3. Trong sheet DuLieu, thêm cột cuối tên LinkPDF để script ghi link vào.
  4. Trong Sheets: Extensions (Tiện ích mở rộng) → Apps Script, dán mã sau, thay 2 ID, bấm Save.
const TEMPLATE_ID = 'DAN_ID_FILE_MAU_VAO_DAY';
const FOLDER_ID = 'DAN_ID_THU_MUC_VAO_DAY';

function onOpen() {
  SpreadsheetApp.getUi().createMenu('Hợp đồng')
    .addItem('Tạo hợp đồng cho dòng chưa có link', 'taoHopDong')
    .addToUi();
}

function taoHopDong() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('DuLieu');
  const data = sheet.getDataRange().getDisplayValues(); // lấy đúng chữ đang hiển thị
  const headers = data[0];
  const colLink = headers.indexOf('LinkPDF');
  if (colLink === -1) throw new Error('Thiếu cột LinkPDF ở dòng tiêu đề');

  const template = DriveApp.getFileById(TEMPLATE_ID);
  const folder = DriveApp.getFolderById(FOLDER_ID);

  for (let i = 1; i < data.length; i++) {
    const row = data[i];
    if (!row[0] || row[colLink]) continue; // bỏ qua dòng trống hoặc đã tạo

    const name = (row[0] + '_' + row[1]).replace(/[\\/:*?"<>|]/g, '-');
    const copy = template.makeCopy(name, folder);
    const doc = DocumentApp.openById(copy.getId());
    const parts = [doc.getBody(), doc.getHeader(), doc.getFooter()];

    headers.forEach((h, c) => {
      if (!h || c === colLink) return;
      parts.forEach(p => { if (p) p.replaceText('\\{\\{' + h + '\\}\\}', row[c]); });
    });
    doc.saveAndClose();

    const pdf = folder.createFile(copy.getAs(MimeType.PDF)).setName(name + '.pdf');
    sheet.getRange(i + 1, colLink + 1).setValue(pdf.getUrl());
  }
}

Tải lại trang Sheets, menu Hợp đồng xuất hiện. Lần chạy đầu Google sẽ hỏi cấp quyền truy cập Drive, Docs, Sheets; chọn tài khoản của bạn và cho phép.

Giải thích:

  • getDisplayValues() tương đương .Text bên VBA: lấy giá trị đúng như đang hiển thị, tránh ngày bị đổi thành dạng dài kiểu Tue Oct 27 2026....
  • replaceText nhận chuỗi tìm theo biểu thức chính quy, nên ngoặc nhọn được viết \\{\\{ để chắc chắn được hiểu là ký tự thường. Vì vậy tiêu đề cột chỉ nên dùng chữ và số.
  • getBody() không bao gồm header/footer, nên mã xử lý thêm getHeader() và getFooter() (hai hàm này trả về null nếu tài liệu không có, nên có kiểm tra if (p)).
  • Dòng đã có link ở cột LinkPDF sẽ bị bỏ qua. Muốn tạo lại hợp đồng nào, xóa link của dòng đó rồi chạy lại.

Nếu đây là lần đầu bạn dùng Apps Script, đọc trước bài Google Apps Script tự động hóa Sheets cơ bản.

Phần hay bị bỏ sót 1: số tiền bằng chữ

Hợp đồng ở Việt Nam luôn ghi số tiền bằng chữ, và đây là chỗ hay sai nhất khi gõ tay. Mail Merge có switch \* CardText nhưng chỉ đọc ra tiếng Anh, nên không dùng được.

Trên Google Sheets, thêm hàm tự tạo sau vào cùng dự án Apps Script, rồi ở ô F2 gõ =DOCSOTIEN(E2):

/** Đọc số tiền thành chữ tiếng Việt. @customfunction */
function DOCSOTIEN(so) {
  so = Math.round(Number(so));
  if (isNaN(so) || so < 0) return '';
  if (so === 0) return 'Không đồng';
  const chuSo = ['không', 'một', 'hai', 'ba', 'bốn', 'năm', 'sáu', 'bảy', 'tám', 'chín'];
  const donVi = ['', ' nghìn', ' triệu', ' tỷ', ' nghìn tỷ'];
  const docBaSo = (n, dayDu) => {
    const tram = Math.floor(n / 100), chuc = Math.floor(n % 100 / 10), dv = n % 10;
    const kq = [];
    if (dayDu || tram > 0) kq.push(chuSo[tram] + ' trăm');
    if (chuc === 0 && dv > 0 && kq.length > 0) kq.push('lẻ');
    if (chuc === 1) kq.push('mười');
    if (chuc > 1) kq.push(chuSo[chuc] + ' mươi');
    if (dv === 1 && chuc > 1) kq.push('mốt');
    else if (dv === 5 && chuc > 0) kq.push('lăm');
    else if (dv > 0) kq.push(chuSo[dv]);
    return kq.join(' ');
  };
  const nhom = [];
  while (so > 0) { nhom.push(so % 1000); so = Math.floor(so / 1000); }
  const kq = [];
  for (let i = nhom.length - 1; i >= 0; i--) {
    if (nhom[i] === 0) continue;
    kq.push(docBaSo(nhom[i], i < nhom.length - 1) + donVi[i]);
  }
  const chu = kq.join(' ');
  return chu.charAt(0).toUpperCase() + chu.slice(1) + ' đồng';
}

Hàm tách số thành từng nhóm 3 chữ số (đơn vị, nghìn, triệu, tỷ), đọc từng nhóm và xử lý các quy tắc riêng của tiếng Việt: "mười" và "mươi", "mốt", "lăm", "lẻ", "không trăm" ở nhóm giữa. Ví dụ: 1005 → "Một nghìn không trăm lẻ năm đồng", 3015000 → "Ba triệu không trăm mười lăm nghìn đồng".

Với Excel, viết hàm tương tự bằng VBA phải mã hóa từng chữ có dấu bằng ChrW(...) vì giới hạn tiếng Việt của trình soạn thảo đã nêu ở trên, nên dài hơn nhiều. Cách đơn giản là giữ cột SoTienChu và kiểm tra kỹ khi nhập, hoặc tính cột này trên Google Sheets.

Phần hay bị bỏ sót 2: phụ lục nhiều dòng hàng hóa

Hợp đồng mua bán thường có bảng hàng hóa, khách A mua 2 mặt hàng, khách B mua 7. Một dòng dữ liệu không chứa được bảng này, nên cách làm đúng là tách thành 2 sheet:

  • Sheet DuLieu: thông tin chung, mỗi hợp đồng một dòng.
  • Sheet ChiTiet: mỗi mặt hàng một dòng, có cột SoHD để biết thuộc hợp đồng nào, cùng các cột TenHang, SoLuong, DonGia, ThanhTien.

Trong file mẫu, bảng hàng hóa chỉ cần một dòng mẫu chứa {{TenHang}}, {{SoLuong}}... Script tìm dòng đó, nhân bản theo số mặt hàng, điền từng dòng rồi xóa dòng mẫu. Với Apps Script, phần lõi như sau (items là mảng các mặt hàng đã lọc theo SoHD, mỗi phần tử là object dạng {TenHang: ..., SoLuong: ...}):

function dienBangHang(body, items) {
  const found = body.findText('\\{\\{TenHang\\}\\}');
  if (!found) return;
  // Text → Paragraph → TableCell → TableRow
  const rowMau = found.getElement().getParent().getParent().getParent().asTableRow();
  const table = rowMau.getParent().asTable();
  const viTri = table.getChildIndex(rowMau);
  items.forEach((item, k) => {
    const rowMoi = table.insertTableRow(viTri + 1 + k, rowMau.copy());
    Object.keys(item).forEach(key => rowMoi.replaceText('\\{\\{' + key + '\\}\\}', String(item[key])));
  });
  table.removeRow(viTri); // xóa dòng mẫu
}

Gọi hàm này trong vòng lặp của taoHopDong, trước doc.saveAndClose(). Đây là phần Mail Merge không làm được và cũng là lý do nhiều văn phòng chuyển sang dùng script.

Lỗi thường gặp

Số tiền mất dấu phân cách, hoặc có thêm số lẻ thập phân. Nguyên nhân: lấy giá trị gốc thay vì giá trị hiển thị. Cách sửa: Mail Merge thêm \# "#,##0"; VBA dùng .Text; Apps Script dùng getDisplayValues().

Ngày bị đảo thành tháng/ngày. Nguyên nhân: Word đọc ngày theo kiểu Mỹ. Cách sửa: switch \@ "dd/MM/yyyy" (MM hoa), hoặc định dạng ô ngày trong Excel/Sheets là dd/mm/yyyy trước khi chạy script.

Mất số 0 đầu CCCD, số điện thoại. Nguyên nhân: cột đang ở dạng số. Cách sửa: chuyển cột sang Text rồi nhập lại; dữ liệu đã nhập không tự lấy lại số 0.

Ô hiện #### và hợp đồng cũng bị điền ####. Nguyên nhân: cột quá hẹp, .Text lấy đúng những gì hiển thị. Cách sửa: nới rộng cột trước khi chạy macro.

Placeholder không được thay. Nguyên nhân thường gặp: tên trong mẫu khác tiêu đề cột (thừa khoảng trắng, sai hoa thường), hoặc Word tự đổi định dạng giữa chừng khiến placeholder bị tách. Cách sửa: gõ lại placeholder liền một lần, không định dạng riêng từng phần.

Chữ tiếng Việt lỗi trong MsgBox, chú thích VBA. Đây là giới hạn của trình soạn thảo VBA, không phải lỗi dữ liệu. Viết thông báo không dấu.

File mẫu bị sửa nhầm. Lưu mẫu ở thư mục riêng, đặt thuộc tính Read-only, hoặc lưu dạng .dotx. Bản Google Docs mẫu chỉ cấp quyền xem cho người khác.

Bảng so sánh 3 cách

Tiêu chí Mail Merge Macro VBA (Excel) Apps Script (Sheets)
Cần viết code Không Có (dán mã mẫu) Có (dán mã mẫu)
Mỗi hợp đồng một file Không, gộp chung Có, Word + PDF Có, Docs + PDF trên Drive
Đặt tên file theo khách Không Có Có
Header/footer, text box Có Có (StoryRanges) Header/footer có, cần xử lý riêng
Số tiền bằng chữ tiếng Việt Phải có cột sẵn Phải có cột sẵn Có hàm DOCSOTIEN
Bảng hàng hóa nhiều dòng Không phù hợp Làm được, cần thêm mã Làm được, cần thêm mã
Chạy trên điện thoại Không Không Nhập dữ liệu được, chạy menu trên máy tính
Phù hợp Vài chục bản in nhanh Văn phòng dùng Word trên Windows Nhóm làm việc online

Nên tự làm hay thuê viết công cụ riêng?

Tự làm theo bài này là hợp lý nếu mẫu hợp đồng của bạn ổn định, dưới khoảng chục trường thông tin, và có người trong văn phòng chịu khó thử nghiệm. Bắt đầu với Mail Merge, khi thấy cần file riêng cho từng khách thì chuyển sang mã mẫu ở trên.

Nên cân nhắc nhờ người viết công cụ khi gặp một trong các trường hợp sau:

  • Nhiều loại hợp đồng, phụ lục, biên bản dùng chung một nguồn dữ liệu.
  • Bảng hàng hóa phức tạp, cần tự tính thành tiền, VAT, tổng bằng chữ.
  • Cần form nhập liệu kiểm tra lỗi (thiếu CCCD, sai định dạng ngày) trước khi tạo.
  • Cần tự động gửi PDF qua email, lưu theo thư mục từng khách hoặc từng tháng.

Trước khi thuê, hãy chuẩn bị sẵn: 1 file mẫu hợp đồng thật, bảng dữ liệu mẫu 3–5 dòng (thông tin có thể làm giả), và danh sách những việc bạn đang làm tay mỗi lần. Ba thứ này giúp bên viết công cụ hiểu đúng yêu cầu ngay từ đầu.

Cần công cụ tạo hợp đồng riêng cho văn phòng?

Điền Nguyễn AZ nhận viết macro VBA và Google Apps Script theo đúng mẫu văn bản, quy trình của từng đơn vị: tạo hợp đồng, phụ lục, biên bản hàng loạt, xuất PDF, lưu và gửi tự động. Gửi file mẫu để được tư vấn cách làm phù hợp.

📞 Hotline / Zalo: 0362 465 361

👉 Xem chi tiết: Dịch vụ viết VBA và Google Apps Script

#tự động tạo hợp đồng word#điền dữ liệu excel vào word#vba tạo hợp đồng#apps script google docs#mail merge hợp đồng

Tài liệu miễn phí

30 phím tắt Excel hay dùng nhất

Tổng hợp phím tắt giúp thao tác Excel nhanh hơn — điền thông tin để tải file PDF miễn phí về dùng ngay.