This attempts to document all of CFScript, as a resource for people migrating from old-school tag-based code to script-based code. The reason I am doing this is because neither ColdFusion nor Railo/Lucee provide much (or in the case of Railo/Lucee: any) useful documentation of CFScript.
This is not a document for converting tags to script. It is not written from a point of view of “if you use <cfsometag> then you need to instead use [some script construct]”. It simply documents CFScript.
There are some syntax-neutral solutions to some CFML constructs which are listed at the bottom of the document. These are the CFCs found in [customtag]/com/adobe/coldfusion. These are not CFScript constructs per se, but related enough to be relevant here.
It is also not an exercise in teaching CFML (or at least the script part). It assumes you know what you’re doing, and is purely a reference.
I assume Railo 4.2/Lucee 4.5 or ColdFusion 11, except where stated.
Comments
1
// single line comment
1
a=1; // single line comment at end of line
1 2 3 4 5
/* multiple line comment */
1 2 3 4 5
/* multiple line /* comments */ cannot be nested */
In this case, the commented bit is /* multiple line /* comments */ , making the next bit a syntax error.
Statements
Statements end in semi-colons:
1
a = 1;
Semi-colons are generally optional on Railo/Lucee:
1
a = 1
Where “generally” means “if the end of the statement is unambiguous without a semi-colon”. It is better to just always use semi-colons.
Block statements (with curly braces) do not have semi-colons:
1 2 3
while (condition) { // statements }
Variables
Assigning a variable:
1
varName = "foo";
Assigning a function-local variable:
1
var varName = "foo"; // analogous to local.varName = "foo";
Note that the var keyword can appear inline in most statements where a variable is first initialised, eg:
1
for (var i=1; i <= 10; i++);
Assigning a dynamically-named variable:
1 2 3
varName = "foo"; "#varName#" = "bar"; writeOutput(foo); // bar
This is the same as with a <cfset> tag, but confuses some people due to it being slightly odd-looking. Obviously one can also use associative array syntax too (eg: variables[varName] = "bar"; . This is preferable as it’s more clear what’s going on).
Defaulting a variable:
1
param numeric variableName=defaultValue; // where "numeric" could be any type
For more complex situations:
1
param name="variableName" type="regex" pattern="."; // any cfparam attribute is supported
Operators
All operators available to tag-based code still work in CFScript. In addition, CFScript has these ones:
Decision
1 2 3 4 5 6 7
a == 1; // equality a < 1; // less than a <= 1; // less-than-or-equal a >= 1; // greater-than-or-equal a > 1; // greater than a != 1; // inequality a <> 1; // inequality (Railo/Lucee only)
// increment a = 1; b = a++; // b=1, a=2 // postfix operators returns value, then peforms action (in this case: increments a) c = ++a; // c=3, a=3 // prefix operator peforms action then returns result
a += 2; // equivalent to a=a+2 a -= 3; // equivalent to a=a-3 a *= 4; // equivalent to a=a*4 a /= 5; // equivalent to a=a/5 a %= 6; // equivalent to a=a%6 s &= "a"; // equivalent to s = s & "a"
Note that whilst this appears to be the same as other language’s “Elvis” operator, it is not the same. The “Elvis” operator checks for false (and in some languages null is falsey); this operator checks specifically for null . This is likely due to a misunderstanding one the part of Adobe (and perpetuated by the Lucee devs when copying it).
Conditions
if/elseif/else
1 2 3 4 5 6
if (booleanExpression) // single statement executed if booleanExpression is true else if (anotherBooleanExpression) // single statement executed if anotherBooleanExpression is true else // single statement executed if condition(s) are false
1 2 3 4 5 6 7
if (booleanExpression) { // multiple statements executed if booleanExpression is true } else if (anotherBooleanExpression) { // multiple statements executed if anotherBooleanExpression is true } else { // multiple statements executed if condition(s) are false }
switch (expression) { case "some constant value": // value can be dynamic on Railo/Lucee // statements executed if expression = "some constant value" break; // exit switch statement case "a different constant value": // statements executed if expression = "a different constant value" // if there is no break, then processing continues to execute statements until a break is encountered... // ... but subsequent case evaluations are not made. A switch is basically a GOTO mechanism, which does a... // single GOTO the first matching case. It is NOT a series of if/elseif/else statements case "third constant value": // statements executed if expression = "a different constant value" or "third constant value" break; case "4th value": case "5th value": // statements executed if expression is one of "4th value" or "5th value" break; default: // statements executed if no case was fulfilled (or if the last fulfilled case did not have a break) break; }
throw "message"; // throws an Application exception, with the given message
// or throw (type="ExceptionType", message="message", detail="detail", errorCode="errorCode", extendedInfo="extendedInfo"); // despite appearances, this is NOT a function
// or throw (object=JavaExceptionObject); } catch (SomeExceptionType variableContainingExceptionObject) { // statements executed if code in try block errors with a SomeExceptionType exception
rethrow; // rethrows the caught exception } catch (SomeOtherExceptionType variableCOntainingExceptionObject) { // statements executed if code in try block errors with a SomeOtherExceptionType exception } catch (any variableCOntainingExceptionObject) { // statements executed if code in try block errors with any not-yet-caught exception type } finally { // statements executed in any case, INCLUDING unhandled exceptions. This code ALWAYS runs }
Iteration
General-purpose for loop
1
for (initialisation; condition; repetition) statement;
or:
1 2 3
for (initialisation; condition; repetition) { // statements }
EG:
1
for (i=1; i <=5; i++) writeOutput(i); // just the following single statement is looped over
or:
1 2 3 4 5
for (i=1; i <=5; i++) { // all statements within the block are looped over result = i * 2; writeOutput(result); }
The general perception is that this is the only form of a general-purpose for() loop: initialising a counter variable, testing it and adjusting it (increment, decrement). This is not the case. Each of the statements can be anything (the condition needs to evaluate to a boolean), and indeed are optional. This is an endless loop, equivalent to while (true):
1
for (;;)
A very contrived example to demonstrate the freedom one has with the parameters of the for():
1 2 3 4
i=0; for (; true; writeOutput(i)) { if (++i > 5) break; }
In general, all looping constructs have either the single-statement or block-of-statements syntax. I’ll only offer the more common (and recommended, for code-clarity) block syntax henceforth.
Pre-condition loop
This form of loop evaluates a single condition at the beginning of each iteration, and continues to loop whilst the condition is true:
1 2 3
while (condition) { // statements }
This form of loop will execute zero or more times.
Post-condition loop
This form of loop evaluates a single condition at the beginning of each iteration, and continues to loop whilst the condition is true:
1 2 3
do { // statements } while (condition);
This form of loop will execute one or more times. It’s important to consider that the body of the loop will always run the first time, because no condition is evaluated until the end of the loop.
Array loop
For statement
1 2 3
for (element in [1,2,3,4,5]) { writeOutput(element); // 12345 }
Note that the comment for annotations is /** not simply /* .
Also note that the latter syntax does not currently work on Railo (see RAILO-3169).
Interface
1 2 3
interface { public void function f(required numeric x); // note no braces, and ends with semi-colon }
Properties
Basic:
1
property string myProperty;
With additional parameters:
1
property type="string" name="myProperty" default="default value"; // and all the same attributes as `<cfproprty>`
Functions
Basic:
1 2 3
function f() { // assumes public function, returntype any
}
With access and return type modifiers:
1 2 3
private void function f() { // statements }
Arguments
Basic:
1 2 3
function f(x) { // optional argument of type "any" //statements }
Type:
1 2 3
function f(numeric x) { // optional argument of type "numeric" //statements }
Required:
1 2 3
function f(required numeric x) { // required argument of type "numeric" // statements }
Default value:
1 2 3
function f(numeric x = 0) { // optional argument of type "numeric" with default value of 0 // statements }
Function/argument annotations
1 2 3 4 5 6 7 8
/** * @x.hint hint for argument x * @x.type numeric * @x.required true */ function f(x) { // statements }
Note these annotations do not current correctly work on Railo (see RAILO-3170)
Also note that this does not currently work on ColdFusion (see 3808960)
1 2 3 4 5 6 7
/** * @x.type numeric * @x.default 0 // this causes a compile error */ function f(x) { // statements }
Function expressions
1 2 3
f = function(x) { // statements };
Functions defined by function expressions use closure, functions defined by a function statement do not
Annotations for function expressions are not supported on ColdFusion (3808978); are supported on Railo, but have same shortcomings as noted above.
Calling functions dynamically
1 2 3 4
test = new Test(); methodToCall = "f"; argumentsToPass = {x=1}; result = invoke(test, methodToCall, argumentsToPass);
Railo/Lucee-only:
1
result = test[methodToCall](argumentCollection=argumentsToPass);
Import
1
import com.domain.app.package.*;
Object creation
1 2 3 4 5
myObj = createObject(type, "path.to.class"); // along with other type/situation-specific arguments
// or
myObj = new path.to.some.cfc.file(); // NB: will call the CFC's init() (by default), or method identified by the initmethod attribute of the component (bug in Railo: [RAILO-2294](https://issues.jboss.org/browse/RAILO-2294))
// using other optional attributes cfdirectory(action="create", directory="path/to/directory", mode="777");
1 2
// Railo/Lucee only directory action="create" directory="path/to/directory" mode="777";
1 2
// delete directoryDelete("path/to/directory");
1 2
// list listing = directoryList("path/to/directory", true, "query", "*.cfm", "size desc"); // CF11 added an additional "type" attribute. Not currently supported on Railo/Lucee
/* cflocation */ // ColdFusion location(url="http://example.com", statuscode="301 OR 302", addtoken=false);
// Railo/Lucee location url="http://example.com", statuscode="301 OR 302", addtoken=false;
Database
Query
1 2
// general form recordset = queryExecute(sqlString, params, options);
1 2 3 4 5 6 7 8 9 10 11
// with params array numbers = queryExecute(" SELECT columns FROM table WHERE id BETWEEN ? AND ? ", [1,4], { datasource ="myDsn", result = "result" // this is analogous to the result attribute of `<cfquery>` });
1 2 3 4 5 6
// with params struct numbers = queryExecute(" SELECT columns FROM table WHERE id BETWEEN :low AND :high ",{low=2,high=3});
To qualify parameters with SQL types you can specify the equivalent of the cfqueryparam options with the parameters in the following way:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// with sql types in params struct numbers = queryExecute(" SELECT columns FROM table WHERE id BETWEEN :low AND :high ", { low = { value = 2, cfsqltype = "cf_sql_integer" }, high = { value = 3, cfsqltype = "cf_sql_integer" } });
For versions prior to ColdFusion 11 (in which queryExecute() was implemented), there is a CFC-based solution: Query.cfc. An example is as follows:
1 2 3 4 5 6 7 8 9 10 11
numbers = new Query( sql = " SELECT columns FROM table WHERE id BETWEEN :low AND :high ", parameters =[ {name="low", value=2}, {name="high", value=3} ] ).execute().getResult();
writeLog("text to log"); // can use either ordered or named arguments.
Trace
1 2 3 4
// Railo/Lucee only trace category="test" text="trace text" { // plus all same params as `<cftrace>` // stuff to trace }
1 2 3 4 5
// COLDFUSION only trace(category="test", text="trace text") { // plus all same params as `<cftrace>` // stuff to trace } // note that CF11 incorrectly records timing information (see [3811003](https://bugbase.adobe.com/index.cfm?event=bug&id=3811003))
Timer
1 2 3
cftimer(label="timer label" type="outline") { // plus all same params as `<cftimer>` // stuff to time }
1 2 3 4
// Railo/Lucee only timer label="timer label" type="outline" { // plus all same params as `<cftimer>` // stuff to time }
General / Miscellaneous
Output
1
writeOutput(expression); // expression must resolve to a string
Railo/Lucee only:
1
echo(expression); // expression must resolve to a string
File Encoding
1
pageencoding "UTF-8";
Note that this only works in CFC files on ColdFusion (3712167). It works correctly on Railo/Lucee.
Save content
1 2 3
savecontent variable="saved" { writeOutput("stuff to save"); }
Threading
1 2 3 4
thread action="run" name="threadName" { // code to run in separate thread here } thread action="join" name="threadName,anotherThreadName";
Locking
1 2 3
lock name="lockName" timeout=1 throwontimeout=true { // code to lock }
Image / XLS manipulation
The function equivalents of <cfimage> and <cfspreadsheet> are all well documented, and are not specifically CFScript constructs.
PDF Manipulation
I have to concede I have never ever done any work with PDFs, so cannot make an informed comment on the CFScript equivalents. However in lieu of particular CFScript-specific constructs that I am aware of, the generic syntax ought to work, eg:
ColdFusion:
1 2 3
cfdocument(format="PDF") { // mark-up here }
Railo/Lucee:
1 2 3
document format="PDF" { // mark-up here }
The same should work on other PDF-oriented tags. For versions of ColdFusion prior to CF11, there is a PDF.cfc (similar to Query.cfc, and also in cfusion/CustomTags/com/adobe/coldfusion). I have never used it, do not know how it works, and have no interest in finding out. If someone would like to donate some example code, I will integrate it here.
Elements of tag-based CFML with no specific CFScript implementation
CFC-based solutions
As far as I can tell, there is no CFScript-specific implementations for the following pieces of functionality:
<cfftp>
<cfpop>
<cfimap>
<cffeed>
<cfldap>
<cfcollection>
<cfindex>
<cfsearch>
There are CFC wrappers for these in cfusion/CustomTags/com/adobe/coldfusion. These are not CFScript-specific solutions per se - one can just as easily use them in tag-based code - but can be included here for the sake of completeness. I personally do not use these constructs, and as they are not part of CFScript, did not attempt to include them here. Feel free to document and include them if you so choose.
http.cfc
ColdFusion (CF9+):
The pattern seems to be set{ATTRIBUTENAME}( value ); for setting attributes that are normally set via the <cfhttp> tag
// there's a built-in CFC that handles this in CF9+ httpService = new http(); httpService.setMethod( "post" ) httpService.setCharset( "utf-8" );
// the API provider may require you to export this cert // so you’ll need to save it in a secure place… // and reference it here httpService.setClientCert( "#ExpandPath(‘.’)#/my_cert.p12" ); httpService.setClientCertPassword( "mypassword!" ); httpService.setUrl( "https://api.sample.com/" );
// these params are form fields to POST httpService.addParam(type="formfield", name="field1", value="1111"); httpService.addParam(type="formfield", name="field2", value="some text here");
// this is the cfscript way to grab the response httpResponse = httpService.send().getPrefix();
writeDump(httpResponse);
mail.cfc
ColdFusion (CF9+):
The pattern seems to be set{ATTRIBUTENAME}( value ); for setting attributes that are normally set via the <cfmail> tag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
mailerService = new mail(); /* set mail attributes using implicit setters provided */ mailerService.setType("html"); mailerService.setCharset( "utf-8" ); mailerService.setTo( "adam@sample.com" ); mailerService.setFrom( "test@sample.com;joe@sample.com" ); mailerService.setSubject( "Super interesting subject" );
/* add mailparams */ mailerService.addParam( file=expandpath(form.attachment), type="text/plain", remove=false ); // create the body savecontent variable="mailBody"{ WriteDump( CGI ); WriteDump( SESSION ); } // send the email mailerService.send( body=mailBody );
The rest
To use any other functionality not listed here within CFScript, one needs to use the generalised syntax.
On Railo/Lucee this is a matter of removing the " <cf " and the " > ", and using normal block syntax (curly braces) where the tag-version is a block-oriented tag.
On ColdFusion (CF11+), replace the " <cftagname " with " cftagname( ", and the " > " with " ) ", and comma-separate the attributes. Note that this will make the construct look like a function, but it actually is not, and cannot be used like a function, eg this is invalid syntax:
1
result = cfhttp(method="post", url="http://example.com");
Some tags take a block of code / text within them. Blocks are represented with curly braces, similar to a flow-contrl statement.
1 2 3 4 5 6 7 8 9
cfmail(attributeCollection = attributesforCfmail) { cfmailpart(type="text/plain") { writeOutput("If you are seeing this, your e-mail client does not support HTML messages."); } cfmailpart(type="text/html") { writeOutput(htmlVersionOfMessage); } }
Note how text needs to be “output” using writeOutput . Note also how sub-tags ( cfmailpart here) are also expressed using the same rules as parent tags.