# How Preview Images Are Generated for Template Documents in DocuSeal

> Learn how DocuSeal generates template document preview images using Pdfium and Vips through the Templates::ProcessDocument module for efficient storage with Active Storage.

- Repository: [DocuSeal/docuseal](https://github.com/docusealco/docuseal)
- Tags: internals
- Published: 2026-05-05

---

**DocuSeal generates preview images for template documents using the `Templates::ProcessDocument` module, which rasterizes PDFs via Pdfium and processes images via Vips, storing results as Active Storage attachments named `preview_images`.**

When users upload documents to a template in DocuSeal—an open-source document signing platform—the system automatically creates web-ready preview images to facilitate visual editing and review. This process handles both PDF and image formats through a modular pipeline that converts source files into optimized PNG or JPEG attachments. Understanding how preview images are generated for template documents reveals the architecture behind DocuSeal's document visualization capabilities.

## Core Processing Logic in Templates::ProcessDocument

The central orchestration happens in [`lib/templates/process_document.rb`](https://github.com/docusealco/docuseal/blob/main/lib/templates/process_document.rb), where the `Templates::ProcessDocument` module determines file types and delegates to specialized generators.

### Entry Point and Format Detection

When a new attachment is saved, the system invokes `Templates::ProcessDocument.call(attachment, data, …)`. This method inspects the content type and branches accordingly: PDFs trigger `generate_pdf_preview_images`, while standard images trigger `generate_preview_image`.

### Image Attachment Processing

For image uploads, the `generate_preview_image` method performs the following operations:

- Deletes any existing preview images for the record to prevent accumulation
- Loads the original file using Vips (with special handling for BMP formats via `LoadBmp`)
- Rotates and resizes the image to a maximum width of 1400 pixels
- Encodes the result as a PNG with modest compression
- Creates a new `ActiveStorage::Attachment` named `preview_images` pointing to a fresh `ActiveStorage::Blob`

### PDF Attachment Processing

For PDF documents, `generate_pdf_preview_images` handles multi-page rasterization:

- Clears previous preview images
- Opens the PDF using HexaPDF for metadata extraction and Pdfium for rendering
- Determines page limits (up to 15 pages or fewer if file size thresholds are exceeded)
- Renders each page to a bitmap with a maximum width of 1400px using `Pdfium::Document#render_to_bitmap`
- Converts bitmaps to Vips images and writes them as PNG or JPEG depending on configuration
- Uploads each page as a separate `ActiveStorage::Blob` with `preview_images` attachments

## Background Processing with Sidekiq

Large PDFs can overwhelm synchronous request cycles, so DocuSeal queues intensive processing via `GeneratePreviewImagesJob` in [`app/jobs/generate_preview_images_job.rb`](https://github.com/docusealco/docuseal/blob/main/app/jobs/generate_preview_images_job.rb). This Sidekiq job loads the attachment by ID, calculates the valid page range, and invokes `Templates::ProcessDocument.generate_document_preview_images` with a concurrency limit of 1 to prevent system overload.

## Storage Architecture and Associations

The `preview_images` attachment is declared globally in [`config/initializers/active_storage.rb`](https://github.com/docusealco/docuseal/blob/main/config/initializers/active_storage.rb) using `has_many_attached :preview_images`, making the relationship available across models. Controllers like [`app/controllers/templates_preview_controller.rb`](https://github.com/docusealco/docuseal/blob/main/app/controllers/templates_preview_controller.rb) preload these associations for efficient access, while [`lib/templates/clone_attachments.rb`](https://github.com/docusealco/docuseal/blob/main/lib/templates/clone_attachments.rb) ensures preview images are duplicated when templates are cloned.

## Implementation Examples

To generate a preview for a single image attachment:

```ruby

# Called from Templates::ProcessDocument.call for a non‑PDF attachment

Templates::ProcessDocument.generate_preview_image(attachment, data)

```

To generate per-page previews for a PDF document:

```ruby

# Called from Templates::ProcessDocument.call for a PDF attachment

Templates::ProcessDocument.generate_pdf_preview_images(attachment, pdf_data, max_pages: 15)

```

The background job implementation handles large documents asynchronously:

```ruby
class GeneratePreviewImagesJob
  include Sidekiq::Job

  def perform(params = {})
    attachment = ActiveStorage::Attachment.find(params['attachment_id'])
    max_page   = [attachment.metadata['pdf']['number_of_pages'].to_i - 1,
                  Templates::ProcessDocument::MAX_NUMBER_OF_PAGES_PROCESSED].min

    Templates::ProcessDocument.generate_document_preview_images(
      attachment,
      attachment.download,
      1..max_page,
      concurrency: 1
    )
  end
end

```

## Summary

- **Format-specific pipelines**: The system branches between `generate_preview_image` for bitmaps and `generate_pdf_preview_images` for PDFs in [`lib/templates/process_document.rb`](https://github.com/docusealco/docuseal/blob/main/lib/templates/process_document.rb).
- **Rendering engines**: Vips handles image manipulation and PDF page conversion, while Pdfium rasterizes PDF source files and HexaPDF extracts metadata.
- **Storage model**: All previews are persisted as `preview_images` Active Storage attachments, configured in the initializer and accessible across template models.
- **Scalability**: The `GeneratePreviewImagesJob` background worker processes large documents asynchronously with controlled concurrency to maintain system stability.
- **Data integrity**: When templates are duplicated, [`lib/templates/clone_attachments.rb`](https://github.com/docusealco/docuseal/blob/main/lib/templates/clone_attachments.rb) ensures preview images are copied alongside source documents.

## Frequently Asked Questions

### What file formats does DocuSeal support for preview generation?

DocuSeal supports standard image formats including BMP (handled specially via `LoadBmp`), JPEG, and PNG through the Vips library. For documents, it processes PDF files using Pdfium for rendering and HexaPDF for metadata extraction.

### Why does DocuSeal use Pdfium instead of ImageMagick for PDF previews?

According to the source code in [`lib/templates/process_document.rb`](https://github.com/docusealco/docuseal/blob/main/lib/templates/process_document.rb), DocuSeal uses Pdfium specifically for PDF rasterization because it provides direct bitmap rendering capabilities through `Pdfium::Document#render_to_bitmap`, while HexaPDF handles metadata extraction. This separation allows precise control over page-by-page processing up to the 15-page limit.

### How does DocuSeal prevent preview generation from blocking web requests?

For large PDFs, DocuSeal queues processing in `GeneratePreviewImagesJob`, a Sidekiq background job defined in [`app/jobs/generate_preview_images_job.rb`](https://github.com/docusealco/docuseal/blob/main/app/jobs/generate_preview_images_job.rb). The job executes with `concurrency: 1` to limit resource usage, processing pages asynchronously after the initial upload completes.

### What happens to preview images when a template is cloned?

When duplicating templates, the [`lib/templates/clone_attachments.rb`](https://github.com/docusealco/docuseal/blob/main/lib/templates/clone_attachments.rb) module explicitly handles copying of preview image attachments through methods like `clone_document_preview_images_attachments`, ensuring that cloned templates retain visual previews without requiring regeneration.