Skip to footer content
MIGRATION GUIDES

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:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. Heavy Dependencies: The full platform requires more resources with a larger package footprint, more initialization time, and additional configuration.

Nutrient.io vs IronPDF Comparison

AspectNutrient.io (PSPDFKit)IronPDF
FocusDocument intelligence platformPDF library
PricingEnterprise (contact sales)Transparent, published
ArchitectureComplex platformSimple library
API StyleAsync-firstSync with async options
DependenciesHeavyLightweight
ConfigurationComplex config objectsStraightforward properties
Learning CurveSteep (platform)Gentle (library)
Target UsersEnterpriseAll 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

  1. .NET Environment: .NET Framework 4.6.2+ or .NET Core 3.1+ / .NET 5/6/7/8/9+
  2. NuGet Access: Ability to install NuGet packages
  3. 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 IronPdf
SHELL

License 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";
$vbLabelText   $csharpLabel

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" .
SHELL

Complete API Reference

Initialization Mappings

Nutrient.io (PSPDFKit)IronPDFNotes
await PdfProcessor.CreateAsync()new ChromePdfRenderer()No async required
processor.Dispose()(automatic or manual)Simpler lifecycle
new PdfConfiguration { ... }renderer.RenderingOptionsProperty-based

Document Loading Mappings

Nutrient.io (PSPDFKit)IronPDFNotes
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)IronPDFNotes
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)IronPDFNotes
await processor.MergeAsync(docs)PdfDocument.Merge(pdfs)Sync
document.PageCountpdf.PageCountSame pattern
await document.SaveAsync(path)pdf.SaveAs(path)Sync
document.ToBytes()pdf.BinaryDataByte array

Annotations and Watermarks Mappings

Nutrient.io (PSPDFKit)IronPDFNotes
await document.AddAnnotationAsync(index, annotation)pdf.ApplyWatermark(html)HTML-based
new TextAnnotation("text")HTML in watermarkMore flexible
annotation.Opacity = 0.5CSS opacity: 0.5CSS styling
annotation.FontSize = 48CSS font-size: 48pxCSS 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");
    }
}
$vbLabelText   $csharpLabel

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");
    }
}
$vbLabelText   $csharpLabel

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");
    }
}
$vbLabelText   $csharpLabel

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");
    }
}
$vbLabelText   $csharpLabel

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");
    }
}
$vbLabelText   $csharpLabel

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");
    }
}
$vbLabelText   $csharpLabel

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");
$vbLabelText   $csharpLabel

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 management
$vbLabelText   $csharpLabel

Configuration 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);
$vbLabelText   $csharpLabel

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>"
$vbLabelText   $csharpLabel

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}"
};
$vbLabelText   $csharpLabel

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();
$vbLabelText   $csharpLabel

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);
$vbLabelText   $csharpLabel

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>");
$vbLabelText   $csharpLabel

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);
$vbLabelText   $csharpLabel

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.NET NuGet package
  • Remove Nutrient NuGet package
  • Install IronPdf NuGet package: dotnet add package IronPdf
  • Update namespace imports

Code Changes

  • Add license key configuration at startup
  • Replace PdfProcessor.CreateAsync() with new ChromePdfRenderer()
  • Replace processor.GeneratePdfFromHtmlStringAsync() with renderer.RenderHtmlAsPdf()
  • Replace processor.MergeAsync() with PdfDocument.Merge()
  • Convert TextAnnotation watermarks to HTML watermarks
  • Replace config objects with RenderingOptions properties
  • Update header/footer to use HtmlHeaderFooter with 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

Curtis Chau
Technical Writer

Curtis Chau holds a Bachelor’s degree in Computer Science (Carleton University) and specializes in front-end development with expertise in Node.js, TypeScript, JavaScript, and React. Passionate about crafting intuitive and aesthetically pleasing user interfaces, Curtis enjoys working with modern frameworks and creating well-structured, visually appealing manuals.

...

Read More