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
- A .NET project.
- Aspose.PDF for .NET 26.9 or later, which is the release that introduced
FileSpecification.GetFileName. Install it from NuGet:
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.Createsilently overwrites the first one. This also happens when several attachments have no name and all fall back toattachment.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.GetFileNamewith 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
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.
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.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.GetFileNamekeeps only the file name, so every attachment is written inside your output folder.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.Can I process an attachment without saving it to disk? Yes. The
Contentsproperty is aStream, so you can copy it to aMemoryStream, upload it, or pass it to another library instead of writing it to aFileStream.
