Home
» MS OFFICE
»
How to Create an Automated Mail Merge with Images in LibreOffice Writer
How to Create an Automated Mail Merge with Images in LibreOffice Writer
You can build a perfectly good LibreOffice Writer mail merge for names, addresses, dates, and other text fields, then discover that the moment each recipient needs a different photo, signature, product image, QR code, or badge, the simple workflow stops being simple. The reason is important: Writer’s documented mail merge fields are database fields used as text placeholders. LibreOffice also supports embedded and linked graphics, but its current help does not document a native “mail merge image field” that automatically replaces an image from a file-path column for every record.
That leaves two practical choices. If every merged document uses the same logo or background image, use normal Writer mail merge and keep that image fixed in the template. If the image must change for every row, the more controllable approach is to keep the text and image data in Calc, reserve a named graphic object in the Writer template, and automate the merge with a LibreOffice Basic macro. This guide uses that second approach.
The instructions are aligned with LibreOffice 26.2 documentation available in 2026. Menus can differ slightly in other releases or on macOS. LibreOffice’s current Writer guide states that images can be embedded or linked, while the UNO API exposes Writer graphic objects through XTextGraphicObjectsSupplier and lets automation set a graphic from an external URL. Those pieces make a per-record image workflow possible even though the standard Mail Merge Wizard does not expose it as a dedicated image field.
Choose the right workflow before you start
Requirement
Best approach
Main tradeoff
Same logo or photo on every letter
Standard Writer mail merge
Simple, but the image never changes by record
Different image for every recipient
Writer template + Calc + Basic/UNO automation
More setup, but predictable control over the image
Database stores the image itself rather than a path
Database/form workflow or custom UNO code
Better for managed databases, but heavier than a spreadsheet-based merge
Hundreds or thousands of documents in unattended batches
Macro or external UNO script
Requires testing, logging, and file-path discipline
If your use case is only a fixed company logo, stop here and use the normal LibreOffice form-letter workflow. The rest of this article is for variable images.
Step 1: Prepare a clean Calc data source
Create a Calc sheet with one header row and one record per row. Keep image references in their own column. A practical layout is CustomerID, FirstName, LastName, Email, ImagePath, and OutputName.
Keep one image path and one output filename per row so the automation can resolve each record without guessing.
Use absolute paths while you are building and testing the merge. They are less portable, but they remove ambiguity. For example, C:\MailMerge\Imageslice.jpg on Windows or /home/user/mailmerge/images/alice.jpg on Linux is easier to diagnose than a relative path whose base directory changes depending on how the document was opened.
Do not mix Windows and Linux path formats in the same production file. The mixed examples in the illustration simply show the type of value the column holds. Your real data source should use paths valid on the machine that runs the merge.
A good OutputName column also prevents filename collisions. Prefer a stable ID or sanitized name rather than an email address or arbitrary free text. If the same value appears twice, decide in advance whether the macro should overwrite, append a suffix, or fail.
Step 2: Confirm that Writer can read the records
Writer’s standard mail merge uses a registered data source. You can use the Mail Merge Wizard, an address data source, or the data-source browser. Current LibreOffice help documents Tools > Mail Merge Wizard and also allows database fields to be inserted through Insert > Field > More Fields > Database. See the official Database fields reference.
Use Writer’s data-source view to verify that the expected columns and records are visible before adding image automation.
This check is useful even if the final macro reads the spreadsheet directly. It catches common problems early: the wrong sheet, unexpected header names, empty records, or a spreadsheet that was moved after registration.
For ordinary text-only mail merge, you can drag a column heading into the document or insert a mail merge field from the Database tab. The official Writer help confirms that these fields are replaced with database content when the merge runs. For the variable image, however, do not assume that dragging ImagePath into the page will turn the path into an image. It will remain data unless code interprets it and updates a graphic object.
Step 3: Build the Writer template and name the image placeholder
Create the letter, certificate, badge, or report layout in Writer. For the text values, either use normal mail merge fields or use unmistakable tokens such as {FirstName}, {LastName}, and {CustomerID} if your macro will replace them itself.
Next insert a sample image where the variable image should appear. Set the desired size, wrapping, and anchor before automating it. LibreOffice documents image sizing, positioning, anchoring, wrapping, and linked-image behavior in the current Writer 26.2 Images and Graphics chapter.
Reserve the final image position in the template and give the graphic object a stable name such as MergeImage.
Give the graphic object a stable name such as MergeImage. The exact properties dialog can vary by release, so if you are unsure which object Writer exposes, use Tools > Development Tools. LibreOffice’s Development Tools can inspect graphic objects, properties, methods, and interfaces in the current document. That makes it a safer diagnostic tool than guessing an internal object name.
For predictable layout, use the same aspect ratio for all source images or decide how you want them cropped. Writer can resize a graphic to the placeholder’s dimensions, but a portrait photo inserted into a wide banner-shaped frame will still look wrong unless you normalize the files first.
Step 4: Automate the variable image and document export
The key automation operation is straightforward: open the template, replace the text placeholders, find the named graphic object, load the row’s image file into that object, then export the result. LibreOffice’s UNO API documents getGraphicObjects() for accessing embedded and linked graphics and the TextGraphicObject service for graphic properties. The GraphicURL property is deprecated for general object storage in favor of Graphic, but the current API explicitly notes that setting an external URL loads that image into the graphic object. That makes it convenient for a simple Basic macro using local file paths.
The automation layer is what converts an ImagePath value into the actual graphic shown in each generated document.
The following example is intentionally small. Change the three paths, sheet name, column indexes, and placeholder names to match your own files. Test it on two records before running a large batch.
Sub MailMergeWithImages()
Dim calcDoc As Object, sheet As Object, doc As Object
Dim graphics As Object, imageObj As Object
Dim row As Long
Dim firstName As String, lastName As String
Dim imagePath As String, outputName As String
Dim sourceURL As String, templateURL As String, outputDir As String
sourceURL = ConvertToURL("C:\MailMerge\customers.ods")
templateURL = ConvertToURL("C:\MailMerge\LetterTemplate.odt")
outputDir = ConvertToURL("C:\MailMerge\Output")
calcDoc = StarDesktop.loadComponentFromURL(sourceURL, "_blank", 0, Array())
sheet = calcDoc.Sheets.getByName("Sheet1")
row = 1 ' row 0 contains headers
Do While sheet.getCellByPosition(0, row).String <> ""
firstName = sheet.getCellByPosition(1, row).String
lastName = sheet.getCellByPosition(2, row).String
imagePath = sheet.getCellByPosition(4, row).String
outputName = sheet.getCellByPosition(5, row).String
doc = StarDesktop.loadComponentFromURL(templateURL, "_blank", 0, Array())
ReplaceAll doc, "{FirstName}", firstName
ReplaceAll doc, "{LastName}", lastName
graphics = doc.getGraphicObjects()
If graphics.hasByName("MergeImage") Then
imageObj = graphics.getByName("MergeImage")
imageObj.GraphicURL = ConvertToURL(imagePath)
Else
MsgBox "Graphic object MergeImage was not found."
doc.close(True)
Exit Sub
End If
Dim args(1) As New com.sun.star.beans.PropertyValue
args(0).Name = "FilterName"
args(0).Value = "writer_pdf_Export"
args(1).Name = "Overwrite"
args(1).Value = True
doc.storeToURL(outputDir & outputName & ".pdf", args())
doc.close(True)
row = row + 1
Loop
calcDoc.close(True)
End Sub
Sub ReplaceAll(doc As Object, findText As String, replacement As String)
Dim d As Object
d = doc.createReplaceDescriptor()
d.SearchString = findText
d.ReplaceString = replacement
doc.replaceAll(d)
End Sub
This macro is a pattern, not a universal drop-in program. LibreOffice installations can have different macro-security policies, operating-system path rules, and template layouts. The important verified API concepts are that Writer exposes its graphic-object collection and LibreOffice can set a graphic from an external URL. For larger deployments, consider loading the graphic through the modern Graphic API rather than relying on the compatibility behavior of GraphicURL. The official references are XTextGraphicObjectsSupplier and TextGraphicObject.
Step 5: Run a small batch and verify the result
Before processing the entire data source, run two or three deliberately different rows: one JPG, one PNG, and ideally one image with a different aspect ratio. Confirm all of the following before scaling up:
The recipient name in the document matches the image from the same spreadsheet row.
No image path is shown as literal text in the final document.
The output filename matches the intended record and does not overwrite another recipient.
The image fits the reserved area without pushing text to a new page or covering another object.
The generated PDF or ODT opens independently after LibreOffice and Calc are closed.
That last check matters because linked graphics and embedded graphics behave differently. The current Writer guide explains that a linked image remains a separate file reference, while an embedded image becomes part of the document. If you are delivering ODT files to another machine, verify that the result does not depend on a local path that only exists on the computer that performed the merge.
Common failures and the quickest fix
The output shows the file path instead of the image
You inserted ImagePath as a normal mail merge field. That is expected to produce text. Keep the path in the data source, but let the macro load it into the named graphic object.
Every record gets the same image
The template image was never changed between records, or your loop is reading a fixed cell. Log the row number and ImagePath during a two-record test. If the path changes but the image does not, inspect the graphic object name with Development Tools.
The macro says MergeImage does not exist
The graphic was renamed, replaced, or is not exposed under the name you expect. Select the image and inspect it with Tools > Development Tools. The UNO API exposes graphic objects by name, so the macro and template must agree exactly.
Images disappear on another computer
You probably saved a document that still references external files. If portability matters, ensure the generated deliverable embeds the image or export to PDF after the image has been loaded. LibreOffice’s image guide explains the difference between linked and embedded graphics and how links can be broken to embed files.
HTML mail merge loses images
This is a documented limitation worth checking before choosing email output. LibreOffice Help states that when a form letter is in HTML format, embedded or linked images are not sent with the email. If the image is essential, use a PDF attachment or test another delivery workflow rather than assuming HTML mail merge will preserve it. See Creating a Form Letter.
When to use LibreOffice's MailMerge UNO service instead
If your text fields already use Writer’s native database mail merge and you need programmatic control over output, LibreOffice also exposes a MailMerge service. Its documented properties include DataSourceName, Command, DocumentURL, OutputURL, FileNameFromColumn, and SaveAsSingleFile. That API is useful when you want to automate standard mail merge from code. It still does not, by itself, document a per-record image-file-path field, so you may need to combine mail merge execution with graphic-object handling. See the official MailMerge service reference.
Final checklist
Use normal Writer mail merge when the image is fixed for every recipient.
Use one clean ImagePath column when the image varies by record.
Name the Writer graphic placeholder and keep its dimensions stable.
Resolve the image path in automation; do not expect a text merge field to become a picture automatically.
Test two or three records before running a full batch.
Open the exported files after closing the source documents to prove the output is self-contained.
For most small and medium jobs, a Calc sheet plus a Writer template and a short Basic macro gives the best balance of transparency and control. It requires more setup than ordinary text mail merge, but you can see exactly which row supplies each image, choose the output filename, and validate the result before sending hundreds of personalized files.