dịch vụ
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:
- 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. - Không gộp ô, không để dòng trống xen giữa dữ liệu.
- 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).
- 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.
- Lưu và đóng file Excel dữ liệu.
- Mở file mẫu Word → tab Mailings → Start Mail Merge → Letters.
- Select Recipients → Use an Existing List... → chọn file Excel → chọn sheet
DuLieu$. - Đặ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». - Bấm Preview Results, dùng nút mũi tên để xem từng hợp đồng.
- 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:
- Nhấn Alt + F9 để hiện mã trường (hiện cho cả văn bản).
- 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" }
- 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).Textlấy đúng chữ đang hiển thị trong ô: số tiền có dấu chấm, ngày dạng27/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ùngdoc.Content.Find, placeholder nằm trong header hay text box sẽ bị bỏ sót. LamSachTenthay dấu/trong15/2026/HĐMBbằ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:
- 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à
MsgBoxtrong 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. - 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ị:
- 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à/edittrên thanh địa chỉ. - 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/). - Trong sheet
DuLieu, thêm cột cuối tênLinkPDFđể script ghi link vào. - 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.Textbê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ểuTue Oct 27 2026....replaceTextnhậ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êmgetHeader()vàgetFooter()(hai hàm này trả vềnullnếu tài liệu không có, nên có kiểm traif (p)).- Dòng đã có link ở cột
LinkPDFsẽ 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ộtSoHDđể biết thuộc hợp đồng nào, cùng các cộtTenHang,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à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.
Bài viết liên quan

01/11/2026
Phần mềm quản lý riêng hay có sẵn: doanh nghiệp nhỏ nên chọn gì?
Doanh nghiệp nhỏ nên mua phần mềm quản lý có sẵn hay làm phần mềm riêng? Checklist 10 câu tự đánh giá, so sánh chi phí, rủi ro và cách thử trước khi quyết định.

31/10/2026
Tạo PDF hàng loạt từ Google Sheets bằng Apps Script và tự gửi email
Tự động tạo PDF hàng loạt từ Google Sheets bằng Apps Script: mỗi nhân viên một phiếu lương PDF, lưu Drive, gửi email đính kèm, có chế độ chạy thử và mã mẫu.

29/10/2026
Landing page là gì? Khi nào cửa hàng nhỏ nên làm thay vì website
Landing page là gì, khác website ở đâu? Checklist giúp cửa hàng nhỏ chọn landing page, website hay chưa cần cả hai, kèm cấu trúc 1 trang bán hàng hiệu quả.