Output
Return structured, user-friendly results from your tasks
Airplane tasks are designed not just run and do work, but also to return meaningful data. The task's
output is like the return value of your task, which becomes immediately visible to the end user on
the task run page.
Airplane output also plays a pivotal role in the broader Airplane ecosystem. When
one task executes other tasks, the other tasks' outputs are often used as
inputs to the current task. Similarly, when integrating tasks with views,
the task output is used to provide data-rich visualizations, ensuring that the end user is presented
with insightful and actionable information.
To make this presentation even more seamless, Airplane provides a range of
output display components to best showcase your task's output.
Output data model
Output data model
Airplane output from a task is similar to the return value of a function. Airplane output can be any
JSON value such as primitive values (strings, numbers), lists, or even complex objects.
Different Airplane tasks will return different kinds of output. For example, a
SQL task will return its SELECT results as a list of objects (which is rendered as a
table of values). JavaScript tasks can output values however the author desires.
Producing output from tasks
Producing output from tasks
Non-code tasks (SQL, REST) produce outputs automatically e.g. from the result of a SQL
SELECT
query or REST GET
request.For most code tasks (JavaScript, Python), the easiest way to produce output from a task is by
returning a value from the main function.
typescriptCopied1export default async function (params) {2return { id: 123, name: "abc" };3}
pythonCopied1import airplane23@airplane.task()4def my_task(params):5return {"id": 123, "name": "abc"}
For code tasks that are not based on a main function (Shell, Docker), Airplane captures task output
by filtering stdout lines that start with certain keywords.
For example, in a shell task you can produce output by echoing a string that starts with
airplane_output_set
.shellCopied1#!/bin/bash23data='{"id": 123, "name": "abc"}'4echo "airplane_output_set ${data}"
For more information about creating task outputs via stdout, see the Advanced section
below.
Output displays
Output displays
Output displays are a powerful tool to control how output is visualized in the UI. By default,
Airplane output is rendered as a table, but there are many other output displays to choose from
depending on how you want to visualize your output.
Default table output display
Default table output display
Table is a rich, interactive table output display that supports sorting, filtering, and CSV exports.
Table is the default output display, so tasks without any custom output display configuration will
render their outputs as tables.
Table does a best effort rendering of output depending on the output data structure.
- If the output is a primitive value (string, number, boolean), it will be rendered as a single cell in the table.
- If the output is a list of primitive values, it will be rendered as a single column table with multiple rows.
- If the output is a list of objects, it will be rendered as a table, one row per item, and one
column per key. For example,
[{"id": 1, "name": "John"}, {"id": 2, "name": "Sally"}]
will render as two rows with two columns, "id" and "name". - If the output is a nested object, it will be rendered as collapsed values inside the table.
It's also possible to display multiple tables. If the output data structure is an object containing
only lists as values, the UI will render each separate list as its own table, with the table name
set to the key of the list.
Configuring an output display
Configuring an output display
You can configure the output display for a task by setting the output display field in the task
definition.
For example, you might decide that you want to render the output as a description list rather than
the default table. The description list output display expects the output to be a list of objects,
where each object has a
term
and description
key, so your task and task definition might look
like this:typescriptCopied1import airplane from "airplane";23export const catsTask = airplane.task(4{5slug: "cats",6name: "Cats task",7outputDisplay: { type: "descriptionList" },8},9async function (params) {10return [11{ term: "Mittens", description: "Maine coon" },12{ term: "Hamilton", description: "Ragdoll" },13{ term: "Whiskers", description: "British shorthair" },14];15},16);
pythonCopied1import airplane23@airplane.task(4output_display=airplane.DescriptionListOutputDisplay(),5)6def my_task(params):7return [8{ "term": "Mittens", "description": "Maine coon" },9{ "term": "Hamilton", "description": "Ragdoll" },10{ "term": "Whiskers", "description": "British shorthair" },11]
sqlCopied1-- cats.sql2SELECT name as term, breed as description FROM cats;
yamlCopied1# cats.task.yaml2name: cats3slug: cats4outputDisplay:5type: descriptionList
The simple configuration works for many cases. However, you may have a task where the output doesn't
conform to the output display format. In the above example, it would be more natural for the task to
return the cat metadata as an object of
name
, breed
, etc. fields rather than the term
and
description
fields that the description list expects.Fortunately, many of the output displays accept other props that affect how the display is rendered.
The description list output display accepts a
value
prop that supports
JS templates. The output is represented as the output
global, which you
can reference as part of a JS template expression.In the following example, the output is transformed to the expected description list format and the
name is made uppercase to demonstrate that any valid JS expression is supported.
typescriptCopied1import airplane from "airplane";23export const catsTask = airplane.task(4{5slug: "cats",6name: "Cats task",7outputDisplay: {8type: "descriptionList",9value: "{{ output.map((o) => ({term: o.name.toUpperCase(), description: o.breed}) ) }}",10},11},12async function (params) {13return [14{ name: "Mittens", breed: "Maine coon", age: 4 },15{ name: "Hamilton", breed: "Ragdoll", age: 1 },16{ name: "Whiskers", breed: "British shorthair", age: 8 },17];18},19);
pythonCopied1import airplane23@airplane.task(4output_display=airplane.DescriptionListOutputDisplay(5value="{{ output.map((o) => ({term: o.name.toUpperCase(), description: o.breed}) ) }}"6),7)8def my_task(params):9return [10{ "term": "Mittens", "description": "Maine coon", "age": 4 },11{ "term": "Hamilton", "description": "Ragdoll", "age": 1 },12{ "term": "Whiskers", "description": "British shorthair", "age": 8 },13]
sqlCopied1-- cats.sql2SELECT name, breed FROM cats;
yamlCopied1# cats.task.yaml2name: cats3slug: cats4outputDisplay:5type: descriptionList6value: "{{ output.map((o) => ({term: o.name.toUpperCase(), description: o.breed}) ) }}"
Adding actions and row actions to an output display
Adding actions and row actions to an output display
To link to tasks, runbooks, pages, views, or a url from a run, you can configure action buttons that
will be displayed at the top of the run output. By default, actions will open a preview of the
entity on the right side of the run. You can set the
as
field to center_peek
to open it in a
modal or full_page
to open it your current window. The action takes in a label
field and a
task
, view
, runbook
, page
, or href
field representing the slug of the task, view, or
runbook or the path of the page or the url to preview. If no label
is provided, the entity slug or
href will be used for the label.You can also pass Task params, Runbook params, or
Views params to the preview by including a
paramValues
field in the action.
This will pre-fill the parameters in the task/runbook/view when the preview is opened.For table output displays, you can also configure row actions that will be displayed in a column at
the end of the table, one for each row. Row actions are configured in the same way as actions. If
more than one row action is provided, they will show up as a dropdown menu.
All of the fields in actions and row actions support JS templates. The
output is represented as the
output
global, which you can reference as part of a JS template
expression. Row actions can refer to output
or row
, which is the data from the row of the table.typescriptCopied1import airplane from "airplane";23export const listTeamsTask = airplane.task(4{5slug: "list_teams",6name: "List teams",7outputDisplay: {8type: "table",9actions: [10{11page: "team_inspector",12label: "Open team inspector",13as: "center_peek",14},15],16rowActions: [17{18task: "update_revenue",19label: "Update revenue",20paramValues: {21id: "{{row.id}}",22revenue: "{{row.revenue}}",23},24},25],26},27},28...
pythonCopied1import airplane23@airplane.task(4slug="list_teams",5name="List teams",6output_display=airplane.TableOutputDisplay(7actions=[8airplane.PageAction(9page="team_inspector",10label="Open team inspector",11as_="center_peek"12),13],14row_actions=[15airplane.TaskAction(16task="update_revenue",17label="Update revenue",18param_values={"id": "{{row.id}}", "revenue": "{{row.revenue}}"}19),20],21),22)23...
yamlCopied1# list_teams.task.yaml2slug: list_teams3name: List teams4outputDisplay:5type: table6actions:7- page: team_inspector8label: Open team inspector9as: center_peek10rowActions:11- task: update_revenue12label: Update revenue13paramValues:14id: "{{row.id}}"15revenue: "{{row.revenue}}"
Reference
Reference
Many additional output displays are supported. The full list of output displays is: code,
description list, file, JSON, statistic, table, text.
For detailed information on how to configure each output display, see the output display reference
for each task configuration documentation:
JavaScript,
Python, SQL,
GraphQL, REST,
Shell, and Docker.
Advanced
Advanced
Output retention
Output retention
If your task outputs contain sensitive information, you can set a TTL (time-to-live) on them. In
your environment variables, set
AIRPLANE_TASK_OUTPUT_TTL_DAYS
to a positive
integer value, up to 90. Once the TTL is reached, the outputs will be permanently deleted from
Airplane's infrastructure. Values that are not multiples of 10 will be rounded up.Log output protocol
Log output protocol
Under the hood, Airplane captures task output by filtering stdout lines that start with either
airplane_output_set
or airplane_output_append
. In other words, returning from a main function,
or calling any of the SDK output functions, generates an airplane_output_set
or
airplane_output_append
line in stdout, which will be parsed by Airplane to obtain the final
task output.Examples of such log lines are provided below:
Copied1airplane_output_set {"id": 1, "name": "abc"}
Copied1airplane_output_append {"id": 1, "name": "abc"}2airplane_output_append {"id": 2, "name": "def"}
For languages that do not have a SDK provided (e.g. Docker image, shell), printing such log lines to
stdout is the recommended method for generating output from a task.
To produce an output that is a string, ensure the string value is wrapped in double quotes.
Copied1// This will work2airplane_output_set "value"34// This will not work5airplane_output_set value
SDK output methods
SDK output methods
If more complicated output structures are needed, the Airplane SDK provides functions for modifying
the output incrementally over the course of the task.
Set output
Set output
You can set the entire output object by calling the appropriate SDK function:
typescriptCopied1import airplane from "airplane";23export default async function (params) {4airplane.setOutput("ABC");5airplane.setOutput("DEF");6// The final output value will be "DEF".7}
pythonCopied1import airplane23@airplane.task()4def my_task():5airplane.set_output("ABC")6airplane.set_output("DEF")7# The final output value will be "DEF".
Note that returning a value from a main function is equivalent to calling the corresponding set
output function at the end of task execution.
Append to output
Append to output
If your output is intended to be an array, instead of setting the entire output to be an array, you
can use the following:
typescriptCopied1import airplane from "airplane";23export default async function (params) {4airplane.appendOutput({ name: "Alabama", capital: "Montgomery" });5airplane.appendOutput({ name: "Alaska", capital: "Juneau" });6airplane.appendOutput({ name: "Arizona", capital: "Phoenix" });7}
pythonCopied1import airplane23@airplane.task()4def my_task():5airplane.append_output({"name": "Alabama", "capital": "Montgomery"})6airplane.append_output({"name": "Alaska", "capital": "Juneau"})7airplane.append_output({"name": "Arizona", "capital": "Phoenix"})
The resulting output will be
[{"name": "Alabama", "capital": "Montgomery"}, {"name": "Alaska", "capital": "Juneau"}, {"name": "Arizona", "capital": "Phoenix"}]
which will be displayed in the UI as:JSON paths
JSON paths
Setting and appending can also apply to a subset of the output instead of overwriting the entire
output or appending to the main array. The airplane output set and airplane output append commands
have an optional path parameter, which allows part of an existing output to be edited.
Copied1airplane_output_set {"k1": "v1", "k2": {"nested": "abc"}, "k3": [1, 2, 3]}2airplane_output_set:k1 "new value"34# Output is now: {"k1": "new value", "k2": {"nested": "abc"}, "k3": [1, 2, 3]}56# Note that these are all valid path syntaxes.7airplane_output_set:k2.nested "def"8airplane_output_set:k2["nested"] "def"9airplane_output_set:["k2"]["nested"] "def"10airplane_output_set:["k2"].nested "def"1112# Output is now: {"k1": "new value", "k2": {"nested": "def"}, "k3": [1, 2, 3]}1314airplane_output_set:k3[2] 41516# Output is now: {"k1": "new value", "k2": {"nested": "def"}, "k3": [1, 2, 4]}1718# Append also accepts paths.19airplane_output_append:k3 82021# Output is now: {"k1": "new value", "k2": {"nested": "def"}, "k3": [1, 2, 4, 8]}2223# If a set or append command encounters a null or undefined value along the24# path to the final output location, it automatically generates an empty object25# in the output, for convenience.26airplane_output_set:k4.new 52728# Output is now: {"k1": "new value", "k2": {"nested": "def"}, "k3": [1, 2, 4, 8], "k4": {"new": 5}}
The set/append output functions in the SDKs will help to automatically construct the JSON path for
you, from an array of path components. Refer to the documentation of each individual SDK for the
correct format.
Handling large output
Handling large output
Due to limitations of Docker/Kubernetes, output lines in stdout that are too long (greater than
approximately 16000 characters) may not be parsed correctly, so we provide a "chunking" mechanism to
split up such lines.
If you are using a SDK, this chunking is automatically handled for you.
Copied1airplane_chunk:key1 airplane_outp2airplane_chunk:key1 ut_set "Hello World!"3airplane_chunk_end:key145airplane_chunk:key2 airplane_outp6airplane_chunk:key2 ut_set "How ar7airplane_chunk:key2 e you?"8airplane_chunk_end:key2910(is equivalent to)1112airplane_output_set "Hello World!"13airplane_output_set "How are you?"
However, note that there is still a maximum possible size set on output lines, even with chunking
applied. If the size of all combined output line chunks is greater than 4MiB, the output will not be
processed, and a warning message will be included in the run logs instead. Note that this does not
affect any other part of the run (i.e. the run still can succeed) - only the output will be
affected.
Files
Files
Files can be directly returned from a task, and will be displayed in the UI as a
downloadable link. If the file is a text, csv, image, video, or audio file, it will also be rendered
inline in the UI.
typescriptCopied1import airplane from "airplane";23export default airplane.task(4{5slug: "return_image",6parameters: {7image_upload: "upload",8},9},10async (params) => {11return params.image_upload;12},13);
pythonCopied1import airplane23@airplane.task()4def return_image(image_upload: airplane.File):5return image_upload