DEV Community

IronSoftware
IronSoftware

Posted on

CraftMyPDF vs IronPDF: an unbiased look for .NET teams

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

Things to plan around with the template-driven model:

  1. Conditional logic lives in the template engine and component system rather than in arbitrary code, so complex business rules may need API-side preprocessing.
  2. Format expectations are set by the template; data formatting on the client must match what the designer was configured to render.
  3. Rendering happens server-side, so iteration loops go through API calls rather than a local debugger.
  4. Template changes happen in the web designer, which is a different review surface than your code Git workflow.
  5. Local unit testing of the rendered output requires either mocking the HTTP call or hitting the live API.
  6. 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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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";
    }
}
Enter fullscreen mode Exit fullscreen mode

Coordination patterns to plan around:

  1. Template versioning lives on the platform, not in your Git repo — pin template IDs explicitly and treat editor changes as a deploy event.
  2. Field-name changes in the template can silently mismatch the JSON payload, so contract tests against a known-good template ID help catch drift.
  3. Rollback depends on the platform's history feature; export and back up templates if you need a defensible recovery path.
  4. Code review for template changes happens through the designer rather than a PR diff; document the change process for whoever owns the template.
  5. Local preview without an API call typically is not available; staging template IDs help isolate test traffic.
  6. 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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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";
    }
}
Enter fullscreen mode Exit fullscreen mode

Things to evaluate against the template engine:

  1. Component set — predefined components (text, table, chart, section) define the layout vocabulary; check whether your design fits within them.
  2. Pagination control — page breaks are typically handled by the template; verify whether explicit "break before this section" rules are supported on your plan.
  3. In-template calculations — sums, averages, and derived fields generally need to be precomputed in your request payload.
  4. Per-page headers/footers — running totals and "Page X of Y" depend on what the header/footer components expose.
  5. Repeating sections — dynamic row counts work, but per-row layout flexibility is bounded by the section's design surface.
  6. 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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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"));
Enter fullscreen mode Exit fullscreen mode

IronPDF:

Install-Package IronPdf
Enter fullscreen mode Exit fullscreen mode
using IronPdf;

IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";

var renderer = new ChromePdfRenderer();
using var pdf = renderer.RenderHtmlAsPdf("<h1>Hello</h1>");
pdf.SaveAs("hello.pdf");
Enter fullscreen mode Exit fullscreen mode

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)