Two architectures, one decision: a hosted template designer that renders JSON to PDF through a REST endpoint, versus an in-process .NET library that turns HTML and C# into a PdfDocument. The right answer depends less on features and more on where you want the rendering boundary to sit — in a vendor's cloud or inside your own process. This article walks through the trade-offs for .NET teams weighing CraftMyPDF against IronPDF.
CraftMyPDF is a cloud template service emphasizing visual design and no-code integration — you build a template in their web designer, then POST JSON to the render endpoint. IronPDF is a programmatic .NET library where developers control rendering with HTML, CSS, and C# code. The scenarios below contrast how each approach handles dynamic content, template evolution, and complex layouts.
Understanding IronPDF
IronPDF is a .NET library where developers write HTML and CSS to define PDF layouts. Install via Install-Package IronPdf. The ChromePdfRenderer converts HTML to PDF using a Chromium-based engine — the same family as the Chrome browser. Changes to layout? Edit HTML. Conditional content? Write C# logic. Debugging? Standard .NET debugger and browser preview.
For development teams, this means version control for templates (HTML files), unit testing for PDF generation, and familiar debugging workflows. No external template editor, no web UI dependency.
Where CraftMyPDF Fits
Product Status
CraftMyPDF is an active SaaS PDF generation service. The product centers on a visual drag-and-drop template designer plus a REST API that renders JSON data through those templates. Integrations with Zapier, Make.com (formerly Integromat), and Bubble.io support no-code workflows, and the documented focus is automation scenarios and low-code users.
Pricing is credit-based and tiered (Free, Lite, Plus, Professional, Premium, Business). Verify current limits and pricing on craftmypdf.com/pricing.
Architectural Boundaries
CraftMyPDF is a template-driven cloud service, not a code library. Templates are designed in the web-based drag-and-drop editor, and PDF generation happens server-side through the REST endpoint. Practical implications:
- No programmatic HTML-to-PDF: layouts are authored in the visual designer, not in raw HTML files inside your repo.
- No local processing: PDF generation runs on CraftMyPDF servers, so every render requires internet connectivity and an API call.
- Template logic is bounded by the component system: conditional rendering, dynamic layouts, and business rules live within the features exposed by the editor.
- Template storage is in the vendor platform: there is no built-in Git workflow for templates, so versioning relies on the platform's own history features rather than your source control.
- Debugging is template-driven: errors surface as failed API calls, and inspection happens through the designer rather than a step-through debugger.
Common Friction Points
Template field mapping: CraftMyPDF templates bind to specific JSON field names (e.g., {customer_name}, {invoice_total}). When backend data models evolve, the template typically needs to be updated by someone with editor access to keep field names in sync.
Conditional rendering in templates: Show/hide logic depends on what the template engine and component system expose. Patterns like "show this section only when discount > 0" may require API-side preprocessing or separate templates rather than inline if/else — verify your version's capabilities in the CraftMyPDF docs.
Component customization: Components (text, table, chart) ship with predefined behavior. Heavily custom HTML or pixel-tuned CSS may not be fully expressible through the designer; teams targeting print-exact layouts sometimes hit the editor's ceiling.
External dependency for every render: Every PDF requires an HTTP call. Network latency, API rate limits, and service availability all sit on the critical path, and offline or air-gapped scenarios are not supported by design.
Support and Integration Model
Email support is provided by the CraftMyPDF team, with documentation at craftmypdf.com/docs. There is no official .NET SDK — integration happens via direct HTTP/REST calls or third-party automation tools (Zapier, Make). That means your code owns the HTTP client, retry/backoff, error handling, and authentication.
Coordination Considerations
The template-first architecture creates a few coordination patterns worth planning around: templates are edited in the web designer rather than in your IDE, rendering logic lives in the vendor's environment, and template changes generally happen outside your normal Git review flow. Rollback, diff, and code-review workflows for templates depend on what the platform itself exposes.
Feature Comparison Overview
| Aspect | CraftMyPDF | IronPDF |
|---|---|---|
| Current Status | Active (SaaS) | Active (library) |
| HTML Support | Template components in designer | Full HTML/CSS/JavaScript |
| Rendering Engine | Cloud renderer (managed) | Local Chromium-based |
| Installation | REST integration + account | Single NuGet package |
| Support | CraftMyPDF (email) | Commercial (Iron Software) |
| Deployment | Hosted SaaS | Local / on-prem |
Scenario 1: Dynamic Content Based on Business Rules
CraftMyPDF — Template-Driven Flow
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
public class CraftMyPdfGenerator
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private readonly string _templateId;
public CraftMyPdfGenerator(string apiKey, string templateId)
{
_httpClient = new HttpClient();
_apiKey = apiKey;
_templateId = templateId;
}
public async Task<string> GenerateInvoiceAsync(InvoiceData invoice)
{
// Templates bind to a fixed set of JSON field names defined in the designer.
// Conditional rendering capabilities depend on the template engine — verify
// against your CraftMyPDF plan and the current docs.
var templateData = new
{
customer_name = invoice.CustomerName,
invoice_number = invoice.InvoiceNumber,
// The template reserves a discount field; emit "N/A" when not applicable
// so the bound field has a value to render.
discount_amount = invoice.Discount > 0
? $"${invoice.Discount:F2}"
: "N/A",
total = $"${invoice.Total:F2}"
};
// Dates and currency are formatted on the client to match what the template
// expects; changing format conventions means updating the template.
var requestBody = new
{
template_id = _templateId,
data = templateData,
export_type = "json" // "json" returns a CDN URL; "file" returns binary PDF
};
var json = JsonSerializer.Serialize(requestBody);
var content = new StringContent(json, Encoding.UTF8, "application/json");
_httpClient.DefaultRequestHeaders.Add("X-API-KEY", _apiKey);
var response = await _httpClient.PostAsync(
"https://api.craftmypdf.com/v1/create",
content);
if (!response.IsSuccessStatusCode)
{
var error = await response.Content.ReadAsStringAsync();
throw new Exception($"PDF generation failed: {error}");
}
var result = await response.Content.ReadAsStringAsync();
var pdfUrl = JsonSerializer.Deserialize<dynamic>(result).pdf_url;
return pdfUrl.ToString();
}
}
Things to plan around with the template-driven model:
- Conditional logic lives in the template engine and component system rather than in arbitrary code, so complex business rules may need API-side preprocessing.
- Format expectations are set by the template; data formatting on the client must match what the designer was configured to render.
- Rendering happens server-side, so iteration loops go through API calls rather than a local debugger.
- Template changes happen in the web designer, which is a different review surface than your code Git workflow.
- Local unit testing of the rendered output requires either mocking the HTTP call or hitting the live API.
- Data model evolution typically requires coordinated updates between the backend and the template.
IronPDF — Code-Based Control
using IronPdf;
using System.Text;
using System.Threading.Tasks;
public class IronPdfGenerator
{
public async Task<byte[]> GenerateInvoiceAsync(InvoiceData invoice)
{
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
// Conditional logic in C# — full control
var html = BuildInvoiceHtml(invoice);
var renderer = new ChromePdfRenderer();
using var pdf = await renderer.RenderHtmlAsPdfAsync(html);
return pdf.BinaryData;
}
private string BuildInvoiceHtml(InvoiceData invoice)
{
var html = new StringBuilder();
html.Append(@"
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial; margin: 20px; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
</style>
</head>
<body>
<h1>Invoice #" + invoice.InvoiceNumber + @"</h1>
<p><strong>Customer:</strong> " + invoice.CustomerName + @"</p>
<table>
<tr><th>Description</th><th>Amount</th></tr>
<tr><td>Services</td><td>$" + invoice.Subtotal.ToString("F2") + @"</td></tr>");
// Conditional section: only emit the discount row when there is a discount.
if (invoice.Discount > 0)
{
html.Append(@"
<tr><td>Discount</td><td>-$" + invoice.Discount.ToString("F2") + @"</td></tr>");
}
html.Append(@"
<tr><td><strong>Total</strong></td><td><strong>$" + invoice.Total.ToString("F2") + @"</strong></td></tr>
</table>
<p>Date: " + invoice.Date.ToString("MMMM dd, yyyy") + @"</p>
</body>
</html>");
return html.ToString();
}
}
IronPDF puts layout control in code. Conditional rendering, format control, and debugging use standard .NET workflows. See the HTML conversion guide.
Scenario 2: Template Evolution and Version Control
CraftMyPDF — Template Management
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
public class CraftMyPdfTemplateEvolution
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
public CraftMyPdfTemplateEvolution(string apiKey)
{
_httpClient = new HttpClient();
_apiKey = apiKey;
}
public async Task<string> GeneratePdfAsync(string templateId, object data)
{
// Template IDs reference whatever version of the template currently lives
// in the CraftMyPDF workspace. Versioning, change notification, and rollback
// capabilities depend on the platform's template management features —
// verify what your plan exposes.
//
// A common pattern is to maintain separate template IDs for dev and prod
// and treat the template ID itself as a configuration value that the app
// pins explicitly.
var requestBody = new
{
template_id = templateId,
data = data,
export_type = "file"
};
var json = JsonSerializer.Serialize(requestBody);
var content = new StringContent(json, Encoding.UTF8, "application/json");
_httpClient.DefaultRequestHeaders.Add("X-API-KEY", _apiKey);
var response = await _httpClient.PostAsync(
"https://api.craftmypdf.com/v1/create",
content);
response.EnsureSuccessStatusCode();
var bytes = await response.Content.ReadAsByteArrayAsync();
// Persist or return the binary PDF as needed.
return $"Generated PDF: {bytes.Length} bytes";
}
}
Coordination patterns to plan around:
- Template versioning lives on the platform, not in your Git repo — pin template IDs explicitly and treat editor changes as a deploy event.
- Field-name changes in the template can silently mismatch the JSON payload, so contract tests against a known-good template ID help catch drift.
- Rollback depends on the platform's history feature; export and back up templates if you need a defensible recovery path.
- Code review for template changes happens through the designer rather than a PR diff; document the change process for whoever owns the template.
- Local preview without an API call typically is not available; staging template IDs help isolate test traffic.
- Deploy coordination between backend code changes and template edits is the responsibility of the team; align them in your release checklist.
IronPDF — Template as Code
using IronPdf;
using System.IO;
using System.Threading.Tasks;
public class IronPdfTemplateVersioning
{
public async Task<byte[]> GeneratePdfAsync(InvoiceData data)
{
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
// Template is an HTML file in source control.
var templatePath = "Templates/Invoice_v2.html";
var template = File.ReadAllText(templatePath);
template = template.Replace("{{CustomerName}}", data.CustomerName);
template = template.Replace("{{InvoiceNumber}}", data.InvoiceNumber);
template = template.Replace("{{Total}}", data.Total.ToString("C"));
// Or use a templating engine such as Razor or Handlebars.
var renderer = new ChromePdfRenderer();
using var pdf = await renderer.RenderHtmlAsPdfAsync(template);
return pdf.BinaryData;
}
}
When templates are HTML files in your repo, version control, code review, and automated testing work like any other code change. See PDF generation settings.
Scenario 3: Complex Layouts and Multi-Page Documents
CraftMyPDF — Component-Based Layout
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
public class CraftMyPdfComplexLayouts
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private readonly string _templateId;
public CraftMyPdfComplexLayouts(string apiKey, string templateId)
{
_httpClient = new HttpClient();
_apiKey = apiKey;
_templateId = templateId;
}
public async Task<string> GenerateReportAsync(ReportData data)
{
// Multi-page reports rely on the template's section, table, and pagination
// components. Dynamic row counts typically use a repeating section bound to
// an array in the JSON payload.
//
// Totals, running sums, and other calculations are generally computed in the
// request payload before calling the API, since template-side computation is
// bounded by what the designer exposes.
//
// Per-page running headers and footers (e.g., "Page X of Y") depend on the
// template's header/footer components — verify capabilities against your plan
// and the current CraftMyPDF docs.
var requestBody = new
{
template_id = _templateId,
data = data,
export_type = "file"
};
var json = JsonSerializer.Serialize(requestBody);
var content = new StringContent(json, Encoding.UTF8, "application/json");
_httpClient.DefaultRequestHeaders.Add("X-API-KEY", _apiKey);
var response = await _httpClient.PostAsync(
"https://api.craftmypdf.com/v1/create",
content);
response.EnsureSuccessStatusCode();
var bytes = await response.Content.ReadAsByteArrayAsync();
return $"Generated report: {bytes.Length} bytes";
}
}
Things to evaluate against the template engine:
- Component set — predefined components (text, table, chart, section) define the layout vocabulary; check whether your design fits within them.
- Pagination control — page breaks are typically handled by the template; verify whether explicit "break before this section" rules are supported on your plan.
- In-template calculations — sums, averages, and derived fields generally need to be precomputed in your request payload.
- Per-page headers/footers — running totals and "Page X of Y" depend on what the header/footer components expose.
- Repeating sections — dynamic row counts work, but per-row layout flexibility is bounded by the section's design surface.
- Iteration loop — debugging a layout typically involves editing in the designer and re-rendering through the API.
IronPDF — Full HTML/CSS Control
using IronPdf;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
public class IronPdfComplexLayouts
{
public async Task<byte[]> GenerateReportAsync(ReportData data)
{
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
var html = BuildReportHtml(data);
var renderer = new ChromePdfRenderer();
using var pdf = await renderer.RenderHtmlAsPdfAsync(html);
return pdf.BinaryData;
}
private string BuildReportHtml(ReportData data)
{
var html = new StringBuilder();
html.Append(@"
<!DOCTYPE html>
<html>
<head>
<style>
@page {
margin: 1in;
@top-center { content: 'Monthly Report'; }
@bottom-right { content: 'Page ' counter(page) ' of ' counter(pages); }
}
body { font-family: Arial; }
table { width: 100%; page-break-inside: avoid; }
.page-break { page-break-before: always; }
.subtotal { font-weight: bold; background: #f0f0f0; }
</style>
</head>
<body>
<h1>Monthly Report - " + data.Month + @"</h1>");
// Dynamic table with calculated totals
html.Append("<table><thead><tr><th>Item</th><th>Amount</th></tr></thead><tbody>");
decimal runningTotal = 0;
foreach (var item in data.Items)
{
html.Append($"<tr><td>{item.Name}</td><td>${item.Amount:F2}</td></tr>");
runningTotal += item.Amount;
}
html.Append($@"
<tr class='subtotal'><td>Total</td><td>${runningTotal:F2}</td></tr>
</tbody></table>");
// Conditional section
if (data.Items.Count > 10)
{
html.Append(@"<div class='page-break'></div>
<h2>Summary</h2>
<p>Report contains " + data.Items.Count + @" items...</p>");
}
html.Append("</body></html>");
return html.ToString();
}
}
IronPDF uses standard HTML/CSS for layouts. Page breaks, headers, totals, and conditional sections work like building web pages. See pixel-perfect rendering.
API Mapping Reference
| CraftMyPDF Concept | IronPDF Equivalent |
|---|---|
| Template designer (web UI) | HTML files in source control |
| Template ID | HTML template path or string |
| JSON data payload | C# objects or view models |
Field: {field_name}
|
HTML/C# templating |
| Repeating section |
foreach loop in C# |
| API returns PDF URL or binary |
pdf.BinaryData (immediate bytes) |
| Template versioning (platform) | Git version control |
| Component library | Full HTML/CSS |
| Zapier/Make integration | Direct .NET integration |
| Cloud PDF generation | Local in-process generation |
| Designer-based template iteration | Browser preview + debugger |
Comprehensive Feature Comparison
| Feature Category | CraftMyPDF | IronPDF |
|---|---|---|
| Status | ||
| Maintenance | Active (SaaS) | Active |
| Architecture | Cloud template service | Local .NET library |
| Primary Use Case | No-code / low-code automation | Programmatic PDF generation |
| Development | ||
| Template System | Designer (web-based) | HTML/CSS (code-based) |
| Conditional Logic | Template engine bounds | Full C# control |
| Version Control | Platform-managed | Git-compatible |
| Code Review | Via designer | HTML diffs |
| Local Testing | Requires API | In-process |
| Debugging | Designer + API iteration | Browser + debugger |
| Content Creation | ||
| HTML to PDF | Designer-driven | Full HTML/CSS/JS |
| Dynamic Tables | Section component | HTML tables + loops |
| Conditional Sections | Engine-dependent | C# if/else
|
| Calculated Fields | Pre-compute in payload | Calculate in C# |
| Integration | ||
| Installation | HTTP client | NuGet package |
| Data Format | JSON payload | C# objects |
| Output | PDF URL or binary | PDF bytes (immediate) |
| Offline Support | No (cloud service) | Yes (local processing) |
| Support | ||
| Type | Email (CraftMyPDF) | Commercial (Iron Software) |
| Documentation | API docs | Comprehensive |
Installation Comparison
CraftMyPDF:
# No NuGet package — integrate via HTTP / REST.
# 1. Sign up at craftmypdf.com
# 2. Create templates in the web designer
# 3. Get an API key from the portal
# 4. POST JSON payloads to the render endpoint
using System.Net.Http;
using System.Text;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-KEY", "your-api-key");
var json = @"{ ""template_id"": ""abc123"", ""data"": {}, ""export_type"": ""file"" }";
var response = await client.PostAsync(
"https://api.craftmypdf.com/v1/create",
new StringContent(json, Encoding.UTF8, "application/json"));
IronPDF:
Install-Package IronPdf
using IronPdf;
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
var renderer = new ChromePdfRenderer();
using var pdf = renderer.RenderHtmlAsPdf("<h1>Hello</h1>");
pdf.SaveAs("hello.pdf");
Conclusion
CraftMyPDF fits no-code and low-code scenarios where a visual designer lets non-developers shape PDF layouts. The drag-and-drop editor and the Zapier/Make/Bubble integrations make it a strong fit for automation workflows, certificate generation, and form-filling cases where the template surface is stable and the data shape is simple.
The trade-off is architectural: templates live in the vendor's designer rather than your repo, the render boundary is over the network, and the template engine's feature set bounds what conditional logic and layout work you can express. Teams whose requirements grow toward complex conditionals, calculated fields, per-page running headers, or strict offline / on-prem constraints typically hit those boundaries.
IronPDF takes the opposite stance: HTML, CSS, and C# live in your repo; rendering happens in-process; and the template "engine" is whatever your code does before handing HTML to the renderer. That makes Git review, unit tests, and step-through debugging the default rather than the workaround.
A pragmatic way to choose:
- Choose CraftMyPDF when the team values a visual designer, the templates change rarely, the data shape is stable, and the no-code integration story is a primary requirement.
- Choose IronPDF when templates need to evolve with code, when conditional logic and computed fields belong in C#, when offline / on-prem rendering matters, or when version control and review of the template itself are non-negotiable.
Have you migrated between a template-driven cloud service and a code-driven library? Where did your team draw the line?
Related Resources:
Top comments (0)