How to Migrate from Nutrient.io to IronPDF in C#
Migrating from Nutrient.io (formerly PSPDFKit) to IronPDF simplifies your .NET PDF workflow by moving from a complex document intelligence platform with async-first patterns to a focused PDF library with straightforward synchronous APIs. This guide provides a comprehensive, step-by-step migration path that eliminates platform overhead while maintaining all essential PDF capabilities.
Why Migrate from Nutrient.io to IronPDF
The Platform Complexity Problem
Nutrient.io (formerly PSPDFKit) has evolved from a PDF SDK into a comprehensive "document intelligence platform." While this transformation broadens capabilities, it introduces significant challenges for teams that simply need reliable PDF operations:
Platform Overengineering: What was once a PDF SDK is now a full document intelligence platform with AI features and document workflow capabilities that may be unnecessary for straightforward PDF tasks.
Enterprise Pricing: Nutrient.io is positioned for large organizations with opaque pricing that requires contacting sales. This creates barriers for small-to-medium teams and makes budget planning difficult.
Rebrand Confusion: The PSPDFKit → Nutrient transition has created documentation issues where references to both names exist. Package names may still use PSPDFKit, and migration paths during the transition remain unclear.
Async-First Complexity: Everything in Nutrient.io requires async/await patterns. Even simple operations need
PdfProcessor.CreateAsync()for initialization and async methods for basic tasks, adding overhead for synchronous workflows.- Heavy Dependencies: The full platform requires more resources with a larger package footprint, more initialization time, and additional configuration.
Nutrient.io vs IronPDF Comparison
| Aspect | Nutrient.io (PSPDFKit) | IronPDF |
|---|---|---|
| Focus | Document intelligence platform | PDF library |
| Pricing | Enterprise (contact sales) | Transparent, published |
| Architecture | Complex platform | Simple library |
| API Style | Async-first | Sync with async options |
| Dependencies | Heavy | Lightweight |
| Configuration | Complex config objects | Straightforward properties |
| Learning Curve | Steep (platform) | Gentle (library) |
| Target Users | Enterprise | All team sizes |
For teams planning .NET 10 and C# 14 adoption through 2025 and 2026, IronPDF provides a simpler foundation that integrates cleanly without the overhead of a full document intelligence platform.
Before You Start
Prerequisites
- .NET Environment: .NET Framework 4.6.2+ or .NET Core 3.1+ / .NET 5/6/7/8/9+
- NuGet Access: Ability to install NuGet packages
- IronPDF License: Obtain your license key from ironpdf.com
NuGet Package Changes
# Remove Nutrient/PSPDFKit packages
dotnet remove package PSPDFKit.NET
dotnet remove package PSPDFKit.PDF
dotnet remove package Nutrient
dotnet remove package Nutrient.PDF
# Install IronPDF
dotnet add package IronPdf# Remove Nutrient/PSPDFKit packages
dotnet remove package PSPDFKit.NET
dotnet remove package PSPDFKit.PDF
dotnet remove package Nutrient
dotnet remove package Nutrient.PDF
# Install IronPDF
dotnet add package IronPdfLicense Configuration
// Add at application startup (Program.cs or Startup.cs)
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";// Add at application startup (Program.cs or Startup.cs)
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";Identify Nutrient.io Usage
# Find all Nutrient/PSPDFKit references
grep -r "PSPDFKit\|Nutrient\|PdfProcessor\|PdfConfiguration" --include="*.cs" .# Find all Nutrient/PSPDFKit references
grep -r "PSPDFKit\|Nutrient\|PdfProcessor\|PdfConfiguration" --include="*.cs" .Complete API Reference
Initialization Mappings
| Nutrient.io (PSPDFKit) | IronPDF | Notes |
|---|---|---|
await PdfProcessor.CreateAsync() | new ChromePdfRenderer() | No async required |
processor.Dispose() | (automatic or manual) | Simpler lifecycle |
new PdfConfiguration { ... } | renderer.RenderingOptions | Property-based |
Document Loading Mappings
| Nutrient.io (PSPDFKit) | IronPDF | Notes |
|---|---|---|
await processor.OpenAsync(path) | PdfDocument.FromFile(path) | Sync by default |
Document.LoadFromStream(stream) | PdfDocument.FromStream(stream) | Stream support |
Document.LoadFromBytes(bytes) | new PdfDocument(bytes) | Byte array |
PDF Generation Mappings
| Nutrient.io (PSPDFKit) | IronPDF | Notes |
|---|---|---|
await processor.GeneratePdfFromHtmlStringAsync(html) | renderer.RenderHtmlAsPdf(html) | Sync method |
await processor.GeneratePdfFromUrlAsync(url) | renderer.RenderUrlAsPdf(url) | Direct URL |
await processor.GeneratePdfFromFileAsync(path) | renderer.RenderHtmlFileAsPdf(path) | HTML file |
Document Operations Mappings
| Nutrient.io (PSPDFKit) | IronPDF | Notes |
|---|---|---|
await processor.MergeAsync(docs) | PdfDocument.Merge(pdfs) | Sync |
document.PageCount | pdf.PageCount | Same pattern |
await document.SaveAsync(path) | pdf.SaveAs(path) | Sync |
document.ToBytes() | pdf.BinaryData | Byte array |
Annotations and Watermarks Mappings
| Nutrient.io (PSPDFKit) | IronPDF | Notes |
|---|---|---|
await document.AddAnnotationAsync(index, annotation) | pdf.ApplyWatermark(html) | HTML-based |
new TextAnnotation("text") | HTML in watermark | More flexible |
annotation.Opacity = 0.5 | CSS opacity: 0.5 | CSS styling |
annotation.FontSize = 48 | CSS font-size: 48px | CSS styling |
Code Migration Examples
Example 1: HTML to PDF Conversion
Before (Nutrient.io):
// NuGet: Install-Package PSPDFKit.Dotnet
using PSPDFKit.Pdf;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var htmlContent = "<html><body><h1>Hello World</h1></body></html>";
using var processor = await PdfProcessor.CreateAsync();
var document = await processor.GeneratePdfFromHtmlStringAsync(htmlContent);
await document.SaveAsync("output.pdf");
}
}// NuGet: Install-Package PSPDFKit.Dotnet
using PSPDFKit.Pdf;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var htmlContent = "<html><body><h1>Hello World</h1></body></html>";
using var processor = await PdfProcessor.CreateAsync();
var document = await processor.GeneratePdfFromHtmlStringAsync(htmlContent);
await document.SaveAsync("output.pdf");
}
}After (IronPDF):
// NuGet: Install-Package IronPdf
using IronPdf;
class Program
{
static void Main()
{
var htmlContent = "<html><body><h1>Hello World</h1></body></html>";
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(htmlContent);
pdf.SaveAs("output.pdf");
}
}// NuGet: Install-Package IronPdf
using IronPdf;
class Program
{
static void Main()
{
var htmlContent = "<html><body><h1>Hello World</h1></body></html>";
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(htmlContent);
pdf.SaveAs("output.pdf");
}
}The Nutrient.io approach requires several async steps: creating a PdfProcessor with await PdfProcessor.CreateAsync(), then calling await processor.GeneratePdfFromHtmlStringAsync(), and finally await document.SaveAsync(). The entire method must be marked async Task, and the processor requires a using statement for proper disposal.
IronPDF simplifies this dramatically. Create a ChromePdfRenderer, call RenderHtmlAsPdf(), and save with SaveAs(). No async/await required, no processor lifecycle to manage, and no using blocks needed for simple operations. This pattern is more intuitive for developers who don't need async patterns for their PDF workflow. See the HTML to PDF documentation for additional rendering options.
Example 2: Merging Multiple PDFs
Before (Nutrient.io):
// NuGet: Install-Package PSPDFKit.Dotnet
using PSPDFKit.Pdf;
using System.Threading.Tasks;
using System.Collections.Generic;
class Program
{
static async Task Main()
{
using var processor = await PdfProcessor.CreateAsync();
var document1 = await processor.OpenAsync("document1.pdf");
var document2 = await processor.OpenAsync("document2.pdf");
var mergedDocument = await processor.MergeAsync(new List<PdfDocument> { document1, document2 });
await mergedDocument.SaveAsync("merged.pdf");
}
}// NuGet: Install-Package PSPDFKit.Dotnet
using PSPDFKit.Pdf;
using System.Threading.Tasks;
using System.Collections.Generic;
class Program
{
static async Task Main()
{
using var processor = await PdfProcessor.CreateAsync();
var document1 = await processor.OpenAsync("document1.pdf");
var document2 = await processor.OpenAsync("document2.pdf");
var mergedDocument = await processor.MergeAsync(new List<PdfDocument> { document1, document2 });
await mergedDocument.SaveAsync("merged.pdf");
}
}After (IronPDF):
// NuGet: Install-Package IronPdf
using IronPdf;
using System.Collections.Generic;
class Program
{
static void Main()
{
var pdf1 = PdfDocument.FromFile("document1.pdf");
var pdf2 = PdfDocument.FromFile("document2.pdf");
var merged = PdfDocument.Merge(pdf1, pdf2);
merged.SaveAs("merged.pdf");
}
}// NuGet: Install-Package IronPdf
using IronPdf;
using System.Collections.Generic;
class Program
{
static void Main()
{
var pdf1 = PdfDocument.FromFile("document1.pdf");
var pdf2 = PdfDocument.FromFile("document2.pdf");
var merged = PdfDocument.Merge(pdf1, pdf2);
merged.SaveAs("merged.pdf");
}
}The Nutrient.io merge operation requires creating a processor with await PdfProcessor.CreateAsync(), opening each document with separate await processor.OpenAsync() calls, creating a List<PdfDocument>, calling await processor.MergeAsync() with that list, and finally await mergedDocument.SaveAsync(). That's five async operations for a basic merge.
IronPDF reduces this to four synchronous lines: load each PDF with PdfDocument.FromFile(), merge with the static PdfDocument.Merge() method, and save. No processor lifecycle, no list creation required (you can pass documents directly), and no async overhead. Learn more about merging and splitting PDFs.
Example 3: Adding Watermarks
Before (Nutrient.io):
// NuGet: Install-Package PSPDFKit.Dotnet
using PSPDFKit.Pdf;
using PSPDFKit.Pdf.Annotation;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
using var processor = await PdfProcessor.CreateAsync();
var document = await processor.OpenAsync("document.pdf");
for (int i = 0; i < document.PageCount; i++)
{
var watermark = new TextAnnotation("CONFIDENTIAL")
{
Opacity = 0.5,
FontSize = 48
};
await document.AddAnnotationAsync(i, watermark);
}
await document.SaveAsync("watermarked.pdf");
}
}// NuGet: Install-Package PSPDFKit.Dotnet
using PSPDFKit.Pdf;
using PSPDFKit.Pdf.Annotation;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
using var processor = await PdfProcessor.CreateAsync();
var document = await processor.OpenAsync("document.pdf");
for (int i = 0; i < document.PageCount; i++)
{
var watermark = new TextAnnotation("CONFIDENTIAL")
{
Opacity = 0.5,
FontSize = 48
};
await document.AddAnnotationAsync(i, watermark);
}
await document.SaveAsync("watermarked.pdf");
}
}After (IronPDF):
// NuGet: Install-Package IronPdf
using IronPdf;
using IronPdf.Editing;
class Program
{
static void Main()
{
var pdf = PdfDocument.FromFile("document.pdf");
pdf.ApplyWatermark("<h1 style='color:gray;opacity:0.5;'>CONFIDENTIAL</h1>",
50,
VerticalAlignment.Middle,
HorizontalAlignment.Center);
pdf.SaveAs("watermarked.pdf");
}
}// NuGet: Install-Package IronPdf
using IronPdf;
using IronPdf.Editing;
class Program
{
static void Main()
{
var pdf = PdfDocument.FromFile("document.pdf");
pdf.ApplyWatermark("<h1 style='color:gray;opacity:0.5;'>CONFIDENTIAL</h1>",
50,
VerticalAlignment.Middle,
HorizontalAlignment.Center);
pdf.SaveAs("watermarked.pdf");
}
}This example highlights a fundamental architectural difference. Nutrient.io uses an annotation-based approach: you create a TextAnnotation object with properties like Opacity and FontSize, then loop through every page calling await document.AddAnnotationAsync(i, watermark) for each one. This requires understanding the annotation system and managing the loop yourself.
IronPDF uses an HTML-based approach: the ApplyWatermark() method accepts an HTML string with CSS styling. The watermark is automatically applied to all pages in a single call. You control appearance through familiar CSS properties (color, opacity, font-size) rather than annotation-specific object properties. This approach provides more styling flexibility—you can use any HTML/CSS including gradients, images, and complex layouts. See the watermark documentation for advanced examples.
Critical Migration Notes
Async to Sync Conversion
The most significant change is removing unnecessary async/await patterns:
// Nutrient.io: Async-first
using var processor = await PdfProcessor.CreateAsync();
var document = await processor.GeneratePdfFromHtmlStringAsync(html);
await document.SaveAsync("output.pdf");
// IronPDF: Sync by default (async available when needed)
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");// Nutrient.io: Async-first
using var processor = await PdfProcessor.CreateAsync();
var document = await processor.GeneratePdfFromHtmlStringAsync(html);
await document.SaveAsync("output.pdf");
// IronPDF: Sync by default (async available when needed)
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");If you do need async operations, IronPDF provides async variants like RenderHtmlAsPdfAsync().
Processor Lifecycle Elimination
Nutrient.io requires processor creation and disposal:
// Nutrient.io: Processor lifecycle management
using var processor = await PdfProcessor.CreateAsync();
// ... use processor ...
// Processor disposed at end of using block
// IronPDF: No processor lifecycle
var renderer = new ChromePdfRenderer();
// Reuse renderer, no complex lifecycle management// Nutrient.io: Processor lifecycle management
using var processor = await PdfProcessor.CreateAsync();
// ... use processor ...
// Processor disposed at end of using block
// IronPDF: No processor lifecycle
var renderer = new ChromePdfRenderer();
// Reuse renderer, no complex lifecycle managementConfiguration Pattern Change
Nutrient.io uses configuration objects; IronPDF uses properties:
// Nutrient.io: Config object
var config = new PdfConfiguration
{
PageSize = PageSize.A4,
Margins = new Margins(20, 20, 20, 20)
};
var doc = await processor.GeneratePdfFromHtmlStringAsync(html, config);
// IronPDF: Properties on RenderingOptions
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperSize = PdfPaperSize.A4;
renderer.RenderingOptions.MarginTop = 20;
renderer.RenderingOptions.MarginBottom = 20;
renderer.RenderingOptions.MarginLeft = 20;
renderer.RenderingOptions.MarginRight = 20;
var pdf = renderer.RenderHtmlAsPdf(html);// Nutrient.io: Config object
var config = new PdfConfiguration
{
PageSize = PageSize.A4,
Margins = new Margins(20, 20, 20, 20)
};
var doc = await processor.GeneratePdfFromHtmlStringAsync(html, config);
// IronPDF: Properties on RenderingOptions
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperSize = PdfPaperSize.A4;
renderer.RenderingOptions.MarginTop = 20;
renderer.RenderingOptions.MarginBottom = 20;
renderer.RenderingOptions.MarginLeft = 20;
renderer.RenderingOptions.MarginRight = 20;
var pdf = renderer.RenderHtmlAsPdf(html);Annotation to HTML Watermarks
Replace annotation objects with HTML strings:
// Nutrient.io: Annotation object with properties
new TextAnnotation("CONFIDENTIAL") { Opacity = 0.5f, FontSize = 48 }
// IronPDF: HTML with CSS
"<h1 style='opacity:0.5; font-size:48px;'>CONFIDENTIAL</h1>"// Nutrient.io: Annotation object with properties
new TextAnnotation("CONFIDENTIAL") { Opacity = 0.5f, FontSize = 48 }
// IronPDF: HTML with CSS
"<h1 style='opacity:0.5; font-size:48px;'>CONFIDENTIAL</h1>"Page Number Handling
Nutrient.io requires manual page counting; IronPDF has built-in placeholders:
// Nutrient.io: Manual loop and page counting
for (int i = 0; i < doc.PageCount; i++)
{
var footer = new TextAnnotation($"Page {i + 1} of {doc.PageCount}");
await doc.AddAnnotationAsync(i, footer);
}
// IronPDF: Built-in placeholders
renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
{
HtmlFragment = "Page {page} of {total-pages}"
};// Nutrient.io: Manual loop and page counting
for (int i = 0; i < doc.PageCount; i++)
{
var footer = new TextAnnotation($"Page {i + 1} of {doc.PageCount}");
await doc.AddAnnotationAsync(i, footer);
}
// IronPDF: Built-in placeholders
renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
{
HtmlFragment = "Page {page} of {total-pages}"
};Troubleshooting
Issue 1: PdfProcessor Not Found
Problem: PdfProcessor class doesn't exist in IronPDF.
Solution: Use ChromePdfRenderer:
// Nutrient.io
using var processor = await PdfProcessor.CreateAsync();
// IronPDF
var renderer = new ChromePdfRenderer();// Nutrient.io
using var processor = await PdfProcessor.CreateAsync();
// IronPDF
var renderer = new ChromePdfRenderer();Issue 2: GeneratePdfFromHtmlStringAsync Not Found
Problem: Async HTML method doesn't exist.
Solution: Use RenderHtmlAsPdf():
// Nutrient.io
var document = await processor.GeneratePdfFromHtmlStringAsync(html);
// IronPDF
var pdf = renderer.RenderHtmlAsPdf(html);// Nutrient.io
var document = await processor.GeneratePdfFromHtmlStringAsync(html);
// IronPDF
var pdf = renderer.RenderHtmlAsPdf(html);Issue 3: TextAnnotation Not Found
Problem: Annotation classes don't exist in IronPDF.
Solution: Use HTML-based watermarks:
// Nutrient.io
var watermark = new TextAnnotation("DRAFT") { Opacity = 0.5 };
await document.AddAnnotationAsync(0, watermark);
// IronPDF
pdf.ApplyWatermark("<div style='opacity:0.5;'>DRAFT</div>");// Nutrient.io
var watermark = new TextAnnotation("DRAFT") { Opacity = 0.5 };
await document.AddAnnotationAsync(0, watermark);
// IronPDF
pdf.ApplyWatermark("<div style='opacity:0.5;'>DRAFT</div>");Issue 4: MergeAsync Not Found
Problem: Async merge method doesn't exist.
Solution: Use static PdfDocument.Merge():
// Nutrient.io
var mergedDocument = await processor.MergeAsync(documentList);
// IronPDF
var merged = PdfDocument.Merge(pdf1, pdf2);// Nutrient.io
var mergedDocument = await processor.MergeAsync(documentList);
// IronPDF
var merged = PdfDocument.Merge(pdf1, pdf2);Migration Checklist
Pre-Migration
- Inventory all PSPDFKit/Nutrient usages in codebase
- Document async patterns that may need adjustment
- List all configuration objects and their properties
- Identify annotation-based features (watermarks, headers)
- Review form handling requirements
- Obtain IronPDF license key
Package Changes
- Remove
PSPDFKit.NETNuGet package - Remove
NutrientNuGet package - Install
IronPdfNuGet package:dotnet add package IronPdf - Update namespace imports
Code Changes
- Add license key configuration at startup
- Replace
PdfProcessor.CreateAsync()withnew ChromePdfRenderer() - Replace
processor.GeneratePdfFromHtmlStringAsync()withrenderer.RenderHtmlAsPdf() - Replace
processor.MergeAsync()withPdfDocument.Merge() - Convert
TextAnnotationwatermarks to HTML watermarks - Replace config objects with
RenderingOptionsproperties - Update header/footer to use
HtmlHeaderFooterwith placeholders - Remove unnecessary async/await patterns
Post-Migration
- Remove async/await where no longer needed
- Run regression tests comparing PDF output
- Verify headers/footers with page numbers
- Test watermark rendering
- Update CI/CD pipeline






