Creating a multi file image uploader with ColdFusion and Uploadify

For some time I have wanted to update the image upload system I have on my photography website. I made it years ago and it only allowed me to upload a single image at a time. For each image the system would upload the file, make a thumbnail, allow me to classify the image and then write the data to the database. Each time I would add a new gallery I would go through this process for each image as well as incrementing a shot number in the description field for each image. Obviously, there was room for improvement as well a the possibility of saving time each time I wanted to upload a new gallery.

I looked into the new multiple file capabilities in ACF 9, but this site runs on Railo and at the time cffile on railo did not support multiple file uploads. So, I did a little searching and found the Uploadify Jquery plugin which looked like it would be a good fit for my needs.

I had the following requirements for my new upload system:

  • Needed to be able to select multiple images to upload in one action.
  • Needed to be able to specify certain fields that would apply to each image.
  • Wanted the system to increment a number within a string for all of the images in a specific upload batch.
  • Required a preview of all of the images in the set as well as the data fields to associate with that image so that manual changes could be made if necessary.
  • Write all images and data to the database as part of the process.
  • I started with a simple form to supply the data which would go to all of the images.

    view plain print about
    <cfform action="process.cfm" name="uploadform" id="uploadform" method="post" enctype="multipart/form-data">
    <label for="event_id">Default Event</label><a href="javascript:NewWindow('addNewItem.cfm?formname=uploadform&fieldname=event_id','Add_Item','550','450', 'no')" class="whitecapslink">Add Event</a>
    < cfselect name="event_id" query="getevents" required="yes" message="Please choose a event" display="event" value="ID" selected="#event_id#" width="50">
    <label for="category_id">Default Category</label><a href="javascript:NewWindow('addNewItem.cfm?formname=uploadform&fieldname=category_id','Add_Item','550','450', 'no')" class="whitecapslink">Add Category:</a>
    <cfselect name="category_id" query="getcategories" required="yes" message="Please choose a category" display="category" selected="#category_id#" value="ID" width="50">
    <label for="lens_id">Default Lens</label><a href="javascript:NewWindow('addNewItem.cfm?formname=writeimagedata&fieldname=lens_id','Add_Item','550','450', 'no')" class="whitecapslink">Add Lens</a>
    <cfselect name="lens_id" query="getlenses" width="50" required="yes" display="lens" value="id" selected="#lens_id#">
    <label for="private">Private</label>
    <cfinput type="radio" name="private" value="0">YES <cfinput type="radio" name="private" value="1" checked="yes">NO
    <label for="title">Default Title</label>
    <input name="title" type="text" maxlength="145" />
    <label for="note">Default Note</label>
    <textarea name="notes" height="200px" width="200px"></textarea>
    <label for="imagefiles">Images</label>
    <div id="file_upload"></div>
    <input onclick="$('#file_upload').uploadifyUpload()" type="button" value="Submit" />
    <invalidTag language="javascript" type="text/javascript">
    $(function() {
    uploader: '/scripts/jquery_uploadify/uploadify.swf',
    folder: 'images',
    cancelImg: '/scripts/jquery_uploadify/cancel.png',
    script: 'uploadify.cfc?method=uploadimage',
    onAllComplete: function(event,data) {document.getElementById('uploadform').submit();},
    onSelect: function(event,ID,fileObj) { var form = document.forms['uploadform'];
    var i = 0;
    var el = document.createElement("input");
    el.type = "hidden"; = "images";
    el.value =;
    multi: true,
    auto: false,
    fileDesc: 'Image files',
    fileExt: '*.jpg;*.jpeg;'

    The onAllComplete event is used to submit the form containing the other data. A hidden input field is also created that contains a list of the file names that were included in the upload.

    view plain print about
    <cffunction name="uploadimage" access="remote" returntype="any">
    <cffile action="upload" filefield="form.fileData" destination="#APPLICATION.ImagePath#" nameconflict="makeunique" />
    <!--- Start - Use image.cfc to create a thumbnail --->
    <!--- Invoke image.cfc component --->
    <cfset imageCFC = createObject("component","image") />
    <!--- Get image information --->
    <cfset imgInfo = imageCFC.getImageInfo("""#cffile.serverDirectory#\#cffile.serverFile#")>
    <!--- Compare height to width --->
    <cfif imgInfo.height gt imgInfo.width>
    <!--- scaleX image to 150px wide --->
    <cfset scaleX150 = imageCFC.scaleheight("""#cffile.serverDirectory#\#cffile.serverfile#""#cffile.serverDirectory#\thumbs\#cffile.serverfile#", 150, 95)>
    <!--- scaleY image to 150px tall --->
    <cfset scaleY150 = imageCFC.scalewidth("""#cffile.serverDirectory#\#cffile.serverfile#""#cffile.serverDirectory#\thumbs\#cffile.serverfile#", 150, 95)>
    <!--- End - Use image.cfc to create a thumbnail --->

    The uploadify.cfc is simply a cffile tag with the upload attribute. If your unsure of the file contents and type make sure to first upload the file outside of your webroot. I'm using imagecfc to create a thumbnail for each image during this step as well.

    view plain print about
    <cfif IsDefined('URL.complete')>
    <cfset TodaysDate = '#createodbcdatetime(now())#'>
    <cfset variables.imagecount=0 />
    <cfloop list="#FORM.images#" index="imagename" delimiters=",">
    <cfset variables.imagecount++ />
    <cfset variables.title = evaluate("FORM.title_" & imagecount) />
    <cfset variables.event_id = evaluate("FORM.event_id_" & imagecount) />
    <cfset variables.category_id = evaluate("FORM.category_id_" & imagecount) />
    <cfset variables.lens_id = evaluate("FORM.lens_id_" & imagecount) />
    <cfset variables.notes = evaluate("FORM.notes_" & imagecount) />
    <cfset variables.filename = evaluate("FORM.filename_" & imagecount) />
    <cfset variables.private = evaluate("FORM.private_" & imagecount) />
    <cfset variables.Camera_Make_Model = evaluate("FORM.Camera_Make_Model_" & imagecount) />
    <cfset variables.Focal_Length = evaluate("FORM.Focal_Length_" & imagecount) />
    <cfset variables.Scene_Mode = evaluate("FORM.Scene_Mode_" & imagecount) />
    <cfset variables.Shutter_Speed_Value = evaluate("FORM.Shutter_Speed_Value_" & imagecount) />
    <cfset variables.Exposure_Time = evaluate("FORM.Exposure_Time_" & imagecount) />
    <cfset variables.F_Stop = evaluate("FORM.F_Stop_" & imagecount) />
    <cfset variables.Exposure_Adjust_Bias = evaluate("FORM.Exposure_Adjust_Bias_" & imagecount) />
    <cfset variables.ISO_Speed = evaluate("FORM.ISO_Speed_" & imagecount) />
    <cfset variables.Flash = evaluate("FORM.Flash_" & imagecount) />
    <cfset variables.Metering_Mode = evaluate("FORM.Metering_Mode_" & imagecount) />
    <cfset variables.Exposure_Program = evaluate("FORM.Exposure_Program_" & imagecount) />
    <cfset variables.White_Balance = evaluate("FORM.White_Balance_" & imagecount) />
    <cfset variables.Resolution = evaluate("FORM.Resolution_" & imagecount) />
    <cfset variables.Height = evaluate("FORM.Height_" & imagecount) />
    <cfset variables.Width = evaluate("FORM.Width_" & imagecount) />
    <cfset variables.Software = evaluate("FORM.Software_" & imagecount) />
    <cfset variables.ColorSpace = evaluate("FORM.ColorSpace_" & imagecount) />
    <cfset variables.Date_Time_Taken = evaluate("FORM.Date_Time_Taken_" & imagecount) />
    <cfset variables.Last_Modified = evaluate("FORM.Last_Modified_" & imagecount) />
    <cfset variables.exiflens = evaluate("FORM.exiflens_" & imagecount) />
    <cfset variables.subjectdistance = evaluate("FORM.subjectdistance_" & imagecount) />
    <cfquery datasource="#APPLICATION.datasource2#" name="writephoto" result="writephotoresult">
    INSERT INTO photos (title, lens_id, event_id, category_id, notes, filename, private, lastvieweddate, directory)
    VALUES (<cfqueryparam cfsqltype="cf_sql_varchar" maxlength="145" value="#variables.title#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="50" value="#variables.lens_id#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="50" value="#variables.event_id#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="50" value="#variables.category_id#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="255" value="#variables.notes#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="75" value="#variables.filename#">,
    <cfqueryparam cfsqltype="cf_sql_integer" maxlength="1" value="#variables.private#">,
    <cfqueryparam cfsqltype="cf_sql_timestamp" value="#TodaysDate#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" value="#APPLICATION.ImagePath#">)
    <cfquery datasource="#APPLICATION.datasource2#" name="writemetadata">
    INSERT INTO metadata (Photo_id, Camera_Make_Model, Focal_Length, Scene_Mode, Shutter_Speed_Value, Exposure_Time, F_Stop, Exposure_Adjust_Bias, ISO_Speed, Flash, Metering_Mode, Exposure_Program, White_Balance, Resolution, Height, Width, Software, ColorSpace, Date_Time_Taken, Last_Modified, exiflens, subjectdistance)
    VALUES (<cfqueryparam cfsqltype="cf_sql_bigint" value="#writephotoresult.GENERATED_KEY#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="45" value="#variables.Camera_Make_Model#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="45" value="#variables.Focal_Length#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="45" value="#variables.Scene_Mode#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="45" value="#variables.Shutter_Speed_Value#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="45" value="#variables.Exposure_Time#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="45" value="#variables.F_Stop#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="45" value="#variables.Exposure_Adjust_Bias#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="45" value="#variables.ISO_Speed#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="45" value="#variables.Flash#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="45" value="#variables.Metering_Mode#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="45" value="#variables.Exposure_Program#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="45" value="#variables.White_Balance#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="45" value="#variables.Resolution#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="45" value="#variables.Height#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="45" value="#variables.Width#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="45" value="#variables.Software#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="45" value="#variables.ColorSpace#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="45" value="#variables.Date_Time_Taken#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="45" value="#variables.Last_Modified#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="45" value="#variables.exiflens#">,
    <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="45" value="#variables.subjectdistance#">)
    <a href="index.cfm">Upload another gallery</a><br />
    <a href="../index.cfm">Back to Admin</a>
    <cfform name="galleryupload" id="galleryupload" action="process.cfm?complete=true">
    <cfset variables.imagecount=0 />
    <cfloop list="#FORM.images#" index="imagename" delimiters=",">
    <cfset imageInfoEXIF = createobject("component""uploadify").readimage(#APPLICATION.ImagePath#,#imagename#) />
    <cfset variables.previousshotnumber = "Shot #variables.imagecount#" />
    <cfset variables.imagecount++ />
    <cfset variables.shotnumber = "Shot #variables.imagecount#" />
    <cfset FORM.notes = #Replace(FORM.notes, variables.previousshotnumber, variables.shotnumber)# />
    <div id="image#variables.imagecount#">
    <img src="#APPLICATION.ImagePath#thumbs/#imagename#">
    <cfselect name="event_id_#variables.imagecount#" query="getevents" required="yes" message="Please choose a event" display="event" value="ID" selected="#FORM.event_id#" width="50">
    <cfselect name="category_id_#variables.imagecount#" query="getcategories" required="yes" message="Please choose a category" display="category" selected="#category_id#" value="ID" width="50">
    <cfselect name="lens_id_#variables.imagecount#" query="getlenses" width="50" required="yes" display="lens" value="id" selected="#lens_id#">
    <cfinput type="text" name="title_#variables.imagecount#" value="#FORM.title#" />
    <cfinput type="text" name="notes_#variables.imagecount#" value="#FORM.notes#" />
    <cfinput type="text" name="filename_#variables.imagecount#" value="#imagename#" />
    <cfinput type="radio" name="private_#variables.imagecount#" value="0">YES
    <cfinput type="radio" name="private_#variables.imagecount#" value="1" checked="yes">NO
    <cfinput type="text" value="#StructFind(imageInfoEXIF.Exif, "model")#" readonly="yes" name="Camera_Make_Model_#variables.imagecount#">
    <cfinput type="text" value="#StructFind(imageInfoEXIF.Exif, "focal length")#" readonly="yes" name="Focal_Length_#variables.imagecount#">
    <cfinput type="text" value="#StructFind(imageInfoEXIF.Exif, "scene capture type")#" readonly="yes" name="Scene_Mode_#variables.imagecount#" size="50">
    <cfinput type="text" value="#StructFind(imageInfoEXIF.Exif, "shutter speed value")#" readonly="yes" name="Shutter_Speed_Value_#variables.imagecount#">
    <cfinput type="text" value="#StructFind(imageInfoEXIF.Exif, "exposure time")#" readonly="yes" name="Exposure_Time_#variables.imagecount#" size="50">
    <cfinput type="text" value="#StructFind(imageInfoEXIF.Exif, "f-number")#" readonly="yes" name="F_Stop_#variables.imagecount#">
    <cfinput type="text" value="#StructFind(imageInfoEXIF.Exif, "exposure bias value")#" readonly="yes" name="Exposure_Adjust_Bias_#variables.imagecount#" size="50">
    <cfinput type="text" value="#StructFind(imageInfoEXIF.Exif, "iso speed ratings")#" readonly="yes" name="ISO_Speed_#variables.imagecount#">
    <cfinput type="text" value="#StructFind(imageInfoEXIF.Exif, "flash")#" readonly="yes" name="Flash_#variables.imagecount#" size="50">
    <cfinput type="text" value="#StructFind(imageInfoEXIF.Exif, "metering mode")#" readonly="yes" name="Metering_Mode_#variables.imagecount#">
    <cfinput type="text" value="#StructFind(imageInfoEXIF.Exif, "exposure program")#" readonly="yes" name="Exposure_Program_#variables.imagecount#" size="50">
    <cfinput type="text" value="#StructFind(imageInfoEXIF.Exif, "white balance")#" readonly="yes" name="White_Balance_#variables.imagecount#">
    <cfinput type="text" value="#StructFind(imageInfoEXIF.Exif, "x resolution")#" readonly="yes" name="Resolution_#variables.imagecount#" size="50">
    <cfinput type="text" value="#StructFind(imageInfoEXIF.Jpeg, "Image Height")#" readonly="yes" name="Height_#variables.imagecount#">
    <cfinput type="text" value="#StructFind(imageInfoEXIF.Jpeg, "Image Width")#" readonly="yes" name="Width_#variables.imagecount#" size="50">
    <cfinput type="text" value="#StructFind(imageInfoEXIF.Exif, "software")#" readonly="yes" name="Software_#variables.imagecount#">
    <cfinput type="text" value="#StructFind(imageInfoEXIF.Exif, "color space")#" readonly="yes" name="ColorSpace_#variables.imagecount#" size="50">
    <cfinput type="text" value="#StructFind(imageInfoEXIF.Exif, "date/time original")#" readonly="yes" name="Date_Time_Taken_#variables.imagecount#">
    <cfinput type="text" value="#StructFind(imageInfoEXIF.Exif, "date/time")#" readonly="yes" name="Last_Modified_#variables.imagecount#" size="50">
    <cfinput type="text" value="#StructFind(imageInfoEXIF.Exif, "Unknown tag (0xa434)")#" size="50" readonly="yes" name="exiflens_#variables.imagecount#">
    <cfinput type="text" value="#StructFind(imageInfoEXIF.Exif, "Subject Distance")#" size="50" readonly="yes" name="subjectdistance_#variables.imagecount#">
    <cfinput type="hidden" name="images" value="#FORM.images#" />
    <cfinput type="submit" name="submit" id="submit" value="submit" />

    At the bottom of this code I loop through the form field that contained the list of the images and create new form fields for each of the images in the upload. I attach the image number to the end of each form input name to make each images data inputs unique to each other. This is my preview page. Much of this code is the exif metadata that I like to store in the database. I'm using imagecfc, but I believe you can get this data using cfimage as well. When the new form is submitted I again pass the images field which contains the list of the images uploaded in this batch.

    After the preview form is submitted I again loop through the list of images and set variables for all of the data. I used the evaluate function to get the image number appended to the end of the input field name. I'm aware the people try to avoid using evaluate, but I could not come up with a better approach. I would love to hear any feedback as to a better way to approach or even if evaluate was a good way to approach this. At this point, I simply write the appropriate data to the database in each loop iteration and then the process is complete. Any feedback or suggestions are always appreciated.



    Cool! Thanks for sharing this!


    I have been looking for something like this. I have tried to find your photographs. Where can I find them?


    This is really great and exciting and may just save the life of one of my apps. I constructed an interface to push scanned documents into our clinical document management system. It works great, but was just nixed yesterday since it performed the actions one doc at a time. I'm going to rework the procedure to enable this multi-document approach given your suggestions and I just might be able to save my app. Thanks a lot for the contribution.

    John Sieber

    @TD - Thnaks! @Jens - Hope this solution will work for your needs. I added the link to my photo site into the first paragraph and you can also find a link over on the right in the "Favorite Sites" links. @Cameron - I hope that this will help you with your document app. Let me know if you run into any issues I might be able to help with.

    James Moberg

    I recently started using an open source script that uses XHR (instead of Flash & swfObject) to upload multiple files, drag-and-drop file select and a progress-bar. It also comes with a sample CFC written by Sidney Maestre.

    John Sieber

    @James - Thanks for the tip! I'm going to check that out.

    James Moberg

    I just realized one "gotcha" regarding Valums Ajax-Upload... he tends to change the project entirely while using the same exact name and changing the API. (Not cool!) I used some of his previous ajax-upload libraries and have now discovered that the documentation for them doesn't exist anymore. The documentation for the current API doesn't seem complete and I was unable to get things working properly and reverted to an older library of his that is no longer available. While the benefit is XHR uploading and no Flash requirement, I might recommend waiting until he figures out what he's going to do and publishes a more complete API with better examples.

    Robert Barrett

    Thanks for the post John! I've modified your code for my site and it works perfectly. Well almost. I'm unsure how to handle cases where the uploaded images are renamed by the cffile tag in the cfc. For example, if I upload an image called Koala.jpg and then later upload an image with the same name, Koala.jpg, the cffile renames the second image to Koala1.jpg. However, in the process.cfm page, it still contains the original name of Koala.jpg. Do you know how to pass along the new name? Thanks, Robert

    John Sieber

    @Robert - Glad that you found this to be useful! I did not think of the issue with redundant file names, but I can see where that would cause issues. I'm not sure if you could use cfreturn to return the cffile.serverfile value back to the process.cfm page? Could you use the filename that imagecfc reads from the image in process.cfm, or is it causing an error before it gets to that point? If I think of anything else I will let you know.

    Robert Barrett

    Thanks John. I'll try the cftreturn option and see if that works. I don't use imagecfc in process.cfm. I just read the images from the #form.images# variable to display the images I uploaded. However, #form.images# contains the actual image file names I selected; not the names from the #file.serverFile# created during the cffile process. Thanks, Robert

    Robert Barrett

    Tried within uploadify.cfc, but I don't know how to access back at my parent window. If I were uploading only one file, I could include some javascript to modify the hidden field "images" in the parent form. However, with multiple hidden fields called "images", I have no idea how to modify the one that needs to be changed. Hmmmmm....

    John Sieber

    Would it be possible to read the filename from the image in the parent form at the preview stage?

    Robert Barrett

    The parent form has the original image name. When selecting multiple images to upload, the javascript in your code creates multiple hidden fields in the parent "form" all with the name "images". What I need to be able to do is create some javascript to run onComplete: in order to change the previously created (during onSelect) hidden field value. Unfortunately, I'm not an expert in javascript.

    Robert Barrett

    John - I have it working, at least in IE9. I return the new name from uploadify.cfc in JSON format. Then, I made the following changes: 1) add id parameter to hidden field. 2) using id, I delete the original hidden form field. See code: onSelect: function(event,ID,fileObj) { var form = document.forms['uploadform']; var i = 0; var el = document.createElement("input"); el.type = "hidden"; = "images"; =; // added id el.value =; form.appendChild(el); }, onComplete: function(event, ID, fileObj, response, data) { var objnew = jQuery.parseJSON(response); var objold = fileObj['name']; if ( objnew != objold) { // add a new hidden field with the new name var form = document.forms['uploadform']; var i = 0; var el = document.createElement("input"); el.type = "hidden"; = "images"; = objnew; el.value = objnew; form.appendChild(el); // now remove the orginal hidden field containing original name var ele = document.getElementById(objold); ele.parentNode.removeChild(ele); } For some reason, the onComplete code does not seem to run in Chrome. Robert

    John Sieber

    @Robert - very nice job coming up with a working solution. Strange that the onComplete is not working in Chrome. Did you try Firefox by any chance to see what behavior it follows?

    Robert Barrett

    Just tired Firefox. I didn't even get past first base. It doesn't even show the "Select Files" button in the opening page. Ah...the joys of programming!

    John Sieber

    Your right about the "joy" for sure... That is odd that the "select file" button is not appearing. I typically use firefox, so it "should" work?

    Robert Barrett

    It occurred to me later that I had yet to install Flash in Firefox. Once installed, the button appeared. However, once I tried the upload, it failed to return the new file name just like Chrome. Bummer.

    Bo Reahard

    I would love to implement this on my web sites. Unfortunately I am having a hard time getting started as the index.cfm that you displayed has so much code unique to the queries that only work on your site. Do you have a simpler example of how to implement this in CF?

    John Sieber

    @ Bo I should have used a much simpler example, sorry about that. You should be able to eliminate the second query writing to the metadata table as the first table should be all you need. You can eliminate or change fields in the first query so that they match your needs.

    Write your comment

    (it will not be displayed)

    Leave this field empty: