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

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

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.
typescript
Copied
1
export default async function (params) {
2
return { id: 123, name: "abc" };
3
}
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.
shell
Copied
1
#!/bin/bash
2
3
data='{"id": 123, "name": "abc"}'
4
echo "airplane_output_set ${data}"
For more information about creating task outputs via stdout, see the Advanced section below.

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

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

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:
typescript
Copied
1
import airplane from "airplane";
2
3
export const catsTask = airplane.task(
4
{
5
slug: "cats",
6
name: "Cats task",
7
outputDisplay: { type: "descriptionList" },
8
},
9
async function (params) {
10
return [
11
{ term: "Mittens", description: "Maine coon" },
12
{ term: "Hamilton", description: "Ragdoll" },
13
{ term: "Whiskers", description: "British shorthair" },
14
];
15
},
16
);
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.
typescript
Copied
1
import airplane from "airplane";
2
3
export const catsTask = airplane.task(
4
{
5
slug: "cats",
6
name: "Cats task",
7
outputDisplay: {
8
type: "descriptionList",
9
value: "{{ output.map((o) => ({term: o.name.toUpperCase(), description: o.breed}) ) }}",
10
},
11
},
12
async function (params) {
13
return [
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
);

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.
typescript
Copied
1
import airplane from "airplane";
2
3
export const listTeamsTask = airplane.task(
4
{
5
slug: "list_teams",
6
name: "List teams",
7
outputDisplay: {
8
type: "table",
9
actions: [
10
{
11
page: "team_inspector",
12
label: "Open team inspector",
13
as: "center_peek",
14
},
15
],
16
rowActions: [
17
{
18
task: "update_revenue",
19
label: "Update revenue",
20
paramValues: {
21
id: "{{row.id}}",
22
revenue: "{{row.revenue}}",
23
},
24
},
25
],
26
},
27
},
28
...

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

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

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:
Copied
1
airplane_output_set {"id": 1, "name": "abc"}
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.
Copied
1
// This will work
2
airplane_output_set "value"
3
4
// This will not work
5
airplane_output_set value

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

You can set the entire output object by calling the appropriate SDK function:
typescript
Copied
1
import airplane from "airplane";
2
3
export default async function (params) {
4
airplane.setOutput("ABC");
5
airplane.setOutput("DEF");
6
// The final output value will be "DEF".
7
}
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

If your output is intended to be an array, instead of setting the entire output to be an array, you can use the following:
typescript
Copied
1
import airplane from "airplane";
2
3
export default async function (params) {
4
airplane.appendOutput({ name: "Alabama", capital: "Montgomery" });
5
airplane.appendOutput({ name: "Alaska", capital: "Juneau" });
6
airplane.appendOutput({ name: "Arizona", capital: "Phoenix" });
7
}
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

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.
Copied
1
airplane_output_set {"k1": "v1", "k2": {"nested": "abc"}, "k3": [1, 2, 3]}
2
airplane_output_set:k1 "new value"
3
4
# Output is now: {"k1": "new value", "k2": {"nested": "abc"}, "k3": [1, 2, 3]}
5
6
# Note that these are all valid path syntaxes.
7
airplane_output_set:k2.nested "def"
8
airplane_output_set:k2["nested"] "def"
9
airplane_output_set:["k2"]["nested"] "def"
10
airplane_output_set:["k2"].nested "def"
11
12
# Output is now: {"k1": "new value", "k2": {"nested": "def"}, "k3": [1, 2, 3]}
13
14
airplane_output_set:k3[2] 4
15
16
# Output is now: {"k1": "new value", "k2": {"nested": "def"}, "k3": [1, 2, 4]}
17
18
# Append also accepts paths.
19
airplane_output_append:k3 8
20
21
# Output is now: {"k1": "new value", "k2": {"nested": "def"}, "k3": [1, 2, 4, 8]}
22
23
# If a set or append command encounters a null or undefined value along the
24
# path to the final output location, it automatically generates an empty object
25
# in the output, for convenience.
26
airplane_output_set:k4.new 5
27
28
# 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

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.
Copied
1
airplane_chunk:key1 airplane_outp
2
airplane_chunk:key1 ut_set "Hello World!"
3
airplane_chunk_end:key1
4
5
airplane_chunk:key2 airplane_outp
6
airplane_chunk:key2 ut_set "How ar
7
airplane_chunk:key2 e you?"
8
airplane_chunk_end:key2
9
10
(is equivalent to)
11
12
airplane_output_set "Hello World!"
13
airplane_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 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.
typescript
Copied
1
import airplane from "airplane";
2
3
export default airplane.task(
4
{
5
slug: "return_image",
6
parameters: {
7
image_upload: "upload",
8
},
9
},
10
async (params) => {
11
return params.image_upload;
12
},
13
);