ColdFusion MX Forms and Database Queries

Build database-backed forms with explicit validation, parameterized SQL, safe output, and predictable request flow.

Core rule: Treat URL, FORM, COOKIE, CGI, uploaded files, and remote-service data as untrusted. Validate before use, parameterize SQL, and encode values at output time.

A Plain HTML Form

<form method="post" action="save-customer.cfm">
    <label for="name">Name</label>
    <input id="name" name="name" type="text" maxlength="100">

    <label for="email">Email</label>
    <input id="email" name="email" type="text" maxlength="254">

    <button type="submit">Save customer</button>
</form>

Period ColdFusion applications often used cfform and generated client-side validation. Plain HTML keeps the request visible and avoids depending on obsolete generated JavaScript.

Normalize and Validate Input

<cfparam name="form.name" default="">
<cfparam name="form.email" default="">

<cfset variables.cleanName = Trim(form.name)>
<cfset variables.cleanEmail = LCase(Trim(form.email))>
<cfset variables.errors = ArrayNew(1)>

<cfif Len(variables.cleanName) LT 1 OR Len(variables.cleanName) GT 100>
    <cfset ArrayAppend(variables.errors, "Name must be between 1 and 100 characters.")>
</cfif>

<cfif Len(variables.cleanEmail) LT 3 OR Len(variables.cleanEmail) GT 254
    OR NOT Find("@", variables.cleanEmail)>
    <cfset ArrayAppend(variables.errors, "Enter a valid email address.")>
</cfif>

MX 7 includes additional validation functions, but explicit checks remain useful when documenting exact business rules.

Insert with cfqueryparam

<cfif ArrayLen(variables.errors) EQ 0>
    <cfquery name="insertCustomer" datasource="support_portal">
        INSERT INTO customers (name, email, status, created_at)
        VALUES (
            <cfqueryparam value="#variables.cleanName#"
                cfsqltype="cf_sql_varchar" maxlength="100">,
            <cfqueryparam value="#variables.cleanEmail#"
                cfsqltype="cf_sql_varchar" maxlength="254">,
            <cfqueryparam value="active"
                cfsqltype="cf_sql_varchar" maxlength="20">,
            CURRENT_TIMESTAMP
        )
    </cfquery>

    <cflocation url="customers.cfm?saved=1" addtoken="false">
</cfif>
Do not build SQL by concatenating form input. cfqueryparam provides type checking and bind parameters. It should be used for dynamic values, not only values you believe are dangerous.

Query and Display

<cfquery name="customers" datasource="support_portal">
    SELECT id, name, email, status
    FROM customers
    WHERE status = <cfqueryparam value="active" cfsqltype="cf_sql_varchar">
    ORDER BY name
</cfquery>

<cfoutput query="customers">
    <p>
        <a href="customer.cfm?id=#URLEncodedFormat(id)#">
            #HTMLEditFormat(name)#
        </a><br>
        #HTMLEditFormat(email)#
    </p>
</cfoutput>

Editing a Record

<cfparam name="url.id" default="0">
<cfset variables.customerId = Val(url.id)>

<cfquery name="customer" datasource="support_portal" maxrows="1">
    SELECT id, name, email, status
    FROM customers
    WHERE id = <cfqueryparam value="#variables.customerId#" cfsqltype="cf_sql_integer">
</cfquery>

<cfif customer.recordCount EQ 0>
    <cfheader statuscode="404" statustext="Not Found">
    <cfoutput>Customer not found.</cfoutput>
    <cfabort>
</cfif>

Post/Redirect/Get

After a successful insert or update, redirect to a normal GET page. This prevents an accidental form resubmission when the user refreshes the browser.

CSRF Protection in a Legacy Application

MX-era applications generally need a custom anti-CSRF token implementation. Generate a random value, store it in the session, place it in a hidden field, and compare it on POST.

<!--- Form page --->
<cflock scope="session" type="exclusive" timeout="5">
    <cfset session.formToken = Hash(CreateUUID() & Now() & RandRange(100000, 999999))>
</cflock>

<cfoutput>
<form method="post" action="save-customer.cfm">
    <input type="hidden" name="formToken" value="#HTMLEditFormat(session.formToken)#">
    <!--- fields --->
</form>
</cfoutput>
<!--- Processing page --->
<cfparam name="form.formToken" default="">
<cfset variables.tokenIsValid = false>

<cflock scope="session" type="readonly" timeout="5">
    <cfif StructKeyExists(session, "formToken")
        AND Compare(form.formToken, session.formToken) EQ 0>
        <cfset variables.tokenIsValid = true>
    </cfif>
</cflock>

<cfif NOT variables.tokenIsValid>
    <cfthrow type="Security.CSRF" message="Invalid form token.">
</cfif>

A modern platform may offer stronger built-in primitives. This example is a learning pattern for maintaining legacy code, not a claim that MX-era random-number or hash defaults meet modern cryptographic requirements.

Displaying Validation Errors

<cfif ArrayLen(variables.errors) GT 0>
    <div class="error-summary">
        <h2>Correct the following:</h2>
        <ul>
        <cfloop index="i" from="1" to="#ArrayLen(variables.errors)#">
            <cfoutput><li>#HTMLEditFormat(variables.errors[i])#</li></cfoutput>
        </cfloop>
        </ul>
    </div>
</cfif>