Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Using ColdFusion's CFLocation Tag For Inline Image SRC Attributes

By Ben Nadel on
Tags: ColdFusion

Not so long ago, I discovered that you could use ColdFusion's CFLocation tag in conjunction with inline image tag SRC attributes. I actually found this out by accident. Before hand, I would have never assumed that this would work. The idea here is that the SRC attribute points to a proxy page which then CFLocates the request on to the actual image file. How does this work? No idea... as far as I am concerned, it's just some crazy voodoo browser request magic.

To help explain what I am talking about, take a look at this HTML page:

  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "">
  • <html>
  • <head>
  • <title>ColdFusion CFLocation And Image Soures</title>
  • <style type="text/css">
  • body {
  • font: 12px georgia ;
  • }
  • </style>
  • </head>
  • <body>
  • <p>
  • Hey dude, you gotta check out the photo of
  • this chick that I found:
  • </p>
  • <p>
  • <!--- NOTE: Image proxy. --->
  • <img
  • src="image_proxy.cfm?id=1"
  • width="316"
  • height="217"
  • border="0"
  • />
  • </p>
  • <p>
  • She's a bit thick, but I think it goes to
  • show that woman of all shapes and sizes can
  • be smokin' hot!
  • </p>
  • </body>
  • </html>

Notice that the SRC attribute of the IMG tag points to image_proxy.cfm ColdFusion template, not to an actual, physical image file. Now, if the image_proxy.cfm template used CFContent to return an image binary, that would make sense to me; but, as you will see, the image_proxy.cfm template simply forwards the request to the image file:

  • <!--- Kill extra output. --->
  • <cfsilent>
  • <!--- Param the image ID that will be coming in. --->
  • <cfparam
  • name=""
  • type="string"
  • default=""
  • />
  • <!---
  • Check the ID to see if this is a valid photo ID
  • request. If it is, we are going to set the target
  • URL so that we only need ONE CFLocation tag.
  • --->
  • <cfswitch expression="">
  • <cfcase value="1">
  • <cfset strURL = "thick_chick_still_a_hottie.jpg" />
  • </cfcase>
  • <cfdefaultcase>
  • <cfset strURL = "image_not_found.gif" />
  • </cfdefaultcase>
  • </cfswitch>
  • <!---
  • ASSERT: At this point, even if we were given an
  • invalid image ID, we are going to have a target URL
  • thanks to the CFDefaultCase tag.
  • --->
  • <!--- Forward image request to actual image file. --->
  • <cflocation
  • url="#strURL#"
  • addtoken="false"
  • />
  • </cfsilent>

Again, notice that ColdFusion is not really dealing with the images directly; it's just forwarding the browser's request on to the image. Now, the ColdFusion CFLocation tag actually sends a header value back to the browser which means that the IMG tag loads a page, which returns a header, which then forwards the request to the image.

Not sure how that works, but it does:


CFLocation For Use With Inline IMG tag SRC Attributes  

Pretty cool, eh? This could be really cool if you don't have a fancy image database or anything, but you don't want the image location fixed. You could set up a proxy that then knows where the image directory is and can forward appropriately.

Reader Comments

Not much mystery here - HTTP is a very simple protocol - a GET is a GET, whether it's triggered by typing a URL, or a IMG SRC attribute - the browser fetches it in exactly the same way, and as long as the final result to the browser is something it expects, it will work fine.

This is a really handy tip though - even though it makes perfect sense that it works, it hadn't ever occurred to me to try this...


I understand that the HTTP request protocol is very simple. I guess what makes it hard for me (mental visualization) is that when you request a page, no problem, it the primary request... but then on the page it has to load various other things like SCRIPT tags and IMG tags... those are like sub-requests that can then interact with the returned headers.

I guess I can't think of it as a single page request - it's really multiple data requests that the browser then merges (ie. putting in image content into a different request data set). Just seems funky to think about. I get it, just seems funky.

Glad you find it a good tip though.

One particular situation I use this mechanism for quite frequently is generating and caching dynamic content. To follow the image theme, if you want to generate thumbnails for your photo gallery, you point your thumbnails at tn.cfm?id=1234, which then gets the full-size image, shrinks it, writes it to cache storage, and redirects to the cache file. Subsequent requests can check for existence of the cache file, and if it's already there, just redirect directly.

I actually use mod_rewrite to do that (which saves the extra HTTP roundtrip), but the concept is the same. Managing the cache is a snap too, just delete the cache file and it'll be regenerated on next request. If you name you cache files intelligently, you can create very granular caching mechanisms for dynamic content, but (if you're using mod_rewrite) serve it in a 100% static way, without involving the app server in any way.


Good suggestion. I like the idea of the thumbnail generation (but more so, the cache idea in general). Thanks. Caching is something that I don't do enough of (my poor server!).


Oh, I understand - it's not necessarily intuitive, but when you look as http log files it's pretty clear that, under the hood, all the requests are really the same, and that it's the browser that molds it all together into the proper context.

The only reason I am underscoring the point is this is something that a lot of people seem to not quite "get", and I've just seen a couple of examples of it in the last few days.

Ben, there's another use: track visualizations in e-mails.

You can use a image inside the e-mail, via <img src=""> pointing to a ColdFusion page, passing URL parameters to identify which user openned and correctly visualized the e-mail.

BTW, your blog became obligatory nowadays!

Best wishes,

Fernando S. Trevisan

Fernando, et. all

I recently wrote an email tracker as part of a CRM system using this trick not too long ago. Unfortunately, Outlook doesn't automatically download the image so tracking only occurs when the image is downloaded. Do you know of anyway to force the image to download, so tracking work regardless..??

Many (most?) email clients don't automatically download images for exactly this reason. It's a rather devious mechanism from the perspective of non-techie users, and as such, should be frowned upon. Therefore, clients disable it by default.

If you need to include images in emails, much better to inline the images. Makes the email bigger, and removes the ability to [sneakily] track reads, but it's a trusted method for getting image content in front of users.

Ben, what's the advantage of using the cflocation over the cfcontent? The code is slightly neater, but apart from that I don't see any difference in how the image is served.


As far as I know, there's no way to track which user received the e-mail, even when using "request read receipt", user has the right to answer yes or no. As I said in my comment, this is a way to track which user correctly visualized your e-mail; if you send it as a full image, the only way user can see the contents is downloading the image and then... :)


I agree with you, but there's a lot of good reasons to use this type of verification. In a intranet, you can send reports to users and then scheduled warnings for those who doesn't correctly visualized it. And it's just a dummy example.


Firstly, thanks for the kind words. I am glad you appreciate the stuff that I do. Secondly, I think what you are referring to actually has a name. I think it's called an "email bug", but that may have been something someone just told me in passing.

I heard somewhere that while email clients do not automatically download images, more often, they will download external style sheet links. I assume you could do the same thing with the URL and tracking.


The advantage of using CFLocation over CFContent is one of speed. If you use CFContent, then ColdFusion has to set aside a thread that takes care of reading in the binary and streaming it to the client. While it can do this, it is SLOOOOOW. If you use CFLocation instead, then IIS (in my case) takes care of streaming the file to the browser and it is much faster.

The downside is that CFLocation must use web-accessible URLs where as CFContent can use private directory access. But there are work arounds for that.


I experimented with using a full image in an email, this wasn't to track anything, the clients design was just so crazy i didn't want to slice it. One problem i found though while testing was that it often went into my spam folder, especially in gmail, Outlook and Hotmail. Do you know how to get around that?


I've been pondering the same question too. Why does some email go into Junk folders while other does not.

I'd also be interested in anyone's thoughts on this. Is it a ratio of text to images? An unsubscribe line?, how the images are handled within an email? Simpicity of the email, or complexity? I've lightly experimented with this a little today. But if anyone has some knowldege on this, it'd be desirable to know from a best practice sort of thing.


There are a pile of rules that are used. Best bet is to get access to an email account that has visibility into it's spam filtering. SpamAssassin is one that I've used, and it'll send you a nice report about each message that it tagged as spam, with a breakdown of what factors influenced it's decision. High HTML-to-text ratio is one factor that I recall being a significant detriment to a given message's spam score.

Does anyone have an actual example of how to implement an email bug.

I have tried this but it doesn't seem to work in terms of updated the DB to let me know that the user has viewed the email I have sent. Using outlook 2007.

I include the img in the email being sent:
<img src="#Application.http_host#track.cfm?track_id="123">

In the track.cfm I have the following code

<cfset transGif = "#GetDirectoryFromPath(ExpandPath('*.*'))#images\blank.gif">
<cfparam name="url.track_id" default="0">

<CFQUERY name="qUpdate" datasource="#Application.dsn#" username="#Application.usr#" password="#Application.psw#">
UPDATE mytable
SET has_viewed = 1
WHERE uuid = <CFQUERYPARAM cfsqltype="CF_SQL_VARCHAR" value="#url.track_id#">

<cfcontent type="image/gif" file="#transGif#">

When I open the email I just sent to myself in outlook it doesn't update the db column has_viewed.

Any help would be greatly appreciated..


Oliver, if the code you posted is exactly the one you`re using, maybe the problem is with the line:

<img src="#Application.http_host#track.cfm?track_id="123">

You may want to use it this way:

<img src="#Application.http_host#track.cfm?track_id=123">

Please note that I removed the second " before the number, that must "close" the src info and what the CF page would get was:


As you have a param setting the track_id to 0 the script would not throw an error and also would not update the field as it would be always 0.

Just a thought.


What a rookie mistake I have made.. Thanks soo much.

Removing the param setting did the trick.
I actually pasted the line incorrectly. I had the formatting correct...

Thanks again.


Also keep in mind that this will only work IF the email client allows the image to be loaded (from what I have read). A lot of clients prevent this from happening by default (only with approval from user).

That said, I have used 3rd party email campaign software (Campaign Monitor) that uses this technique and it appears that a LOT of people do actually load images in their email, so I guess this really does work.


Great article.

I am building a mass email application and have been working on this notification issue for awhile. Outlook and many other applications to not load images so it has posed quite an issue for me trying to determine who opened the email.

Do you think it would work if I embeded a bogus CSS tag in the email? I create a dynamic css file from settings pulled from a database using something like this embedded in my pages.

<link href="../styles/styles.cfm?id=23" rel="stylesheet" type="text/css" />

Do you think outlook would download and execute the css line even if it blocks images?

SO I got so dang excited about my own question I just had to blow off my important work today and test it out. I did not want Ben to have all of the fun with this one.

Ok, this is pretty freaking cool. I tested out calling a .cfm file in the stylesheet line from the email I sent out and it does execute the code even without downloading the pictures in outlook.

Just when you think this cannot get any cooler it inserts when I view it in the preview window or when I double click and open the full screen.

If you already viewed the email in Outlook you do not get another insert after that even if you get out of outlook and get back in.

For some reason good or bad Outlook does seem to cache that email so when I click on it in to view it in the preview window then click another email and go back to the original email it does not insert a new row.

Here is what I have tested so far sending test emails from my new email application.

The 'yes; means I got a row inserted into the database the 'no' means no matter what I did I got zilch....

1. Sent directly to outlook - yes

2. Forward from outlook to gmail - no

3. Send directly to gmail - yes

4. Forward from Gmail to Outlook - no - and it does not matter if you download the pitures or not before you send it.

In each case it only inserted a row the first time the email was accesed. Kind of wierd, maybe it was because IE cached it in Gmail, I have no idea. To make it even more wierd if I get out of IE and login to Gmail through Mozilla it does not insert a new row when I view that email again.

I suspect most email clients will behave the same way and at least execute it once.

Who woulda thought... At this early stage of testing it seems to accomplish what I needed which was to determine who the heck opened the email. I could care less at this point if they view it 1 or 100 times I just need to know who opened it that one time.

Thoughts anyone?


Whoo Hooo this also inserts a row into the DB when I forward this test email to my Motorola Droid phone and open it. So this proves that you can cheat the system a bit.

So I think I am on to something here.....



Really awesome findings! I'll need to try it for myself (just to see it in action). Thanks a lot for posting this.

So after further trials it looks like outlook web access may strip the stylesheet tag out but the Outlook 2007 application does not.

Wierd and a little deflating. Maybe there is another way to fool it.

I really hate testing.



Test is bad... testing across different CLIENTS (web, OS, brands) is even worse; especially when the various clients need logins. I don't want to create a login to HotMail *just* to test this feature :(

Actually it worked on the following web clients

1. Outlook 2003 - 2007
2. Gmail - should also work on hotmail
3. Yahoo
4. Smarter Mail Web Client
5. Android Mail Client

The only browser based email client I have come across that has stripped it out so far is OWA.

I sent test emails to someone I know with msn ,aol and thunderbird, Blackberry and IPhone to see if it works.

That has to cover about 80% or more of the email clients out there right?


Ok so I created a test yahoo and hotmail account and cleared my test email database.

Test emails to both did not generate an open response with the stylesheet tag but they did generate one with the image tag once images were allowed. I was bummed but there is hope. I am not sure why GMail allows it. Maybe I will start another round of tests to be sure

I am 100% sure it allows the execution of the stylesheet tag in the outlook application. It has worked 100% of the time during the last 25 tests with 3 different users.

I will explore other ways to generate responses from the yahoo and hotmail web clients. It is a safe bet the people my clients will be emailing will be using one or the other.

There has to be a more consistant way to get open responses. If an image works for web clients and the stylesheet works for windows clients then I may send both.


The plot thickens.... I need a beer..... I may have to rely on the images more than the stylesheet after this deflating day.

It seems that when I embedded a non dynamic stylesheet line in the email and not in the middle of the template it worked more often than throwing the dynamic tag with the uuid in the top of th email.

Maybe I will start over and see if I can reproduce the results from my first tests with a hard coded tag.


Take a break, it sounds like you've earned it :) Maybe images IS the way that services traditionally do it? We use Campaign Monitor to organize newsletter campaign for some of our clients and the definitely provide the "X people have opened this email" statistic. Perhaps I'll take a peek at the source of one of the emails that gets sent out and see what they do.

It has to be a URL of *some* sort! It just comes down to what URL is most likely to be used.

I'm using the same approach for images, caching them into a directory using the pseudocode

if fileexist
stream the image out
generate and stream

But I discovered the webserver never provides a statuscode "304 NOT MODIFIED" even when working with cfheader along last-modified and etag.

This method seems to be bandwitdh intensive.

Any suggestion?


So are you saying you're passing the 304 status code back with the image request explicitly? I am not too familiar with etag stuff, so sorry if I just asked a silly question.

To say the truth I'm not using cflocation cause Im trying to hide the physical location of my images.

As like in your example my page contains

<img src="image_proxy.cfm?id=1"/>

let me better explain my case:

-the very first time the image not exists, so I create, save in a cache directory, and display it.

-The user visits the page for the first time: the web browser should display and store it into its cache. ( webserver response : "100 OK" )

-The user visits again the same page: the browser should use the cached version of the file since it is not modified (avoiding a new download).
In this case the webserver should response as "304 Not Modified".

-ETag header is essentially the CRC of the page content.
When a new HTTP response contains the same ETag as an older HTTP response, the client can conclude that the content is the same without further downloading.

You can easily set ETag using
<cfheader name="ETag" value='"#hash(GetPageContext().getCFOutput().getString())#"' />

Since I'm developing a gallery-like site, I should find a way to optimize cpu/bandwidth utilization.

With the method described, the webserver always replies with "100 OK" status message, never using a (browser)cached version


So, when a browser is making a HTTP request, does it read the headers and then potentially shut down the HTTP request if the ETag is a certain value? Or, is it actually making two requests: one for the headers and one for the content?

From the FireBug network activity, it would appear that it is only making a single request. I'll have to play around with this stuff some more.

Is this was causes the "Not Modified" status in FireBug's network activity?


Yes, the client reads the headers and if it has a cached version of the file (same ETAG) then shuts down the request and render the cached version.
( I think it is done in the same request/connection )

a sample of Apache log with a gif image - - [08/Feb/2010:18:54:08 +0100] "GET /CFIDE/debug/images/topdoc.gif HTTP/1.1" 304 -

the same with a image returned from a cfm page - - [19/Mar/2010:17:38:41 +0100] "GET /tests/getimage_cfimage.cfm HTTP/1.1" 200 384812

As you can see, the first one have no size, the second one downlaod 384k of image everytime.


I sent you an "Ask Ben" email just yesterday that looks like it my be sort of related to this.

I'm trying to use ColdFusion to send an Expires Header along with the image.

I'm on a shared host and don't have access to IIS, so that's not an option for setting the headers.

I was thinking a proxy type situation like you outline above would be the way to go, and in fact yes it works fine to show the image, but using the cfheader on my img.cfm file:

<cfheader name="Expires" value="#GetHttpTimeString(DateAdd('m', 1, Now()))#">

still doesn't pass the expires header (according to YSlow and PageSpeed).

Any ideas/suggestions?



I am not too knowledgeable about the expires stuff. I have seen in practicality that using the expires header will prevent the browser from pulling it down. But I don't know how well that works on different browsers. Then there's ETags as well, which I don't know too much about.


To be honest, I don't know all the ins and outs of it either. Basically, I was just trying to follow PageSpeed and YSlow's advice for site speed optimization. I found support articles of what they were saying as well.

In any event, basically, I'm just looking to find a way that I can use CF to attach headers to elements other than pages, javascript and css (images, flash, etc.). I was able to create a headers for the cmf, js, and css files, but can't figure out how to do it for the others since I'm on a shared sever and don't have the IIS access that keeps coming up in my searches for the fix.




In order to use ColdFusion to add headers, you have to use ColdFusion to serve up the files. If you wanted to add headers without routing things through CF, I am not sure how to do that (or if it is possible) without access to the web server itself (IIS in your case).

@Ben Nadel,

My post below goes beyond what I was talking about with just the images, but the images was part of a whole process I was working through. I thought in case someone else was looking for something similar this might help (since I did a lot of collecting via google to get everything together). Hopefully it isn't too off topic. I'm definitely not pretending to have come up with all of this on my own, and am very thankful to the people that put work into the parts I have grabbed.

Here's what I came up with (after my host gave me the correct absolute path to my site, which was causing <cfconent> issues in my code . . .)

For each <img> tag, I use the following syntax:

<img src="" width="" height="" etc="">

Then on the img.cfm page I have the following:

<cfdirectory action="list" directory="#ExpandPath(".")#\" name="qGetLastdateModified"

filter="#ListLast(CGI.SCRIPT_NAME, "/")#">


<cfheader name="Last-Modified" value="#DateFormat(qGetLastdateModified.dateLastModified, "mm/dd/yyyy")#">


<cfset imgType = #ListLast(, ".")#>

<cfcontent type="image/#imgType#" file="c:\websites\">

*Note the "c:\websites\mysite - this is representational obviously and if anyone else was trying to use this method they would need to get the absolute path from their host.

I should also note that in my application.cfm I'm using the following:

<cfheader name="Expires" value="#GetHttpTimeString(DateAdd('m', 1, Now()))#">

This is successful in doing the following:

1) Adds the "Expires" header (set to one month) to the image, since it is a .cfm file that is getting passed to the browser - this header is coming from the application.cfm

2) Sets the last modified date of the image with the cfdirectory bit on the img.cfm page.

3) Sets the cfcontent image type to the correct extension with the <cfset imgType = #ListLast(, ".")#> bit.

So this all lets me serve up an image with headers designed to (hopefully) increase page speed, without having access to IIS because of a shared environment.

I was also able to do the same type of proxy page for a flash banner on one of the pages.

For JS and CSS, I'm using Combine.cfm and Combine.cfc which help to serve of the JS and CSS with the headers as well.

Additionally, an issue I was having was being able to use Gzip, again because of the shared hosting. I was able to find the following to use for that.

I created an OnRequestEnd.cfm page with this code:

<cfif cgi.HTTP_ACCEPT_ENCODING contains "gzip"


AND NOT getPageContext().getResponse().isCommitted()>


pageOut = getPageContext().getCFOutput().getString();

fileOut = createobject("java", "").init();

out = createobject("java","").init(fileOut);

out.write(variables.pageOut.getBytes(), 0, len(variables.pageOut.getBytes()));




This Gzips my .cfm pages (with exception to IE6 which according to the resource I got this code from has issues with Gzip).

All in all, I upped my Google Page Speed (now at 97 - I finally have the little green check mark - my life is complete!), and my YSlow (now at 99!).

At any rate Ben, you have a GREAT site that inspired me along the way for sure. Thanks.



You have some very interesting stuff going on here, especially the GZip stuff - I've never actively used any compression stuff. I'll have to take a look at your onRequestEnd() event handler a bit more closely.

The GZip stuff, that's not for images, right? That's for the straight up CFM-type requests? I say that only because I was under the impression that using CFContent[file] actually haults the entire request afterwards (not letting you rewrite the type of output).

Very cool stuff. You've given me some stuff to think about.


Yes, the Gzip only compresses the the CFM-type requests as you suggest. A good point I saw about why you should even worry about trying to compress images, went like this, "have you ever tried to zip a jpg?" - it doesn't do anything in file reduction because it is already a compressed format.

An interesting thing though that I did figure out is that if I wanted to compress the CSS or JS, all that needed to be done was to save those files with a .cfm extension, and a cfcontent tag at the top giving the correct file type, and they would still be completely functional in the browser and would be compressed as well.

Of course, the combine.cfm and Combine.cfc that I ref'd earlier will use the compression too if there are multiple css or js files that you are linking to with it. But in the case where there was only one CSS or JS, then using the method above with changing the file extension worked great.


After trying various ways to get around outlook, gmail, hotmail and the other programs out there I arrived back at a modified version of this inline image sample to track opens.

I have noticed that gmail, hotmail, yahoo etc and outlook 2010 are making it much harder to track email opens by limiting what html is processed and stripping the rest.

I am currently working on trying to figure out how some of the larger vendors like mail chimp and icontact obtain such high delivery rates and tracking. We are getting about an 80 percent delivery rate on averate so not bad but not great either.

It may boil down to searching the web logs for a file or image entry that is specific to the system.

I did finish building my email marketing application, I am considering selling a license with the source after I finish adding a few more features like bounce tracking and link tracking.

Let me know if you have time to give some feedback and I will send you a link.

Thanks for for the work you put into this blog it is a huge help and gives a lot of great ideas.


i am interested to know more about that gziping routine of .cfm pages you talked about in your post above.

I am trying to gzip my.cfm pages as well (i do have access to my server) and finally got it working using this:

which is quite old.

However, I have since learned it chokes on a cflocation tag (I think.. can't figure out what is going on) .. it just seems to quite processing. normal get requests seem to work fine

was wondering if you were still using that gzip routine or if you have come across something better?

I also have never been able to get the "Dyanamic pages" option in IIS to work either.

@ Scott,

I am indeed still using that same routine. I created an OnRequestEnd.cfm file, and this is what is there:

<cfset pageContent = getPageContext().getOut().getString()>
<cfset getPageContext().getOut().clearBuffer()>
<cfset pageContent = reReplace(pageContent, chr(9),"", "all" )>
<cfset pageContent = reReplace(pageContent, chr(10),"", "all" )>
<cfset pageContent = reReplace(pageContent, chr(13),"", "all" )>
<cfset pageContent = reReplace(pageContent, chr(13)&chr(10),"", "all" )>
<cfset writeOutput(pageContent)>

The first chunk is a whitespace remover, which decreases the file size a slight bit - but you will want to make sure you have clean javascript when using it. I had a couple instances where I had to go back and make sure all my semi's were there. That's true to for using the "combine" routines as well.

<cfif cgi.HTTP_ACCEPT_ENCODING contains "gzip"
AND NOT getPageContext().getResponse().isCommitted()>
pageOut = getPageContext().getCFOutput().getString();
fileOut = createobject("java", "").init();
out = createobject("java","").init(fileOut);
out.write(variables.pageOut.getBytes(), 0, len(variables.pageOut.getBytes()));
<cfheader name="Content-Encoding" value="gzip">
<cfcontent reset="true" variable="#fileOut.toByteArray()#">
<cfset getPageContext().getOut().flush()>

The other part is the gzip part and from what I can tell it all works like it is supposed to.

Hope that helps . . .


I am not sure I understand what you mean here:

It may boil down to searching the web logs for a file or image entry that is specific to the system.

Can you explain what you mean as far as tracking goes?


Cool stuff!