<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Gerardo Barrera</title>
    <description>The latest articles on DEV Community by Gerardo Barrera (@gerardo_barrera_538fc60b7).</description>
    <link>https://dev.to/gerardo_barrera_538fc60b7</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3993280%2F0297a7a0-d323-44a0-82c4-8a664f7f21e6.png</url>
      <title>DEV Community: Gerardo Barrera</title>
      <link>https://dev.to/gerardo_barrera_538fc60b7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gerardo_barrera_538fc60b7"/>
    <language>en</language>
    <item>
      <title>How to Let an AI Agent Generate Real PDFs (with an MCP server)</title>
      <dc:creator>Gerardo Barrera</dc:creator>
      <pubDate>Sat, 20 Jun 2026 04:37:27 +0000</pubDate>
      <link>https://dev.to/gerardo_barrera_538fc60b7/how-to-let-an-ai-agent-generate-real-pdfs-with-an-mcp-server-28e3</link>
      <guid>https://dev.to/gerardo_barrera_538fc60b7/how-to-let-an-ai-agent-generate-real-pdfs-with-an-mcp-server-28e3</guid>
      <description>&lt;p&gt;AI agents are great at producing &lt;em&gt;text&lt;/em&gt;. But the moment you need an actual &lt;strong&gt;document&lt;/strong&gt; — an invoice, a report, a certificate — they fall apart. You get markdown you have to format yourself, or (with Code Interpreter) a rough PDF from a Python lib with generic fonts and tables that never quite look right. There's no clean way to hand an agent the job of "produce a polished PDF."&lt;/p&gt;

&lt;p&gt;So I gave my agents one tool that does exactly that, over &lt;strong&gt;MCP&lt;/strong&gt; (Model Context Protocol). The agent describes the document, and gets back a link to a finished, &lt;strong&gt;editable&lt;/strong&gt; PDF. Here's how to wire it up in about 5 lines of config.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure: I work on &lt;a href="https://pdfmakerapi.com" rel="noopener noreferrer"&gt;PDFMakerAPI&lt;/a&gt; — but the pattern here (give an agent a single, well-scoped tool that returns a reviewable artifact) applies to any MCP server. The config below just happens to use mine.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  30-second MCP refresher
&lt;/h2&gt;

&lt;p&gt;MCP is the open standard for giving an AI agent &lt;em&gt;tools&lt;/em&gt;. You point your client (Claude Desktop, Cursor, Windsurf, Cline, VS Code, ChatGPT…) at an MCP server, and the agent can now call whatever functions that server exposes. No glue code, no custom integration per client — that's the whole appeal.&lt;/p&gt;

&lt;p&gt;The server we're adding exposes exactly &lt;strong&gt;one&lt;/strong&gt; tool: &lt;code&gt;create_document&lt;/code&gt;. (One tool on purpose — a small, predictable surface area is easier for an agent to use correctly than a grab-bag of twenty.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Add the server
&lt;/h2&gt;

&lt;p&gt;Drop this into your client's MCP config (Claude Desktop: &lt;em&gt;Settings → Developer → Edit Config&lt;/em&gt;; Cursor: &lt;code&gt;~/.cursor/mcp.json&lt;/code&gt;; same idea elsewhere):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"pdfmakerapi"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@pdfmakerapi/mcp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart the client. That's the entire setup — no API key, no account.&lt;/p&gt;

&lt;p&gt;Using a web client like &lt;strong&gt;Claude.ai&lt;/strong&gt; or &lt;strong&gt;ChatGPT&lt;/strong&gt; that can't run &lt;code&gt;npx&lt;/code&gt;? Add the hosted endpoint as a custom connector instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://api.pdfmakerapi.com/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2 — Ask for a document
&lt;/h2&gt;

&lt;p&gt;Now just ask the agent in plain English:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Make a professional invoice for Acme Corp — 3 line items, net 30."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent calls &lt;code&gt;create_document&lt;/code&gt;, and you get back a link like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://app.pdfmakerapi.com/d/019ee2fe-c503-71c0-aaf6-68cf33ca096c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open it and you'll see the finished invoice — and you can &lt;strong&gt;edit any field&lt;/strong&gt; before downloading the PDF. Here's a real one to click:&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://app.pdfmakerapi.com/d/019ee2fe-c503-71c0-aaf6-68cf33ca096c" rel="noopener noreferrer"&gt;Live invoice example&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It works for invoices, receipts, certificates, reports, resumes, letters — if the agent can describe it, it can lay it out with headings, tables, and your data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the "editable link" matters for agents
&lt;/h2&gt;

&lt;p&gt;This is the part I actually care about, and it's why I didn't make the tool just spit out a final PDF.&lt;/p&gt;

&lt;p&gt;If you've spent any time running agents in production, you know the failure mode isn't "bad answer" — it's &lt;strong&gt;a wrong action that's already done&lt;/strong&gt;. An agent that emails the wrong customer or files the wrong number is worse than one that just says something dumb.&lt;/p&gt;

&lt;p&gt;Returning an &lt;strong&gt;editable document&lt;/strong&gt; instead of a finalized file builds a natural &lt;strong&gt;human-in-the-loop checkpoint&lt;/strong&gt; into the workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The agent &lt;strong&gt;drafts&lt;/strong&gt; the document from the request.&lt;/li&gt;
&lt;li&gt;A human &lt;strong&gt;opens the link, checks it, fixes anything&lt;/strong&gt;, and downloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the agent does the tedious 90% (layout, structure, pulling the data together), but a person stays in control of what actually ships. For anything that leaves the building — an invoice to a client, a certificate with someone's name on it — that checkpoint is worth a lot more than full autonomy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does &lt;em&gt;not&lt;/em&gt; do (so you scope it right)
&lt;/h2&gt;

&lt;p&gt;Being straight about the edges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It &lt;strong&gt;generates&lt;/strong&gt; documents — it does &lt;strong&gt;not&lt;/strong&gt; read or edit a PDF you upload. Different problem.&lt;/li&gt;
&lt;li&gt;It returns an &lt;strong&gt;editable web document + a downloadable PDF&lt;/strong&gt;, not a blank fillable form to hand out.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your agent needs to &lt;em&gt;produce&lt;/em&gt; structured documents from data or a prompt, this fits. If it needs to &lt;em&gt;parse&lt;/em&gt; existing PDFs, you want a different tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Under the hood (for the curious)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;create_document&lt;/code&gt; takes a document model — a small JSON tree of nodes (text, containers, tables) with optional &lt;code&gt;{{variables}}&lt;/code&gt; — and stores it, returning the share link. The agent can build that structure from a prompt, or you can POST it yourself to the same REST API if you'd rather skip the agent entirely. It's open source (MIT) if you want to read the tool definition: &lt;a href="https://github.com/GerardoBarrera/pdfmakerapi-mcp" rel="noopener noreferrer"&gt;github.com/GerardoBarrera/pdfmakerapi-mcp&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;Giving an agent the ability to produce a real, reviewable document turned out to be a small change with a big payoff:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add one MCP server (~5 lines).&lt;/li&gt;
&lt;li&gt;Ask for the document in plain English.&lt;/li&gt;
&lt;li&gt;Get an editable link a human can sign off on before it ships.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No markdown-to-PDF wrangling, no headless browser, and a built-in review step instead of blind autonomy.&lt;/p&gt;

&lt;p&gt;If you're building agents that produce deliverables (not just text): &lt;strong&gt;do you let the agent finalize, or always hand off to a human to review first?&lt;/strong&gt; Genuinely curious how others are drawing that line — drop it in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>claude</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Generate a PDF from JSON in Node.js (without a headless browser)</title>
      <dc:creator>Gerardo Barrera</dc:creator>
      <pubDate>Sat, 20 Jun 2026 04:29:56 +0000</pubDate>
      <link>https://dev.to/gerardo_barrera_538fc60b7/how-to-generate-a-pdf-from-json-in-nodejs-without-a-headless-browser-3i9h</link>
      <guid>https://dev.to/gerardo_barrera_538fc60b7/how-to-generate-a-pdf-from-json-in-nodejs-without-a-headless-browser-3i9h</guid>
      <description>&lt;p&gt;If you've ever had to generate PDFs from a Node app — invoices, receipts, reports, certificates — you've probably reached for &lt;br&gt;
  &lt;strong&gt;Puppeteer&lt;/strong&gt; or some headless-Chrome setup. It works… until you have to run it in production. Now you're shipping a 300MB Chromium &lt;br&gt;
  binary, babysitting a browser process, fighting memory leaks, and your "render a PDF" endpoint times out under load.&lt;/p&gt;

&lt;p&gt;There's a simpler way: &lt;strong&gt;describe the document as JSON, POST it to an API, get a PDF back.&lt;/strong&gt; No browser. In this post I'll show how &lt;br&gt;
  to go from a JSON payload to a finished, editable PDF in about 15 lines of Node.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Full disclosure: I built &lt;a href="https://pdfmakerapi.com" rel="noopener noreferrer"&gt;PDFMakerAPI&lt;/a&gt;, the tool I'm using here. There's a free tier, no API key needed to&lt;br&gt;
  try it, and the code below runs as-is.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;## The idea: separate the data from the design&lt;/p&gt;

&lt;p&gt;The trick to sane PDF generation is to stop gluing strings of HTML together. Instead you keep two things separate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The design&lt;/strong&gt; — a layout of text, tables, and containers (you can design this visually, or describe it as JSON).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The data&lt;/strong&gt; — the actual values that change per document.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You merge them with one request and get back a link to the finished PDF. Let's do it.&lt;/p&gt;

&lt;p&gt;## Step 1 — Describe the document as JSON&lt;/p&gt;

&lt;p&gt;A document is just a tree of nodes. Here's a minimal one — a short welcome letter with a &lt;code&gt;{{customer_name}}&lt;/code&gt; placeholder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Welcome Letter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;letter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;var_name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customer_name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Customer Name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;defaultValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alex Morgan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;full&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Welcome aboard 🎉&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;xl&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;fontWeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bold&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;textColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#111827&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;full&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hi {{customer_name}}, thanks for joining. This whole PDF was generated from JSON in a single API call — no headless browser
  involved.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;textColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#374151&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anything in &lt;code&gt;{{double_braces}}&lt;/code&gt; is a variable, so you can reuse the same layout with different data. The full schema (containers,&lt;br&gt;
  tables, images, fonts, page settings) is in the &lt;a href="https://github.com/GerardoBarrera/pdfmakerapi-mcp" rel="noopener noreferrer"&gt;docs and repo&lt;/a&gt; — but the shape&lt;br&gt;
  above is all you need to start.&lt;/p&gt;

&lt;p&gt;## Step 2 — POST it and get a link back&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.pdfmakerapi.com/api/v1/documents&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// → https://app.pdfmakerapi.com/d/&amp;lt;id&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The response is &lt;code&gt;{ id, url }&lt;/code&gt;, where &lt;code&gt;url&lt;/code&gt; opens the finished document.&lt;/p&gt;

&lt;p&gt;Prefer &lt;code&gt;curl&lt;/code&gt;?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.pdfmakerapi.com/api/v1/documents &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{ "document": { "name": "Welcome Letter", "pageSize": "letter", "children": [ { "id": "t", "type": "text", "order": 1, "width":
  "full", "content": "Generated from JSON ✅", "fontSize": "xl", "fontWeight": "bold" } ] } }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;## Step 3 — The part Puppeteer can't do: an &lt;em&gt;editable&lt;/em&gt; result&lt;/p&gt;

&lt;p&gt;Here's the difference that matters. The link you get back isn't a flat, one-shot render — it opens an &lt;strong&gt;editable document&lt;/strong&gt;. You (or&lt;br&gt;
  anyone you send the link to) can change any field in the browser and the preview updates live, then download the PDF.&lt;/p&gt;

&lt;p&gt;Here's a real one generated this way — open it and edit a field:&lt;br&gt;
  👉 &lt;strong&gt;&lt;a href="https://app.pdfmakerapi.com/d/019ee2fe-c503-71c0-aaf6-68cf33ca096c" rel="noopener noreferrer"&gt;Live invoice example&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That turns out to be a big deal in practice: your app drafts the document, but a human can review and fix it before it goes out —&lt;br&gt;
  instead of trusting whatever got rendered.&lt;/p&gt;

&lt;p&gt;## Bonus: you don't even have to write the JSON&lt;/p&gt;

&lt;p&gt;If hand-building the node tree feels tedious, you can skip it entirely. PDFMakerAPI ships an &lt;strong&gt;MCP server&lt;/strong&gt;, so an AI agent (Claude,&lt;br&gt;
  Cursor, ChatGPT, etc.) can generate the document structure from a plain-English prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"pdfmakerapi"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@pdfmakerapi/mcp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then just ask: &lt;em&gt;"make an invoice for Acme with 3 line items"&lt;/em&gt; and it returns the same kind of editable link. Same engine, no JSON by&lt;br&gt;
  hand.&lt;/p&gt;

&lt;p&gt;## When you should NOT use this&lt;/p&gt;

&lt;p&gt;Being honest: an API isn't always the right call.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you need to &lt;strong&gt;screenshot an existing web page&lt;/strong&gt; to PDF, a headless browser is the right tool — that's literally what it's for.&lt;/li&gt;
&lt;li&gt;If you're generating &lt;strong&gt;one PDF, once, locally&lt;/strong&gt;, a library like &lt;code&gt;pdfkit&lt;/code&gt; is fine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where this approach wins is &lt;strong&gt;generating structured documents from data, repeatedly, in production&lt;/strong&gt; — invoices, receipts, reports,&lt;br&gt;
  certificates — without running a browser farm.&lt;/p&gt;

&lt;p&gt;## Wrap-up&lt;/p&gt;

&lt;p&gt;To generate a PDF from JSON in Node:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Describe the document (or let an AI agent describe it).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST&lt;/code&gt; it to &lt;code&gt;/api/v1/documents&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Get back a link to an editable, downloadable PDF.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No Chromium, no rendering servers, no timeouts. If you want to try it, the &lt;a href="https://pdfmakerapi.com" rel="noopener noreferrer"&gt;free tier&lt;/a&gt; gives you 100 PDFs/month with no card, and the MCP server is open source (MIT) on &lt;a href="https://github.com/GerardoBarrera/pdfmakerapi-mcp" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you're generating PDFs in production today — what are you using, and what's been the most painful part? Curious to hear in the&lt;br&gt;
  comments.&lt;/p&gt;

</description>
      <category>node</category>
      <category>tutorial</category>
      <category>pdf</category>
      <category>api</category>
    </item>
  </channel>
</rss>
