DevLearningTools

MODULE 14 · LESSON 06

cfhttp

Making outbound HTTP requests from ColdFusion with cfhttp: GET and POST, sending headers and a JSON body, reading the result structure, downloading files, and a real cross-vendor-docs discrepancy worth knowing about.

New lessons are added one at a time as the course gets built out — a graded quiz for each lesson is still on the way.

The previous lesson read an Authorization header sent to your own endpoint. This one sends the request in the first place: cfhttp is how ColdFusion calls another server over HTTP, whether that's a third-party REST API, another internal service, or just fetching a remote file.

Learning Objectives

After completing this lesson, you'll be able to:

  • Make a basic GET request with cfhttp and read the response.
  • Send query parameters, headers, and a JSON body with cfhttpparam.
  • Read the cfhttp result structure: statusCode, fileContent, responseHeader, and more.
  • Download a binary file (like an image) with getAsBinary.
  • Know the real, documented default for throwOnError, and why setting it explicitly matters.

How cfhttp Fits Together

Build the Request

url, method, cfhttpparam

cfhttp Sends It

GET, POST, PUT, DELETE...

Remote Server Responds

status code + body

Read the Result

result.fileContent, .statusCode

A Basic GET Request

Tag Syntax
<cfhttp url="https://api.example.com/users" method="GET" result="apiResult" timeout="10">
    <cfhttpparam type="header" name="Accept" value="application/json">
</cfhttp>

<cfif apiResult.statusCode CONTAINS "200">
    <cfset users = deserializeJSON(apiResult.fileContent)>
</cfif>
CFScript
cfhttp(url = "https://api.example.com/users", method = "GET", result = "apiResult", timeout = 10) {
    cfhttpparam(type = "header", name = "Accept", value = "application/json");
}

if (apiResult.statusCode contains "200") {
    users = deserializeJSON(apiResult.fileContent);
}
NOTE

result defaults to a variable literally named cfhttp if you don't set it, giving the result variable its own explicit name (like apiResult above) keeps overlapping or nested cfhttp calls from clobbering each other.

The cfhttp Result Structure

KeyContains
statusCodeThe status line, e.g. "200 OK" or "404 Not Found"
fileContentThe response body (text, or binary if getAsBinary applies)
responseHeaderA struct of every response header, by name
mimeTypeThe Content-Type of the response
charsetThe character encoding, read from the response
textBoolean, true if ColdFusion treated the response as text
errorDetailConnection-level failure details; empty string on success

Sending Data With cfhttpparam

A single cfhttp tag can carry several cfhttpparam children, each one attaching a different piece of the outgoing request, distinguished by its type attribute.

typeWhat It Attaches
urlA query-string parameter
formfieldA form field (for a traditional form-encoded POST)
headerA custom HTTP header, like Authorization or Content-Type
bodyA raw request body, commonly a JSON string for a modern API
fileA file to upload (requires multipart="yes" on cfhttp itself)

A Real Example: POST With a JSON Body and Auth Header

This is the client side of the exact pattern the previous lesson protected on the server side: an Authorization header, checked on arrival there, attached here before the request ever leaves.

CFScript
payload = { "name": "New Product", "price": 29.99 };

cfhttp(url = "https://api.example.com/products", method = "POST", result = "apiResult", timeout = 10) {
    cfhttpparam(type = "header", name = "Authorization", value = "Bearer " & application.apiToken);
    cfhttpparam(type = "header", name = "Content-Type", value = "application/json");
    cfhttpparam(type = "body", value = serializeJSON(payload));
}

if (apiResult.statusCode contains "201") {
    created = deserializeJSON(apiResult.fileContent);
}

Downloading a File

getAsBinary controls whether the response is treated as text or raw bytes, essential for anything that isn't plain text, like an image or a PDF.

Tag Syntax
<cfhttp url="https://example.com/logo.png" method="GET" getAsBinary="yes" result="imgResult">
</cfhttp>

<cffile action="write" file="#expandPath('./downloaded-logo.png')#" output="#imgResult.fileContent#">
NOTE

getAsBinary="auto" (rather than "yes") lets ColdFusion decide based on the response's Content-Type, which is often good enough, but "yes" is the explicit, unambiguous choice for a file you already know is binary.

A Real Discrepancy: throwOnError's Actual Default

Worth flagging directly: cfdocs.org's community reference currently lists throwOnError's default as true, but Adobe's own official documentation and Lucee's own official documentation both state the default is false, neither engine throws an exception on an HTTP error status by default, it's left in the result struct's statusCode and errorDetail instead.

NOTE

The practical fix regardless of which default you trust: set throwOnError explicitly. Relying on an unstated or disputed default for error-handling behavior is a bad practice on its own, separate from which source is right.

Common Beginner Mistakes

Assuming a failed request automatically throws an exception

It doesn't, by default on either engine. Check apiResult.statusCode (or set throwOnError="yes" explicitly and wrap the call in cftry/cfcatch) rather than assuming a bad response interrupts execution on its own.

Forgetting getAsBinary for a file download

Without it, ColdFusion may mangle binary content by trying to treat it as text. getAsBinary="yes" (or "auto" to let ColdFusion decide from the Content-Type) avoids corrupting the downloaded bytes.

Sending a JSON body without also setting the Content-Type header

A cfhttpparam type="body" with a JSON string still needs an explicit Content-Type: application/json header, most APIs won't parse the body correctly without it, regardless of what the actual content looks like.

Leaving timeout unset on a call to an external service

Without a timeout, a slow or hanging third-party server can leave a ColdFusion request (and the thread handling it) waiting indefinitely. An explicit timeout keeps a single bad external call from tying up server resources.

Best Practices

  • Set result to a descriptive variable name, not the cfhttp default, especially with more than one cfhttp call on a page.
  • Set throwOnError explicitly rather than relying on the default, and check the documented behavior differs depending on which source you read.
  • Always set a timeout when calling an external service.
  • Set Content-Type explicitly (via cfhttpparam type="header") whenever sending a body, don't assume the receiving API will guess correctly.

Interview Questions

How do you send a JSON POST request with cfhttp?

A cfhttpparam with type="body" carrying the serializeJSON()'d payload, plus a cfhttpparam type="header" setting Content-Type to application/json, on a cfhttp tag with method="POST".

What's in the result structure after a cfhttp call?

Keys including statusCode (the status line), fileContent (the response body), responseHeader (a struct of response headers), mimeType, charset, text (whether it was treated as text), and errorDetail (populated on a connection-level failure).

Does cfhttp throw an exception automatically when the remote server returns a 404 or 500?

No, not by default, on either Adobe ColdFusion or Lucee, per both engines' own official documentation. The failure shows up in the result struct's statusCode and errorDetail, throwOnError="yes" needs to be set explicitly to get exception-based error handling instead.

Summary

In this lesson, you made GET and POST requests with cfhttp, attached query parameters, headers, and a JSON body with cfhttpparam, read the result structure's statusCode and fileContent, downloaded a binary file with getAsBinary, and covered a real, verified discrepancy in how throwOnError's default is documented across sources.

What's Next?

The next lesson covers cfldap: connecting to an LDAP directory server (like Active Directory) to query, add, modify, or delete directory entries, commonly used for centralized authentication against a company's existing user directory.