Add Attachments to Generated PDF

EVO HTML to PDF Converter can embed arbitrary files directly into the generated PDF as document-level attachments. The attached files appear in the Attachments panel of the viewer and travel with the document. The receiver does not need access to the original source files.

Attachments are configured through the PdfDocumentOptions object exposed by the HtmlToPdfConverterPdfDocumentOptions property. Each attachment is created as a PdfFileAttachment instance and then registered with PdfDocumentOptionsAddFileAttachment(PdfFileAttachment). You can call AddFileAttachment multiple times to embed several files in a single conversion.

PdfFileAttachment instances are obtained through static factory methods. Use PdfFileAttachmentFromBytes(Byte, String) when the payload is built in memory. The first argument is the byte buffer. The second argument is the file name shown in the Attachments panel. Use PdfFileAttachmentFromFile(String) when the payload is already on disk. The file is read at registration time and embedded in the document. The file name in the Attachments panel is taken from the path.

Two additional factory methods exist for non-embedded references. PdfFileAttachmentFromExternalPath(String) stores a reference to an external file path. PdfFileAttachmentFromUrl(String) stores a reference to a URL. Neither variant embeds the bytes. The resulting PDF is not portable and is not allowed under any PDF/A standard. Prefer the embedding variants for most workflows.

After creation each attachment can be tagged with metadata. The MimeType property helps viewers select a helper application when the attachment is opened. The Description property is shown as the tooltip in the Attachments panel. The Relationship property describes how the attached file relates to the PDF content. It is relevant when the target standard requires it.

Attachment support depends on the target PDF standard configured through PdfDocumentOptionsPdfStandard. PDF/A-2 (PdfA2b, PdfA2a) and PDF/A-4 base conformance (PdfA4, with their PDF/UA combinations) restrict embedded files to PDF/A-conformant content only. Calling AddFileAttachment with an arbitrary file type under one of those standards throws an InvalidOperationException at save time. To attach XML, CSV, XLSX or similar arbitrary file types use the PDF/A-3 family (PdfA3b, PdfA3u, PdfA3a, PdfUa1PdfA3a) or PDF/A-4f (PdfA4f, PdfUa2PdfA4f). PDF/UA-1 and PDF/UA-2 by themselves do not restrict attachments.

Under the standards that require it, the library writes the relationship metadata automatically. When Relationship is left at its default PdfAttachmentRelationshipUnspecified value, the engine defaults it to PdfAttachmentRelationshipSource. Setting Relationship explicitly is recommended for clarity but is not strictly required. The PdfAttachmentRelationshipEncryptedPayload value is only valid under PdfA4f and PdfUa2PdfA4f. Using it under any other standard throws an InvalidOperationException.

Setting HtmlToPdfConverterPdfViewerPreferences.PageMode to ViewerPageModeUseAttachments instructs the viewer to open the Attachments panel automatically when the document is loaded. This is convenient when the attachments are the primary payload of the document.

Embed In-Memory Data with FromBytes

Use PdfFileAttachmentFromBytes(Byte, String) to embed a byte buffer that was assembled in memory. Typical sources include the output of a serializer, a generated CSV string or the output of another converter.

MimeType and Description are optional but recommended. Viewers use the MimeType to pick the right helper application when the attachment is opened. The Description appears as a tooltip in the Attachments panel.

Embed an in-memory XML payload
byte[] xmlBytes = Encoding.UTF8.GetBytes(BuildSampleXml());
var xmlAttachment = PdfFileAttachment.FromBytes(xmlBytes, "data.xml");
xmlAttachment.MimeType = "application/xml";
xmlAttachment.Description = "Source XML data";
xmlAttachment.Relationship = PdfAttachmentRelationship.Source;

htmlToPdfConverter.PdfDocumentOptions.AddFileAttachment(xmlAttachment);

Embed a File from Disk with FromFile

Use PdfFileAttachmentFromFile(String) when the payload is already on disk. The factory reads the file at registration time and embeds its bytes. The file name shown in the Attachments panel is taken from the path.

Embed a file from disk
string alphabetFilePath = Path.Combine(GetDemoTextsPath(), "Alphabet.txt");
var textAttachment = PdfFileAttachment.FromFile(alphabetFilePath);
textAttachment.MimeType = "text/plain";
textAttachment.Description = "Sample alphabet text";

htmlToPdfConverter.PdfDocumentOptions.AddFileAttachment(textAttachment);

PDF Standards Compatibility

Whether and how an attachment is preserved depends on the standard configured through PdfDocumentOptionsPdfStandard:

  • None, PdfUa1, PdfUa2. Any attachment is accepted with no relationship requirement.

  • PdfA2b, PdfA2a, PdfUa1PdfA2b, PdfUa1PdfA2a, PdfUa2PdfA2b, PdfUa2PdfA2a. Embedded files are restricted to PDF/A-conformant content. Attaching arbitrary file types throws InvalidOperationException at save time.

  • PdfA4, PdfUa2PdfA4. Same restriction as PDF/A-2. Use PdfA4f or PdfUa2PdfA4f instead to attach arbitrary files under the PDF 2.0 archival rules.

  • PdfA3b, PdfA3u, PdfA3a, PdfUa1PdfA3a, PdfA4f, PdfUa2PdfA4f. Attachments of arbitrary type are accepted. The relationship metadata is required and the engine defaults Relationship to PdfAttachmentRelationshipSource when it is left as Unspecified. Set Relationship explicitly to one of the EvoPdf.NextPdfAttachmentRelationship values to override the default.

Code Sample - Add Attachments to Generated PDF from HTML

C#
using System;
using System.IO;
using System.Text;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using EvoPdf_Next_AspNetDemo.Models;
using EvoPdf_Next_AspNetDemo.Models.HTML_to_PDF;

// Use EVO PDF Namespace
using EvoPdf.Next;

namespace EvoPdf_Next_AspNetDemo.Controllers.HTML_to_PDF
{
    public class Add_Attachments_to_Generated_PDFController : Controller
    {
        private readonly IWebHostEnvironment m_hostingEnvironment;
        public Add_Attachments_to_Generated_PDFController(IWebHostEnvironment hostingEnvironment)
        {
            m_hostingEnvironment = hostingEnvironment;
        }

        public IActionResult Index()
        {
            var model = SetViewModel();
            return View(model);
        }

        [HttpPost]
        public ActionResult ConvertHtmlToPdf(Add_Attachments_to_Generated_PDF_ViewModel model)
        {
            if (!ModelState.IsValid)
            {
                var errorMessage = ModelStateHelper.GetModelErrors(ModelState);
                throw new ValidationException(errorMessage);
            }

            // Set license key received after purchase to use the converter in licensed mode
            // Leave it not set to use the library in demo mode
            Licensing.LicenseKey = "3FJDU0ZDU0NTQkddQ1NAQl1CQV1KSkpKU0M=";

            // Create a HTML to PDF converter object with default settings
            HtmlToPdfConverter htmlToPdfConverter = new HtmlToPdfConverter();

            // Open the Attachments panel on document load
            htmlToPdfConverter.PdfViewerPreferences.PageMode = ViewerPageMode.UseAttachments;

            // Sets the PDF standard for the generated document
            // Leave as None to generate a plain PDF without an accessibility structure tree or archival metadata
            htmlToPdfConverter.PdfDocumentOptions.PdfStandard = model.PdfStandard;

            // ===== Document-level attachments =====
            // Attachments added via PdfDocumentOptions.AddFileAttachment appear
            // in the viewer's Attachments panel (opened with the paperclip icon
            // in Acrobat's left sidebar).  They have no visible marker on any
            // page.  Two factory methods cover the common cases: FromBytes for
            // in-memory data and FromFile for files on disk

            // FromBytes -- embed an in-memory XML invoice
            byte[] invoiceXmlBytes = Encoding.UTF8.GetBytes(BuildSampleInvoiceXml());
            var xmlAttachment = PdfFileAttachment.FromBytes(invoiceXmlBytes, "invoice.xml");
            xmlAttachment.MimeType = "application/xml";
            xmlAttachment.Description = "Source XML invoice data";
            xmlAttachment.Relationship = PdfAttachmentRelationship.Source;
            htmlToPdfConverter.PdfDocumentOptions.AddFileAttachment(xmlAttachment);

            // FromFile -- embed a file from disk
            string alphabetFilePath = Path.Combine(GetDemoTextsPath(), "Alphabet.txt");
            var textAttachment = PdfFileAttachment.FromFile(alphabetFilePath);
            textAttachment.MimeType = "text/plain";
            textAttachment.Description = "Sample alphabet text";
            htmlToPdfConverter.PdfDocumentOptions.AddFileAttachment(textAttachment);

            byte[] outPdfBuffer = null;

            if (model.HtmlPageSource == "Url")
            {
                string url = model.Url;

                outPdfBuffer = htmlToPdfConverter.ConvertUrl(url);
            }
            else
            {
                string htmlWithForm = model.HtmlString;
                string baseUrl = model.BaseUrl;

                outPdfBuffer = htmlToPdfConverter.ConvertHtml(htmlWithForm, baseUrl);
            }

            // Send the PDF file to browser
            FileResult fileResult = new FileContentResult(outPdfBuffer, "application/pdf");
            fileResult.FileDownloadName = "PdfAttachmentsDemo.pdf";

            return fileResult;
        }

        private Add_Attachments_to_Generated_PDF_ViewModel SetViewModel()
        {
            var model = new Add_Attachments_to_Generated_PDF_ViewModel();

            var contentRootPath = Path.Combine(m_hostingEnvironment.ContentRootPath, "wwwroot");

            HttpRequest request = ControllerContext.HttpContext.Request;
            UriBuilder uriBuilder = new UriBuilder();
            uriBuilder.Scheme = request.Scheme;
            uriBuilder.Host = request.Host.Host;
            if (request.Host.Port != null)
                uriBuilder.Port = (int)request.Host.Port;
            uriBuilder.Path = request.PathBase.ToString() + request.Path.ToString();
            uriBuilder.Query = request.QueryString.ToString();

            string currentPageUrl = uriBuilder.Uri.AbsoluteUri;
            string rootUrl = currentPageUrl.Substring(
                0, currentPageUrl.Length - "Add_Attachments_to_Generated_PDF".Length);

            model.HtmlString = System.IO.File.ReadAllText(Path.Combine(contentRootPath, "DemoAppFiles/Input/HTML_Files/PDF_Standards.html"));
            model.BaseUrl = rootUrl + "DemoAppFiles/Input/HTML_Files/";

            return model;
        }

        // ===== Sample data builders =====

        private static string BuildSampleInvoiceXml()
        {
            return
                "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<Invoice>\n" +
                "  <InvoiceNumber>2026-0042</InvoiceNumber>\n" +
                "  <IssueDate>2026-05-18</IssueDate>\n" +
                "  <Customer>Acme Corporation</Customer>\n" +
                "  <Items>\n" +
                "    <Item><Name>Widget</Name><Quantity>10</Quantity><UnitPrice>1.50</UnitPrice></Item>\n" +
                "    <Item><Name>Gadget</Name><Quantity>5</Quantity><UnitPrice>3.75</UnitPrice></Item>\n" +
                "    <Item><Name>Sprocket</Name><Quantity>2</Quantity><UnitPrice>12.00</UnitPrice></Item>\n" +
                "  </Items>\n" +
                "  <Total>57.75</Total>\n" +
                "</Invoice>\n";
        }

        private string GetDemoFilesPath() => m_hostingEnvironment.ContentRootPath + "/wwwroot" + "/DemoAppFiles/Input/";
        private string GetDemoTextsPath() => Path.Combine(GetDemoFilesPath(), "Text_Files");
    }
}

See Also