Generate PDFs in Rails

Add a PDF download to an existing Rails page, or generate a PDF from a template. The examples use PDFCrowd's HTML to PDF API and work for invoices, reports, receipts, and other documents built from application data.

Set up the client

Add the Ruby client to your application:

bundle add pdfcrowd

You can run the examples below with the included demo credentials, or replace them with your PDFCrowd username and API key.

Add a PDF download to an existing page

An existing controller action can display its usual HTML page and return a PDF when the user clicks a download button. Reuse the action's template, data preparation, and access checks.

Choose what to send to PDFCrowd:

Convert rendered HTML

This example extends a document page. In config/routes.rb, make its route accept GET and POST inside the existing Rails.application.routes.draw block:

match "/documents/:id", to: "documents#show", via: [:get, :post],
      constraints: { id: /\d+/ }, as: :document

Extend DocumentsController in app/controllers/documents_controller.rb. get_document_for_current_user(params[:id]) represents your application's document lookup and permission checks; substitute your own logic and keep the controller's existing access restrictions. The template at app/views/documents/show.html.erb receives @document.

require "pdfcrowd"

class DocumentsController < ApplicationController
  def show
    @document = get_document_for_current_user(params[:id])

    if request.post? && params[:download_pdf]
      @pdf_base_url = Rails.application.config.x.pdf.base_url
      html = render_to_string(
        template: "documents/show", formats: [:html], layout: true
      )

      client = Pdfcrowd::HtmlToPdfClient.new("demo", "demo") # Username, API key
      client.setContentViewportWidth("balanced")
      pdf = client.convertString(html) # Binary String

      send_data pdf, type: "application/pdf", disposition: "attachment",
                     filename: "document-#{params[:id]}.pdf"
      return
    end

    render :show, formats: [:html]
  end
end

Add this form to the ERB template, outside any existing form:

<%= form_with url: request.fullpath, method: :post, local: true,
              data: { turbo: false }, class: "pdfcrowd-remove" do %>
  <button type="submit" name="download_pdf" value="1">Download PDF</button>
<% end %>

Keep Rails' CSRF protection enabled; form_with supplies the authenticity token. local: true and data: { turbo: false } make this a normal browser submission, so the browser handles the PDF attachment. The latter disables Turbo interception for this form.

GET requests display the HTML page. Submitting the download button sends a POST, renders the same template and layout, converts the HTML, and returns a PDF download. pdfcrowd-remove hides the form in the PDF while leaving it visible on the webpage. The @pdf_base_url value supports the CSS and image setup below.

The PDF stays in memory until conversion succeeds. send_data then sends the binary String without a second HTML render. Rendering and conversion errors propagate to Rails' error handler; keep detailed error pages disabled in production so private details remain server-side. See SDK error handling for details.

To display the PDF in the browser, change attachment to inline. Customize page size, margins, or other PDF options on the client before conversion.

Make CSS and images available

When you send rendered HTML to PDFCrowd, relative paths to CSS and images need a base URL so PDFCrowd can locate the files and include them in the PDF.

Check the asset URLs in the rendered HTML. If all URLs are complete, such as https://cdn.your-site.example/assets/style.css, or the layout already provides a correctly configured <base> element, no additional setup is needed.

For relative paths, add this inside your application class in config/application.rb:

config.x.pdf.base_url = "https://your-site.example/"

Replace https://your-site.example/ with your website's public URL, where PDFCrowd can access the assets. Add this to the layout's <head>, before stylesheet links:

<% if @pdf_base_url.present? %>
  <base href="<%= @pdf_base_url %>">
<% end %>

The controller supplies @pdf_base_url only when rendering for conversion. ERB escapes the attribute value. Use the existing <base> element if the layout already has one.

For example, with a base of https://your-site.example/reports/, images/logo.png resolves to https://your-site.example/reports/images/logo.png. A path starting with /, such as /assets/style.css, resolves from the site root. Keep the trailing slash when the base refers to a directory, and use the directory that matches your relative paths.

The base URL does not upload files or grant access to private assets. For files that are only available on your server, see convert HTML with local assets.

Convert the page URL instead

For a page PDFCrowd can load through its normal address, use URL conversion. Remove the @pdf_base_url assignment and render_to_string(...) call from the download branch, then replace client.convertString(html) with:

pdf = client.convertUrl(request.original_url) # Binary String

Keep the client setup, settings, and send_data call. The URL preserves the current path and query parameters. PDFCrowd makes a separate GET request, so the action returns HTML; only the original POST triggers conversion. Your server must be able to handle that GET while the POST waits for the PDF.

Use a URL that PDFCrowd can reach; localhost cannot reach your development machine. Ensure request.original_url uses your public address, with the appropriate allowed-host and proxy configuration.

The separate request does not inherit the user's Rails session. For protected pages, configure cookies or HTTP authentication as appropriate, or use rendered HTML from the original request. Use rendered HTML if application locking would make that GET wait for the POST to finish.

Generate a PDF from a template

You can also generate a PDF directly from a template and application data. Use an existing ERB template or a separate template designed for the document.

Add this class to app/services/pdf_renderer.rb. It renders the supplied template and layout and returns the PDF as a binary String, using the same asset configuration as the controller:

require "pdfcrowd"

class PdfRenderer
  def self.render(template, context, layout: "application")
    html = ApplicationController.render(
      template: template,
      formats: [:html],
      layout: layout,
      assigns: context.merge(
        pdf_base_url: Rails.application.config.x.pdf.base_url
      )
    )

    client = Pdfcrowd::HtmlToPdfClient.new("demo", "demo") # Username, API key
    client.setContentViewportWidth("balanced")
    client.convertString(html)
  end
end

For example, given an authorized document object and a template at app/views/documents/pdf.html.erb, call:

pdf = PdfRenderer.render("documents/pdf", { document: document })

Pass all data the template needs in context, using symbol keys. Each key becomes an instance variable: document: supplies @document. Use the returned bytes for a download, storage, or email. Rendering and conversion errors propagate to the caller.

The helper uses the booted application's renderer, so it also works outside a controller action. It does not inherit the active request's user or session, or run controller access checks. Supply authorized data and use a template and layout that do not depend on request-only helpers or forms. Pass layout: "pdf" to use your own PDF layout, or layout: false if the template contains a complete HTML document.

If the template uses absolute URL helpers such as document_url, configure the application's Rails.application.routes.default_url_options with the public host and protocol. A <base> element resolves relative URLs in the HTML; it does not configure Rails' URL helpers.

For more conversion inputs and PDF settings, see the Ruby examples and API reference.