Driver FixRecommendedSound, Wi-Fi or graphics acting up? Check drivers firstFind missing or outdated drivers fast.Check DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PC×
Skip to content
Active Storage

How to Generate a PDF and Return Its URL in Ruby

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Generating a PDF and returning a usable URL are two separate operations. Ruby can render PDF bytes or write a file; your application must then store those bytes somewhere reachable and construct a link to them. In Rails, the usual solution is to generate the document, attach it with Active Storage, save the record, and return a Rails blob URL.

This guide covers programmatic PDFs with Prawn, HTML-based PDFs with PDFKit or Wicked PDF, Rails URL delivery, storage and access-control choices, complete examples, failure modes, and an option that avoids browser-based PDF capture entirely.

Choose the PDF generation path

Start with the format of your source document rather than the URL requirement. The URL is added after rendering.

Source and goal Ruby option Important dependency
Programmatic layout, invoices, reports and certificates Prawn, using Prawn::Document or Prawn::Document.generate Ruby gem; you define the layout in Ruby
Existing HTML and CSS views PDFKit wkhtmltopdf executable; it renders HTML through WebKit
Rails HTML views with Rails-oriented helpers Wicked PDF wkhtmltopdf executable and asset configuration

PDFKit exposes PDF data with to_pdf and can write a file with to_file. Wicked PDF renders a Rails HTML view through wkhtmltopdf. Neither gem, by itself, creates a public URL.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall

Generate PDF bytes with Prawn

Add Prawn to your Gemfile:

gem "prawn"

Run bundle install, then generate a document in memory:

require "prawn"

def invoice_pdf(invoice)
  Prawn::Document.new do |pdf|
    pdf.text "Invoice #{invoice.number}", size: 20, style: :bold
    pdf.move_down 12
    pdf.text "Customer: #{invoice.customer_name}"
    pdf.text "Issued: #{invoice.issued_on}"
    pdf.move_down 18

    invoice.items.each do |item|
      pdf.text "#{item.description} — #{format('%.2f', item.amount)}"
    end

    pdf.move_down 12
    pdf.text "Total: #{format('%.2f', invoice.total)}", style: :bold
  end.render
end

render returns a binary string containing the PDF. If you prefer a file, Prawn also supports:

Prawn::Document.generate("tmp/invoice-#{invoice.id}.pdf") do |pdf|
  pdf.text "Invoice #{invoice.number}"
end

Keep the binary string or file handle available for the storage step; returning it directly from a controller downloads the file but does not create a reusable URL.

Generate a PDF from HTML with PDFKit or Wicked PDF

PDFKit

PDFKit is appropriate when your source is already HTML. It relies on wkhtmltopdf, so install that executable in every development, test and production environment that renders PDFs.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
require "pdfkit"

html = ApplicationController.render(
  template: "invoices/show",
  assigns: { invoice: invoice }
)
pdf_bytes = PDFKit.new(html).to_pdf

For a temporary file, use to_file:

file = PDFKit.new(html).to_file("tmp/invoice-#{invoice.id}.pdf")

When the HTML references stylesheets, images or fonts, make their URLs resolvable by the PDF process. PDFKit documentation also notes a development setup issue when rendering requires the server to call itself; verify host, port and asset URLs instead of assuming browser-relative paths will work.

Wicked PDF in Rails

Wicked PDF integrates HTML views with Rails controllers. A typical controller response is:

def show
  @invoice = Invoice.find(params[:id])
  render pdf: "invoice-#{@invoice.id}", template: "invoices/show"
end

That response sends a PDF to the requester. To obtain bytes for storage, use the gem’s documented rendering API for your installed version, or render the HTML and pass it to PDFKit explicitly. Exact option names can vary by gem release, so check the versioned README used by your application.

Attach the PDF with Active Storage and return a Rails URL

Active Storage supplies the attachment and URL layer. It can use local disk during development or a cloud service such as Amazon S3 in production. Create the attachment on a persisted model:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
# app/models/invoice.rb
class Invoice < ApplicationRecord
  has_one_attached :pdf
end

# app/controllers/invoices_controller.rb
class InvoicesController < ApplicationController
  def create_pdf
    invoice = Invoice.find(params[:id])
    bytes = invoice_pdf(invoice)

    invoice.pdf.attach(
      io: StringIO.new(bytes),
      filename: "invoice-#{invoice.id}.pdf",
      content_type: "application/pdf"
    )
    invoice.save!

    render json: {
      id: invoice.id,
      url: rails_blob_url(invoice.pdf, disposition: "inline")
    }, status: :created
  end
end

Require stringio if your application does not already load it:

require "stringio"

Use rails_blob_path when you need a relative path. For a full URL, Rails must know the host. In a request, the controller’s URL options usually provide it; in a background job or command-line task, configure the application default URL options for the target environment.

Attaching a generated file

If your generator writes a file, attach it with an open handle and close it afterward:

path = Rails.root.join("tmp", "invoice-#{invoice.id}.pdf")
Prawn::Document.generate(path.to_s) { |pdf| pdf.text "Invoice #{invoice.number}" }

File.open(path, "rb") do |file|
  invoice.pdf.attach(
    io: file,
    filename: path.basename.to_s,
    content_type: "application/pdf"
  )
end
invoice.save!

Understand what URL Rails returns

A Rails blob URL is normally an application URL that redirects to the storage service’s download endpoint. Callers can keep using the application URL even if the storage host changes. The service endpoint itself is signed and typically short-lived, while the application-level blob reference is designed as a durable indirection.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Rails also supports proxying: the application streams the file contents instead of redirecting. Proxying can help when you place a CDN in front of Rails or need every request to pass through application logic, but it consumes application bandwidth and processing. Redirect delivery lets the storage service carry the file transfer.

Serving mode Bytes travel through Use when
Redirect Storage endpoint after an application redirect You want storage to handle downloads and callers to use an app-owned URL
Proxy Your Rails application (optionally behind a CDN) You need application-controlled streaming or CDN proxy behavior

Do not treat an obscure blob URL as authorization. Active Storage controllers are publicly accessible by default. If a PDF contains private information, put authentication in front of the download route or implement an authenticated controller that checks the current user before serving the blob. Confirm the exact behavior and configuration for your Rails release before promising expiration or privacy.

Storage and deployment checklist

  • Run the Active Storage installation and migrations for your Rails version.
  • Use the local disk service for development and tests only when files do not need to survive machine changes.
  • Configure a shared or cloud-backed service for links that must work across processes, hosts or deployments. Rails supports services including Amazon S3.
  • Set filename and content_type: "application/pdf" so browsers and downstream systems handle the response correctly.
  • Generate the URL after the record and attachment have been saved successfully.
  • For jobs, supply an explicit host and protocol when calling rails_blob_url.
  • Delete temporary files after attachment to avoid filling the application disk.

Return the URL from a JSON API

A service object keeps generation and persistence testable:

class GenerateInvoicePdf
  def self.call(invoice)
    bytes = Prawn::Document.new do |pdf|
      pdf.text "Invoice #{invoice.number}"
      pdf.text "Total: #{invoice.total}"
    end.render

    invoice.pdf.attach(
      io: StringIO.new(bytes),
      filename: "invoice-#{invoice.id}.pdf",
      content_type: "application/pdf"
    )
    invoice.save!
    invoice
  end
end

class Api::InvoicesController < ApplicationController
  def pdf
    invoice = Invoice.find(params[:id])
    GenerateInvoicePdf.call(invoice)
    render json: { url: rails_blob_url(invoice.pdf, disposition: "attachment") }
  end
end

Use disposition: "inline" when a browser should attempt to display the PDF, or "attachment" when it should download it. The returned value is still a URL; the disposition changes the response behavior when that URL is opened.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your real input is a web page that must become a PDF, ScreenshotNeo can capture a URL through one HTTP request instead of requiring you to install and operate a headless browser. It can return PNG, JPEG, WebP or PDF, accepts options such as paper size, margins, landscape mode and page ranges, and can wait for selectors, delays or network idle before capture. Cookie banners, newsletter popups and chat widgets are removed before the shot; bot checks, blank pages and failed loads are never billed. Its MCP server lets AI agents use take_screenshot, get_page_info and capture_pdf.

For a PDF capture, call the API endpoint shown in the ScreenshotNeo documentation and save the response:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Change the output filename and PDF options according to the API documentation when requesting PDF output. The same endpoint can be called from Ruby:

require "net/http"
require "uri"

uri = URI("https://api.screenshotneo.com/v1/shot")
uri.query = URI.encode_www_form(
  access_key: ENV.fetch("SCREENSHOTNEO_API_KEY"),
  url: "https://example.com"
)
response = Net::HTTP.get_response(uri)
raise "Screenshot failed: #{response.code}" unless response.is_a?(Net::HTTPSuccess)
File.binwrite("page.pdf", response.body)

Python and Node.js equivalents are useful for mixed-language systems:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo reports page and billing outcomes in X-Page-Verdict and X-Billed headers. Plans include 1,000 screenshots per month free with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

Troubleshooting common failures

The URL is nil or points at the wrong host

Check that the attachment was saved and that Rails has a configured host and protocol. Request-time helpers can infer these values; jobs cannot. Set environment-specific default URL options before generating links.

The PDF downloads as HTML or a blank file

Inspect the response status and Content-Type. For Active Storage, verify the attachment is present and that the storage service is reachable. For HTML-to-PDF tools, confirm wkhtmltopdf is installed and executable, then test every stylesheet, image and font URL from the renderer’s environment.

Images or CSS are missing

Use absolute, reachable asset URLs or configure the renderer’s host and asset settings. A browser session that can see localhost does not guarantee that a separate PDF process can reach it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Works locally, fails in production

Compare executable versions, installed fonts, environment variables and Active Storage service configuration. Production needs shared storage when more than one machine must serve the same attachment.

A supposedly private PDF is publicly accessible

That is the default risk with Active Storage routes. Replace the public route with an authenticated controller or authorization layer and verify behavior against your Rails version.

Performance, reliability and cost considerations

  • Prawn avoids an external browser executable and is a good fit for deterministic, code-defined layouts.
  • HTML-to-PDF rendering adds process startup, HTML asset loading and wkhtmltopdf maintenance.
  • Generate large documents in a background job, attach them transactionally, and return a job status or persisted URL when work is complete.
  • Cloud storage moves download bandwidth away from Rails when redirect delivery is used; proxy delivery does the opposite.
  • Do not claim a URL is permanent, private or expiring without checking the Rails version, storage service and serving mode in use.

Frequently Asked Questions

Can I return a PDF URL without Rails?

Yes. Write the PDF to a durable storage service, make it downloadable through an authenticated or signed endpoint, and return that endpoint from your Ruby application. The storage and authorization design replaces Active Storage.

Should I store PDF bytes in the database?

Usually no. Store the file in object or attachment storage and keep metadata and the storage reference in the database. Database blobs increase backup and query costs.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Which generator should I use for an existing Rails view?

Use an HTML-to-PDF path such as PDFKit or Wicked PDF when preserving an HTML view is the priority; use Prawn when the document is naturally laid out in Ruby.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.