diff --git a/_includes/tutorial/sending-images.md b/_includes/tutorial/sending-images.md
new file mode 100644
index 000000000..5018e8c71
--- /dev/null
+++ b/_includes/tutorial/sending-images.md
@@ -0,0 +1,165 @@
+## Sending Images
+
+When you want to have R generate a plot and send it to the client browser, the `renderPlot()` function will in most cases do the job. But when you need finer control over the process, you might need to use the `renderImage()` function instead.
+
+### About renderPlot()
+
+`renderPlot()` is useful for any time where R generates an image using its normal graphical device system. In other words, any plot-generating code that would normally go between `png()` and `dev.off()` can be used in `renderPlot()`. If the following code works from the console, then it should work in `renderPlot()`:
+
+{% highlight r %}
+png()
+# Your plotting code here
+dev.off()
+{% endhighlight %}
+
+{% highlight r %}
+# This would go in shinyServer()
+output$myPlot <- renderPlot({
+ # Your plotting code here
+})
+{% endhighlight %}
+
+`renderPlot()` takes care of a number of details automatically: it will resize the image to fit the output window, and it will even increase the resolution of the output image when displaying on high-resolution ("Retina") screens.
+
+The limitation to `renderPlot()` is that it won't send just any image file to the browser -- the image must be generated by code that uses R's graphical output device system. Other methods of creating images can't be sent by `renderPlot()`. For example, the following won't work:
+
+* Image files generated by the `writePNG()` function from the png package.
+* Image files generated by the `rgl.snapshot()` function, which creates images from 3D plots made with the rgl package.
+* Images generated by an external program.
+* Pre-rendered images.
+
+The solution in these cases is the `renderImage()` function.
+
+
+### Using renderImage()
+
+Image files can be sent using `renderImage()`. The expression that you pass to `renderImage()` must return a list containing an element named `src`, which is the path to the file. Here is a very basic example of a Shiny app with an output that generates a plot and sends it with `renderImage()`:
+
+#### server.R
+
+{% highlight r %}
+shinyServer(function(input, output, clientData) {
+ output$myImage <- renderImage({
+ # A temp file to save the output.
+ # This file will be removed later by renderImage
+ outfile <- tempfile(fileext='.png')
+
+ # Generate the PNG
+ png(outfile, width=400, height=300)
+ hist(rnorm(input$obs), main="Generated in renderImage()")
+ dev.off()
+
+ # Return a list containing the filename
+ list(src = outfile,
+ contentType = 'image/png',
+ width = 400,
+ height = 300,
+ alt = "This is alternate text")
+ }, deleteFile = TRUE)
+})
+{% endhighlight %}
+
+Each time this output object is re-executed, it creates a new PNG file, saves a plot to it, then returns a list containing the filename along with some other values.
+
+Because the `deleteFile` argument is `TRUE`, Shiny will delete the file (specified by the `src` element) after it sends the data. This is appropriate for a case like this, where the image is created on-the-fly, but it wouldn't be appropriate when, for example, your app sends pre-rendered images.
+
+In this particular case, the image file is created with the `png()` function. But it just as well could have been created with `writePNG()` from the png package, or by any other method. If you have the filename of the image, you can send it with `renderImage()`.
+
+#### Structure of the returned list
+
+The list returned in the example above contains the following:
+
+* `src`: The output file path.
+* `contentType`: The MIME type of the file. If this is missing, Shiny will try to autodetect the MIME type, from the file extension.
+* `width` and `height`: The desired output size, in pixels.
+* `alt`: Alternate text for the image.
+
+Except for `src` and `contentType`, all values are passed through directly to the `` DOM element on the web page. The effect is similar to having an image tag with the following:
+
+{% highlight xml %}
+
+{% endhighlight %}
+
+Note that the `src="..."` is shorthand for a longer URL. For browsers that support the [data URI scheme](http://en.wikipedia.org/wiki/Data_URI_scheme), the `src` and `contentType` from the returned list are put together to create a special URL that embeds the data, so the result would be similar to something like this:
+
+{% highlight xml %}
+
+{% endhighlight %}
+
+For browsers that don't support the data URI scheme, Shiny sends a URL that points to the file.
+
+
+### Sending pre-rendered images with renderImage()
+
+If your Shiny app has pre-rendered images saved in a subdirectory, you can send them using `renderImage()`. Suppose the images are in the subdirectory `images/`, and are named `image1.jpeg`, `image2.jpeg`, and so on. The following code would send the appropriate image, depending on the value of `input$n`:
+
+#### server.R
+
+{% highlight r %}
+shinyServer(function(input, output, clientData) {
+ # Send a pre-rendered image, and don't delete the image after sending it
+ output$preImage <- renderImage({
+ # When input$n is 3, filename is ./images/image3.jpeg
+ filename <- normalizePath(file.path('./images',
+ paste('image', input$n, '.jpeg', sep='')))
+
+ # Return a list containing the filename and alt text
+ list(src = filename,
+ alt = paste("Image number", input$n))
+
+ }, deleteFile = FALSE)
+})
+{% endhighlight %}
+
+In this example, `deleteFile` is `FALSE` because the images aren't ephemeral; we don't want Shiny to delete an image after sending it.
+
+Note that this might be less efficient than putting images in `www/images` and emitting HTML that points to the images, because in the latter case the image will be cached by the browser.
+
+
+### Using clientData values
+
+In the first example above, the plot size was fixed at 400 by 300 pixels. For dynamic resizing, it's possible to use values from `clientData` to detect the output size.
+
+In the example below, the output object is `output$myImage`, and the width and height on the client browser are sent via `clientData$output_myImage_width` and `clientData$output_myImage_height`. This example also uses `clientData$pixelratio` to multiply the resolution of the image, so that it appears sharp on high-resolution (Retina) displays:
+
+#### server.R
+
+{% highlight r %}
+shinyServer(function(input, output, clientData) {
+
+ # A dynamically-sized plot
+ output$myImage <- renderImage({
+ # Read myImage's width and height. These are reactive values, so this
+ # expression will re-run whenever they change.
+ width <- clientData$output_myImage_width
+ height <- clientData$output_myImage_height
+
+ # For high-res displays, this will be greater than 1
+ pixelratio <- clientData$pixelratio
+
+ # A temp file to save the output.
+ outfile <- tempfile(fileext='.png')
+
+ # Generate the image file
+ png(outfile, width=width*pixelratio, height=height*pixelratio,
+ res=72*pixelratio)
+ hist(rnorm(input$obs))
+ dev.off()
+
+ # Return a list containing the filename
+ list(src = outfile,
+ width = width,
+ height = height,
+ alt = "This is alternate text")
+ }, deleteFile = TRUE)
+
+ # This code reimplements many of the features of `renderPlot()`.
+ # The effect of this code is very similar to:
+ # renderPlot({
+ # hist(rnorm(input$obs))
+ # })
+})
+{% endhighlight %}
+
+The `width` and `height` values passed to `png()` specify the pixel dimensions of the saved image. These can differ from the `width` and `height` values in the returned list: those values are the pixel dimensions to used display the image. For high-res displays (where `pixelratio` is 2), a "virtual" pixel in the browser might correspond to 2 x 2 physical pixels, and a double-resolution image will make use of each of the physical pixels.
diff --git a/tutorial/index.html b/tutorial/index.html
index e4f25b0e1..59695cdf8 100644
--- a/tutorial/index.html
+++ b/tutorial/index.html
@@ -184,6 +184,9 @@ hljs.initHighlightingOnLoad();