> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ensemble.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# html Operation

> Render HTML templates with data (used in ensembles with http trigger)

The `html` operation renders HTML templates using template engines. It's typically used in ensemble flows when an HTTP trigger needs to return server-rendered HTML.

**Note**: The `html` operation is for rendering only. HTTP routing, authentication, CORS, and middleware are configured in the `trigger:` section using `type: http`.

## HTTP Middleware

When using the `html` operation with HTTP triggers, you can add Hono middleware for cross-cutting concerns like logging, compression, and security headers:

```yaml theme={null}
name: blog-post
trigger:
  - type: http
    path: /blog/:slug
    methods: [GET]
    public: true

    # Add middleware for performance and security
    middleware:
      - logger          # Log requests
      - compress        # Gzip compression
      - secure-headers  # Security headers
      - etag            # Cache validation

    responses:
      html: {enabled: true}

agents:
  - name: render-page
    operation: html
    config:
      template: |
        <article>
          <h1>{{ title }}</h1>
          <div>{{ content }}</div>
        </article>
```

Middleware executes before the HTML rendering, allowing you to add:

* Request/response logging (`logger`)
* Response compression for faster page loads (`compress`)
* Security headers (`secure-headers`)
* Cache validation with ETags (`etag`)
* Custom authentication and rate limiting

See the [HTTP Middleware Guide](/conductor/building/http-middleware) for complete documentation on using middleware with HTML responses.

## Usage in Ensembles with HTTP Trigger

```yaml theme={null}
name: blog-post
trigger:
  - type: http
    path: /blog/:slug
    methods: [GET]
    public: true
    responses:
      html: {enabled: true}
    templateEngine: liquid

flow:
  # Fetch data
  - agent: fetch-post
    input: {slug: ${input.params.slug}}

  # Render HTML
  - operation: html
    config:
      template: |
        <article>
          <h1>{{ fetch-post.title }}</h1>
          <div>{{ fetch-post.content }}</div>
        </article>
      data: ${fetch-post}
```

## Configuration

```yaml theme={null}
operation: html
config:
  template: string    # HTML template with variables
  data: object       # Data to render in template
  engine: string     # Template engine (liquid, handlebars, simple)
```

## Template Engines

### Liquid (Default)

```yaml theme={null}
operation: html
config:
  template: |
    <h1>{{ title }}</h1>
    {% for item in items %}
      <p>{{ item.name }}</p>
    {% endfor %}
  data: ${previous-step.output}
```

### Handlebars

```yaml theme={null}
operation: html
config:
  template: |
    <h1>{{title}}</h1>
    {{#each items}}
      <p>{{name}}</p>
    {{/each}}
  data: ${previous-step.output}
  engine: handlebars
```

### Simple (String Interpolation)

```yaml theme={null}
operation: html
config:
  template: |
    <h1>{{title}}</h1>
    <p>{{description}}</p>
  data: ${previous-step.output}
  engine: simple
```

## Complete Example

```yaml theme={null}
name: user-dashboard
trigger:
  - type: http
    path: /dashboard/:userId
    methods: [GET]
    auth:
      type: bearer
      secret: ${env.API_KEY}
    responses:
      html: {enabled: true}
    templateEngine: liquid

flow:
  # Step 1: Fetch user data
  - agent: get-user
    input: {userId: ${input.params.userId}}

  # Step 2: Fetch user stats
  - agent: get-user-stats
    input: {userId: ${input.params.userId}}

  # Step 3: Render HTML dashboard
  - operation: html
    config:
      template: |
        <!DOCTYPE html>
        <html>
        <head>
          <title>Dashboard - {{ get-user.name }}</title>
        </head>
        <body>
          <h1>Welcome, {{ get-user.name }}</h1>
          <div class="stats">
            <p>Total Orders: {{ get-user-stats.orders }}</p>
            <p>Revenue: ${{ get-user-stats.revenue }}</p>
          </div>
        </body>
        </html>
      data:
        get-user: ${get-user}
        get-user-stats: ${get-user-stats}
```

## Personalization with Cookies

Use [cookies](/conductor/operations/cookies) to personalize HTML content based on user preferences or session data:

```yaml theme={null}
name: personalized-page
trigger:
  - type: http
    path: /home
    methods: [GET]
    public: true
    responses:
      html: {enabled: true}

agents:
  # Check for preference cookie
  - name: get-theme
    operation: cookies
    config:
      action: get
      name: theme_preference

  # Load user session if logged in
  - name: get-session
    condition: ${input.cookies.session_id}
    operation: storage
    config:
      type: kv
      action: get
      key: session-${input.cookies.session_id}

  # Render personalized page
  - name: render
    operation: html
    config:
      template: |
        <!DOCTYPE html>
        <html data-theme="{{ theme }}">
        <head>
          <title>{% if user %}Welcome back, {{ user.name }}{% else %}Welcome{% endif %}</title>
        </head>
        <body>
          {% if user %}
            <p>Last visit: {{ user.lastVisit }}</p>
          {% else %}
            <p>Create an account for a personalized experience</p>
          {% endif %}
        </body>
        </html>
      data:
        theme: ${get-theme.output.value || 'light'}
        user: ${get-session.output.value}
```

Set preference cookies when users change settings:

```yaml theme={null}
agents:
  - name: save-theme
    operation: cookies
    config:
      action: set
      name: theme_preference
      value: ${input.body.theme}
      maxAge: 31536000  # 1 year
      purpose: personalization
```

## Next Steps

<CardGroup cols={2}>
  <Card title="pdf" icon="file-pdf" href="/conductor/operations/pdf">
    Generate PDFs
  </Card>

  <Card title="Triggers" icon="globe" href="/conductor/core-concepts/triggers">
    HTTP trigger for web routing
  </Card>
</CardGroup>
