A PDF can carry other files inside it. It can be a portfolio that bundles contracts and spreadsheets, or a report with its source data attached. When you pull those files out in code, you want each one saved under the name its author gave it, not attachment1.bin. This tutorial shows how to extract embedded files from a PDF with original names in C# using Aspose.PDF for .NET. You will open a PDF, loop through its embedded files, resolve each file’s name with FileSpecification.GetFileName, and write the contents to a folder.

Where a PDF stores the name of an embedded file

Each embedded file is described by a file specification, and a file specification can record the name in more than one place. There is a Unicode file name, a standard file name, and the key under which the file is listed in the document’s embedded files collection. PDF producers don’t fill these in consistently, and some leave the name out entirely.

Aspose.PDF for .NET provides FileSpecification.GetFileName proprty to deal with this. It resolves the name from the Unicode name, the regular file name, or the collection key, whichever is available, so you still get a usable name when the Unicode name is missing. You can pass a fallback name for files that have no name at all. An optional second parameter controls whether an empty name is allowed when no file name is available. You get one call instead of your own logic for reading PDF dictionaries.

Note: The examples in this article use only the fallback argument.

The Document.EmbeddedFiles holds document-level attachments, which includes the files in a PDF portfolio. Files attached to a specific page through a file attachment annotation (usually shown as a paperclip icon) are stored on the annotation instead; Extract files attached to pages covers those.

Using Aspose.PDF to extract embedded files from a PDF

Install-Package Aspose.PDF

Or with the .NET CLI:

dotnet add package Aspose.PDF
  • A PDF that contains embedded files. The example uses a portfolio named PDFPortfolio.pdf.

Without a license, Aspose.PDF runs in evaluation mode. To test without evaluation restrictions, request a free temporary license.

Extract every embedded file with its original name

The following method opens the PDF, saves each embedded file to an output folder under its resolved name, and prints the path of each file it writes.

using System;
using System.IO;
using Aspose.Pdf;

public static class EmbeddedFileExtractor
{
    public static void ExtractEmbeddedFiles(string inputPdf, string outputDir)
    {
        Directory.CreateDirectory(outputDir);

        using (var document = new Document(inputPdf))
        {
            foreach (FileSpecification fileSpec in document.EmbeddedFiles)
            {
                // Resolve the stored name, then keep only the file name part.
                string fileName = Path.GetFileName(fileSpec.GetFileName("attachment.bin"));
                string outputPath = Path.Combine(outputDir, fileName);

                using (var output = new FileStream(outputPath, FileMode.Create))
                {
                    fileSpec.Contents.CopyTo(output);
                }

                Console.WriteLine($"Saved: {outputPath}");
            }
        }
    }
}

Call it with the PDF path and the folder you want the files in:

EmbeddedFileExtractor.ExtractEmbeddedFiles("PDFPortfolio.pdf", "ExtractedFiles");

What each step does

Open the document. new Document(inputPdf) loads the PDF. The using block releases the file handle when processing finishes, even if an exception is thrown.

Loop through the attachments. document.EmbeddedFiles returns one FileSpecification for each document-level embedded file.

Resolve the name. fileSpec.GetFileName("attachment.bin") returns the stored name, or attachment.bin if the PDF provides none. Choose a fallback that makes sense for your application.

Strip folder segments. A stored name can include a path such as reports/2026/q3.xlsx, or a crafted value such as ..\..\startup.bat. Passing the result through Path.GetFileName reduces it to q3.xlsx or startup.bat, so the file can’t be written outside outputDir. Keep this step even if you trust the PDFs you process today.

Write the contents. fileSpec.Contents is a stream of the embedded file’s data. CopyTo writes it to the new file without any conversion.

Expected result

The output folder contains one file for each document-level attachment, named as it was stored in the PDF. For a portfolio that holds a spreadsheet and a Word document, the console output looks similar to this:

Saved: ExtractedFiles\budget-2026.xlsx
Saved: ExtractedFiles\service-agreement.docx

The file names depend on your PDF. The extracted files contain the same data that was embedded, so each one opens in whatever application handles its format.

Handle duplicate and unsafe names

The basic example has three weak points you’ll meet with real-world PDFs:

  • Two attachments can share a name, and FileMode.Create silently overwrites the first one. This also happens when several attachments have no name and all fall back to attachment.bin.
  • A name that is valid on one operating system can contain characters that are invalid on another, such as : on Windows.
  • A stored name that ends with a path separator leaves Path.GetFileName with an empty string.

This helper handles all three. It replaces invalid characters, falls back when the name is empty, and adds a counter when the file already exists:

private static string GetSafeOutputPath(string outputDir, string resolvedName, string fallbackName)
{
    string fileName = Path.GetFileName(resolvedName);

    foreach (char c in Path.GetInvalidFileNameChars())
    {
        fileName = fileName.Replace(c, '_');
    }

    if (string.IsNullOrWhiteSpace(fileName))
    {
        fileName = fallbackName;
    }

    string baseName = Path.GetFileNameWithoutExtension(fileName);
    string extension = Path.GetExtension(fileName);
    string outputPath = Path.Combine(outputDir, fileName);

    int counter = 1;
    while (File.Exists(outputPath))
    {
        outputPath = Path.Combine(outputDir, $"{baseName} ({counter}){extension}");
        counter++;
    }

    return outputPath;
}

In the extraction loop, replace the two lines that build fileName and outputPath with:

string outputPath = GetSafeOutputPath(outputDir, fileSpec.GetFileName("attachment.bin"), "attachment.bin");

Because the helper checks for existing files, running the extraction twice into the same folder produces numbered copies rather than overwriting. Clear the folder first if you want a fresh result on each run.

Extract files attached to pages

If EmbeddedFiles is empty but you can see a paperclip icon in a PDF viewer, the file is probably attached through a file attachment annotation on a page. The annotation holds its own FileSpecification in the File property, so the same name resolution and the same helper apply. This method checks every page and saves each attached file to the output folder:

using Aspose.Pdf.Annotations;

public static void ExtractPageAttachments(string inputPdf, string outputDir)
{
    Directory.CreateDirectory(outputDir);

    using (var document = new Document(inputPdf))
    {
        foreach (Page page in document.Pages)
        {
            foreach (Annotation annotation in page.Annotations)
            {
                if (annotation is FileAttachmentAnnotation attachment && attachment.File != null)
                {
                    string outputPath = GetSafeOutputPath(outputDir, attachment.File.GetFileName("attachment.bin"), "attachment.bin");

                    using (var output = new FileStream(outputPath, FileMode.Create))
                    {
                        attachment.File.Contents.CopyTo(output);
                    }

                    Console.WriteLine($"Saved: {outputPath}");
                }
            }
        }
    }
}

To collect every attachment in a PDF, run both methods with the same output folder. If a PDF references the same file from both places, GetSafeOutputPath saves the second copy with a numbered name rather than overwriting the first.

Next steps

To go further with attachments, such as adding, removing, or listing them without extracting, see the Aspose.PDF for .NET documentation. The FileSpecification class reference lists the other name and description properties you can read from each embedded file. If a PDF doesn’t extract as expected, post a sample in the Aspose.PDF support forum.

FAQs

  1. How does FileSpecification.GetFileName choose a name? It resolves the name from the embedded file’s Unicode name, regular file name, or collection key, whichever is available. If none is available, it returns the fallback name you pass in. The method was added in Aspose.PDF for .NET 26.9.

  2. Does this work for PDFs that are not portfolios? Yes, for files attached at the document level. Files attached to a specific page through a file attachment annotation (usually shown as a paperclip icon) are stored on the annotation instead, so read them from FileAttachmentAnnotation.File, as shown in Extract files attached to pages.

  3. Why wrap the result of GetFileName in Path.GetFileName? The name stored in a PDF is set by whoever created the file and can include folder segments. Path.GetFileName keeps only the file name, so every attachment is written inside your output folder.

  4. What happens if two attachments have the same name? With FileMode.Create, the second file overwrites the first. Check whether the path already exists and add a numeric suffix, as shown in Handle duplicate and unsafe names.

  5. Can I process an attachment without saving it to disk? Yes. The Contents property is a Stream, so you can copy it to a MemoryStream, upload it, or pass it to another library instead of writing it to a FileStream.