The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →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.
#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.
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.
Rank #2
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:
# 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.
Rank #3
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.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →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
filenameandcontent_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:
Rank #4
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.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOr 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.
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.
Best Value
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.
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
wkhtmltopdfmaintenance. - 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.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteWhich 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.
Quick Recap
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.




