---
title: "Convert a PDF to PDF/A in C#"
url: https://recursiveai.org/onepdf/cookbook/convert-pdf-to-pdfa/
description: "Convert a PDF to PDF/A in C# with one call: PdfAConverter writes PDF/A-2b, 2u, 3b or 3u, or tells you every reason it can’t. Runnable code, real output."
version: "1.15.0"
---

# Convert a PDF to PDF/A in C#

Call `PdfAConverter.Convert(input, output)`. It repairs what PDF/A forbids without changing how the pages look, writes PDF/A-2b, and returns what it changed. When a file can’t be converted, it throws `PdfAException` with every reason and writes nothing.

```shell
dotnet new console -o PdfToPdfA && cd PdfToPdfA
dotnet add package RecursiveAI.OnePdf --version 1.15.0
dotnet add package RecursiveAI.OnePdf.Fonts --version 1.15.0
curl -LO https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf
curl -LO https://www.irs.gov/pub/irs-pdf/fw9.pdf
curl -LO https://www.irs.gov/pub/irs-pdf/f1040es.pdf
```

Put this in `Program.cs`:

```csharp
using OnePdf;
using OnePdf.Fonts;

// Faces for fonts a file names but doesn't embed (Arial, Helvetica, Times...).
FontsPackage.Register();

string input = args.Length > 0 ? args[0] : "dummy.pdf";
string output = Path.GetFileNameWithoutExtension(input) + "-pdfa.pdf";

try
{
    PdfAConversionResult result = PdfAConverter.Convert(input, output);
    Console.WriteLine($"Saved {output} as {result.Profile}");
    foreach (PdfAChange change in result.Changes)
        Console.WriteLine($"  {change}");
    foreach (string warning in result.Warnings)
        Console.WriteLine($"  Warning: {warning}");
}
catch (PdfAException e)
{
    // Nothing was written. Each reason names its ISO 19005 clause.
    Console.WriteLine($"Refused, nothing written: {e.Violations.Count} reason(s)");
    foreach (PdfAViolation v in e.Violations)
    {
        string where = v.PageIndex >= 0 ? $"page {v.PageIndex + 1}" : "file";
        Console.WriteLine($"  {v.Clause} ({where}): {v.Message}");
    }
}
```

Run it on the W3C test file, a one-page document exported from OpenOffice Writer:

```shell
dotnet run
```

```text
Saved dummy-pdfa.pdf as PDF/A-2b
  Font widths that disagreed with the embedded program by rounding were corrected.
  An sRGB output intent was added.
```

Then on the IRS Form W-9, which has a fillable form, JavaScript and fonts it doesn’t embed:

```shell
dotnet run -- fw9.pdf
```

```text
Saved fw9-pdfa.pdf as PDF/A-2b
  The document-level JavaScript was removed.
  Substitute fonts were embedded for fonts the file did not embed. (x3)
  Font descriptors' /CIDSet and /CharSet hints were removed. (x6)
  An sRGB output intent was added.
  The XFA form data was removed; the AcroForm fields remain. (x2)
  The XMP packet was written again from the Info dictionary.
  Warning: Font HelveticaLTStd-Bold is not embedded in the file and has no metric-compatible substitute; LiberationSans-Bold was embedded in its place, so the letter shapes differ.
```

The converter removed the JavaScript and the XFA copy of the form (the ordinary form fields stay), embedded fonts for the three the file only names, and wrote the color profile and metadata PDF/A requires. HelveticaLTStd-Bold has no metric-compatible face, so Liberation Sans Bold is drawn in its place, at the file’s own widths: no text moves, but the bold letters look slightly different, and the warning says so.

The [veraPDF](https://verapdf.org/software/) validator passes `dummy-pdfa.pdf` and `fw9-pdfa.pdf` as PDF/A-2b, and both files converted to 2u, 3b and 3u pass too. Check your own output the same way: `verapdf --flavour 2b fw9-pdfa.pdf`.

And on the IRS Form 1040-ES (the 2026 edition), which it refuses:

```shell
dotnet run -- f1040es.pdf
```

```text
Refused, nothing written: 1 reason(s)
  6.2.4.3 (page 1): DeviceCMYK is used but the output intent profile is RGB, not CMYK, and not every resource dictionary has a DefaultCMYK space.
```

## Reading a refusal

- **Nothing is written.** The output file isn’t created, and an existing one isn’t replaced.
- **Every reason is listed**, not just the first. Each `PdfAViolation` has `Clause` (the ISO 19005-2 clause), `Message`, `ObjectNumber` (the PDF object, or -1 for the file as a whole) and `PageIndex` (from 0, or -1 when it isn’t on a page). `e.Message` also lists them.
- **What it means.** A refusal is something the converter can’t repair without changing how a page looks. The 1040-ES draws in CMYK on its first page in a way the converter couldn’t bring under one color profile (clause 6.2.4.3, device color). The commonest reasons are an embedded font that lacks a glyph the page shows, font widths that disagree with the file by more than 12/1000 em, and a font with no program and no usable substitute.
- **What to do.** Go back to the source: export the document again with PDF/A switched on, or generate it with OnePDF, which writes PDF/A directly (see [Create a PDF/A file](https://recursiveai.org/onepdf/cookbook/create-pdf-a/)).

## What gets repaired

- **Removed:** encryption (pass `Password` for a protected file), JavaScript and other forbidden actions, XFA, hidden and forbidden annotations, attachments in part 2, external stream references, and signature values (the signature field stays, empty).
- **Fonts:** embedded from the Fonts package for fonts the file only names, at the file’s own widths; other unembedded fonts get a sans, serif or mono stand-in, with a warning. Font dictionaries that disagree slightly with their font programs are corrected.
- **Color:** an sRGB output intent, or a CMYK one for a CMYK file; where RGB and CMYK are mixed, default color spaces bring both under the intent.
- **The rest:** LZW streams stored again with Flate, forbidden graphics-state entries reset, and the XMP metadata written fresh from the document information. For level U, the Unicode text maps are written.

Upstream’s own test, in [the PDF/A docs](https://recursiveai.org/onepdf/docs/pdfa/#results-on-real-files): “Every converted file was validated with veraPDF (2b, 2u and 3b), and none failed. Of 200 sampled govdocs files 181 converted to 2b, of 200 web-crawl files 182”.

## 2b, 2u, 3b or 3u

| Choose | When |
|---|---|
| PDF/A-2b (default) | The pages must look the same in years to come. |
| PDF/A-2u | Also searchable and copyable: every character maps to Unicode. A good choice for text documents. |
| PDF/A-3b or 3u | The file carries attachments you need to keep, such as the XML behind an invoice. Part 2 removes them. |

```csharp
var options = new PdfAConversionOptions
{
    Part = PdfAPart.Part3,            // keep attachments
    Conformance = PdfAConformance.U,  // Unicode text
};
PdfAConverter.Convert("fw9.pdf", "fw9-pdfa3u.pdf", options);
```

A document you already have open, with `AllowEditing = true` in its `PdfOpenOptions`, converts with `doc.ConvertToPdfA("out.pdf", options)`.

## Limits

- **Not level A.** PDF/A-2a and 3a need a tagged structure the converter doesn’t invent: tag the file first (see [Read, edit and remove PDF tags](https://recursiveai.org/onepdf/cookbook/edit-pdf-tags/)). PDF/A-1 and PDF/A-4 aren’t written.
- **Substituted fonts look different.** Widths are kept, so nothing moves, but the letter shapes are the substitute’s. Each substitution is in `Warnings`.
- **Signatures are removed.** The file is rewritten, so sign it after converting.
- **OnePDF isn’t a validator.** The converter runs its output through OnePDF’s own PDF/A check and never writes a file that fails it, but that check doesn’t cover everything. Run veraPDF before you rely on an archive.

To embed missing fonts without converting, see [Embed missing fonts](https://recursiveai.org/onepdf/cookbook/embed-missing-fonts/). Every repair and refusal, clause by clause, is in [PDF/A](https://recursiveai.org/onepdf/docs/pdfa/#converting-existing-files).
