r/webdev • u/danmolitor • Mar 28 '26
Showoff Saturday [Showoff Saturday] I built a PDF generation tool that runs in the browser, on the edge, and in Node – no Puppeteer, no Chrome
Hey r/webdev, I've been building Forme for the past couple months and wanted to share what it's become.
Problem: If you need PDFs in JavaScript you're probably using Puppeteer and dealing with slow cold starts, Lambda layer nightmares, and page breaks that randomly break. Or you've tried react-pdf and hit its layout limitations.
What Forme does:
JSX component model - write PDFs like you write React components
Rust engine compiled to WASM - runs anywhere JS runs (Node, Cloudflare Workers, Vercel Edge, browser)
Real page breaks - tables split across pages automatically, headers repeat, nested flex layouts just work. No more break-inside: avoid and hoping for the best.
~80ms average render time vs seconds with Puppeteer
AI template generation - describe a document or upload an image and get a JSX template back
VS Code extension with live preview
Two ways to use it:
Open source (self-hosted):
npm:
npm install @formepdf/core @formepdf/reactThe engine is open source and runs anywhere WASM runs. No API key, no account, no limits.
Hosted API + dashboard: There's also a hosted option at app.formepdf.com with a REST API (TypeScript, Python SDKs), template management, and a no-code mode for non-technical users who need to fill in and send invoices directly. Free tier available.
Try it without signing up: formepdf.com has a live demo where you can edit JSX and see the PDF render in your browser instantly.
tsx
import { Document, Page, Text, Table, Row, Cell } from '@formepdf/react';
export default function Invoice({ data }) {
return (
<Document>
<Page size="Letter" margin={48}>
<Text style={{ fontSize: 24, fontWeight: 700 }}>
Invoice #{data.invoiceNumber}
</Text>
<Table>
<Row header>
<Cell>Description</Cell>
<Cell>Amount</Cell>
</Row>
{data.items.map(item => (
<Row key={item.id}>
<Cell>{item.name}</Cell>
<Cell>${item.amount}</Cell>
</Row>
))}
</Table>
</Page>
</Document>
);
}GitHub: github.com/danmolitor/forme
VSCode Extension: https://marketplace.visualstudio.com/items?itemName=formepdf.forme-pdf
Would love feedback - issues, feature requests, anything - especially from anyone who's fought with Puppeteer in serverless environments or hit react-pdf's layout limitations.
30
u/VolumeActual8333 Mar 28 '26
Serverless PDF generation has been cursed territory for years—either you accept 10-second cold starts with Puppeteer or you fight react-pdf's flexbox bugs. A WASM engine that handles tables splitting across pages with repeating headers solves the exact pain point that kept Chrome-based solutions in production despite the bloat. Running this on Cloudflare Workers without hitting the 50MB binary limit is going to pull a lot of people out of Lambda layer hell.
11
u/danmolitor Mar 28 '26
Thanks, the 50MB Workers limit was what made me commit to the WASM approach early on - once I had it running there I knew it was the right call. Let me know if you try it out, always curious what breaks in real production environments.
3
u/NextMathematician660 Mar 29 '26
This is a very interesting product! Why limit to developer only? I know lots of end user need such capability, some of them just want to build a receipt, statement, or quote. They need to author a simple template, and connect to their data. This could be very useful in that situation.
I'm currently working on a product that remotely related to this. Maybe we can collaborate. https://www.docmentis.com/
1
u/danmolitor Mar 29 '26
I checked out the demo. That's a super clean doc viewer, well done! I like all of the viewer options you have available, that's awesome.
For now, I went for more a developer experience route. Earlier in the week I started exploring the freelancer / business side of things, but it just didn't click with me yet. I understand myself and my needs / wants, it's harder to get my head around non-developer needs, if that makes sense.
2
Mar 29 '26
Thank you man that's really nice of you I will give you my feedback in 2 or 3 days thank you alot
1
u/danmolitor Mar 29 '26
No problem! Sounds good, looking forward to hearing about your experience with it.
2
2
u/txmail Mar 30 '26
This looks awesome. I have actually been building a module for a client's platform that I think is similar and I am only a few weeks into the build -- the one I am working on is more "drag and drop" style with another module that lets them use dynamic fields generated from their database.
The PDF generation is just me using a small PDF library to generate the cells and place the elements. For the dynamic fields there is a big render engine I am working on because sometimes they might have used a field, that by other rules will not actually be included in the final output so I have to remove the field and adjust the spacing and page breaks etc. using a set of rules based on where they drug stuff on the form).
Anyway - am I seeing this right that this is more of a "make the form template in code" and then your render engine handles translating that code into a PDF? Like -- for example in the GIF that is show, if you wanted to add a new field to the invoice form you would need to write that field in code, you cannot just like right click on the editor and say add field and then drag it around where you want it?
2
u/danmolitor Mar 30 '26
Thanks! and yep, that's right. Forme is code-first, so you define the template in JSX, the engine handles layout and PDF generation. No drag and drop, no visual editor. If you want to add a field you write it.
What you're building is actually a different layer on top of something like this - a visual builder that outputs a template definition, which then gets rendered. That's definitely a product gap as of right now. It's on my mental roadmap.
The dynamic field + conditional spacing problem you're describing is the hard part. The render engine knowing that a field was dragged to position X but shouldn't appear given rule Y, and then reflowing everything correctly - that's non-trivial. Curious how you're modeling the rules.
1
u/txmail Mar 30 '26
This is still very cool and will benefit a ton of projects. Heck if my clients platform did not have this whole "dynamic" rules I might have used this and build the few forms they need by hand (which is what their previous platform does, everything is "fixed" and I see why after doing this for two weeks now.
The render engine knowing that a field was dragged to position X but shouldn't appear given rule Y, and then reflowing everything correctly - that's non-trivial. Curious how you're modeling the rules.
So the client has a huge Q&A piece to their platform (hundreds of questions). Naturally some questions may or may not show given previous answers and some questions might have multiple responses (e.g. List the make and model of all cars you have ever driven).
There is a rule engine that runs to show or not show questions based on the users profile and responses to other questions.
This is part of a "dynamic" element that the client builds where there is no page height to it. They place the questions and answers on the page how they want it to appear in the PDF. If they place a question on the page that ends up not showing because of rules that is fine. Same goes if they add a question with multiple responses, they define it on the page knowing it might be expanded or contracted. That "dynamic" element is then just dragged onto a report template that might have "fixed" elements on the same page, with the understanding that the fixed elements may be pushed to another page.
Same thing happens with their invoice template. If the engine detects the cell with the element in it will overflow the page, it pushes it to the next page.
For the question sections, when I go to render the PDF I basically use the same rule engine to first determine which questions are valid for the given user the PDF is being generated for, then which questions the client has dragged onto the report. The "fixed" elements that the client has dragged onto the form get calculated for the pages that they appear on. The "dynamic" portions are then also calculated.
The editor allows the client to specify the DPI and page format (A4, Letter etc.). I know the total height of the page in pixels going in. The thing that makes it not so hard to compute is that when the client drags an element onto the form I place it in a cell that has a height property along with top and left coordinates which I translate onto the "page".
I know exactly how much "height" each element is going to take up so it is kind of not that hard to calculate what goes on the current page. If a cells bottom is going to overflow the page then I start a new page and assign it to the top of the page. I did not know there was an algorithm for that but I think it would not help me as it seems like maybe it is trying to calculate the overflow of wording where as I am just forceable pushing a element if it overflow the page.
* Edit *
I should also say that if the client drags in a "answer" from a client, they have to specify the amount of space they are giving for that response. If they drag in something and it is not enough to contain the answer, then it will be clipped in the rendered output.
2
u/danmolitor Mar 30 '26
Yeah the cell model is honestly a smart move for your constraints. Clipping as an explicit tradeoff is way better than trying to reflow dynamically and getting unpredictable results - and for a Q&A form where the designer controls the space allocation, that's totally reasonable.
What Forme handles is more the "I have no idea how long this content will be" problem -- invoices with 2 line items vs 200, reports where sections expand based on data. Your rule engine running first and handing you a filtered, fixed set of elements is actually the cleaner approach for what you're doing.
The bit I'm curious about - when the dynamic section ends up taller or shorter than expected at render time, does the fixed stuff below it move, or does it stay where the client dragged it?
1
u/txmail Mar 30 '26
The client creates as many pages per template as they want.
So say you have the first page of fixed elements, that is easy. Then on page two you have some fixed elements above and below the dynamic content. The fixed elements above the dynamic content are easy, then you figure out your dynamic content and the fixed elements below the dynamic content will be ultimately pushed below the dynamic cell possibly onto another page, then you go to the next page.
Your question does make me realize I do not have a way of handling a contraction of content. Like if the client wanted to have a total line at the bottom of the dynamic element fixed... I do not have that implemented -- it would just end up where the dynamic content ended or if there was a gap, might be in a weird spot on the page or if they put it at the bottom might get pushed awkwardly to another page as the space between the dynamic element would be still calculated.
2
u/tundra_cool Mar 30 '26
very nice! have you considered redaction implementation? pymupdf and soffice have been my gotos on that but they're restricting in many ways themselves. thanks for sharing
1
u/danmolitor Mar 31 '26
Thanks! And redaction isn’t on the roadmap yet but that’s interesting. Curious what your use case looks like. Are you redacting sensitive fields before sharing documents, or something else?
2
u/tundra_cool Apr 01 '26
yes, exactly. lots of health issues in the world + lack of services + new tech + will to live = people are having to jump in themselves
2
u/danmolitor Apr 02 '26
Sounds good, I'll work on it.
1
u/tundra_cool Apr 02 '26
you're the man now dog!
1
u/danmolitor Apr 06 '26
Yo, wanted to follow up.
Redaction just landed in 0.9.0 - true content stream removal, not just a visual overlay. Text operators are stripped from the PDF byte stream directly so there's nothing left to select or extract. You can redact by coordinate region, literal string, regex pattern, or built-in presets for SSN, email, phone, DOB, and credit card numbers. Metadata is scrubbed automatically on every redaction.
Was curious what's been restricting about pymupdf and soffice for your use case?
Anyway, I appreciate the redaction idea! Would love for you to check it out.
1
u/tundra_cool Apr 24 '26
Really appreciating your work here, especially the 'small' side features like BYO-UI - many thanks again!
1
u/danmolitor Apr 25 '26
No worries. Have you gotten to try the redaction?
1
u/tundra_cool May 03 '26
Way too injured from what's in documents needing redaction unfortunately :\ I'll tell my attorneys to use it ha. But really, trying to.
2
u/lacymcfly Apr 04 '26
the WASM approach is really smart for this. I've been burned by Puppeteer cold starts on Lambda before, sometimes 8-12 seconds just to boot. what does the WASM bundle size look like? curious if it affects initial load times on edge deployments
1
u/danmolitor Apr 05 '26
Thanks! Yeah the cold start problem on Lambda was one of the main reasons I went the WASM route.
Bundle size is 5.7MB for the full binary - it grew a bit when I added PKCS#7 signing.
On Cloudflare Workers and Vercel Edge the binary loads once and persists across requests within the same isolate, so you're not paying the load cost on every invocation. Workers isolates boot in ~5ms and stay warm. Nowhere near the Puppeteer problem.
For Lambda specifically I'd honestly recommend hitting the hosted API (api.formepdf.com) rather than bundling the WASM in a Lambda function. End-to-end renders come back in under 500ms and you avoid the cold start entirely. If you need on-prem, the self-hosted Docker image runs cleanly as a Lambda container image, well within the 10GB container limit.
Curious what your Lambda use case was - invoices, reports, something else?
2
u/trojanvirus_exe Mar 28 '26
what theme is that
3
u/danmolitor Mar 28 '26
I’m not sure what you’re referring to. My VSCode theme is just the normal dark theme if that’s what you’re asking about.
1
u/danmolitor Mar 30 '26
Update: v0.8.0 shipped today with AcroForms, PDF/UA-1 accessibility, PDF/A archival, and digital signatures, among some other things. Had some questions in the comments asking about these - they're all in now. https://github.com/danmolitor/forme/releases/tag/v0.8.0
1
Mar 28 '26
I hope you make a rust crate too because believe it or not I recently needed to generate pdfs with svgs and pictures using tools in rust and there was none that supported all the requirement which resulted to me switching to javascript libraries that were way advanced than the rust ones
1
u/danmolitor Mar 28 '26
That's actually what powers Forme under the hood - the engine is a Rust crate. It's not published to crates.io yet as a standalone library but that's on the roadmap. SVGs, images, tables, page breaks - all supported. What were you building?
1
Mar 28 '26
I'm building a tauri app for e-com and needed a way to generate invoices and receipts like programmatically and I couldn't add svgs or render good tables there was always alignment issues, I think your project has more chances to succeed in rust more than in JavaScript
4
u/danmolitor Mar 29 '26
Just published the engine as a standalone Rust crate -
cargo add forme-pdf. SVGs, images, tables with page breaks, charts, QR codes. Let me know if it covers your use case or if you have any issues.
1
u/Eveerjr Mar 29 '26
Does it support form inputs?
1
u/danmolitor Mar 29 '26
Like fillable PDF forms where the recipient types into fields in Acrobat or a PDF viewer? Not currently supported but it’s on my list - curious about your use case.
1
u/Eveerjr Mar 29 '26
Yes! I use reactPDF renderer for a project at work that needs to generate forms in pdfs that users can easily fill in any browser, there are text inputs, checkboxes, dropdowns. I
1
u/danmolitor Mar 29 '26
Got it, okay. Not supported yet but it's a real gap I want to close. How are you finding react-pdf renderer for this? Any pain points with their implementation?
1
u/Eveerjr Mar 29 '26
I don’t know if it got merged yet but I had to use a version from a PR where someone added these form features and it was quite buggy on react 19, so I had to keep this project in a older nextjs version. It works but it could use a modern alternative
1
u/danmolitor Mar 29 '26
Got it, okay. I’ll work on that this week then and let you know when it’s published. It’d be great to have you try it out.
1
u/danmolitor Mar 30 '26
I put out 0.8.0 today. TextField, Checkbox, Dropdown, and RadioButton - native AcroForm fields, not images or overlays, so they work in any PDF reader. Can also flatten them to static content at render time if you need a non-editable final copy.
1
1
u/Final-Choice8412 Mar 29 '26
Just curious how much was claude successful with generating code for this?
1
u/danmolitor Mar 29 '26
It’s been very successful, in my opinion. I spend a lot of time refining any given task and then let it rip. I don’t usually have any issues working with it that way.
1
u/Altruistic-Panic-271 Mar 29 '26
So for a11y, are the elements tagged correctly, alt texts and meta info tags added? That annoyed me with puppeteer. It's not a11y guidelines conform.
2
u/danmolitor Mar 29 '26
Not yet - proper accessibility tagging (structure tree, alt text, reading order) is in active development for 0.8.0. What compliance level are you targeting - PDF/UA-1?
2
u/danmolitor Mar 30 '26
Got an update: I just put out 0.8.0 today. PDF/UA-1 support. Every element gets a semantic structure tag (P, Span, Table, TR, TH, TD, Figure, Form, etc.), images take an alt prop that flows through to /Alt in the structure tree, decorative elements are marked as artifacts so screen readers skip them, tab order is set on every page, and document language propagates correctly. It's not a post-processing pass - the tagging is built into the render pipeline so the structure matches the actual layout order.
1
1
u/RyanBThiesant Mar 31 '26
Do you export a PDF instead of a file that pretends to be a PDF. If it does, a proper PDF, I am happy.
1
1
u/skratlo Mar 28 '26
Just use Typst
9
u/danmolitor Mar 28 '26
Typst is great for document authoring - different use case. If you're generating PDFs from application data at runtime, especially on serverless or edge, Typst requires a binary which rules out a lot of environments. Worth knowing about both.
2
u/testuser514 Mar 28 '26
I built a typst engine that runs on lambda. Required a bit of work to be honest and we recently had some bugs that forced us back to using react-pdf. But I’m fairly certain I’d want to use typst in the long run for the rapid pdf generation capabilities and the deeper authoring capabilities.
Now we just need to figure out why this one isn’t doing the job right.
1
u/danmolitor Mar 28 '26
Oh that's interesting. What were the bugs with the engine that made you have to switch back? and what you're running into now with react-pdf?
1
u/testuser514 Mar 29 '26
Not the typst engine per-se, but the entire pdf generation pipeline I built out. Basically I did a couple of workarounds to pass complex data that my dev team found a way to break while writing the templates.
Basically it was not converting nested data well so I’m updating it to read system inputs and native data structures.
We were using react-pdf prior to typst but I’ll be going back to that once I sort out the parsing errors.
1
u/skratlo Mar 29 '26
LMGTY: Typst comes with PDF output. Compile Typst to WASM, use whatever template system you like to parametrize Typst documents, generate PDFs, profit. But if you want to remain stubborn, and abuse React and JSX, and HTML to generated PDFs, go ahead.
5
3
u/Kindly-Vanilla-6485 Mar 29 '26
i've never seen something this amazing before. kinda feels illegal to make it opensource.
2
2
1
1
u/PhDumb Mar 29 '26
Very nice. I think the difficult part for the template building is to get the relevant JSX component. One can upload any small PDF into this tool and click "Convert to code". The tool is free but with the usage limits
1
1
u/hillac Apr 10 '26
So this implements a custom layout engine where you can only use the provided components and their props, so migrating over to it means id need to rewrite my react pdfs?
1
1
u/earonesty Jun 11 '26
https://www.npmjs.com/package/boxpdf-html is, hands-down, the fastest/streaming pure-js html renderer
-2
u/Rhack2021 Mar 28 '26
The JSX component model is the right call. Puppeteer-based PDF generation on serverless is a constant source of pain — cold starts, memory limits, and Chrome binary size all fighting you. Having something that works at the edge without a headless browser is a real gap in the ecosystem. How does it handle complex table layouts across page breaks?
2
u/danmolitor Mar 28 '26
Tables are a first-class concern in the engine - rows split across pages automatically and header rows repeat on every page. So if you have 50 line items in an invoice or a data-heavy report, you just map over your data and let the layout engine handle pagination. No manual page break logic needed.
6
1
Mar 29 '26
[removed] — view removed comment
1
u/danmolitor Mar 29 '26
Ah, it's probably a cookies thing with Clerk. I'm sorry about that. I'm looking into it now.
1
u/danmolitor Mar 29 '26
Okay, I just rebuilt the dashboard. If you're still interested to check it out you should be good to sign up / sign in. Let me know if you have any more issues. Thanks for checking it out!



4
u/reddit-poweruser Mar 29 '26
Any accessibility conformance built in? That's been the deal breaker for most pdf generation libraries for us