Asset Declaration Reference
For the complete documentation index, see llms.txt. For a full content snapshot, see llms-full.txt. Append.mdto anykestra.io/docs/*URL for plain Markdown.
Available on:Enterprise EditionCloud
This reference describes the assets property you add to a task, the fields of an asset, and the ways a task can register assets.
For what assets are and when to use them, see Assets. For triggers, tasks, and functions that act on assets, see the asset tasks and triggers reference.
The assets property
Every task accepts an assets property:
| Property | Type | Default | Description |
|---|---|---|---|
inputs | List of asset references | — | Assets the task reads. Each entry needs an id. Add type to type an asset that doesn’t exist yet. |
outputs | List of assets | — | Assets the task creates or updates. Each entry needs an id and a type. |
enableAuto | Boolean | false | Accept assets emitted by the plugin or by script log lines. See Automatic emission. |
assetFailureBehavior | WARN, FAIL, or IGNORE | WARN | What happens to the task state when an asset can’t be written. See Failure behavior. |
Asset declarations are an Enterprise Edition feature. In the open-source edition, a flow with an assets property fails validation with the message assets are only available in Enterprise Edition.
Declare inputs and outputs
This flow loads raw orders into a reporting table. The task reads raw.orders and writes analytics.orders_daily:
id: build_orders_dailynamespace: company.data
tasks: - id: build_table type: io.kestra.plugin.jdbc.postgresql.Queries url: jdbc:postgresql://postgres:5432/analytics username: "{{ secret('POSTGRES_USER') }}" password: "{{ secret('POSTGRES_PASSWORD') }}" sql: | CREATE TABLE IF NOT EXISTS analytics.orders_daily AS SELECT order_date, count(*) AS orders, sum(amount) AS revenue FROM raw.orders GROUP BY order_date; assets: inputs: - id: raw.orders outputs: - id: analytics.orders_daily namespace: company.data type: io.kestra.plugin.ee.assets.Table displayName: Daily orders description: One row per day with order count and revenue. metadata: system: postgres database: analytics schema: analytics name: orders_daily owner: data-teamWhen build_table finishes:
- If the task succeeded, Kestra creates
analytics.orders_daily, or updates it if it exists. - If
raw.ordersdoesn’t exist, Kestra creates it as anio.kestra.core.models.assets.Externalplaceholder incompany.data. - Kestra records lineage from
raw.orderstoanalytics.orders_daily, fromraw.ordersto the flow, and from the flow toanalytics.orders_daily. - Kestra records a usage entry for both assets on this execution.
Rendering and task outputs
Kestra renders the assets block after the task finishes, so expressions in it can read the task’s own outputs with outputs.<task_id>.<output>. Use this when the resource name is only known at runtime:
- id: download type: io.kestra.plugin.core.http.Download uri: https://example.com/exports/latest.csv assets: outputs: - id: "exports.{{ execution.startDate | date('yyyyMMdd') }}" type: io.kestra.plugin.ee.assets.File metadata: path: "{{ outputs.download.uri }}"If an expression in inputs can’t be rendered, Kestra still processes outputs, and the reverse.
What happens when a task fails
When a task ends in FAILED, Kestra records lineage and usage for its inputs and outputs, and still creates placeholders for unknown inputs, but doesn’t create or update its output assets. A failed run never overwrites an asset’s metadata or updated timestamp.
A task with allowFailure: true that fails ends in WARNING instead, so Kestra writes its output assets.
Asset fields
An asset in outputs has these fields:
| Field | Required | Description |
|---|---|---|
id | Yes | Unique within the tenant. Starts with a letter or digit, then letters, digits, ., _, :, or -. Up to 150 characters. |
type | Yes | A built-in type or any custom string. |
namespace | No | Namespace the asset belongs to, used for filtering and access control. Lowercase letters, digits, ., _, and -. See Namespace and Global assets. |
displayName | No | Human-readable name shown in the UI |
description | No | Markdown description |
metadata | No | Map of custom key-value pairs |
Any other key you add to an output entry is stored in metadata. For example, system: postgres at the top level of the entry and metadata.system: postgres produce the same asset.
Identity
Kestra identifies an asset by its id within a tenant. The namespace and the type aren’t part of the identity. Two declarations with the same id always refer to the same asset, even in different namespaces or flows.
Update rules
When a task writes an asset that already exists, Kestra merges the new declaration into the stored asset:
| Field | Rule |
|---|---|
type | The declared type replaces the stored one. An External placeholder never replaces a real type. |
namespace | A declared namespace replaces the stored one. An omitted namespace keeps the stored one. |
displayName, description | A declared value replaces the stored one. An omitted value keeps the stored one. |
metadata | Merged key by key. Declared keys replace stored keys. Keys you don’t declare are kept. A flow can’t remove a key; to remove one, edit the asset in the UI or through the API. |
Each write updates the asset’s updated timestamp and emits an UPDATED event, even when no field changed.
Namespace and Global assets
The namespace controls who can see and change an asset. Users with namespace-scoped ASSET permissions only see assets in the namespaces they have access to.
An asset without a namespace is a Global asset. Namespace-level access control doesn’t apply to Global assets: any user with an ASSET permission in the tenant can see them. Use Global assets for resources that every namespace should be able to reference.
How the namespace is set depends on where the asset comes from:
| Source | Namespace when you omit it |
|---|---|
New asset in outputs | None. The asset is created as a Global asset. |
Existing asset in outputs | The stored namespace is kept. |
Placeholder created from inputs | The namespace of the flow that references it. |
| New asset emitted by a plugin | None. Plugins emit assets without a namespace, so they’re created as Global assets. |
A flow can’t send an explicit empty namespace. Omitting namespace on a new output is the only way to create a Global asset from a flow.
Cross-namespace writes
When a flow writes an asset in another namespace, Kestra checks that the flow’s namespace is allowed to access the target namespace. The check applies to the asset’s current namespace and, when it changes, to the new one. It also applies when a typed input retypes an existing asset. If the check fails, the write is rejected and the failure behavior applies.
Namespaces allow all others by default, so the check only blocks a write when the target namespace restricts its allowed namespaces. To allow the write, add the flow’s namespace to that list. Global assets aren’t checked.
Typed inputs
An input entry can include a type. If the asset doesn’t exist yet, Kestra creates it with that type instead of External. If it exists with another type, Kestra changes its type, subject to locks and the cross-namespace check.
assets: inputs: - id: warehouse.raw.customers type: io.kestra.plugin.ee.assets.TableBuilt-in asset types
| Type | Common metadata keys |
|---|---|
io.kestra.plugin.ee.assets.Table | system, database, schema, name |
io.kestra.plugin.ee.assets.Dataset | system, location, format |
io.kestra.plugin.ee.assets.File | system, path |
io.kestra.plugin.ee.assets.VM | provider, region, state |
io.kestra.core.models.assets.External | None. Kestra assigns this type to placeholders created from untyped inputs. |
The metadata keys are conventions. Kestra stores them in metadata like any other key, and the dependencies graph uses system and schema for grouping.
You can also use any custom string as a type, such as AWS_BUCKET or company.infra.LoadBalancer. Custom types behave like built-in types, except that no plugin emits them unless the plugin defines them. Some plugins define their own types, for example io.kestra.plugin.ee.assets.HelmRelease.
Automatic emission
Some plugins detect the resources a task reads and writes and emit assets themselves. Script tasks can do the same by printing a special log line. In both cases, set assets.enableAuto: true on the task. Without it, Kestra discards emitted assets.
Some plugins log that they emitted assets even when enableAuto is off, while Kestra records nothing. If a task logs asset emission but no asset appears, check that the task has assets.enableAuto: true.
Plugins emit assets without a namespace, so a new emitted asset is a Global asset. To move it into a namespace, declare the same ID in a task’s outputs with a namespace. Later emissions keep that namespace.
Emitted assets and declared assets combine. You can turn on enableAuto and also declare inputs or outputs, for example to connect a plugin’s outputs to upstream assets the plugin can’t see.
Plugins that emit assets
| Plugin and tasks | Emits | Asset IDs |
|---|---|---|
JDBC Query and Queries (all databases) | One Table output per CREATE TABLE statement. Query emits at most one. No inputs. | <database>.<table>. The schema isn’t part of the ID. |
dbt DbtCLI, dbt Cloud CheckStatus, and dbt Cloud TriggerRun when wait is true | One Table output per model, seed, and snapshot, with its parents and sources as Table inputs. Adds test results as dbtTestStatus (pass, fail, or warn), dbtTestsTotal, and dbtTestsFailed when parseRunResults is true. | <database>.<schema>.<name> |
Fivetran connectors.Sync and connectors.Status | One Dataset output per connector schema and one Table output per enabled table | Tables: <database>.<schema>.<table> |
Google Cloud BigQuery RunTransferConfig | One Table output for the destination table, or one Dataset output when the table isn’t fixed. Emitted only after a successful run. | <project>.<dataset>.<table> or <project>.<dataset> |
Hex projects.Run | One Dataset output for the project, with system: hex. No inputs. | The projectId |
Qlik Cloud apps.Reload | One io.kestra.plugin.qlikcloud.assets.App output for the reloaded app. Set the task’s emitAssets to false to turn this off. | The app ID |
Ansible AnsibleCLI | One VM input per host in an INI inventory file named exactly inventory.ini at the working directory root | The host name |
Helm Upgrade, Rollback, Status, and Uninstall | One io.kestra.plugin.ee.assets.HelmRelease output, with the chart and valuesFrom files as inputs, plus one io.kestra.plugin.ee.assets.KubernetesResource per managed resource. Uninstall deletes these assets. | Built from the cluster, Kubernetes namespace, and release name |
dbt and Fivetran use the same <database>.<schema>.<table> ID format, so a Fivetran table and the dbt source that reads it join into one lineage chain. JDBC IDs leave out the schema, so they don’t join with dbt or Fivetran IDs. To connect them, declare the matching IDs yourself.
Plugins that only emit outputs, such as Hex and Qlik Cloud, can’t see their upstream tables. Declare assets.inputs on those tasks with the IDs the upstream plugins emit:
- id: reload_dashboard type: io.kestra.plugin.qlikcloud.apps.Reload tenantUrl: https://mytenant.eu.qlikcloud.com apiKey: "{{ secret('QLIK_API_KEY') }}" appId: 60f2e3b1a1b2c3d4e5f6a7b8 assets: enableAuto: true inputs: - id: analytics.marts.fct_orders type: io.kestra.plugin.ee.assets.TableFor each plugin’s properties, see its page in the plugin catalog. For a complete dbt example, see Track lineage across a dbt pipeline.
Emit assets from a script
A script task can emit assets by printing a line in this format to standard output:
::{"assets": {"inputs": [{"id": "<input id>"}], "outputs": [{"id": "<output id>", "type": "<type>"}]}}::The task must have assets.enableAuto: true. Each output entry accepts the same fields as a declared output.
This Shell task writes a CSV export and emits it as an asset that reads from crm.contacts:
id: export_contactsnamespace: company.data
tasks: - id: export type: io.kestra.plugin.scripts.shell.Commands taskRunner: type: io.kestra.plugin.core.runner.Process outputFiles: - contacts.csv commands: - printf 'id,email\n1,ada@example.com\n' > contacts.csv - | echo '::{"assets": {"inputs": [{"id": "crm.contacts"}], "outputs": [{"id": "exports.contacts_csv", "type": "io.kestra.plugin.ee.assets.File", "metadata": {"path": "contacts.csv"}}]}}::' assets: enableAuto: trueIn Python, the Kestra library sends the same structure with Kestra.assets(). Add kestra to the task’s dependencies:
- id: export_report type: io.kestra.plugin.scripts.python.Script dependencies: - kestra assets: enableAuto: true script: | from kestra import Kestra
Kestra.assets({ "outputs": [ { "id": "reports.weekly_revenue", "type": "io.kestra.plugin.ee.assets.File", "metadata": {"path": "s3://company-reports/weekly_revenue.csv"}, } ] })Failure behavior
Writing an asset can fail after the task itself succeeds, for example when the asset is locked, when the cross-namespace check rejects the write, or when an expression in the assets block can’t be rendered. assetFailureBehavior decides what happens to the task state:
| Value | Effect |
|---|---|
WARN | A successful task ends in WARNING. This is the default. |
FAIL | The task ends in FAILED. |
IGNORE | The task state doesn’t change. The failure only appears in the task logs. |
Kestra tries to write every declared asset before it applies the failure behavior, so one failed asset doesn’t stop the others. The task’s allowFailure and allowWarning properties still apply after the escalation. A task that already failed keeps its failed state.
assets: assetFailureBehavior: FAIL outputs: - id: finance.ledger type: io.kestra.plugin.ee.assets.TableAn invalid declaration is different. If a rendered asset breaks a field rule, for example an id with a space, the task fails regardless of assetFailureBehavior.
The value accepts expressions.
Next steps
Was this page helpful?