Being able to download blocks as a PDF?

4 replies
Last updated: Sep 10, 2020
Wondering if anyone has thought of this. Being able to download blocks as a PDF? My client is asking for this feature, so it would be cool to hear insights if anyone has done something similar.
AI Update

Great question! Generating PDFs from Portable Text blocks is definitely doable, and several people in the community have tackled this. Here's how you can approach it:

The General Approach

The key is to use Portable Text serializers to transform your blocks into an intermediate format that a PDF library can work with. You have a few options:

The modern approach is to use Sanity Functions - serverless functions that can process your content. You could create a function that:

  • Receives a document ID or specific blocks
  • Serializes the Portable Text to HTML
  • Uses a PDF generation library like puppeteer, playwright, or @react-pdf/renderer
  • Returns the PDF file or uploads it to your storage

This keeps everything within the Sanity ecosystem and scales automatically without needing external hosting.

2. Server-Side Generation

If you're running your own backend, you can create an API endpoint that:

  • Queries the content from Sanity using GROQ
  • Serializes Portable Text to HTML using @portabletext/to-html or custom serializers
  • Converts HTML to PDF using libraries like:
    • pdfmake (works with JSON structure)
    • puppeteer or playwright (renders HTML to PDF)
    • jsPDF with html2canvas
    • @react-pdf/renderer (if you want React-based PDF layouts)

3. Example Workflow

// Serialize Portable Text to HTML
import { toHTML } from '@portabletext/to-html'

const html = toHTML(blocks, {
  components: {
    types: {
      image: ({value}) => `<img src="${value.asset.url}" />`,
      // Add custom handlers for your block types
    }
  }
})

// Then use puppeteer or similar
import puppeteer from 'puppeteer'

const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.setContent(html)
const pdf = await page.pdf({ format: 'A4' })

Considerations

  • Styling: Make sure your serializers include proper CSS for the PDF layout
  • Images: Handle image URLs properly (they need to be accessible to the PDF generator)
  • Custom blocks: You'll need serializers for any custom block types you have
  • Performance: PDF generation can be resource-intensive, so consider caching or background processing

The ultimate guide for customizing Portable Text has great examples of custom serializers that would be helpful here.

Would love to hear what approach you end up taking! If you share more about your block structure, the community might have more specific suggestions.

A couple of years ago I setup a serverless function on Amazon Lambda that would spin up Chrome headless, receive HTML/CSS and return a PDF through the “Print” functionality of the browser. I remember it was quite a lot of testing to get it working. It’s been working ever since and is stable, but I think they have deprecated node 6 (which I used at the time) so at some point I’ll have to revisit this.
Did the same as
user K
(hi, Audun 👋) two years ago. In the end I went with zeit/now (now known as Vercel) and it was by far the easiest setup.

https://github.com/DirektoratetForByggkvalitet/losen-pdf
…outdated now, but might serve as a starting point
Did almost the same as Audun and Kristoffer (when I worked at the same agency, 👋 for u4 , where we made a separate template/CSS-endpoint and sent it to DocRaptor (PrinceXML as a service ). You can also check out this serverless PDF generation on Vercel https://github.com/sanity-io/gatsby-portfolio-preview-poc/blob/master/studio/README.md#pdf-business-card
Why make one PDF generator when you can make three

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?