Skip to content
Full-stack asset management for Ruby on Rails. With Paperclip, SWFUpload, jQuery, jgrow, Jcrop and Facebox.
JavaScript Ruby
Find file
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.
test papermill 2 first release



  • Asset management made easy, 10 minutes integration.

  • All-you-can-eat glue around Polymorphic Paperclip table, SWFUpload & JQuery.

  • Associate any image or list of images with any model and any key.

Install the gem

sudo gem install papermill

Try the demo

rails -m papermill-example

Out-of-the-box compatibility with :

  • Formtastic # use :as => :[image|asset](s)_upload

  • JGrowl # for notifications (included)

  • FaceBox # for popups (included)

  • Stringex # (or any String#to_url) for asset filename/url generation

Navigator minimal requirements:

  • IE6+

  • Flash 9+

  • Javascript ON

Check your audience.

Server requirements:

  • Rails 2.3.[4~>8]

  • Paperclip (loaded with gem dependency)

  • Front web server serving static assets if present, and forwarding demand to rails if not. Any classic installation will do that by default.

  • NOT compatible with Heroku/S3


Once gem is installed

Generate the migration

./script/generate papermill_table PapermillMigration

Edit it and migrate

rake db:migrate

Copy static assets to your public directory

./script/generate papermill_assets

Create the option file config/initializers/papermill.rb

./script/generate papermill_initializer

Go have a look at config/initializers/papermill.rb

In environment.rb

... do |config|
  config.gem papermill

In your layout

Quick version

Inside <head></head>

<%= papermill_stylesheet_tag %>

Before </body> (best practice for javascript loading)

<%= papermill_javascript_tag :with_jquery => "no_conflict" %>

You don't need :with_jquery if load it by yourself. Pass “no_conflict” if you use the default Prototype library, or some other '$' library (mootools..)

In a real-world production application, you could use something like this, and adapt it to your own needs

Inside <head></head>

<% unless @content_for_papermill_inline_js.blank? %>
  <%= javascript_include_tag "/facebox/facebox.js", "/jgrowl/jquery.jgrowl_minimized.js", "/papermill/jquery.Jcrop.min.js", "/swfupload/swfupload.js", "/papermill/papermill.js", :cache => "papermill" %>
  <script type="text/javascript">
    jQuery(document).ready(function() {
      <%= yield :content_for_papermill_inline_js %>
<% end %>

Before </body>

<script src="" type="text/javascript"></script>
<script src="" type="text/javascript"></script>
<% unless @content_for_papermill_inline_js.blank? %>
  <%= stylesheet_link_tag("/facebox/facebox.css", "/jgrowl/jquery.jgrowl.css", "/Jcrop/jquery.Jcrop.css", "/papermill/papermill.css", :cache => "papermill") %>
  <style type="text/css">
    <%= yield :papermill_inline_css %>
<% end %>



Maybe you don't want users to use your application as a thumbnailing farm for their own uploaded images, or you have protected members areas and you don't want users to 'browse' others members file.

  • Brute solution: pass :use_url_key to true in the options (config/initializers/papermill.rb). A crypted hash unique to your application and to each asset and to the requested style will be added to the URL. No more happy-guessing of anything. Do that first before going live, or you'll have to migrate all assets…

  • pass :alias_only to true. This will disable the possibility to generate thumbnails with a papermill string in the url, but won't do anything for the member area thing. Plus you will have to use aliases only, form helpers included (pass :thumbnail => { :style => :some_alias })


Assetable is the class that has_many papermill_assets (i.e. the class with the papermill declaration)

Assetable declaration

You can have one :default association (his settings will be used for unfound associations) and as many other associations as you want in your model. You can define a papermill relationship dynamically: just do smtg like Assetable.papermill(:dynamic_key, {}) when you need to. Perfect for CMS where associations are created by users. Then you'll be able to use assetable.dynamic_key to retrieve the associated assets. If you don't send the {}, default options from default association will be used, which may or may not be what you want.

Actually, the form helper leverages this when you use a :key that doesn't exist: it will create a new Papermill relationship whith :key as the name and options from the :default declaration if any found on the model.

If you don't need dynamic keys, just declare your associations in the model, like this :

class Article
  papermill :default
  papermill :images
  papermill :pdf_version
  papermill :cover_image
  papermill :illustrations

Form helpers

Example form:

form_for @assetable do 
  # I need a simple asset upload field :
  f.asset_upload  :pdf_version

  # Now I need to be able to upload as many documents as I need, and sort them at will
  # no document should be bigger than 1MB (respect the quoting!)
  # and I don't want the mass_edit feature
  f.assets_upload :documentation, :swfupload => { :file_size_limit => "'1 MB'" }, :mass_edit => false

  # I need to display *one* cover *image*, format will be 200x200
  # targetted_size will give the uploader hints when cropping the image after upload : desired display size and wanted aspect-ratio.
  # Better than cropping automatically in the center if the character's head is in the upper-left corner..
  # :thumbnail => { :width & :height } set the dimensions of the preview thumbnail
  # And finally, I need a 200x200# crop for preview, not the default 200x200> that would be generated by default ("#{:width}x#{:heigth}>")
  f.image_upload  :cover_image, :targetted_size => "200x200", :thumbnail => { :width => 200, :height => 200, :style => "200x200#" }

  # Now the image gallery, sortable.
  # I use :gallery => { :lines & :columns } to give the number of lines/columns, 
  # and some CSS will be generated to size the gallery perfectly, 
  # according to the thumb size inside the gallery and their padding/margin/border sizes.
  # the number of lines will increase if needed when uploading
  f.images_upload :illustrations, { 
    :thumbnail => {
      :width => 100,
      :height => 70
    :gallery => {
      :columns => 8,       # number of columns
      :lines => 2,         # number of lines
      :vpadding => 2,      # vertical padding around each thumb
      :hpadding => 2,      # horizontal one
      :vmargin => 3,       # vertical margin
      :hmargin => 1,       # horizontal one 
      :border_thickness => 2 # border size around each thumb

With Formtastic, pass

:as => (:image_upload | :images_upload | :asset_upload | :assets_upload)

And add your options as you would with the normal helpers.

With FormTagHelpers, use (image_upload_tag | images_upload_tag | asset_upload_tag | assets_upload_tag) @assetable, :key, options

image_upload_tag  @article, :cover_image, :targetted_size => "200x200"

Asset editing

  • double-click on any uploaded asset in any form-helper to access & edit his properties

  • then double-click image to crop it if it's an image. You'll then access a Jcrop window. Pass :targetted_size => “widthxheigth” to lock aspect-ratio and default the selection size to widthxheigth.


On-the-fly request time processing:

PapermillAsset#url(papermill string (see 1.))  # path and url behave the same way
PapermillAsset#url(papermill alias (see 2.))

Pros: fast. Nothing done upon page rendering. If asset isn't found by Apache/NGinx, then request is passed to rails, which will create it, once.

Cons: need to setup an alias in the options if you want to define use a hash instead of a papermill string (for custom watermark)

Render time processing:

PapermillAsset#url!(papermill string (see 1.))  # path! and url! behave the same way
PapermillAsset#url!(papermill alias (see 2.))
PapermillAsset#url!(papermill hash (see 3.))

Pros: can use a hash directly in the url call.

Cons: needs a thumbnail presence check at each render.

1. Papermill String

Consist of:

  • an ImageMagick geometry string (ex: “100x100>”, “original”, “100x#”, etc.)

  • an optional watermark (-wm) flag # will use option[:watemark] for URI

  • an optional copyright (©) flag # will use copyright text after the “©” or options[:copyright]


image_tag @article.covers.first.url("100x100")
image_tag @article.covers.first.url("original©")
image_tag @article.covers.first.url("100x100#-wm©")
image_tag @article.covers.first.url("100x200#©papermill")

2. Papermill Alias

Those are application-wide, set them in the options

Consist of:

:geometry => "ImageMagick-geometry-string"
:copyright => true | "copyright"    # If true, the asset copyright field will be used. Edit the asset.
:watermark => true | URI            # If true, will use options[:watemark]



# snip
:aliases => {
  :thumb_copyrighted => {
    :geometry => "100x100",
    :copyright => "papermill",
  :thumb_copyrighted_dynamically => {
    :geometry => "100x100",
    :copyright => true
  :thumb_watermarked_with_rails => {
    :width => "100",
    :height => "100",
    :watermark => "/images/rails.png"

Then in your views, simply do

image_tag @article.covers.first.url(:thumb_copyrighted)

3. Papermill Hash

Same as aliases, but defined directly in #url!() Plus you can add a :name that will be used for style-name (defaults to a md5 of the hash)


image_tag @article.covers.first.url(
  :geometry => "100x100",
  :watermark => "/images/rails.png",
  :copyright => "papermill",
  :name => "thumbnail_watermarked_and_copyrighted"

Resource access

Papermill generates an #<association_key> association

@entry.diaporamas.each do |image| ..
# etc.

Using PapermillAsset

@asset = @entry.mug_shots.first
image_tag @asset.url              # original
image_tag @asset.url("100x>")     # assuming asset is an image
image_tag @asset.url(:big)        # assuming you have a :big alias
# etc.


Papermill is fully I18n-able. Copy config/locales/papermill.yml to your root config/locale folder to modify any wording in a any locale.

Copyright © 2009 Benoit Bénézech, released under the MIT license

Something went wrong with that request. Please try again.