Action Parameters
In DeltaFi, Actions are the building blocks of flows. Each action in the flow can be configured with a known set of parameters that can tailor the action behavior for the flow. These parameters can be set in three ways, with literal values, from plugin variables, or using templates to extract the value from the ActionInput being sent to the Action.
Literal Values
This is a hard coded value used directly in the action parameter. This should be used when you have a parameter that has a fixed value in the flow.
Example - hard code the format parameter to GZIP in the Decompress action
name: gzip-transform
type: TRANSFORM
description: Flow that expects gzipped data. Decompresses and publishes the data.
subscribe:
- topic: gzipped-file
transformActions:
- name: Decompress
type: org.deltafi.core.action.compress.Decompress
parameters:
format: GZIP
publish:
rules:
- topic: decompressed-filePlugin Variables
Plugin variables are typed values that can be referenced in action parameters by name. They allow operators to change an action parameter value at runtime as well as reuse a value across actions. Action parameters should be set with a plugin variable when the value needs to be reconfigurable. To use a plugin variable in your action parameter, you wrap the name of the variable in ${}.
Example - use a plugin variable named egressUrl in url parameter of the HttpEgress action
---
name: example-data-sink
type: DATA_SINK
description: An example data sink
subscribe:
- topic: output-topic
egressAction:
name: HttpPost
type: org.deltafi.core.action.egress.HttpEgress
parameters:
url: ${egressUrl}
method: POST
metadataKey: deltafiMetadataParameter Templating
Parameter templating pulls information out of the current ActionInput being sent to the Action into the action parameter. To use a template wrap the parameter value in {{ }}. Parameter templating should be used when parameters need to be adjusted dynamically based on the ActionInput.
The templates used in action parameters can include SpEL expressions which are evaluated to a string value. This gives operators the ability to do things like call methods against the values or filter/search through content and metadata. The template can be embedded inside a literal value, for example - https://api.service/{{ deltaFileName.toLowerCase() }} would evaluate to https://api.service/input.txt when the DeltaFileName is Input.txt. Templates can also be used within the value of a plugin variable.
The following fields are available to use in a parameter template.
| Field | Description |
|---|---|
{{ deltaFileName }} | The name of the DeltaFile |
{{ did }} | The did of the DeltaFile |
{{ metadata }} | The metadata from the first DeltaFileMessage |
{{ content }} | The content list from the first DeltaFileMessage |
{{ actionContext }} | The current ActionContext (see Action Contex for information about the subfields) |
{{ deltaFileMessages }} | The full list of DeltaFileMessages, useful for joins |
Template Fields Example
Example - use templating to pull out various pieces of information about the DeltaFile being sent to an action
---
name: params-to-content
type: TRANSFORM
description: Sample flow that extracts DeltaFile information into action parameters
subscribe:
- topic: params-to-content
transformActions:
- name: AddMetadata
type: org.deltafi.core.action.metadata.ModifyMetadata
parameters:
addOrModifyMetadata:
type: ${dataType}
- name: ParamsToContent
type: org.deltafi.sample.actions.ParamsToContent
parameters:
contentParams:
did: "{{ did }}"
deltaFileName: "{{ deltaFileName }}"
type: "{{ metadata['type'] }}"
timestamp: "{{ now() }}"
dataSource: "{{ actionContext.dataSource }}"
processedBy: "{{ actionContext.flowName + '.' + actionContext.actionName }}"
contentInfo:
name: "{{ content[0].name }}"
mediaType: "{{ content[0].mediaType }}"
size: "{{ content[0].getSize() }}"
publish:
rules:
- topic: deltafile-param-contentHelper Methods
In addition to the fields above, the following helper methods are available inside {{ }} templates. All helpers are null-safe: passing a missing metadata value (e.g. metadata['absent']) returns null rather than throwing. Helpers may be composed (e.g. {{ sha256(base64Decode(metadata['signed'])) }}).
Time & Identity
| Method | Returns | Description |
|---|---|---|
now() | String | Current timestamp in ISO-8601 format; date.now() gives a date to format or convert (see Dates) |
timestamp() | String | Current time as epoch milliseconds |
uuid() | String | A new random (v4) UUID |
Encoding & Decoding
| Method | Returns | Description |
|---|---|---|
base64Encode(value) | String | Standard Base64 encoding of the UTF-8 bytes |
base64Decode(value) | String | Decode standard Base64 to a UTF-8 string |
base64UrlEncode(value) | String | URL-safe Base64 encoding, no padding |
base64UrlDecode(value) | String | Decode URL-safe Base64 to a UTF-8 string |
hexEncode(value) | String | Lowercase hex encoding of the UTF-8 bytes |
hexDecode(value) | String | Decode hex to a UTF-8 string (errors on invalid hex) |
urlEncode(value) | String | Percent-encode for inclusion in a URL |
urlDecode(value) | String | Decode a percent-encoded URL string |
Hashing
| Method | Returns | Description |
|---|---|---|
sha256(value) | String | Lowercase hex SHA-256 digest |
sha512(value) | String | Lowercase hex SHA-512 digest |
sha1(value) | String | Lowercase hex SHA-1 digest |
md5(value) | String | Lowercase hex MD5 digest |
Escaping
| Method | Returns | Description |
|---|---|---|
escapeJson(value) | String | Escape characters for safe embedding in JSON string literals |
escapeXml(value) | String | Escape characters for safe embedding in XML/HTML content |
escapeHtml(value) | String | Escape characters for safe embedding in HTML |
String Fallbacks
| Method | Returns | Description |
|---|---|---|
defaultIfBlank(value, fallback) | String | Returns value if it is non-blank, otherwise fallback |
coalesce(v1, v2, ...) | String | Returns the first non-blank argument, or null if all are blank |
Examples
Example - hash a customer id, base64-encode a metadata value, fall back through a chain of optional keys
---
name: hash-and-encode
type: TRANSFORM
description: Demonstrates the SpEL helper methods
subscribe:
- topic: incoming
transformActions:
- name: TagMetadata
type: org.deltafi.core.action.metadata.ModifyMetadata
parameters:
addOrModifyMetadata:
customer_hash: "{{ sha256(metadata['customer_id']) }}"
url_safe_name: "{{ urlEncode(metadata['display_name']) }}"
decoded_token: "{{ base64Decode(metadata['signed_token']) }}"
tracking_id: "{{ uuid() }}"
category: "{{ coalesce(metadata['category'], metadata['legacy_category'], 'uncategorized') }}"
json_payload: "{{ escapeJson(metadata['user_message']) }}"
publish:
rules:
- topic: taggedDates
The date. functions create dates. A date is a java.time ZonedDateTime, so once you have one, formatting, comparing, adding time, and changing zones use its own methods, and wherever those methods take a pattern, zone, unit, amount, or adjustment you can pass a string. A date used directly in a template renders as ISO-8601 with its offset. Those methods behave exactly as in Java; see the ZonedDateTime javadoc for details such as how arithmetic handles daylight saving.
Date Functions
| Function | Description |
|---|---|
date.parse(value) | Read ISO-8601 text (2026-10-06, 2026-10-06T14:30:00, 2026-10-06T10:30:00-04:00). The date keeps the value's own offset; a value without one is UTC |
date.parse(value, pattern) | Read text with a DateTimeFormatter pattern, or a formatter from date.pattern(...), date.iso(), or date.rfc1123(). Optional sections accept more than one layout ('[MM/dd/yyyy][yyyy-MM-dd]'). A formatter's zone (.withZone('America/New_York')) is the zone for values without an offset |
date.fromEpochSeconds(value) / date.fromEpochMillis(value) | Read seconds or milliseconds since the epoch, as text or a number, so epoch math works. Decimals are allowed; anything below a nanosecond is dropped, rounding down |
date.builder() | Build a date from separate whole-number values, leading zeros optional: .year(), then .month().day() or .dayOfYear(), then optionally a time, then .build(). A time is either .hour().minute().second(), optionally ending with one of .milliOfSecond(), .microOfSecond(), .nanoOfSecond(), or a count since midnight with one of .secondOfDay(), .milliOfDay(), .microOfDay(), .nanoOfDay() (decimals allowed). Blank time values are 0; .zone(...) sets the zone the values are a wall-clock time in (UTC by default) |
date.now() | The current time in UTC; .withZoneSameInstant('America/New_York') for another zone |
date.pattern(pattern) | A formatter for parsing or formatting, with English names and ISO-8601 weeks. Adjust it with .withZone('America/New_York') or .withLocale('fr'); a locale changes names and week rules |
date.iso() | The ISO-8601 formatter, e.g. .format(date.iso()) |
date.rfc1123() | The RFC 1123 formatter of HTTP and email headers (Tue, 6 Oct 2026 14:30:00 GMT). Parsing keeps the value's offset; formatting always writes GMT |
date.toEpochMillis(d) / date.toEpochMicros(d) / date.toEpochNanos(d) | Whole units since the epoch, rounding down (nanoseconds fit dates up to 2262). For seconds use d.toEpochSecond() |
Date Methods
Common date methods:
| Method | Description |
|---|---|
.format('yyyy-MM-dd') | Text in a DateTimeFormatter pattern, or .format(date.iso()) |
.withZoneSameInstant('UTC') | The same instant in another zone |
.plus('P1D') / .minus('PT2H30M') | Add or subtract an ISO-8601 amount: P1D, P1M, P1Y are calendar amounts, PT2H, PT90M exact ones. .plusDays(1), .minusHours(2), and similar also work |
.truncatedTo('hours') | Drop smaller fields, up to 'days' |
.with('firstDayOfMonth') | One of firstDayOfMonth, lastDayOfMonth, firstDayOfNextMonth, firstDayOfYear, lastDayOfYear, firstDayOfNextYear, startOfWeek (Monday), endOfWeek (Sunday) |
.isBefore(other) / .isAfter(other) | Compare two dates |
.getDayOfWeek(), .getYear(), .toEpochSecond(), … | Fields and other ZonedDateTime methods |
Parsing Rules
Input is parsed strictly, and a problem fails the action with a parameter error rather than being adjusted or guessed: impossible dates (02/30/2026, month 13, day of year 366 outside a leap year), local times without an offset that daylight saving skips or repeats (e.g. 01:30 on a fall-back day), values whose offset and zone disagree (10:30-04:00[America/Chicago]), and zone names (z, v) such as EST in a pattern used for parsing; use XXX for an offset (-05:00), O for GMT-based text such as HTTP dates (GMT, GMT-5), or VV for a zone ID. A missing or blank zone is an error rather than UTC. Years are taken as given, so 26 is year 26, not 2026. A missing or blank value parses to null, so use ?. to continue (date.parse(metadata['ts'])?.format('yyyy')); date.builder() requires a year, month, and day (or day of year) and must end with .build().
Date Examples
# 2026-10-06T10:30:00-04:00 -> 2026-10-06
day: "{{ date.parse(metadata['iso']).format('yyyy-MM-dd') }}"
# 10/06/2026 10:30 AM -> 2026-10-06T14:30:00Z
as_utc: "{{ date.parse(metadata['received'], date.pattern('MM/dd/yyyy hh:mm a').withZone('America/New_York')).withZoneSameInstant('UTC') }}"
# 1791297000 -> 20261006
event_date: "{{ date.fromEpochSeconds(metadata['ts']).withZoneSameInstant('America/Chicago').format('yyyyMMdd') }}"
# 2026-10-06T10:30:00-04:00 -> 1791297000000
epoch_ms: "{{ date.toEpochMillis(date.parse(metadata['eventTime'])) }}"
# yesterday's date
yesterday: "{{ date.now().minus('P1D').format('yyyy-MM-dd') }}"
# 2026-10-06T10:30:00-04:00 -> 2026-10-01T00:00:00-04:00
month_start: "{{ date.parse(metadata['iso']).with('firstDayOfMonth').truncatedTo('days') }}"
# a past expiry -> expired
status: "{{ date.parse(metadata['expires']).isBefore(date.now()) ? 'expired' : 'active' }}"
# Tue, 6 Oct 2026 14:30:00 GMT -> 2026-10-06T14:30:00Z
last_modified: "{{ date.parse(metadata['lastModified'], date.rfc1123()) }}"
# 06 octobre 2026 -> 2026-10-06
from_french: "{{ date.parse(metadata['fr'], date.pattern('dd MMMM yyyy').withLocale('fr')).format('yyyy-MM-dd') }}"
# year=2026 month=4 day=1 hour=2 -> 2026-04-01T02:00:00-04:00
timestamp: "{{ date.builder().year(metadata['year']).month(metadata['month']).day(metadata['day']).hour(metadata['hour']).zone('America/New_York').build() }}"
# year=2026 doy=279 nanos=52215123456789 -> 2026-10-06T14:30:15.123456789Z
ordinal: "{{ date.builder().year(metadata['year']).dayOfYear(metadata['doy']).nanoOfDay(metadata['nanos']).build() }}"Pattern Pitfalls
Pattern letters that are easy to mix up:
yyyyis the calendar year;YYYYis the week-based year, which differs around New Year (2027-01-01formats as2026-01-01withYYYY). Week-based letters (YYYY,ww,e) follow ISO-8601 week rules: weeks start on Monday, and week 1 is the one containing the year's first Thursday, so2027-01-01is2026-W53-5.yyreads two-digit years as 2000–2099, so99becomes2099. A singleyaccepts any number of digits and takes them literally, so26becomes year0026; useyyyyfor four-digit years.HHis the 24-hour clock;hhis the 12-hour clock and needsa(AM/PM) alongside it. An input pattern whose time fields don't form a complete time, such ashh:mmwithouta, fails rather than defaulting to midnight.
Nested Parameter Structures
Action parameters can contain nested objects and lists to organize complex configurations. Use @JsonPropertyDescription on nested class fields to provide descriptions that appear in the UI.
Defining Nested Parameters (Java)
package org.deltafi.myaction;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import lombok.*;
import org.deltafi.actionkit.action.parameters.ActionParameters;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
public class MyActionParameters extends ActionParameters {
@JsonPropertyDescription("Primary endpoint configuration")
private EndpointConfig primaryEndpoint;
@JsonPropertyDescription("Optional fallback endpoints to try if primary fails")
private List<EndpointConfig> fallbackEndpoints;
@Data
public static class EndpointConfig {
@JsonProperty(required = true)
@JsonPropertyDescription("The URL to connect to")
private String url;
@JsonPropertyDescription("Connection timeout in milliseconds")
private int timeoutMs = 30000;
@JsonPropertyDescription("Authentication settings for this endpoint")
private AuthConfig auth;
}
@Data
public static class AuthConfig {
@JsonPropertyDescription("Authentication type: NONE, BASIC, or BEARER")
private String type = "NONE";
@JsonPropertyDescription("Username for BASIC auth")
private String username;
@JsonPropertyDescription("Environment variable containing the password or token")
private EnvVar credential;
}
}Defining Nested Parameters (Python)
from pydantic import BaseModel, Field
from typing import List, Optional
class AuthConfig(BaseModel):
type: str = Field(default="NONE", description="Authentication type: NONE, BASIC, or BEARER")
username: Optional[str] = Field(default=None, description="Username for BASIC auth")
password: Optional[str] = Field(default=None, description="Password or token")
class EndpointConfig(BaseModel):
url: str = Field(description="The URL to connect to")
timeout_ms: int = Field(default=30000, description="Connection timeout in milliseconds")
auth: Optional[AuthConfig] = Field(default=None, description="Authentication settings for this endpoint")
class MyActionParameters(BaseModel):
primary_endpoint: EndpointConfig = Field(description="Primary endpoint configuration")
fallback_endpoints: List[EndpointConfig] = Field(default=[], description="Optional fallback endpoints to try if primary fails")Defining Nested Parameters (Go)
package actions
import actionkit "gitlab.com/deltafi/deltafi/deltafi-go-action-kit/v2"
type MyActionParams struct {
PrimaryEndpoint EndpointConfig `json:"primaryEndpoint" description:"Primary endpoint configuration" required:"true"`
FallbackEndpoints []EndpointConfig `json:"fallbackEndpoints" description:"Optional fallback endpoints to try if primary fails"`
}
type EndpointConfig struct {
URL string `json:"url" description:"The URL to connect to" required:"true"`
TimeoutMs int `json:"timeoutMs" description:"Connection timeout in milliseconds" default:"30000"`
Auth *AuthConfig `json:"auth" description:"Authentication settings for this endpoint"`
}
type AuthConfig struct {
Type string `json:"type" description:"Authentication type: NONE, BASIC, or BEARER" default:"NONE"`
Username string `json:"username" description:"Username for BASIC auth"`
Credential actionkit.EnvVar `json:"credential" description:"Environment variable containing the password or token"`
}Defining Nested Parameters (C++)
#include <deltafi/plugin>
struct AuthConfig {
std::string type = "NONE";
std::string username;
deltafi::EnvVar credential;
DELTAFI_PARAMS(AuthConfig,
FIELD(type, "Authentication type: NONE, BASIC, or BEARER"),
FIELD(username, "Username for BASIC auth"),
FIELD(credential, "Environment variable containing the password or token"))
};
struct EndpointConfig {
std::string url;
int timeout_ms = 30000;
std::optional<AuthConfig> auth;
DELTAFI_PARAMS(EndpointConfig,
FIELD(url, "The URL to connect to"),
FIELD(timeout_ms, "Connection timeout in milliseconds"),
FIELD(auth, "Authentication settings for this endpoint"))
};
struct MyActionParams {
EndpointConfig primary_endpoint;
std::vector<EndpointConfig> fallback_endpoints;
DELTAFI_PARAMS(MyActionParams,
FIELD(primary_endpoint, "Primary endpoint configuration"),
FIELD(fallback_endpoints, "Optional fallback endpoints to try if primary fails"))
};Configuring Nested Parameters in Flows
name: my-flow
type: TRANSFORM
transformActions:
- name: CallAPI
type: org.deltafi.myaction.MyAction
parameters:
primaryEndpoint:
url: https://api.example.com/v1
timeoutMs: 5000
auth:
type: BASIC
username: myuser
credential: API_PASSWORD
fallbackEndpoints:
- url: https://backup.example.com/v1
timeoutMs: 10000The UI displays nested parameters with expandable sections. Each field shows its label and description (via the info icon) as defined in @JsonPropertyDescription.
Secure Parameters with EnvVar
Java, Go, and C++ Action Kits Only
The EnvVar type is currently supported in the Java, Go, and C++ Action Kits. Python support is planned for a future release.
The EnvVar type provides a secure way to reference sensitive values like passwords, API keys, and other credentials in action parameters. Instead of storing secrets directly in flow configurations (which are persisted and may be logged), you store the name of an environment variable that contains the secret. The actual secret value is resolved at runtime from the environment.
Why Use EnvVar?
Storing secrets directly in action parameters creates security risks:
- Flow configurations are stored in the database and may be visible in logs
- Secrets can be exposed when flows are exported or shared
- There's no clear separation between configuration and secrets management
With EnvVar, only the environment variable name (e.g., MY_API_KEY) is stored in the flow configuration. The actual secret is:
- Stored securely in Kubernetes secrets or another secrets manager
- Injected into the plugin's environment at runtime
- Never persisted in DeltaFi's database or logs
Defining an EnvVar Parameter
In your action's parameter class, use the EnvVar type for any field that should reference a secret:
package org.deltafi.myaction;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import lombok.*;
import org.deltafi.actionkit.action.parameters.ActionParameters;
import org.deltafi.actionkit.action.parameters.EnvVar;
@Data
@EqualsAndHashCode(callSuper = true)
public class MyActionParameters extends ActionParameters {
@JsonPropertyDescription("Environment variable containing the API key")
private EnvVar apiKeyEnvVar = new EnvVar();
@JsonPropertyDescription("Environment variable containing the password")
private EnvVar passwordEnvVar = new EnvVar();
}Resolving EnvVar Values
In your action implementation, resolve the environment variable to get the actual secret value:
@Override
public Result myAction(ActionContext context, MyActionParameters params, MyInput input) {
// resolve() throws IllegalStateException if the env var is not set
String apiKey = params.getApiKeyEnvVar().resolve();
// resolveOrDefault() returns the default if the env var is not set
String password = params.getPasswordEnvVar().resolveOrDefault(null);
// isSet() checks if an env var name has been configured
if (params.getPasswordEnvVar().isSet()) {
// use the password
}
// ... use the resolved values
}Defining and Resolving EnvVar in Go
In Go, use the actionkit.EnvVar type in your parameter struct:
type MyActionParams struct {
APIKeyEnvVar actionkit.EnvVar `json:"apiKeyEnvVar" description:"Environment variable containing the API key"`
PasswordEnvVar actionkit.EnvVar `json:"passwordEnvVar" description:"Environment variable containing the password"`
}The EnvVar type resolves the environment variable value at runtime:
func (a *MyAction) Transform(df *actionkit.Deltafile, params MyActionParams) *actionkit.Deltafile {
// Resolve() returns the value of the named environment variable
apiKey, err := params.APIKeyEnvVar.Resolve()
if err != nil {
return df.Errorf("Failed to resolve API key").SetErrorContext(err.Error())
}
// ResolveOrDefault() returns the default if the env var is not set
password := params.PasswordEnvVar.ResolveOrDefault("")
// IsSet() checks if an env var name has been configured
if params.PasswordEnvVar.IsSet() {
// use the password
}
// ... use the resolved values
}Defining and Resolving EnvVar in C++
In C++, use the deltafi::EnvVar type in your parameter struct:
struct MyActionParams {
deltafi::EnvVar api_key_env_var;
deltafi::EnvVar password_env_var;
DELTAFI_PARAMS(MyActionParams,
FIELD(api_key_env_var, "Environment variable containing the API key"),
FIELD(password_env_var, "Environment variable containing the password"))
};The EnvVar type resolves the environment variable value at runtime:
// resolve() throws if the env var is not set
auto api_key = params.api_key_env_var.resolve();
// resolve_or_default() returns the default if the env var is not set
auto password = params.password_env_var.resolve_or_default("");
// is_set() checks if an env var name has been configured
if (params.password_env_var.is_set()) {
// use the password
}Configuring EnvVar in Flows
When configuring a flow, specify just the environment variable name (in UPPER_SNAKE_CASE):
name: my-secure-flow
type: TRANSFORM
transformActions:
- name: CallSecureAPI
type: org.deltafi.myaction.MyAction
parameters:
apiKeyEnvVar: MY_API_KEY
passwordEnvVar: DB_PASSWORDThe UI provides a specialized editor for EnvVar fields that:
- Displays a warning reminding operators to enter the variable name, not the secret value
- Validates that names follow the UPPER_SNAKE_CASE convention
- Shows the
$prefix to indicate this is an environment variable reference
Setting Up Environment Variables
Environment variables referenced by EnvVar parameters must be available to the plugin at runtime. See Plugin Environment Variables for how to configure deltafi.pluginEnv in your site values.
TIP
For production environments, consider using external secrets management solutions like HashiCorp Vault, AWS Secrets Manager, or Kubernetes External Secrets Operator to avoid storing secrets directly in your configuration files.
Backward Compatibility Pattern
When migrating an existing action to use EnvVar, you can support both the old direct value and the new environment variable reference:
@Data
@EqualsAndHashCode(callSuper = true)
public class MyActionParameters extends ActionParameters {
@JsonPropertyDescription("Environment variable containing the password")
private EnvVar passwordEnvVar = new EnvVar();
@Deprecated
@JsonPropertyDescription("The password (DEPRECATED: use passwordEnvVar instead)")
private String password;
/**
* Gets the password from the environment variable or falls back to the deprecated field.
* @throws IllegalStateException if passwordEnvVar is configured but the env var is not set
*/
public String resolvePassword() {
if (passwordEnvVar.isSet()) {
return passwordEnvVar.resolve(); // Throws if env var not found
}
return password; // Fall back to deprecated field only if EnvVar not configured
}
}This allows existing flows using the deprecated password field to continue working while encouraging migration to the more secure passwordEnvVar approach. Note that if passwordEnvVar is configured but the environment variable doesn't exist, an error is thrown rather than silently falling back to the deprecated field.

