Octov0.11.7
Guides

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.

samples/raw-content-demo/config.yaml (excerpt)
resources:
  templates:
    - resource: templates/page.tmpl
      as: page
samples/raw-content-demo/templates/page.tmpl
<!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.

samples/raw-content-demo/config.yaml (excerpt)
- 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-8

Run 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:

samples/raw-content-demo/config.yaml (excerpt)
- 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.

Where to go next

On this page