View Handlers

View handlers are functions that process action results and prepare data for template rendering. They bridge the gap between your action’s output and the HTML display by transforming raw results into data to be injected into your template.

Basic Handler Structure

A view handler is a regular Python function decorated with @app.view_handler:

@app.view_handler(template="my_template.html")
def my_view_handler(output: list[MyActionOutput]) -> dict:
    # Process the action results
    # Return data for template rendering
    return {
        "key": "value",
        "data": processed_data
    }

Handler Parameters

Input: Action Results

View handlers receive the action’s output as their first parameter. The type should match your action’s return type:

def handler(output: list[MyActionOutput]) -> dict:
    return {"results": [r.some_field for r in output]}

Optional Parameters and Summaries

Declare named params and summary arguments to receive the saved action parameters and action-result summaries alongside the outputs:

@app.view_handler(template="query_results.html")
def render_queries(
    outputs: list[QueryOutput],
    params: list[QueryParams],
    summary: list[QuerySummary],
) -> dict:
    return {
        "rows": [
            {"event": event, "query": param.query, "sid": item_summary.sid}
            for event, param, item_summary in zip(outputs, params, summary, strict=True)
        ]
    }

QueryParams is the action’s Params subclass and QuerySummary is its ActionOutput summary model. The SDK validates the saved metadata using the argument annotations. Use list[dict], or omit the annotation on the magic argument, to receive dictionaries instead.

Both arguments are optional and independent. Each requested list has the same length and ordering as outputs: params[i] and summary[i] belong to outputs[i]. For an action result containing three events, its parameters and summary appear three times in their respective lists. This also preserves the association when the view receives multiple action results or app runs, without adding metadata fields to the event data.

Action results with no events contribute no entries to these lists. If there are no events at all, the handler receives empty lists, so these arguments cannot provide metadata for empty action results.

The SDK injects these arguments by name, like the soar and asset arguments of @app.action(). They can appear before, between, or after the usual handler arguments, and can also be keyword-only if you prefer. Ordinary parameters accept both positional and keyword passing, following normal Python calling conventions. SOAR can also call the decorated view using positional or keyword arguments; the SDK supplies the aligned lists to your handler.

The existing handler forms remain supported: (outputs), (context, outputs), and (context, action, outputs). An existing output argument named params or summary keeps its output role. Handlers that omit the magic arguments retain their existing behavior, and their metadata is not read.

Return: Template Data

Handlers must return a dictionary containing data for template rendering. Keys become variables accessible in your Jinja2 template:

def handler(output: list[DetectionOutput]) -> dict:
    return {
        "title": "Detection Results",
        "total_detections": len(output),
        "detections": [
            {
                "id": d.detection_id,
                "message": d.message,
                "severity": d.severity
            }
            for d in output
        ]
    }

Decorator Options

Using Templates

Specify a custom HTML template file:

# Template file: templates/detection_results.html
@app.view_handler(template="detection_results.html")
def render_detections(output: list[DetectionOutput]) -> dict:
    return {"detections": output}

Using Components

Use pre-built reusable components:

  • Return output data will define the reusable component used

@app.view_handler()
def render_as_table(output: list[DetectionOutput]) -> TableData:
    return TableData(
        title="Detection Results",
        headers=["ID", "Message", "Severity"],
        rows=[[d.detection_id, d.message, d.severity] for d in output]
    )

Attaching to Actions

Connect your view handler to an action using the view_handler parameter:

@app.action(
    name="scan file",
    view_handler=render_detections
)
def scan_file_action(params: ScanParams, soar: SOARClient) -> list[DetectionOutput]:
    # Your action implementation
    return detection_results