ENFR
See the add-ins
Practical guide

Filling a Word document from Excel

Word's mail merge can turn out a hundred letters at once. What it cannot do is turn out one document, saved in the right place, under the right name, when you click a row. Here is the theoretical principle in VBA, and the many technical obstacles of a home-made build.

Updated on 17 September 2026 · 11 min read · Excel on Windows

Why mail merge is not enough

Mail merge is designed for a mass mailing: one data source, one template, and a merge that produces one big hundred-page document. Three things are missing from it for day-to-day use:

The method below turns the logic around: Excel drives Word, opens a template, replaces tags with the row's values, and saves the result wherever you like, under whatever name you like.

Preparing the Word template

Create an ordinary Word document — a quote, an engagement letter, a contract — and replace the variable values with tags. The shape does not matter as long as it is unlikely to occur in ordinary text ; here we use <$Nom> :

Subject: <$Categorie> engagement

Dear <$Client>,

We confirm our attendance on <$Date>
for an amount of <$Montant> euros.

On the Excel side, a sheet called Donnees where the column headers carry exactly the names of the tags — that is what will save you touching the code when you add a field:

ABCD
1TemplateD:\Modeles\devis.docx
2OutputD:\Clients\Dupont\devis.docx
3ClientCategorieDateMontant
4Dupont SARLBuilding17/09/20261 250
Row 3 serves twice over: as a readable header, and as the list of tags to replace.
Beware of tags cut in two

Word sometimes splits a word into several internal fragments — after a spellcheck pass, or if you edited the tag character by character. The search then finds nothing at all, with no error and no message. The remedy is simple: in the template, delete the whole tag and retype it in one go.

The VBA outline: the automation principle (~10% of the code)

The code below shows the elementary mechanism: instantiate Word through late binding, open a copy of the document and run a basic replacement on the main body. It is the theoretical skeleton (~10% of the real code).

Module1 — Outline of the principle (~10% of the code)
Option Explicit

' Theoretical principle: drive Word from Excel to replace a piece of text
Public Sub RemplirDocument_Extrait()
    Dim appWord As Object, doc As Object
    Dim modele As String, cle As String, valeur As String

    modele = ThisWorkbook.Worksheets("Donnees").Range("B1").Value
    cle = "<$Client>"
    valeur = ThisWorkbook.Worksheets("Donnees").Range("A4").Text

    ' Elementary start-up of Word through late binding:
    Set appWord = CreateObject("Word.Application")
    appWord.Visible = False
    Set doc = appWord.Documents.Add(modele)

    ' Basic replacement in the body text:
    With doc.Content.Find
        .Text = cle
        .Replacement.Text = valeur
        .Execute Replace:=2 ' 2 = wdReplaceAll
    End With

    ' [ ... Rest of the code not shown: dynamic loop over every column,
    '   replacement in headers and footers, shapes and text boxes,
    '   handling of ghost Word processes, PDF export and tracked changes ... ]
End Sub

Why this extract is not enough in practice

This code illustrates the conversation between Excel and Word. But to generate quotes or contracts in production, this extract solves only a fraction of the problem :

Writing a quick script to replace one word takes an hour. Building a reliable document generator that never jams Word and covers every area of the document takes days of fine-tuning.

The trap of headers and footers

This is the most frequent mistake, and the most discreet. doc.Content only covers the body text: headers, footers, text boxes and notes are excluded from it. A macro that only handles Content produces a perfect document… with the tag <$Client> still showing at the top of every page.

Hence the double loop over Sections, then over Headers and Footers. A document has three headers per section — primary, first page, even pages — and the For Each loop walks through all of them.

For text boxes and shapes, one more loop is needed:

Dim forme As Object
For Each forme In doc.Shapes
    If forme.TextFrame.HasText Then
        forme.TextFrame.TextRange.Find.Execute cle, , , , , , , , , valeur, 2
    End If
Next forme
Check that nothing is left

Before saving, search for <$ in the document produced: if any remain, a tag escaped the process. A simple If InStr(doc.Content.Text, "<$") > 0 Then MsgBox "…" will stop you sending out a half-filled quote.

Saving as PDF, and the special cases

To produce a PDF directly, replace the save with:

doc.ExportAsFixedFormat sortie, 17     ' 17 = wdExportFormatPDF

To produce both, chain them: SaveAs2 then ExportAsFixedFormat with the extension changed.

Name the file from the row rather than typing B2 by hand:

sortie = ws.Range("B2").Value & "\" & Format(Date, "yyyy-mm-dd") & _
         "_quote_" & ws.Cells(ligne, 1).Value & ".docx"

Putting the date first, in year-month-day order, means the files sort themselves chronologically in Explorer.

Handle every row in one go : wrap the body of the macro in a loop For ligne = 4 To derniere, opening Word only once before the loop and closing it afterwards. Launching and closing Word on every row multiplies the processing time by ten.

When the template changes

The macro settles the case of a brand-new document. What remains is the question that turns up six months later: your contract template changes — a clause, a legal mention, a VAT number — and forty documents already produced carry the old version.

Going back over them one by one is out of the question; overwriting them with a new document would erase everything typed since. The only tenable route is to replay the merge on the existing documents as Word tracked changes : the differences appear as corrections you accept or reject one at a time, with a time-stamped backup taken beforehand.

It can be done in VBA — doc.TrackRevisions = True is the starting point — but the devil is in the detail: preserving what was typed by hand, coping with documents a colleague has open, knowing which files to revisit. It is one of the functions of the add-in we sell, and probably the one that saves the most time over the long run.

And if you would rather not maintain it yourself

The add-in that does everything above, finished and proven: path preview before writing, completion that overwrites nothing, standard documents filled in with the client's name, updates as tracked changes when the template changes. VBA source delivered open.

Discover DossiersLocaux — €19 for life → Lifetime licence, no subscription · Refunded for 30 days · Windows + Excel 2016 or newer

Frequently asked questions

Do I need to add a reference to the Word library?

No, and that is deliberate. The code uses late binding (Dim … As Object, CreateObject): it works whatever version of Word is installed on the machine. With a fixed reference, the workbook raises a compilation error as soon as it moves to another computer.

Can the template be a .dotx rather than a .docx?

Yes, and it is even cleaner: Documents.Add on a .dotx is exactly what Word templates are for. The code does not change. A .docx works too, because Add creates a copy of it instead of opening it.

How do I insert a logo or a variable image?

Put a bookmark in the template (Insert, Bookmark) and then, from VBA, doc.Bookmarks("Logo").Range.InlineShapes.AddPicture path. Images do not go through Find-and-Replace, which only handles text.

Does the macro work if Word is already open?

Yes: GetObject picks up the running instance rather than launching a second one. Do beware of appWord.Quit, though, which would then close the user's own Word. In production, remember whether you created the instance and only close it in that case.

Can the same be done with Excel or PowerPoint as the template?

Yes, the principle is identical: Workbooks.Add for Excel, Presentations.Open for PowerPoint, then a text replacement. For Excel, the replacement goes through Cells.Replace; for PowerPoint, you have to walk the shapes of each slide, because there is no equivalent of doc.Content.

Read next

Previous guide Create your folders from Excel Build the tree this document will be filed into. Add-in DossiersLocaux Both steps together, with updates as tracked changes.