How to Migrate from jsreport to IronPDF in C#
Migrating from jsreport to IronPDF transforms your .NET PDF workflow from a Node.js-dependent system with external binary management and separate server processes to a pure .NET library that runs entirely in-process. This guide provides a comprehensive, step-by-step migration path that eliminates infrastructure complexity and JavaScript templating requirements for professional .NET developers.
Why Migrate from jsreport to IronPDF
The jsreport Challenges
jsreport introduces complexity that doesn't belong in a pure .NET environment:
Node.js Dependency: Requires Node.js runtime and binaries, adding infrastructure complexity to what should be a straightforward .NET application.
External Binary Management: Must download and manage platform-specific binaries for Windows, Linux, and OSX through separate NuGet packages (
jsreport.Binary,jsreport.Binary.Linux,jsreport.Binary.OSX).Separate Server Process: Runs as either a utility or web server—additional process management is required with
StartAsync()andKillAsync()lifecycle methods.JavaScript Templating: Forces learning Handlebars, JsRender, or other JavaScript templating systems instead of using native C# capabilities.
Complex Request Structure: Requires verbose
RenderRequestobjects with nestedTemplateconfigurations for even simple PDF generation.Licensing Limitations: Free tier limits template count; scaling requires commercial license.
- Stream-Based Output: Returns streams requiring manual file operations and memory stream management.
jsreport vs IronPDF Comparison
| Feature | jsreport | IronPDF |
|---|---|---|
| Runtime | Node.js + .NET | Pure .NET |
| Binary Management | Manual (jsreport.Binary packages) | Automatic |
| Server Process | Required (utility or web server) | In-process |
| Templating | JavaScript (Handlebars, etc.) | C# (Razor, string interpolation) |
| API Style | Verbose request objects | Clean fluent methods |
| Output | Stream | PdfDocument object |
| PDF Manipulation | Limited | Extensive (merge, split, edit) |
| Async Support | Async-only | Both sync and async |
For teams planning .NET 10 and C# 14 adoption through 2025 and 2026, IronPDF provides a future-proof foundation as a native .NET library without external runtime dependencies.
Migration Complexity Assessment
Estimated Effort by Feature
| Feature | Migration Complexity | Notes |
|---|---|---|
| HTML to PDF | Very Low | Direct method replacement |
| URL to PDF | Very Low | Direct method, no workarounds |
| Headers/Footers | Low | Placeholder syntax change |
| Page Settings | Low | Property mapping |
| Server Lifecycle | Low | Delete entirely |
| Binary Management | Low | Delete entirely |
Paradigm Shift
The fundamental shift in this jsreport migration is from verbose request objects with server management to simple in-process method calls:
jsreport: LocalReporting().UseBinary().AsUtility().Create() → RenderAsync(RenderRequest) → Stream → File
IronPDF: ChromePdfRenderer → RenderHtmlAsPdf(html) → SaveAs()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 jsreport packages
dotnet remove package jsreport.Binary
dotnet remove package jsreport.Binary.Linux
dotnet remove package jsreport.Binary.OSX
dotnet remove package jsreport.Local
dotnet remove package jsreport.Types
dotnet remove package jsreport.Client
# Install IronPDF
dotnet add package IronPdf# Remove jsreport packages
dotnet remove package jsreport.Binary
dotnet remove package jsreport.Binary.Linux
dotnet remove package jsreport.Binary.OSX
dotnet remove package jsreport.Local
dotnet remove package jsreport.Types
dotnet remove package jsreport.Client
# 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 jsreport Usage
# Find all jsreport references
grep -r "using jsreport\|LocalReporting\|RenderRequest\|RenderAsync" --include="*.cs" .
grep -r "JsReportBinary\|Template\|Recipe\|Engine\." --include="*.cs" .# Find all jsreport references
grep -r "using jsreport\|LocalReporting\|RenderRequest\|RenderAsync" --include="*.cs" .
grep -r "JsReportBinary\|Template\|Recipe\|Engine\." --include="*.cs" .Complete API Reference
Class Mappings
| jsreport Class | IronPDF Equivalent | Notes |
|---|---|---|
LocalReporting | ChromePdfRenderer | Main renderer |
ReportingService | ChromePdfRenderer | Same class |
RenderRequest | Method parameters | No wrapper needed |
Template | Method parameters | No wrapper needed |
Chrome | RenderingOptions | Chrome options |
Report | PdfDocument | Result object |
Engine | (not needed) | C# for templating |
Method Mappings
| jsreport Method | IronPDF Equivalent | Notes |
|---|---|---|
LocalReporting().UseBinary().AsUtility().Create() | new ChromePdfRenderer() | One-liner |
rs.RenderAsync(request) | renderer.RenderHtmlAsPdf(html) | Direct call |
rs.StartAsync() | (not needed) | In-process |
rs.KillAsync() | (not needed) | Auto-cleanup |
report.Content.CopyTo(stream) | pdf.SaveAs(path) or pdf.BinaryData | Direct access |
RenderRequest Property Mappings
| jsreport Template Property | IronPDF Equivalent | Notes |
|---|---|---|
Template.Content | First parameter to RenderHtmlAsPdf() | Direct HTML string |
Template.Recipe = Recipe.ChromePdf | (not needed) | Always ChromePdf |
Template.Engine = Engine.None | (not needed) | Use C# templating |
Chrome.HeaderTemplate | RenderingOptions.TextHeader or HtmlHeader | HTML headers |
Chrome.FooterTemplate | RenderingOptions.TextFooter or HtmlFooter | HTML footers |
Chrome.DisplayHeaderFooter | (automatic) | Headers auto-enabled |
Chrome.MarginTop | RenderingOptions.MarginTop | In millimeters |
Placeholder Mappings (Headers/Footers)
| jsreport Placeholder | IronPDF Placeholder | Notes |
|---|---|---|
<span class='pageNumber'></span> | {page} | Current page |
<span class='totalPages'></span> | {total-pages} | Total pages |
{#pageNum} | {page} | Current page |
{#numPages} | {total-pages} | Total pages |
{#timestamp} | {date} | Current date |
Code Migration Examples
Example 1: Basic HTML to PDF
Before (jsreport):
// NuGet: Install-Package jsreport.Binary
// NuGet: Install-Package jsreport.Local
// NuGet: Install-Package jsreport.Types
using jsreport.Binary;
using jsreport.Local;
using jsreport.Types;
using System;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var rs = new LocalReporting()
.UseBinary(JsReportBinary.GetBinary())
.AsUtility()
.Create();
var report = await rs.RenderAsync(new RenderRequest()
{
Template = new Template()
{
Recipe = Recipe.ChromePdf,
Engine = Engine.None,
Content = "<h1>Hello from jsreport</h1><p>This is a PDF document.</p>"
}
});
using (var fileStream = File.Create("output.pdf"))
{
report.Content.CopyTo(fileStream);
}
Console.WriteLine("PDF created successfully!");
}
}// NuGet: Install-Package jsreport.Binary
// NuGet: Install-Package jsreport.Local
// NuGet: Install-Package jsreport.Types
using jsreport.Binary;
using jsreport.Local;
using jsreport.Types;
using System;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var rs = new LocalReporting()
.UseBinary(JsReportBinary.GetBinary())
.AsUtility()
.Create();
var report = await rs.RenderAsync(new RenderRequest()
{
Template = new Template()
{
Recipe = Recipe.ChromePdf,
Engine = Engine.None,
Content = "<h1>Hello from jsreport</h1><p>This is a PDF document.</p>"
}
});
using (var fileStream = File.Create("output.pdf"))
{
report.Content.CopyTo(fileStream);
}
Console.WriteLine("PDF created successfully!");
}
}After (IronPDF):
// NuGet: Install-Package IronPdf
using IronPdf;
using System;
class Program
{
static void Main(string[] args)
{
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf("<h1>Hello from IronPDF</h1><p>This is a PDF document.</p>");
pdf.SaveAs("output.pdf");
Console.WriteLine("PDF created successfully!");
}
}// NuGet: Install-Package IronPdf
using IronPdf;
using System;
class Program
{
static void Main(string[] args)
{
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf("<h1>Hello from IronPDF</h1><p>This is a PDF document.</p>");
pdf.SaveAs("output.pdf");
Console.WriteLine("PDF created successfully!");
}
}The jsreport approach requires three NuGet packages (jsreport.Binary, jsreport.Local, jsreport.Types), three namespace imports, async-only execution, a fluent builder chain (LocalReporting().UseBinary().AsUtility().Create()), a verbose RenderRequest with nested Template object specifying Recipe and Engine, and manual stream-to-file copying with a using block.
IronPDF reduces this to one NuGet package, one namespace, three lines of code, and synchronous execution. The ChromePdfRenderer.RenderHtmlAsPdf() method accepts HTML directly and returns a PdfDocument with a simple SaveAs() method. See the HTML to PDF documentation for additional rendering options.
Example 2: URL to PDF
Before (jsreport):
// NuGet: Install-Package jsreport.Binary
// NuGet: Install-Package jsreport.Local
// NuGet: Install-Package jsreport.Types
using jsreport.Binary;
using jsreport.Local;
using jsreport.Types;
using System;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var rs = new LocalReporting()
.UseBinary(JsReportBinary.GetBinary())
.AsUtility()
.Create();
var report = await rs.RenderAsync(new RenderRequest()
{
Template = new Template()
{
Recipe = Recipe.ChromePdf,
Engine = Engine.None,
Content = "<html><body><script>window.location='https://example.com';</script></body></html>"
}
});
using (var fileStream = File.Create("webpage.pdf"))
{
report.Content.CopyTo(fileStream);
}
Console.WriteLine("Webpage PDF created successfully!");
}
}// NuGet: Install-Package jsreport.Binary
// NuGet: Install-Package jsreport.Local
// NuGet: Install-Package jsreport.Types
using jsreport.Binary;
using jsreport.Local;
using jsreport.Types;
using System;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var rs = new LocalReporting()
.UseBinary(JsReportBinary.GetBinary())
.AsUtility()
.Create();
var report = await rs.RenderAsync(new RenderRequest()
{
Template = new Template()
{
Recipe = Recipe.ChromePdf,
Engine = Engine.None,
Content = "<html><body><script>window.location='https://example.com';</script></body></html>"
}
});
using (var fileStream = File.Create("webpage.pdf"))
{
report.Content.CopyTo(fileStream);
}
Console.WriteLine("Webpage PDF created successfully!");
}
}After (IronPDF):
// NuGet: Install-Package IronPdf
using IronPdf;
using System;
class Program
{
static void Main(string[] args)
{
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderUrlAsPdf("https://example.com");
pdf.SaveAs("webpage.pdf");
Console.WriteLine("Webpage PDF created successfully!");
}
}// NuGet: Install-Package IronPdf
using IronPdf;
using System;
class Program
{
static void Main(string[] args)
{
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderUrlAsPdf("https://example.com");
pdf.SaveAs("webpage.pdf");
Console.WriteLine("Webpage PDF created successfully!");
}
}This example highlights a significant limitation in jsreport: there's no direct URL-to-PDF method. The jsreport code must use a JavaScript redirect workaround (window.location='https://example.com') embedded in HTML content to capture a webpage. This indirect approach can fail with certain websites and adds unnecessary complexity.
IronPDF provides a dedicated RenderUrlAsPdf() method that directly renders any URL with full JavaScript execution and modern CSS support. No workarounds, no embedded scripts—just pass the URL. Learn more about URL to PDF conversion.
Example 3: PDF with Headers and Footers
Before (jsreport):
// NuGet: Install-Package jsreport.Binary
// NuGet: Install-Package jsreport.Local
// NuGet: Install-Package jsreport.Types
using jsreport.Binary;
using jsreport.Local;
using jsreport.Types;
using System;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var rs = new LocalReporting()
.UseBinary(JsReportBinary.GetBinary())
.AsUtility()
.Create();
var report = await rs.RenderAsync(new RenderRequest()
{
Template = new Template()
{
Recipe = Recipe.ChromePdf,
Engine = Engine.None,
Content = "<h1>Document with Header and Footer</h1><p>Main content goes here.</p>",
Chrome = new Chrome()
{
DisplayHeaderFooter = true,
HeaderTemplate = "<div style='font-size:10px; text-align:center; width:100%;'>Custom Header</div>",
FooterTemplate = "<div style='font-size:10px; text-align:center; width:100%;'>Page <span class='pageNumber'></span> of <span class='totalPages'></span></div>"
}
}
});
using (var fileStream = File.Create("document_with_headers.pdf"))
{
report.Content.CopyTo(fileStream);
}
Console.WriteLine("PDF with headers and footers created successfully!");
}
}// NuGet: Install-Package jsreport.Binary
// NuGet: Install-Package jsreport.Local
// NuGet: Install-Package jsreport.Types
using jsreport.Binary;
using jsreport.Local;
using jsreport.Types;
using System;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var rs = new LocalReporting()
.UseBinary(JsReportBinary.GetBinary())
.AsUtility()
.Create();
var report = await rs.RenderAsync(new RenderRequest()
{
Template = new Template()
{
Recipe = Recipe.ChromePdf,
Engine = Engine.None,
Content = "<h1>Document with Header and Footer</h1><p>Main content goes here.</p>",
Chrome = new Chrome()
{
DisplayHeaderFooter = true,
HeaderTemplate = "<div style='font-size:10px; text-align:center; width:100%;'>Custom Header</div>",
FooterTemplate = "<div style='font-size:10px; text-align:center; width:100%;'>Page <span class='pageNumber'></span> of <span class='totalPages'></span></div>"
}
}
});
using (var fileStream = File.Create("document_with_headers.pdf"))
{
report.Content.CopyTo(fileStream);
}
Console.WriteLine("PDF with headers and footers created successfully!");
}
}After (IronPDF):
// NuGet: Install-Package IronPdf
using IronPdf;
using IronPdf.Rendering;
using System;
class Program
{
static void Main(string[] args)
{
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.TextHeader = new TextHeaderFooter()
{
CenterText = "Custom Header",
FontSize = 10
};
renderer.RenderingOptions.TextFooter = new TextHeaderFooter()
{
CenterText = "Page {page} of {total-pages}",
FontSize = 10
};
var pdf = renderer.RenderHtmlAsPdf("<h1>Document with Header and Footer</h1><p>Main content goes here.</p>");
pdf.SaveAs("document_with_headers.pdf");
Console.WriteLine("PDF with headers and footers created successfully!");
}
}// NuGet: Install-Package IronPdf
using IronPdf;
using IronPdf.Rendering;
using System;
class Program
{
static void Main(string[] args)
{
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.TextHeader = new TextHeaderFooter()
{
CenterText = "Custom Header",
FontSize = 10
};
renderer.RenderingOptions.TextFooter = new TextHeaderFooter()
{
CenterText = "Page {page} of {total-pages}",
FontSize = 10
};
var pdf = renderer.RenderHtmlAsPdf("<h1>Document with Header and Footer</h1><p>Main content goes here.</p>");
pdf.SaveAs("document_with_headers.pdf");
Console.WriteLine("PDF with headers and footers created successfully!");
}
}The jsreport approach requires adding a Chrome object to the Template, setting DisplayHeaderFooter = true, and using HTML templates with special CSS class placeholders (<span class='pageNumber'></span>, <span class='totalPages'></span>). The header and footer templates must include full inline styling.
IronPDF provides a cleaner TextHeaderFooter configuration with dedicated properties for CenterText, LeftText, RightText, and FontSize. Page number placeholders use the simpler {page} and {total-pages} syntax. See the headers and footers documentation for HTML header options.
Critical Migration Notes
Eliminate Server Lifecycle Management
jsreport requires explicit server lifecycle management:
// jsreport (DELETE THIS):
var rs = new LocalReporting()
.UseBinary(JsReportBinary.GetBinary())
.AsUtility()
.Create();
// Or for web server mode:
var rs = new LocalReporting()
.UseBinary(JsReportBinary.GetBinary())
.AsWebServer()
.Create();
await rs.StartAsync();
// ... use rs ...
await rs.KillAsync();// jsreport (DELETE THIS):
var rs = new LocalReporting()
.UseBinary(JsReportBinary.GetBinary())
.AsUtility()
.Create();
// Or for web server mode:
var rs = new LocalReporting()
.UseBinary(JsReportBinary.GetBinary())
.AsWebServer()
.Create();
await rs.StartAsync();
// ... use rs ...
await rs.KillAsync();IronPDF runs entirely in-process—no server startup, no process management, no cleanup:
// IronPDF:
var renderer = new ChromePdfRenderer();
// Just use it—no lifecycle management needed// IronPDF:
var renderer = new ChromePdfRenderer();
// Just use it—no lifecycle management neededRemove Platform-Specific Binary Packages
jsreport requires separate NuGet packages for each target platform:
# DELETE these platform-specific packages:
dotnet remove package jsreport.Binary
dotnet remove package jsreport.Binary.Linux
dotnet remove package jsreport.Binary.OSX# DELETE these platform-specific packages:
dotnet remove package jsreport.Binary
dotnet remove package jsreport.Binary.Linux
dotnet remove package jsreport.Binary.OSXIronPDF handles all platform requirements automatically through a single NuGet package.
Update Placeholder Syntax
jsreport uses CSS class-based or curly brace placeholders. IronPDF uses a different syntax:
// jsreport placeholders:
"<span class='pageNumber'></span>" // or {#pageNum}
"<span class='totalPages'></span>" // or {#numPages}
// IronPDF placeholders:
"{page}"
"{total-pages}"
"{date}"
"{html-title}"// jsreport placeholders:
"<span class='pageNumber'></span>" // or {#pageNum}
"<span class='totalPages'></span>" // or {#numPages}
// IronPDF placeholders:
"{page}"
"{total-pages}"
"{date}"
"{html-title}"Replace Handlebars with C# String Interpolation
jsreport often uses Handlebars templating with Engine.Handlebars:
// jsreport Handlebars (DELETE THIS):
Template = new Template
{
Content = "<h1>Hello, {{name}}</h1>",
Engine = Engine.Handlebars
},
Data = new { name = "World" }
// IronPDF with C# string interpolation:
string name = "World";
string html = $"<h1>Hello, {name}</h1>";
var pdf = renderer.RenderHtmlAsPdf(html);// jsreport Handlebars (DELETE THIS):
Template = new Template
{
Content = "<h1>Hello, {{name}}</h1>",
Engine = Engine.Handlebars
},
Data = new { name = "World" }
// IronPDF with C# string interpolation:
string name = "World";
string html = $"<h1>Hello, {name}</h1>";
var pdf = renderer.RenderHtmlAsPdf(html);Simplify Stream Handling
jsreport returns a stream that requires manual copying:
// jsreport stream handling (DELETE THIS):
using (var fileStream = File.Create("output.pdf"))
{
report.Content.CopyTo(fileStream);
}
// Or for byte array:
using (var memoryStream = new MemoryStream())
{
await report.Content.CopyToAsync(memoryStream);
return memoryStream.ToArray();
}
// IronPDF direct access:
pdf.SaveAs("output.pdf");
// Or:
byte[] bytes = pdf.BinaryData;// jsreport stream handling (DELETE THIS):
using (var fileStream = File.Create("output.pdf"))
{
report.Content.CopyTo(fileStream);
}
// Or for byte array:
using (var memoryStream = new MemoryStream())
{
await report.Content.CopyToAsync(memoryStream);
return memoryStream.ToArray();
}
// IronPDF direct access:
pdf.SaveAs("output.pdf");
// Or:
byte[] bytes = pdf.BinaryData;Troubleshooting
Issue 1: LocalReporting Not Found
Problem: Code references LocalReporting class which doesn't exist in IronPDF.
Solution: Replace with ChromePdfRenderer:
// jsreport
var rs = new LocalReporting().UseBinary().AsUtility().Create();
// IronPDF
var renderer = new ChromePdfRenderer();// jsreport
var rs = new LocalReporting().UseBinary().AsUtility().Create();
// IronPDF
var renderer = new ChromePdfRenderer();Issue 2: RenderRequest Not Found
Problem: Code uses RenderRequest and Template wrapper objects.
Solution: Pass HTML directly to render methods:
// jsreport
await rs.RenderAsync(new RenderRequest { Template = new Template { Content = html } });
// IronPDF
var pdf = renderer.RenderHtmlAsPdf(html);// jsreport
await rs.RenderAsync(new RenderRequest { Template = new Template { Content = html } });
// IronPDF
var pdf = renderer.RenderHtmlAsPdf(html);Issue 3: Page Numbers Not Appearing
Problem: Using jsreport placeholder syntax <span class='pageNumber'></span>.
Solution: Update to IronPDF placeholder syntax:
// jsreport syntax (won't work)
"Page <span class='pageNumber'></span> of <span class='totalPages'></span>"
// IronPDF syntax
"Page {page} of {total-pages}"// jsreport syntax (won't work)
"Page <span class='pageNumber'></span> of <span class='totalPages'></span>"
// IronPDF syntax
"Page {page} of {total-pages}"Issue 4: JsReportBinary Not Found
Problem: Code references JsReportBinary.GetBinary().
Solution: Delete entirely—IronPDF doesn't require external binaries:
// DELETE this jsreport pattern:
.UseBinary(JsReportBinary.GetBinary())
// IronPDF needs nothing—just create the renderer:
var renderer = new ChromePdfRenderer();// DELETE this jsreport pattern:
.UseBinary(JsReportBinary.GetBinary())
// IronPDF needs nothing—just create the renderer:
var renderer = new ChromePdfRenderer();Migration Checklist
Pre-Migration
- Identify all jsreport
usingstatements - List templates using Handlebars/JsRender (convert to C# string interpolation)
- Document current Chrome options used (margins, paper size)
- Check for web server vs utility mode (both become in-process)
- Note platform-specific binary packages (delete all)
- Obtain IronPDF license key
Package Changes
- Remove
jsreport.Binarypackage - Remove
jsreport.Binary.Linuxpackage - Remove
jsreport.Binary.OSXpackage - Remove
jsreport.Localpackage - Remove
jsreport.Typespackage - Remove
jsreport.Clientpackage - Install
IronPdfpackage
Code Changes
- Add license key configuration at startup
- Replace
LocalReportingwithChromePdfRenderer - Remove
RenderRequestwrapper - Remove
Templatewrapper - Update placeholder syntax (
<span class='pageNumber'>→{page}) - Replace Handlebars with C# string interpolation
- Remove
StartAsync()/KillAsync()calls - Replace stream copying with
BinaryDataorSaveAs()
Testing
- Test all PDF generation paths
- Verify header/footer rendering
- Check page numbering
- Validate margin spacing
- Test with complex CSS/JavaScript pages
- Benchmark performance
Post-Migration
- Delete jsreport binary files
- Remove Node.js dependencies if no longer needed
- Update deployment scripts
- Update documentation






