Serving HTML
Render HTML pages from templates and parse form submissions.
Flows answer with JSON by default, but an HTTP flow can serve any content type.
This guide renders an HTML page from a template and parses a form submission,
both sides of Octo's raw-content mode, based on
samples/raw-content-demo.
Raw content in one paragraph
In raw-content mode the body carries {contentType, rawData} and the HTTP
source writes rawData verbatim with contentType as the Content-Type header.
You enter it with a producing block (template-resource or set-payload, both
with rawBody: true), not by hand-building the object. See
Raw Content and Streaming.
Declare a template resource
Templates are files declared under resources.templates and referenced by their
as alias. They embed {{ CEL }} placeholders evaluated over the current
message, with body, vars, env, and now in scope.
resources:
templates:
- resource: templates/page.tmpl
as: page<!doctype html>
<html>
<head><title>Octo raw content</title></head>
<body>
<h1>Hello, {{ vars.name }}!</h1>
<p>This page was rendered by a template-resource block and served as
<code>text/html</code> from a raw-content body — no JSON in sight.</p>
</body>
</html>Serve the page
The flow is one block: template-resource renders the template and, with
rawBody: true, writes the result as a raw-content body served with the declared
content type.
- name: page
source:
connector: api
type: http
settings:
path: /page/{name} # {name} -> vars.name
process:
- type: template-resource
settings:
id: page
rawBody: true
contentType: text/html; charset=utf-8Run it and request a page:
octo run --config samples/raw-content-demo
curl -i localhost:8080/page/World
# HTTP/1.1 200 OK
# Content-Type: text/html; charset=utf-8
#
# <!doctype html> ... <h1>Hello, World!</h1> ...template-resource takes four settings: id (the template's alias, required),
rawBody, contentType (required when rawBody is on), and target, which
renders into a variable instead of the body (for example an email fragment
mid-flow). rawBody applies to the body only, so leave target empty when
serving pages.
Small responses need no template file. set-payload with rawBody: true, a
contentType, and a string value (for example value: '"<h1>hi</h1>"')
produces a raw-content body inline, as the error page in
An AI Web App End to End does. The value must evaluate
to a string when rawBody is set.
Accept a form submission
Inbound, rawBody: true on the HTTP source accepts any content type and
stores the request as {contentType, rawData} instead of parsing JSON. The CEL
function fromFormData parses a urlencoded form string into an object:
- name: submit
source:
connector: api
type: http
settings:
path: /submit
rawBody: true # accept any content type, store as raw
process:
- type: set-payload
settings:
value: 'fromFormData(body.rawData)'Post a form to it:
curl -s -X POST localhost:8080/submit \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'name=Ann&city=Berlin'
# -> {"city":"Berlin","name":"Ann"}The body arrives as
{"contentType": "application/x-www-form-urlencoded", "rawData": "name=Ann&city=Berlin"};
fromFormData(body.rawData) turns it into JSON, which the flow returns as a
normal JSON response. A single-valued field parses to a string, so body.name
and body.city read naturally. The inverse, toFormData, serializes an object
into a form string for outbound calls.
Putting it together: serve a form, handle its POST
Combine the two flows for a complete round trip with no frontend stack: a GET
flow renders a template containing <form method="POST" action="/submit">, and
the POST flow parses the submission with fromFormData, validates or stores it,
and responds with JSON or with another rendered template as a confirmation page.