Open-source project
excid3/receipts avatar
excid3/receipts

receipts: PDF receipts and invoices for Rails without a PDF dependency

Easy receipts and invoices for your Ruby on Rails applications

696 stars83 forksRubyMIT

At a glance

What is it?
The receipts gem renders receipts, invoices and statements as PDF from inside a Rails app using pure Ruby, so there is no native PDF library in the dependency tree. It gives you a data-driven layout with inline formatting, column widths, internationalisation and a Prawn-like escape hatch, and the current major version is 3.0.0.
Who is it for?
Adopt the receipts gem if your Rails application needs to send PDF receipts, invoices or statements and you would rather not pull a native PDF toolkit into your bundle, especially if the layout is a simple table plus a few labels. Do not adopt it if you need complex typesetting, since the gem covers tables, text and images rather than page composition, and do not assume it inherits every feature of Prawn, because it provides a small Prawn-like API of its own.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 17 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Pure Ruby PDF output, and what that means for the bundle

The receipts gem does receipts, invoices and statements for a Rails application, and it works with any payment provider. The part that distinguishes it from most options is stated in the first line of the readme: PDFs are generated in pure Ruby with no dependencies.

That is a real constraint that cuts both ways, and it is the main thing to weigh. The good side is that the gem adds no native extension, no C library and no system package to your deployment image, and it does not constrain your Ruby version through a native toolchain. For a Rails app that already has a large dependency tree, one fewer native dependency is a real reduction in build risk and in the chance of a mismatch between your Ruby version and a compiled gem.

The other side is that a from-scratch PDF writer is a smaller toolkit than the established ones. The gem covers what a receipt needs, text, tables, images, fonts and rules, and it gives you a small Prawn-like API when you need to go beyond the default layout. It is not a general page composition engine, and anyone expecting the full Prawn feature set should read the API section carefully before committing.

The project publishes a directory of example PDFs for receipt, invoice and statement, which is the fastest way to see what the output actually looks like before you write a line of integration code.

A receipt is a data structure, not a template file

The API is built around one object. You create a Receipts::Receipt and pass it the content you want rendered, in named sections:

ruby
r = Receipts::Receipt.new(
  # title: "Receipt",
  details: [
    ["Receipt Number", "123"],
    ["Date paid", Date.today],
    ["Payment method", "ACH super long super long super long super long super long"]
  ],
  company: {
    name: "Example, LLC",
    address: "123 Fake Street\nNew York City, NY 10012",
    email: "[email protected]",
    logo: File.expand_path("./examples/images/logo.png")
  },
  recipient: [
    "Customer",
    "Their Address",
    "City, State Zipcode",
    nil,
    "[email protected]"
  ],
  line_items: [
    ["<b>Item</b>", "<b>Unit Cost</b>", "<b>Quantity</b>", "<b>Amount</b>"],
    ["Subscription", "$19.00", "1", "$19.00"],
    [nil, nil, "Subtotal", "$19.00"],
    [nil, nil, "Tax", "$1.12"],
    [nil, nil, "Total", "$20.12"],
    [nil, nil, "<b>Amount paid</b>", "$20.12"],
    [nil, nil, "Refunded on #{Date.today}", "$5.00"]
  ],
  footer: "Thanks for your business. Please contact us if you have any questions."
)

# Returns a string of the raw PDF
r.render

# Writes the PDF to disk
r.render_file "examples/receipt.pdf"

The sections map cleanly onto a document. The company hash carries your name, address, email, and optionally a phone number and a logo, and there is a display array that lets you choose which of those appear under the company name, defaulting to the address, phone and email. The recipient is a plain array of lines, with nil acting as a blank line. The details array is a list of label and value pairs, and the line items are rows of a table.

The nil entries in the line items are the detail that makes the table work: a cell left empty in the first two columns puts a label such as Subtotal or Total into the third column, which is how totals lines are produced without a separate section.

Two output methods exist. render returns the raw PDF as a string, which is the one to use when serving from a controller, and render_file writes it to disk.

Inline formatting and column widths are where the layout work lives

The rendering options are deliberately HTML-like, and this is the part of the API you will use most when a receipt needs to look like a specific thing.

The details, line items, recipient and footer fields all accept inline formatting tags. Bold and strong, italic and em, underline, strikethrough, subscript and superscript, and line breaks are supported. Beyond the usual emphasis tags there is a font tag taking a name, a size and a character spacing value, a color tag accepting either an rgb hex value or explicit cyan, magenta, yellow and key components, and a link tag with an href. Literal angle brackets and ampersands have to be escaped as entities.

Column widths are controlled separately, and the default is that columns are sized to fit their content with any leftover space split evenly between them. You override that with a hash keyed by column index, where the values are widths in points, and this example sets the second column to 400 and the fourth to 50:

ruby
column_widths: {1 => 400,3 => 50 }

An array works too, and that sets every column width assuming the table has four columns:

ruby
column_widths: [100, 200, 240]

Columns without a width are sized automatically. The logo also has a height option, logo_height, which defaults to 16, and the page size can be changed with a page_size keyword argument such as A4.

The company logo accepts a Path, a File, a StringIO or a URL, and a URL is fetched with OpenURI, so a hosted logo works without downloading it yourself. Images in general must be PNG in non-interlaced form or JPEG.

Fonts are TrueType, and only the characters you use get embedded

Typography is the one place where a from-scratch implementation has to be careful, and the gem has an opinionated but sensible stance.

The default is the bundled Inter font, which supports Latin, Greek and Cyrillic characters. You can set a different default for all PDFs in an initializer:

ruby
Receipts.default_font = {
  bold: Rails.root.join('app/assets/fonts/tradegothic/TradeGothic-Bold.ttf'),
  normal: Rails.root.join('app/assets/fonts/tradegothic/TradeGothic.ttf'),
}

Fonts must be TrueType files with a .ttf extension. You may also supply italic and bold_italic, and if you do not, italic text is simulated by slanting the normal font, which is a reasonable approximation for a receipt and will look wrong for anything decorative.

The detail worth internalising is that only the characters actually used are embedded in each PDF. That keeps file sizes small, which matters when you are attaching a PDF to an email, and it is a pleasant contrast with embedding an entire typeface on every document.

The practical consequence for a team outside Latin, Greek and Cyrillic scripts is that the bundled font will not cover it, and you will need to supply a TrueType font that does. The gem will not tell you which characters a given font covers, so that check is on you before you promise localised receipts in a language whose script is not in Inter.

When the default layout is not enough, the document object is open

Every Receipts object inherits from Receipts::PDF::Document, and that inheritance is the escape hatch. If you instantiate the object with no options you get an empty PDF, and you can then draw on it with a small API of your own:

ruby
receipt = Receipts::Receipt.new # creates an empty PDF
receipt.text("hello world", size: 12, style: :bold, align: :center, color: "4b5563")
receipt.text("<b>Bold</b> and <i>italic</i>", inline_format: true)
receipt.move_down 20
receipt.image "logo.png", height: 24, position: :right
receipt.table([["Item", "Amount"], ["Product", "$10"]], width: receipt.bounds.width) do
  row(0).font_style = :bold
  column(1).align = :right
end
receipt.stroke_horizontal_rule
receipt.start_new_page

The API is Prawn-shaped, with text, move_down, image, table, a bounds object giving the current page width, a horizontal rule and start_new_page, and the table block takes row and column accessors for styling individual cells. It is a small subset rather than a full port, so the mental model transfers but the feature set does not.

The Receipts helpers are also usable inside a custom document, at the current cursor position:

ruby
receipt.text("Custom header")
receipt.render_line_items(line_items: [
  ["my line items"]
])
receipt.render_footer("This is a custom footer using the Receipts helper")

That is a useful middle path. Rather than rebuilding a receipt from scratch, you can draw your own header or footer and reuse the line item renderer, which keeps the table styling consistent with the rest of your documents.

Internationalisation is handled by calling I18n.t yourself when you build the content, since the gem renders the strings it is given rather than looking anything up:

ruby
line_items: [
  [I18n.t("receipts.date"),           created_at.to_s],
  [I18n.t("receipts.product"), "GoRails"],
  [I18n.t("receipts.transaction"), uuid]
]

Version 3.0, a two year gap, and where the gem fits against Prawn

The release history has a shape worth noting. v3.0.0 was published on 2026-09-21, and before that v2.4.0 dates from 2024-03-26 with v2.3.0 from 2024-03-05. So the project sat on a 2.x line for a long stretch and then shipped a major version, and the last push was on 2026-10-06, so work is continuing after the 3.0 release.

A major version bump on a library like this means you should read the changelog before upgrading, and the repository has one. The two year gap also suggests a maintainer-led rather than team-led project, with bursts of work rather than a steady stream.

The real alternative is Prawn, or a wrapper around it, used directly. The difference in approach is exactly the thing the gem is built around. Prawn gives you a drawing API and nothing else, so the table layout, the company block, the recipient lines, the details grid and the footer all become your code, which is a few hundred lines you maintain and tune. The receipts gem has already made those decisions, including how line items size themselves, how totals rows are expressed, and how the logo and fonts are placed.

That is a good trade when a receipt should look like a receipt, and a bad one when your document needs to be something else, like a statement with a completely different arrangement. The gem also covers a second case the raw approach does not: the company, recipient, details and line items structure maps directly onto the data you already have from a payment provider, so the integration work is mostly assembling arrays rather than calling drawing primitives.

The licence is MIT, and the repository uses the standard Ruby style tooling with a .standard.yml and a Rakefile, plus a test directory.

Editorial conclusion

Adopt the receipts gem if your Rails application needs to send PDF receipts, invoices or statements and you would rather not pull a native PDF toolkit into your bundle, especially if the layout is a simple table plus a few labels. Do not adopt it if you need complex typesetting, since the gem covers tables, text and images rather than page composition, and do not assume it inherits every feature of Prawn, because it provides a small Prawn-like API of its own. Before you commit, render the example PDFs from the repository, check that your font is a TrueType file, and verify the character set you need is covered by the bundled Inter font or supply your own.

Frequently asked questions

How do I install the receipts gem?

Add gem 'receipts' to your application's Gemfile and run bundle, or install it directly with gem install receipts. The gem generates PDFs in pure Ruby with no dependencies.

How do I render a receipt to PDF in a Rails controller?

Create a Receipts::Receipt with your details, company, recipient and line items, then call render to get the raw PDF as a string, which is the recommended way to serve PDFs from a controller. render_file writes the PDF to disk instead.

What fonts does the receipts gem support?

The bundled Inter font is the default and covers Latin, Greek and Cyrillic. You can set a different default through Receipts.default_font in an initializer, but fonts must be TrueType .ttf files, and only the characters you actually use are embedded in each PDF.

How do I control line item column widths?

By default columns are sized to their content with leftover space split evenly. You can set specific widths in points with a hash keyed by column index such as column_widths: {1 => 400,3 => 50 }, or set all of them with an array such as column_widths: [100, 200, 240].

Can I build a custom PDF with the receipts gem?

Yes. Every Receipts object inherits from Receipts::PDF::Document, which provides a small Prawn-like API with text, image, table, bounds, rules and page breaks. You can also use the Receipts helpers such as render_line_items and render_footer inside a custom document.

Official sources

  1. excid3/receipts on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/excid3-receipts.svg)](https://hysenlabs.com/projects/excid3-receipts)

Was this useful?