Skip to main content

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:

TypeDescriptionExample
table-colRender the value as a column-wise table.return { "table-col": { "foo": [42, 8], "bar": [38, 12] } }
table-rowRender the value as a row-wise table.return { "table-row": [ [ "foo", "bar" ], [ 42, 38 ], [ 8, 12 ] ] }
table-row-objectRender 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 } ] }
s3Render S3 files as a downloadable file and a bucket explorer, when Windmill is connected to a S3 storage.return { "s3": "path_to_file"}
htmlRender the value as HTML.return { "html": "<div>...</div>" }
markdownRender the value as Markdown.return { "markdown": "## Hello World\nNice to meet you" } or return { "md": "## Hello World\nNice to meet you" }
fileRender an option to download a file.return { "file": { "content": encode(file), "filename": "data.txt" } }
pdfRender the value as a PDF document.return { "pdf": base64Pdf }
pngRender the value as a PNG image.return { "png": { "content": base64Image } } or return { "png": base64Image }
jpegRender the value as a JPEG image.return { "jpeg": { "content": base64Image } } or return { "jpeg": base64Image }
gifRender the value as a GIF image.return { "gif": { "content": base64Image } } or return { "gif": base64Image }
svgRender the value as an SVG image.return { "svg": "<svg>...</svg>" }
errorRender the value as an error message.return { "error": { "name": "418", "message": "I'm a teapot", "stack": "Error: I'm a teapot" }}
resumeRender an approval and buttons to Resume or Cancel the step.return { "resume": "https://example.com", "cancel": "https://example.com", "approvalPage": "https://example.com" }
mapRender 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_allRender 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:

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).

Default

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

Rich Table Display

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

Rich Table Display Ordered

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];
}

Columns not ordered

Columns ordered

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] } }

Rich display Table Column

Table row

The table-row key allows returning the value as a row-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-row.

return { [ [ "foo", "bar" ], [ 42, 38 ], [ 8, 12 ] ] }

or

return { "table-row": [ [ "foo", "bar" ], [ 42, 38 ], [ 8, 12 ] ] }

Rich display Table Row

Table row object

The table-row-object key allows returning the value as a row-wise table but where each row is an object (optionally the first row can be an array of strings to enforce column order).

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-row-object.

List of columns is not mandatory but it allows forcing their order.

return [ { "foo": 42, "bar": 38 }, { "foo": 8, "bar": 12 } ]

Rich display Table Row Object 1

or

return [ ["foo", "bar" ], { "foo": 42, "bar": 38 }, { "foo": 8, "bar": 12 } ]

Rich display Table Row Object 2

S3

The s3 key renders S3 files as a downloadable file and a bucket explorer, when Windmill is connected to a S3 storage.

return { "s3": "path/to/file" }

When a script outputs a S3 file, it can be downloaded or previewed directly in Windmill's UI (for displayable files like text files, CSVs, images, PDFs or parquet files).

S3 file download

Even though the whole file is downloadable, the backend only sends the rows that the frontend needs for the preview. This means that you can manipulate objects of infinite size, and the backend will only return what is necessary.

You can even display several S3 files through an array of S3 objects:

export async function main() {
return [{s3: "path/to/file_1"}, {s3: "path/to/file_2", {s3: "path/to/file_3"}}];
}

S3 list of files download

Learn more at:

HTML

The html key allows returning the value as HTML.

return { "html": "<div>...</div>" }

Rich display HTML

Markdown

The markdown key allows returning the value as Markdown.

return { "markdown": "## Hello World\nNice to meet you" }

or

return { "md": "## Hello World\nNice to meet you" }

Rich display Markdown

File

The file key allows returning an option to download a file.

return { "file": { "content": encode(file), "filename": "data.txt" } }

Rich display File

PDF

The pdf key allows returning the value as a PDF.

The PDF must be encoded in base64.

return { "pdf": { "content": base64Pdf } }

or

return { "pdf": base64Pdf }

Rich display PDF

PNG

The png key allows returning the value as a PNG image.

The picture must be encoded in base64.

return { "png": { "content": base64Image } }

or

return { "png": base64Image }

Rich display PNG

JPEG

The jpeg key allows returning the value as a JPEG image.

The picture must be encoded in base64.

return { "jpeg": { "content": base64Image } }

or

return { "jpeg": base64Image }

Rich display JPEG

GIF

The gif key allows returning the value as a GIF.

The gif must be encoded in base64.

return { "gif": { "content": base64Image } }

or

return { "gif": base64Image }

SVG

The svg key allows returning the value as an SVG image.

return { "svg": "<svg>...</svg>" }

Rich display SVG

Error

The error key allows returning the value as an error message.

return { "error": { "name": "418", "message": "I'm a teapot", "stack": "Error: I'm a teapot" }}

Rich display Error

Resume

The resume key allows returning an approval and buttons to Resume or Cancel the step.

return { "resume": "https://example.com", "cancel": "https://example.com", "approvalPage": "https://example.com" }

Rich display Resume

Map

The map key allows returning 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"}]}}

Rich display map

Render all

The render_all key allows returning all results with their specific format.

return { "render_all": [ { "json": { "a": 1 } }, { "table-col": { "foo": [42, 8], "bar": [38, 12] }} ] }

Rich display Render All