Skip to content
Talk to our solutions team

Data and templating

Databases, structured data, templating and pipeline variables. Every parameter with its type, default and description, plus worked examples.

Database operations for PostgreSQL, ClickHouse, and DuckDB.

ParameterTypeRequiredDefaultDescription
driverstringYes-Driver: postgres, clickhouse, duckdb
hoststringYes*-Database host
databasestringYes*-Database name
usernamestringNo-Database username
passwordstringNo-Database password (vault key or literal)
from_vaultboolNotrueWhether password is a vault key
pathstringNo-File path (for DuckDB)
readonlyboolNofalseRead-only mode (DuckDB)
ParameterTypeRequiredDefaultDescription
operationstringYes-Operation: query, exec, insert, update, delete, bulk
sqlstringNo-SQL query/statement
querieslistNo-Multiple SQL statements
argslistNo-Positional query arguments
tablestringNo-Table name (for insert/update/delete)
columnslistNo-Column names
valueslistNo-Values to insert
rowslistNo-Multiple rows for bulk insert
setmapNo-Column-value pairs for update
wherestringNo-WHERE clause
where_argslistNo-WHERE clause arguments
returninglistNo-Columns to return
on_conflictstringNo-ON CONFLICT clause (upsert)
batch_sizeintNo1000Batch size for bulk operations
setvarstringNo-Variable to store result
name: db-query
tasks:
- name: get-active-users
db:
driver: postgres
host: localhost
database: myapp
username: app
password: db-password-key
operation: query
sql: SELECT * FROM users WHERE active = $1 AND role = $2
args: [true, "admin"]
setvar: admins
name: db-insert
tasks:
- name: create-user
db:
driver: postgres
host: localhost
database: myapp
username: app
password: db-key
operation: insert
table: users
columns: [name, email, active]
values: ["John Doe", "[email protected]", true]
returning: [id]
setvar: new_user
name: db-bulk
tasks:
- name: insert-events
db:
driver: postgres
host: localhost
database: myapp
username: app
password: db-key
operation: bulk
table: events
columns: [event_type, user_id, data, timestamp]
rows:
- [page_view, 1, '{"page": "/home"}', "2024-01-01T10:00:00Z"]
- [click, 1, '{"element": "button"}', "2024-01-01T10:00:05Z"]
- [page_view, 2, '{"page": "/about"}', "2024-01-01T10:00:10Z"]
batch_size: 1000
name: duckdb-analysis
tasks:
- name: analyze
db:
driver: duckdb
operation: query
sql: |
SELECT category, SUM(amount) as total
FROM read_csv('/data/sales.csv')
GROUP BY category
ORDER BY total DESC
setvar: category_totals

Database connection and query functions.

FunctionDescription
db.connect()Establish database connection
db.query()Execute SELECT query
db.exec()Execute INSERT/UPDATE/DELETE
db.insert()Insert row(s) into table
db.update()Update rows in table
db.delete()Delete rows from table
db.bulk()Bulk insert operations
db.close()Close connection
ParameterTypeDefaultDescription
driverstring"postgres"Driver: postgres, mysql, sqlite, duckdb
hoststring"localhost"Database host
portint5432Database port
databasestring-Database name
usernamestring-Username
passwordstring-Password
sslModestring"disable"SSL mode
pathstring-File path (SQLite/DuckDB)
readOnlyboolfalseRead-only mode
ParameterTypeDescription
connectionIdstringConnection ID from connect()
sqlstringSQL query/statement
argsarrayPositional arguments
namedobjectNamed arguments
ParameterTypeDescription
connectionIdstringConnection ID
tablestringTable name
columnsarrayColumn names
valuesarrayValues to insert
setobjectColumn-value pairs (update)
wherestringWHERE clause
whereArgsarrayWHERE arguments
returningarrayColumns to return
// Connect to PostgreSQL
let conn = db.connect({
driver: "postgres",
host: "localhost",
database: "myapp",
username: "app",
password: "secret"
})
if (conn.success) {
// Query with parameters
let users = db.query({
connectionId: conn.connectionId,
sql: "SELECT * FROM users WHERE active = $1",
args: [true]
})
log("Found", users.rows.length, "users")
// Insert with returning
let newUser = db.insert({
connectionId: conn.connectionId,
table: "users",
columns: ["name", "email"],
values: ["John", "[email protected]"],
returning: ["id"]
})
log("Created user ID:", newUser.rows[0].id)
// Update
db.update({
connectionId: conn.connectionId,
table: "users",
set: { active: true, updated_at: "NOW()" },
where: "id = $1",
whereArgs: [newUser.rows[0].id]
})
// Bulk insert
db.bulk({
connectionId: conn.connectionId,
table: "events",
columns: ["type", "user_id", "data"],
rows: [
["login", 1, "{}"],
["click", 1, '{"button":"submit"}'],
["logout", 1, "{}"]
],
batchSize: 1000
})
// Close connection
db.close({ connectionId: conn.connectionId })
}
// DuckDB for analytics
let duck = db.connect({
driver: "duckdb",
path: "./analytics.db"
})
let stats = db.query({
connectionId: duck.connectionId,
sql: "SELECT COUNT(*) as total FROM events"
})
log("Total events:", stats.rows[0].total)
db.close({ connectionId: duck.connectionId })

Evaluate jq expressions against the workflow environment. Uses the gojq library for jq-compatible query evaluation.

ParameterTypeRequiredDefaultDescription
expressionstringYes*-JQ expression to evaluate against the workflow environment
setkeystringNo-Variable name to store the result (supports Liquid templates)
queriesstringNo-Path to a YAML file containing named query definitions
querystringNo-Name of a predefined query from the queries file

*Either expression or query (with queries) is required.

tasks:
- name: get-host
jq:
expression: ".config_host"
setkey: db_host
tasks:
- name: count-active
jq:
expression: "[.services[] | select(.active == true)] | length"
setkey: active_count
tasks:
- name: build-summary
jq:
expression: "{host: .db_host, port: .db_port, name: .db_name}"
setkey: db_summary
vars:
key_prefix: "result"
tasks:
- name: dynamic-key
jq:
expression: ".total"
setkey: "{{key_prefix}}_total"
list: true
tasks:
- name: extract-port
jq:
expression: ".app_config | fromjson | .port"
setkey: app_port
- name: start-app
shell: |
echo "Starting app on port {{app_port}}"

Define reusable queries in a YAML file:

## /etc/kis/queries.yaml
queries:
get-db-host:
description: "extract database host"
expression: ".config.database.host"
setkey: "db_host"
count-services:
description: "count running services"
expression: ".services | length"
setkey: "service_count"

Reference named queries in workflows:

tasks:
- name: get-host
jq:
queries: "/etc/kis/queries.yaml"
query: get-db-host
tasks:
- name: count-svcs
jq:
queries: "/etc/kis/queries.yaml"
query: count-services
setkey: total_services

JQ expression evaluation functions.

FunctionInputReturns
jq.query(){expression, data}{success, value, error}
// Query data with a jq expression
var result = jq.query({
expression: ".users | length",
data: {users: [{name: "alice"}, {name: "bob"}]}
});
console.log("User count: " + result.value);
// Extract nested values
var host = jq.query({
expression: ".config.database.host",
data: {config: {database: {host: "localhost", port: 5432}}}
});
console.log("DB host: " + host.value);
// Filter arrays
var active = jq.query({
expression: '[.[] | select(.status == "active") | .name]',
data: {items: [{name: "a", status: "active"}, {name: "b", status: "inactive"}]}
});
console.log("Active: " + JSON.stringify(active.value));

Liquid template rendering.

ParameterTypeRequiredDescription
datastringNoInline template string
filestringNoPath to template file
pathstringNoOutput file path
srcstringNoSource directory
deststringNoDestination directory
setvarstringNoVariable to store rendered output
name: liquid-inline
vars:
name: "World"
tasks:
- name: render
liquid:
data: "Hello, {{name}}!"
setvar: greeting
name: liquid-to-file
vars:
host: localhost
port: 8080
tasks:
- name: render-config
liquid:
data: |
server:
host: {{host}}
port: {{port}}
path: /etc/app/config.yaml
name: liquid-directory
tasks:
- name: process-all
liquid:
src: /templates
dest: /output

Liquid template rendering functions.

FunctionDescription
liquid.render()Render a template string or file
liquid.process()Process templates from directory
ParameterTypeDefaultDescription
datastring-Inline template string
filestring-Path to template file
pathstring-Base path for includes
varsobject-Variables for template
ParameterTypeDefaultDescription
sourcestring-Source directory
destinationstring-Output directory
includearray-Glob patterns to include
excludearray-Glob patterns to exclude
flattenboolfalseFlatten output structure
varsobject-Variables for templates
// Render inline template
let result = liquid.render({
data: "Hello, {{name}}! Your score is {{score}}.",
vars: {
name: "Alice",
score: 95
}
})
log(result.output) // "Hello, Alice! Your score is 95."
// Render from file
let config = liquid.render({
file: "/templates/config.yaml.liquid",
vars: {
host: "localhost",
port: 8080,
env: "production"
}
})
log(config.output)
// Process directory of templates
let processed = liquid.process({
source: "/templates",
destination: "/output",
include: ["**/*.liquid"],
exclude: ["**/partials/**"],
vars: {
version: "1.0.0",
buildDate: new Date().toISOString()
}
})
log("Processed", processed.processed.length, "templates")

Edit YAML and JSON files programmatically while preserving key order.

The task key names the format: yaml: for YAML files, json: for JSON. Both take the same parameters and the same commands: list — the examples below use yaml:, and swapping the key is the only change needed for a JSON file.

A third key, yamlx:, is registered for format-preserving YAML editing — it keeps comments and key order on write, which yaml: does not. Reach for it when a human also maintains the file.

Aliases: yaml, json

ParameterTypeRequiredDefaultDescription
pathstringYes-Path to YAML/JSON file to edit
outputstringNoSame as pathOutput file path
filetypestringNoAuto-detectFile type: yaml or json
setvarstringNo-Variable to store parsed content
commandslistNo-Edit commands to apply
CommandPropertiesDescription
setkey, value, expressionSet a value at key
appendkey, value, expressionAppend to array or merge into object
deletekey, expressionDelete a key

The expression property allows targeting nested maps by matching a key-value pair:

expression:
key: name # Find map where this key...
value: myapp # ...has this value
name: structured-set
tasks:
- name: update-config
yaml:
path: ./config.yaml
commands:
- type: set
key: version
value: "2.0.0"
- type: set
key: debug
value: false
name: structured-nested
vars:
db_host: "db.example.com"
tasks:
- name: update-database-config
yaml:
path: ./config.yaml
commands:
- type: set
key: database
value:
host: "{{db_host}}"
port: 5432
ssl: true
name: structured-expression
tasks:
- name: update-service
yaml:
path: ./docker-compose.yaml
commands:
- type: set
expression:
key: container_name
value: api-server
key: replicas
value: 3
name: structured-append
tasks:
- name: add-dependency
yaml:
path: ./package.yaml
commands:
- type: append
key: dependencies
value:
- lodash: "^4.17.21"
- axios: "^1.0.0"
name: structured-delete
tasks:
- name: remove-deprecated
yaml:
path: ./config.yaml
commands:
- type: delete
key: deprecated_feature
- type: delete
key: old_setting
name: structured-read
list: true
tasks:
- name: load-config
yaml:
path: ./config.yaml
setvar: config
- name: show-version
print:
message: "Version: {{config.version}}"
name: structured-json
tasks:
- name: update-package
json:
path: ./package.json
commands:
- type: set
key: version
value: "1.2.3"
- type: set
key: scripts.build
value: "tsc && vite build"

There is no direct namespace for the yaml:/json: tasks. In a script, parse and serialise instead:

const raw = file.read({ path: './config.yaml' });
const cfg = yaml.parse(raw.content);
cfg.replicas = 6;
file.write({ path: './config.yaml', content: yaml.stringify(cfg) });

For JSON, JSON.parse and JSON.stringify are already there.

Note the difference in what this preserves. The yamlx: task edits in place, keeping comments and key order; parsing and re-serialising does not. When a human also maintains the file, use the task.

Set workflow context variables. Supports literal values, file reading, and glob-based file reading.

ParameterTypeRequiredDescription
setvarstringNoVariable name (single variable mode)
valuestringNoLiteral value (single variable mode)
filestringNoFile path to read into setvar (YAML/JSON auto-parsed)
globstringNoGlob pattern — reads matching files into setvar as map
varsmapNoMap of variable name to literal value
filesmapNoMap of variable name to file path (YAML/JSON auto-parsed)
globsmapNoMap of variable name to glob pattern (YAML/JSON auto-parsed)

Or directly provide key-value pairs inline (legacy).

name: setvars-single
tasks:
- name: set-url
setvars:
setvar: api_url
value: "https://api.example.com"
name: setvars-file
tasks:
- name: read-cert
setvars:
setvar: ssl_cert
file: "/etc/ssl/certs/app.pem"
name: setvars-yaml
list: true
tasks:
- name: read-config
setvars:
setvar: app_config
file: "/etc/app/config.yaml"
- name: use-config
shell:
script: echo "DB host is {{app_config.database.host}}"
name: setvars-json
list: true
tasks:
- name: read-package
setvars:
setvar: pkg
file: "/app/package.json"
- name: use-package
shell:
script: echo "Package {{pkg.name}} v{{pkg.version}}"
name: setvars-glob
tasks:
- name: read-configs
setvars:
setvar: config_files
glob: "/etc/app/conf.d/*.conf"
name: setvars-multiple
tasks:
- name: set-config
setvars:
vars:
database_host: "localhost"
database_port: "5432"
name: setvars-files
tasks:
- name: read-certs
setvars:
files:
ssl_cert: "/etc/ssl/certs/app.pem"
ssl_key: "/etc/ssl/private/app.key"
name: setvars-globs
tasks:
- name: read-config-groups
setvars:
globs:
nginx_configs: "/etc/nginx/conf.d/*.conf"
app_configs: "/etc/app/conf.d/*.yaml"
name: setvars-inline
tasks:
- name: set-values
setvars:
username: "admin"
environment: "production"

Workflow variable functions.

FunctionDescription
vars.set()Set workflow context variables
vars.readFile()Read file content into variables (YAML/JSON auto-parsed)
vars.readGlob()Read files matching glob pattern into variables
// Set variables
vars.set({ vars: { api_url: "https://api.example.com", region: "us-east-1" } })
// Read a plain text file (stored as string)
let result = vars.readFile({ path: "/etc/ssl/certs/app.pem" })
// result.context.content = "<file contents as string>"
// Read a YAML file (auto-parsed into object)
vars.readFile({ path: "/etc/app/config.yaml", setvar: "config" })
// result.context.config = { database: { host: "localhost", port: 5432 }, ... }
// Read a JSON file (auto-parsed into object)
vars.readFile({ path: "/app/package.json", setvar: "pkg" })
// result.context.pkg = { name: "myapp", version: "1.0.0", ... }
// Read multiple files (YAML/JSON auto-parsed, others as strings)
vars.readFile({ files: { cert: "/etc/ssl/certs/app.pem", config: "/etc/app/config.yaml" } })
// Read files by glob pattern (YAML/JSON auto-parsed)
vars.readGlob({ pattern: "/etc/app/conf.d/*.yaml" })
// result.context.files = { "/etc/app/conf.d/db.yaml": { host: "...", ... }, ... }
// Read multiple glob patterns
vars.readGlob({ globs: { configs: "/etc/app/*.yaml", templates: "/etc/app/templates/*.tmpl" } })

Set OS environment variables.

Any key-value pairs provided are set as environment variables. Values support Liquid template interpolation from workflow vars.

name: set-environment
vars:
environment: production
tasks:
- name: configure-env
setenv:
NODE_ENV: "{{environment}}"
DATABASE_HOST: db.example.com
LOG_LEVEL: warn
name: env-before-build
list: true
tasks:
- name: set-build-env
setenv:
CI: "true"
BUILD_NUMBER: "42"
- name: build
shell: |
echo "CI=$CI"
echo "BUILD=$BUILD_NUMBER"
npm run build

There is no setenv namespace. A script sets environment variables where they are used:

shell.execute({ script: './build.sh', env: { NODE_ENV: 'production' }, capture: true });

That is the safer shape anyway — the variable exists for one call rather than for everything after it.

Output messages to stdout.

ParameterTypeRequiredDescription
messagestringYesMessage to print (supports Liquid templating)
name: print-simple
tasks:
- name: hello
print:
message: "Hello, World!"
name: print-template
vars:
name: "John"
count: 42
tasks:
- name: show-message
print:
message: "Hello, {{name}}! You have {{count}} items."
name: print-multiline
vars:
environment: production
version: "1.0.0"
tasks:
- name: show-summary
print:
message: |
========================================
Deployment Summary
========================================
Environment: {{environment}}
Version: {{version}}
Status: Success
========================================
log.info(`Deployed ${service} to ${environment}`);
log.error(`Rollout failed: ${r.error}`);

log is in the base set, so it is available even under --namespaces none. There is no print namespace: print: writes into a run’s log, and a script has log for the same job.

Execute business rules using the Grule rule engine. Supports loading rules from files or inline definitions.

ParameterTypeRequiredDefaultDescription
rulesetstringNo*-Inline GRL rules
pathstringNo*-Single rule file path
pathslistNo*-Multiple rule file/directory paths
recursiveboolNofalseRecursively search directories
factslistNo-JSON files to load as facts
setvarstringNo-Variable to store rule output
historyboolNofalseInclude value change history
auditboolNofalseInclude audit log

*At least one of ruleset, path, or paths is required.

Facts files can be specified as:

  • filepath.json — Merge at root level
  • key=filepath.json — Nest under key
name: rule-inline
vars:
user_age: 25
user_country: US
tasks:
- name: check-eligibility
rule:
ruleset: |
rule CheckAge "Check if user is adult" salience 10 {
when
Age >= 18
then
Result.IsAdult = true;
Retract("CheckAge");
}
rule CheckCountry "Check country eligibility" salience 5 {
when
Country == "US" || Country == "CA"
then
Result.IsEligible = true;
Retract("CheckCountry");
}
setvar: eligibility
name: rule-file
tasks:
- name: apply-pricing
rule:
path: ./rules/pricing.grl
setvar: pricing_result
name: rule-directory
tasks:
- name: apply-all-rules
rule:
paths:
- ./rules/validation
- ./rules/pricing
recursive: true
setvar: rule_output
history: true
audit: true
name: rule-with-facts
tasks:
- name: complex-rules
rule:
path: ./rules/business.grl
facts:
- ./data/config.json
- pricing=./data/pricing.json
- limits=./data/limits.json
setvar: business_result

There is no rule namespace. Rules are evaluated by the Rules block, reached from a flow with this task or over its API.

To batch-process files through rule-shaped logic at the CLI, kis script rules runs one script over a directory — see Commands.