Rich display rendering
Windmill processes some outputs (from scripts or flows) intelligently to provide rich display rendering, allowing you to customize the display format of your results.
By default, all results are displayed in a JSON format. However, some formats are recognised automatically (Rich Table Display), while others can be forced through your code. By leveraging specific keys, you can display images, files, tables, HTML, JSON, and more.
If the result is an object/dict with a single key (except for resume, which needs 3), you can leverage the following rich results:
| Type | Description | Example |
|---|---|---|
| table-col | Render the value as a column-wise table. | return { "table-col": { "foo": [42, 8], "bar": [38, 12] } } |
| table-row | Render the value as a row-wise table. | return { "table-row": [ [ "foo", "bar" ], [ 42, 38 ], [ 8, 12 ] ] } |
| table-row-object | Render the value as a row-wise table but where each row is an object. | return { "table-row-object": [ { "foo": 42, "bar": 38 }, { "foo": 8, "bar": 12 } ] } or return { "table-row-object": [ ["foo", "bar" ], { "foo": 42, "bar": 38 }, { "foo": 8, "bar": 12 } ] } |
| s3 | Render S3 files as a downloadable file and a bucket explorer, when Windmill is connected to a S3 storage. | return { "s3": "path_to_file"} |
| html | Render the value as HTML. | return { "html": "<div>...</div>" } |
| markdown | Render the value as Markdown. | return { "markdown": "## Hello World\nNice to meet you" } or return { "md": "## Hello World\nNice to meet you" } |
| file | Render an option to download a file. | return { "file": { "content": encode(file), "filename": "data.txt" } } |
| Render the value as a PDF document. | return { "pdf": base64Pdf } | |
| png | Render the value as a PNG image. | return { "png": { "content": base64Image } } or return { "png": base64Image } |
| jpeg | Render the value as a JPEG image. | return { "jpeg": { "content": base64Image } } or return { "jpeg": base64Image } |
| gif | Render the value as a GIF image. | return { "gif": { "content": base64Image } } or return { "gif": base64Image } |
| svg | Render the value as an SVG image. | return { "svg": "<svg>...</svg>" } |
| error | Render the value as an error message. | return { "error": { "name": "418", "message": "I'm a teapot", "stack": "Error: I'm a teapot" }} |
| resume | Render an approval and buttons to Resume or Cancel the step. | return { "resume": "https://example.com", "cancel": "https://example.com", "approvalPage": "https://example.com" } |
| map | Render a map with a given location. | return { "map": { lat: 40, lon: 0, zoom: 3, markers: [{lat: 50.6, lon: 3.1, title: "Home", radius: 5, color: "yellow", strokeWidth: 3, strokeColor: "Black"}]}} |
| render_all | Render all the results. | return { "render_all": [ { "json": { "a": 1 } }, { "table-col": { "foo": [42, 8], "bar": [38, 12] }} ] } |
Tables
There are various ways to display results as tables within Windmill. Rich Table Display automatically renders results as an interactive table, or you can force a table view with specific keys.
If the result matches the table format (either table-col, table-row, or table-row-object), it will be automatically detected and displayed as a table even if the data is not nested under the key table-*.
There are 3 table shapes that are supported:
- table-row-object (list of objects)
- table-col (list of columns)
- table-row (list of values)
Rich table display
The rich table display does not require a specific key and will be enabled for scripts or flows when the result is an array of objects.
You can also force table display with a key (table-col, table-row, table-row-object).

Example
Try with this Python:
from typing import List, Dict
def main() -> List[Dict[str, str]]:
pokemon_data = [
{"Pokemon name": "Pikachu", "Type": "Electric", "Main strength": "Speed"},
{"Pokemon name": "Charizard", "Type": "Fire/Flying", "Main strength": "Attack"},
{"Pokemon name": "Bulbasaur", "Type": "Grass/Poison", "Main strength": "Defense"},
{"Pokemon name": "Squirtle", "Type": "Water", "Main strength": "Defense"},
{"Pokemon name": "Jigglypuff", "Type": "Normal/Fairy", "Main strength": "HP"},
]
return pokemon_data

Force column order
As you can see in the example above, the columns are not properly ordered. You can force column order with Table Row Object.
For example, with columns ordered:
from typing import List, Dict
def main() -> List[Dict[str, str]]:
pokemon_data = [
["Pokemon name", "Type", "Main strength"],
{"Pokemon name": "Pikachu", "Type": "Electric", "Main strength": "Speed"},
{"Pokemon name": "Charizard", "Type": "Fire/Flying", "Main strength": "Attack"},
{"Pokemon name": "Bulbasaur", "Type": "Grass/Poison", "Main strength": "Defense"},
{"Pokemon name": "Squirtle", "Type": "Water", "Main strength": "Defense"},
{"Pokemon name": "Jigglypuff", "Type": "Normal/Fairy", "Main strength": "HP"},
]
return pokemon_data

Additionaly, you can force column orders with a variable, for example columns in
export async function main(values: string[]) {
let columns = ["column1","column2","column3"]
return [columns, ...values]
}
Here is a more dense example:
export async function main(): Promise<Array<{ "Pokemon name": string, "Type": string, "Main strength": string } | string[]>> {
const pokemonData = [
{ "Pokemon name": "Pikachu", "Type": "Electric", "Main strength": "Speed" },
{ "Pokemon name": "Charizard", "Type": "Fire/Flying", "Main strength": "Attack" },
{ "Pokemon name": "Bulbasaur", "Type": "Grass/Poison", "Main strength": "Defense" },
{ "Pokemon name": "Squirtle", "Type": "Water", "Main strength": "Defense" },
{ "Pokemon name": "Jigglypuff", "Type": "Normal/Fairy", "Main strength": "HP" }
];
const columns: string[] = ["Pokemon name", "Main strength", "Type"];
return [columns, ...pokemonData];
}


Table column
The table-col key allows returning the value as a column-wise table.
If the result matches the table format, it will be displayed as a table even if the data is not nested under the key table-col.
return { "foo": [42, 8], "bar": [38, 12] }
or
return { "table-col": { "foo": [42, 8], "bar": [38, 12] } }
