Provider Reference#

This document provides a reference for all providers available in scafctl.

Note: All YAML examples in this reference show only the relevant resolver or action snippet. To use them, place each snippet inside a complete solution file with apiVersion, kind, metadata, and spec sections. See the Getting Started tutorial for the full solution structure.

Overview#

Providers are execution primitives used by resolvers and actions. Each provider has capabilities that determine where it can be used:

CapabilityUsed InDescription
fromResolver resolve.withFetch or generate data
transformResolver transform.withTransform data
validationResolver validate.withValidate data
actionAction providerPerform side effects
authenticationHTTP authProvide authentication

Built-in vs Official Plugin Providers#

scafctl providers are split into two categories:

CategoryDescription
Built-inCompiled into the scafctl binary. Always available without network access or plugins.
Official (plugin)Distributed as external plugin binaries via ghcr.io/oakwood-commons/providers/<name>. Must be declared in bundle.plugins to be used by a solution.

Built-in providers (11): cel, debug, file, go-template, http, message, parameter, solution, state, static, validation

Official plugin providers (11): directory, env, exec, git, github, hcl, identity, kubeconfig, metadata, secret, sleep

Built-in providers are always available. Official plugin providers are external binaries, so any solution that references one must declare it in bundle.plugins – this applies to scafctl run solution, scafctl run resolver, and scafctl render solution. Referencing an official provider without declaring it fails preparation before any fetch (providers [...] are not builtins and are not declared in bundle.plugins). The scafctl run provider <name> command is the exception: it fetches an official provider by name on demand (for example, scafctl run provider exec command='ls'). See Plugin Auto-Fetching for details and Plugin Lock Modes for version pinning.

Capabilities Matrix#

ProviderTypefromtransformvalidationaction
celbuilt-in❌✅❌✅
debugbuilt-in✅✅✅✅
directoryofficial✅❌❌✅
envofficial✅❌❌❌
execofficial✅✅❌✅
filebuilt-in✅✅❌✅
gitofficial✅❌❌✅
githubofficial✅✅✅❌
go-templatebuilt-in❌✅❌✅
hclofficial✅✅❌❌
httpbuilt-in✅✅❌✅
identityofficial✅❌❌❌
kubeconfigofficial❌❌❌❌*
messagebuilt-in❌✅❌✅
metadataofficial✅❌❌❌
parameterbuilt-in✅❌❌❌
secretofficial✅❌❌❌
sleepofficial✅✅✅✅
solutionbuilt-in✅❌❌✅
statebuilt-in✅❌❌❌
staticbuilt-in✅✅❌❌
validationbuilt-in❌✅✅❌

* kubeconfig declares its own dedicated kubeconfig capability, not one of the four generic capabilities in this matrix. It cannot be called from resolve.with or workflow.actions; see the kubeconfig section for how it’s actually invoked.


cel#

Transform and evaluate data using CEL (Common Expression Language) expressions.

Capabilities#

transform, action

Inputs#

FieldTypeRequiredDescription
expressionstring✅CEL expression to evaluate. Resolver data available under _
variablesany❌Additional variables for the CEL context

Output#

Returns the evaluation result (any type).

Examples#

# Transform: uppercase a string
transform:
  with:
    - provider: cel
      inputs:
        expression: "__self.toUpperCase()"

# Action: compute a value
provider: cel
inputs:
  expression: "_.items.map(i, i.price).sum()"

debug#

Debugging provider for inspecting resolver data during workflow execution.

Capabilities#

from, transform, validation, action

Inputs#

FieldTypeRequiredDescription
expressionstring❌CEL expression to filter/transform data before output
labelstring❌Label or message for debug output context
formatstring❌Output format: yaml, json, pretty (default: yaml)
destinationstring❌Where to output: stdout, stderr, file (default: stdout)
pathstring❌File path when destination is file
colorizebool❌Whether to colorize terminal output

Examples#

# Debug all resolver data
resolve:
  with:
    - provider: debug
      inputs:
        label: "Resolver Context"

# Debug specific value
transform:
  with:
    - provider: debug
      inputs:
        expression: "_.config"
        format: json

directory#

Directory operations: listing contents with filtering, creating, removing, and copying directories.

Capabilities#

from, action

Inputs#

FieldTypeRequiredDescription
operationstring✅Operation: list, mkdir, rmdir, copy
pathstring✅Target directory path (absolute or relative)
recursivebool❌Enable recursive directory traversal (default: false)
maxDepthint❌Maximum recursion depth, 1–50 (default: 10)
includeContentbool❌Read and include file contents in output (default: false)
maxFileSizeint❌Maximum file size in bytes for content reading (default: 1048576)
filterGlobstring❌Glob pattern to filter entries (e.g., *.go). Mutually exclusive with filterRegex
filterRegexstring❌Regex to filter entry names. Mutually exclusive with filterGlob
excludeHiddenbool❌Exclude hidden files/directories (names starting with .)
checksumstring❌Compute checksum for files: md5, sha256, sha512 (requires includeContent)
createDirsbool❌Create parent directories for mkdir (like mkdir -p)
destinationstring❌Destination path for copy operation
forcebool❌Force removal of non-empty directories for rmdir

Output (list)#

FieldTypeDescription
entriesarrayList of directory entries
entries[].pathstringRelative path from the listed directory
entries[].absolutePathstringAbsolute filesystem path
entries[].namestringFile or directory name
entries[].extensionstringFile extension including dot (e.g., .go)
entries[].sizeintSize in bytes
entries[].isDirboolWhether entry is a directory
entries[].typestringEntry type: file or dir
entries[].modestringFile permission mode (e.g., 0644)
entries[].modTimestringLast modification time (RFC3339)
entries[].mimeTypestringMIME type based on extension
entries[].contentstringFile content (when includeContent is true)
entries[].contentEncodingstringtext or base64
entries[].checksumstringFile checksum (when checksum is specified)
entries[].checksumAlgorithmstringAlgorithm used
totalCountintTotal number of entries
dirCountintNumber of directories
fileCountintNumber of files
totalSizeintTotal size of all files in bytes
basePathstringAbsolute path of the listed directory

Output (mkdir, rmdir, copy)#

FieldTypeDescription
successboolWhether the operation succeeded
operationstringOperation that was performed
pathstringAbsolute path of the target directory

Examples#

# List directory contents
resolve:
  with:
    - provider: directory
      inputs:
        operation: list
        path: ./src

# Recursively find all Go files
resolve:
  with:
    - provider: directory
      inputs:
        operation: list
        path: ./pkg
        recursive: true
        filterGlob: "*.go"
        excludeHidden: true

# List with file contents and checksums
resolve:
  with:
    - provider: directory
      inputs:
        operation: list
        path: ./config
        recursive: true
        includeContent: true
        filterGlob: "*.yaml"
        checksum: sha256
        maxFileSize: 524288

# Create nested directory structure
provider: directory
inputs:
  operation: mkdir
  path: ./output/reports/2026
  createDirs: true

# Force-remove a directory
provider: directory
inputs:
  operation: rmdir
  path: ./tmp/build-output
  force: true

# Copy a directory tree
provider: directory
inputs:
  operation: copy
  path: ./config
  destination: ./config-backup

Security#

The directory provider enforces several security measures:

  • Recursion depth limit: The maxDepth input is clamped to 1–50 (default: 10). This prevents unbounded filesystem traversal that could cause resource exhaustion.
  • Symlink skipping: Symbolic links are silently skipped during listing to prevent symlink escape attacks where a symlink points outside the intended directory tree.
  • File size limit: When includeContent is enabled, maxFileSize (default: 1 MB) caps the content read per file to prevent memory exhaustion from large files.

env#

Read environment variables.

Capabilities#

from

Inputs#

FieldTypeRequiredDescription
operationstring✅Operation: get, list
namestring❌Variable name (required for get)
defaultstring❌Default value if variable not set
prefixstring❌Filter variables by prefix (for list)

Output#

FieldTypeDescription
valuestringVariable value (for get)
variablesmapKey-value pairs (for list)
foundboolWhether the variable exists

Examples#

# Get environment variable with default
resolve:
  with:
    - provider: env
      inputs:
        operation: get
        name: DATABASE_URL
        default: "postgres://localhost/dev"

# List all vars with prefix
resolve:
  with:
    - provider: env
      inputs:
        operation: list
        prefix: "APP_"

Security#

The env provider enforces several security measures:

  • Mandatory prefix for list: The list operation requires a non-empty prefix input. Listing all environment variables without a scope would expose process secrets such as tokens, API keys, and database credentials.
  • No credential-bearing variables in output: By forcing a prefix scope, the provider prevents accidental enumeration of variables like AWS_SECRET_ACCESS_KEY, DATABASE_URL, or GITHUB_TOKEN that happen to exist in the process environment.

exec#

Execute shell commands using an embedded cross-platform POSIX shell interpreter. Commands work identically on Linux, macOS, and Windows without requiring external shell binaries. Supports pipes, redirections, variable expansion, command substitution, and common coreutils on all platforms. Optionally use external shells (bash, pwsh, cmd) for platform-specific features.

Capabilities#

from, transform, action

Inputs#

FieldTypeRequiredDefaultDescription
commandstring✅—Command to execute. Supports POSIX shell syntax including pipes, redirections, variable expansion, and command substitution by default
argsarray❌—Additional arguments appended to the command. Arguments are automatically shell-quoted for safety
stdinstring❌—Standard input to provide to the command
workingDirstring❌—Working directory for command execution
envobject❌—Environment variables to set (key-value pairs). Merged with the parent process environment. NO_COLOR=1 and TERM=dumb are injected automatically to prevent child processes from emitting ANSI escape codes in captured output (override by setting them explicitly)
timeoutint❌—Timeout in seconds (0 or omit for no timeout, max 3600)
shellstring❌autoShell interpreter to use: auto (embedded POSIX shell — works on all platforms), sh (alias for auto), bash (external bash), pwsh (external PowerShell Core), cmd (external cmd.exe — Windows only)

Output#

FieldTypeDescription
stdoutstringStandard output
stderrstringStandard error
exitCodeintExit code
successboolWhether command succeeded (exit code 0) — action capability only
commandstringThe full command that was executed
shellstringThe shell interpreter that was used

Shell Modes#

ValueDescriptionPlatform
autoEmbedded POSIX shell (default). Pure Go — no external shell binary required. Supports pipes, redirections, variable expansion, command substitution, and Go-native coreutils on Windows.All
shAlias for autoAll
bashExternal bash binary from $PATH. Use for bash-specific features (globstar, arrays, etc.)Linux, macOS
pwshExternal PowerShell Core from $PATH. Use for PowerShell cmdlets and Windows administrationAll (where pwsh is installed)
cmdExternal cmd.exe. Use for Windows batch commandsWindows

Examples#

# Simple command — pipes and shell features work by default
provider: exec
inputs:
  command: "echo 'Hello, World!'"

# Command with arguments (automatically shell-quoted)
provider: exec
inputs:
  command: "echo"
  args: ["Hello", "World"]

# Shell pipeline — works on all platforms
provider: exec
inputs:
  command: "echo 'hello world' | tr a-z A-Z"

# With environment variables and working directory
provider: exec
inputs:
  command: "./deploy.sh"
  workingDir: "/opt/app"
  env:
    ENVIRONMENT: production
  timeout: 300

# PowerShell command
provider: exec
inputs:
  command: "Get-ChildItem | Select-Object Name"
  shell: pwsh

# External bash for bash-specific features
provider: exec
inputs:
  command: "shopt -s globstar; echo **/*.go"
  shell: bash

Security#

The exec provider enforces several security measures:

  • Lint rule for command injection: The exec-command-injection lint rule emits a warning when the command input uses a dynamic expression (expr) or template (tmpl). Shell metacharacters in resolved values can escape the intended command and execute arbitrary code.
  • Shell-quoted arguments: The args input automatically shell-quotes each argument before appending it to the command. Always pass dynamic/user-controlled data via args instead of interpolating it into the command string.
# BAD — dynamic value in command string risks injection
provider: exec
inputs:
  command:
    expr: "'echo ' + _.userInput"

# GOOD — static command, dynamic value via args (shell-quoted)
provider: exec
inputs:
  command: echo
  args:
    - expr: "_.userInput"

file#

Filesystem operations: read, write, check existence, delete, and batch write a tree of files.

Capabilities#

from, transform, action

Inputs#

FieldTypeRequiredDescription
operationstring✅Operation: read, write, exists, delete, write-tree
pathstring❌File path — required for read, write, exists, delete
contentstring❌Content to write (required for write)
createDirsbool❌Create parent directories if missing (for write)
encodingstring❌File encoding: utf-8, binary (default: utf-8)
parsestring❌Parse file content into a structured object (for read): json, yaml, or auto (decoder chosen by file extension .json/.yaml/.yml, else content-sniffed). Malformed content errors. Omitted returns raw content only. Ignored when encoding is binary.
basePathstring❌Destination root directory (required for write-tree)
entriesarray❌Array of {path, content} objects (required for write-tree)
outputPathstring❌Go template to transform each entry’s path before writing (write-tree only). Available variables: __filePath, __fileName, __fileStem, __fileExtension, __fileDir. Sprig functions supported.

Output#

FieldTypeDescription
contentstringFile content (for read)
objectanyParsed content (for read, only when parse is set)
existsboolWhether file exists
sizeintFile size in bytes
successboolOperation success (action only)
operationstringOperation performed (write-tree only)
basePathstringResolved base path (write-tree only)
filesWrittenintNumber of files written (write-tree only)
pathsarrayRelative paths of files written (write-tree only)

Examples#

# Read file
resolve:
  with:
    - provider: file
      inputs:
        operation: read
        path: "./config.json"

# Write file
provider: file
inputs:
  operation: write
  path: "./output/result.txt"
  content:
    expr: "_.processedData"
  createDirs: true

# Check if file exists
resolve:
  with:
    - provider: file
      inputs:
        operation: exists
        path: "./optional-config.yaml"

# Write a tree of rendered files, stripping .tpl extensions
provider: file
inputs:
  operation: write-tree
  basePath: ./output
  entries:
    rslvr: rendered
  outputPath: >-
    {{ if .__fileDir }}{{ .__fileDir }}/{{ end }}{{ .__fileStem }}

Security#

The file provider enforces several security measures:

  • Path traversal protection: The write-tree operation validates that every output file resolves to a path inside the declared basePath. Entries containing .. segments that escape the base directory are rejected with an error.
  • outputPath traversal protection: When outputPath is specified, the final templated path is also validated to remain within basePath. This prevents a crafted Go template from writing files outside the intended output directory.

git#

Git version control operations.

Capabilities#

from, action

Inputs#

FieldTypeRequiredDescription
operationstring✅Operation: clone, pull, status, add, commit, push, checkout, branch, log, tag
urlstring❌Repository URL (for clone)
pathstring❌Local repository path
branchstring❌Branch name
messagestring❌Commit message
filesarray❌Files to add
tagstring❌Tag name
remotestring❌Remote name (default: origin)
depthint❌Clone depth for shallow clone
usernamestring❌Username for authentication
passwordstring❌Password/token (secret)
forcebool❌Force the operation

Examples#

# Clone repository
provider: git
inputs:
  operation: clone
  url: https://github.com/org/repo.git
  path: ./repo
  depth: 1

# Commit and push
provider: git
inputs:
  operation: commit
  path: ./repo
  message:
    expr: "'Release ' + _.version"

# Then push
provider: git
inputs:
  operation: push
  path: ./repo

Security#

The git provider enforces several security measures:

  • Credential isolation: Authentication credentials are passed via a temporary .netrc file with 0600 permissions instead of being embedded in command-line arguments. This prevents credentials from being exposed via ps, /proc, or audit logs.
  • Netrc injection prevention: Username and password inputs are validated to reject whitespace and control characters (<= 0x20 or 0x7f). The netrc format is whitespace-delimited, so embedded spaces, tabs, or newlines could inject additional machine entries.
  • Automatic cleanup: The temporary credential directory is automatically removed after the git operation completes.
  • Terminal prompt disabled: GIT_TERMINAL_PROMPT=0 is set to prevent git from hanging on interactive credential prompts in automated environments.

github#

Interact with GitHub via GraphQL (reads, issues, PRs, review threads, signed commits, branches, tags, repos, branch protection) and REST (releases, CI check runs, workflow runs, labels, milestones, reactions, collaborators, webhooks, Actions workflows, variables, environments, repo settings, topics, custom properties, security settings). Uses the configured GitHub auth handler automatically. Commit operations use createCommitOnBranch for GPG-signed multi-file atomic commits. Includes a generic api_call operation for arbitrary GitHub REST endpoints.

For arbitrary GitHub REST endpoints not covered by a named operation, use the built-in api_call operation instead of falling back to the http provider.

Capabilities#

from, transform, action

Inputs#

FieldTypeRequiredDescription
operationstringYesAPI operation (see operations table below)
ownerstringConditionalRepository owner (user or organization). Required for all operations except create_repo (defaults to authenticated user).
repostringYesRepository name. Required for all operations.
api_basestring❌GitHub API base URL (default: https://api.github.com). Set for GitHub Enterprise.
pathstring❌File path within the repository (for get_file)
refstring❌Git reference (branch, tag, or commit SHA). Defaults to repo’s default branch
numberint❌Issue or pull request number
statestring❌Filter by state: open, closed, all, merged (default: open)
per_pageint❌Results per page, 1–100 (default: 30)
titlestring❌Title for issue or pull request create/update
bodystring❌Body text for issue, pull request, release, or comment
labelsarray❌Labels to apply (names for issues)
assigneesarray❌Assignee login usernames
headstring❌Head branch for creating a pull request
basestring❌Base branch for creating a pull request
draftbool❌Create PR as draft
merge_methodstring❌Merge method: MERGE, SQUASH, REBASE (default: MERGE)
branchstring❌Branch name for commit/branch/tag operations
messagestring❌Commit message headline
expected_head_oidstring❌Expected HEAD OID (40-char SHA) for optimistic locking in create_commit
additionsarray❌Files to add/update: [{path, content}]
deletionsarray❌Files to delete: [{path}]
oidstring❌Git object ID (SHA) for branch/tag creation
tagstring❌Tag name for tag operations
tag_namestring❌Tag name for release
namestring❌Release name/title
release_idint❌Release ID for update/delete
prereleasebool❌Whether release is a prerelease
target_commitishstring❌Branch/SHA target for release tag
visibilitystring❌Repository visibility for create_repo: public or private (default: public)
auto_initbool❌Initialize repo with a README (uses REST Contents API)
ruleset_namestring❌Name for the repository ruleset
targetstring❌Ruleset target type: branch or tag (default: branch)
enforcementstring❌Ruleset enforcement level: active, disabled, evaluate (default: active)
include_refsarray❌Ref patterns to include (e.g. refs/heads/main, refs/tags/v*)
exclude_refsarray❌Ref patterns to exclude
required_status_checks_contextsarray❌Required status check context names
required_approving_review_countint❌Minimum approving reviews required (0–10)
required_linear_historybool❌Require linear commit history
allow_force_pushesbool❌Allow force pushes to matching refs
allow_deletionsbool❌Allow deletion of matching refs
requires_commit_signaturesbool❌Require signed commits
commit_shastring❌Commit SHA for list_commit_pulls (alias: sha)
endpointstring❌Relative API path for api_call (e.g. /repos/{owner}/{repo}/labels). Must start with /
methodstring❌HTTP method for api_call: GET, POST, PUT, PATCH, DELETE (default: GET)
request_bodyobject❌Request body for api_call (used with POST/PUT/PATCH)
query_paramsobject❌Query string parameters for api_call (e.g. {per_page: 100})
state_reasonstring❌Reason for closing an issue: completed, not_planned, reopened
thread_idstring❌Review thread node ID for reply_to_review_thread and resolve_review_thread
run_idint❌Workflow run ID for get_workflow_run, cancel_workflow_run, rerun_workflow
commit_titlestring❌Commit title for merge_pull_request
commit_messagestring❌Commit message body for merge_pull_request
message_bodystring❌Extended commit message body for create_commit
descriptionstring❌Description for repo, label, milestone, or environment
label_namestring❌Label name for label CRUD operations
new_label_namestring❌New label name when renaming (update_label)
colorstring❌Hex color code for labels, without # (e.g. 00ff00)
label_descriptionstring❌Description for a label
milestone_numberint❌Milestone number for update/delete operations
due_onstring❌Due date for milestone (ISO 8601: YYYY-MM-DDTHH:MM:SSZ)
reaction_contentstring❌Reaction emoji: +1, -1, laugh, confused, heart, hooray, rocket, eyes
reaction_subjectstring❌Subject type for reaction: issue, pull_request, issue_comment (default: issue)
reaction_idint❌Reaction ID for delete_reaction
comment_idint❌Comment ID for reaction/comment operations
usernamestring❌GitHub username for collaborator operations
permissionstring❌Permission level: pull, triage, push, maintain, admin
webhook_urlstring❌Payload URL for the webhook
webhook_eventsarray❌Events that trigger the webhook (e.g. ["push", "pull_request"])
webhook_content_typestring❌Content type for webhook payloads: json or form (default: json)
webhook_secretstring❌Secret for webhook signature verification (sensitive)
webhook_activebool❌Whether the webhook is active
hook_idint❌Webhook ID for update/delete operations
workflow_idstring❌Workflow file name or numeric ID (e.g. ci.yml)
workflow_inputsobject❌Input parameters for dispatch_workflow
workflow_statusstring❌Filter workflow runs by status (e.g. completed, in_progress, queued)
variable_namestring❌Repository variable name
variable_valuestring❌Repository variable value
environment_namestring❌Deployment environment name
wait_timerint❌Wait timer in minutes before deployments proceed (0–43200)
reviewersarray❌Required reviewers for environment (objects with type and id)
homepagestring❌Repository homepage URL (update_repo)
default_branchstring❌Default branch name (update_repo)
has_issuesbool❌Enable issues feature (update_repo)
has_projectsbool❌Enable projects feature (update_repo)
has_wikibool❌Enable wiki feature (update_repo)
allow_squash_mergebool❌Allow squash merging (update_repo)
allow_merge_commitbool❌Allow merge commits (update_repo)
allow_rebase_mergebool❌Allow rebase merging (update_repo)
delete_branch_on_mergebool❌Auto-delete head branches after merge (update_repo)
archivedbool❌Archive the repository (update_repo)
topicsarray❌Repository topics/tags for replace_topics
organizationstring❌Organization to fork into (fork_repo)
default_branch_onlybool❌Fork only the default branch (fork_repo)
new_repo_namestring❌Name for new repository (create_from_template)
new_ownerstring❌Owner for new repository (create_from_template)
include_all_branchesbool❌Include all branches when creating from template
propertiesobject❌Custom properties to set (key-value map for set_custom_properties)

Operations#

Read operations (capabilities: from, transform):

OperationDescription
get_repoGet repository metadata
get_fileGet file content (returned as plain text, not base64)
list_releasesList releases
get_latest_releaseGet the latest release
list_pull_requestsList pull requests (filterable by state)
get_pull_requestGet a single pull request
list_issuesList issues (filterable by state)
get_issueGet a single issue
list_issue_commentsList comments on an issue
list_branchesList branches
get_branchGet a single branch
list_tagsList tags
get_head_oidGet HEAD commit SHA for a branch
list_pr_commentsList comments on a pull request
list_review_threadsList review threads on a pull request
list_check_runsList check runs for a ref (REST)
get_workflow_runGet a workflow run by ID (REST)
list_workflow_runsList workflow runs (REST, filterable by status)
list_commit_pullsList pull requests associated with a commit SHA (REST)
list_labelsList repository labels (REST)
list_milestonesList milestones (REST)
list_reactionsList reactions on an issue, PR, or comment (REST)
list_collaboratorsList repository collaborators (REST)
list_webhooksList repository webhooks (REST)
list_repo_variablesList repository variables (REST)
list_environmentsList deployment environments (REST)
list_topicsList repository topics (REST)
list_custom_propertiesList custom properties (REST)
api_callGeneric REST API call to any GitHub endpoint (see below)

Write operations (capability: action — returns success boolean):

OperationAPIDescription
create_issueGraphQLCreate a new issue
update_issueGraphQLUpdate an existing issue
create_issue_commentGraphQLAdd a comment to an issue
create_pull_requestGraphQLOpen a new pull request
update_pull_requestGraphQLUpdate a pull request
merge_pull_requestGraphQLMerge a pull request
close_pull_requestGraphQLClose a pull request
create_commitGraphQLCreate a GPG-signed commit (multi-file, atomic)
create_branchGraphQLCreate a branch
delete_branchGraphQLDelete a branch
create_tagGraphQLCreate a lightweight tag
delete_tagGraphQLDelete a tag
create_releaseRESTCreate a release
update_releaseRESTUpdate a release
delete_releaseRESTDelete a release
create_repoGraphQLCreate a repository (REST fallback for EMU)
create_rulesetRESTCreate a repository ruleset (branch or tag protection)
enable_vulnerability_alertsRESTEnable Dependabot vulnerability alerts
enable_automated_security_fixesRESTEnable Dependabot automated security fixes
update_repoRESTUpdate repository settings
reply_to_review_threadGraphQLReply to a review thread
resolve_review_threadGraphQLResolve a review thread
create_labelRESTCreate a repository label
update_labelRESTUpdate a label (name, color, description)
delete_labelRESTDelete a label
add_labels_to_issueRESTAdd labels to an issue
remove_label_from_issueRESTRemove a label from an issue
create_milestoneRESTCreate a milestone
update_milestoneRESTUpdate a milestone
delete_milestoneRESTDelete a milestone
add_reactionRESTAdd a reaction to an issue, PR, or comment
delete_reactionRESTRemove a reaction
add_collaboratorRESTAdd a repository collaborator
remove_collaboratorRESTRemove a repository collaborator
create_webhookRESTCreate a repository webhook
update_webhookRESTUpdate a webhook
delete_webhookRESTDelete a webhook
dispatch_workflowRESTTrigger a workflow dispatch event
cancel_workflow_runRESTCancel a workflow run
rerun_workflowRESTRe-run a workflow
create_or_update_variableRESTCreate or update a repository variable
delete_variableRESTDelete a repository variable
create_or_update_environmentRESTCreate or update a deployment environment
delete_environmentRESTDelete a deployment environment
replace_topicsRESTReplace all repository topics
fork_repoRESTFork a repository
create_from_templateRESTCreate a repository from a template
set_custom_propertiesRESTSet custom properties on a repository

api_call operation#

A generic escape hatch for any GitHub REST API endpoint not covered by a named operation. Keeps auth handling, GHE hostname resolution, and API versioning consistent without needing the http provider.

  • endpoint is required and must be a relative path starting with /
  • Path traversal (..) and query strings (?) in endpoint are rejected – use query_params instead
  • For GET requests, the output uses readOutput (same shape as other read operations)
  • For mutating methods (POST/PUT/PATCH/DELETE), the output uses actionOutput
  • api_call is intentionally excluded from the write-operation guard – the user controls the HTTP method, so it cannot be classified at the provider level

Output#

Read operations:

FieldTypeDescription
resultanyAPI response — structure varies by operation

Write operations (action):

FieldTypeDescription
successboolAlways true on success (failures return a Go error)
operationstringThe operation that was performed
resultanyAPI response data

Write operations return a Go error on failure, which stops dependent workflow actions and respects onError handling.

The create_commit result includes additions and deletions as integer counts (e.g., "additions": 3), not nested objects.

Examples#

# Get repository info
resolve:
  with:
    - provider: github
      inputs:
        operation: get_repo
        owner: octocat
        repo: hello-world

# Get file content from a specific branch (returns plain text)
transform:
  with:
    - provider: github
      inputs:
        operation: get_file
        owner: octocat
        repo: hello-world
        path: README.md
        ref: main

# Create a GPG-signed commit with multiple files
action:
  with:
    - provider: github
      inputs:
        operation: create_commit
        owner: my-org
        repo: my-repo
        branch: feature-branch
        message: "feat: add scaffolded files"
        expected_head_oid: abc123def456789012345678901234567890abcd
        additions:
          - path: src/main.go
            content: "package main\n\nfunc main() {}\n"
          - path: README.md
            content: "# My Project\n"

# Create an issue with labels
action:
  with:
    - provider: github
      inputs:
        operation: create_issue
        owner: my-org
        repo: my-repo
        title: "Bug: something is broken"
        body: "Steps to reproduce..."
        labels:
          - bug
          - priority/high

# Create a release (uses REST API)
action:
  with:
    - provider: github
      inputs:
        operation: create_release
        owner: my-org
        repo: my-repo
        tag_name: v1.0.0
        name: "Release 1.0.0"
        body: "First stable release"

# Create a repository with auto-init
action:
  with:
    - provider: github
      inputs:
        operation: create_repo
        owner: my-org
        repo: my-new-repo
        description: "A scaffolded project"
        visibility: private
        auto_init: true

# Create a branch protection ruleset
action:
  with:
    - provider: github
      inputs:
        operation: create_ruleset
        owner: my-org
        repo: my-repo
        ruleset_name: main branch protection
        target: branch
        enforcement: active
        include_refs:
          - refs/heads/main
        required_status_checks_contexts:
          - test
          - lint
        required_approving_review_count: 1
        required_linear_history: true
        requires_commit_signatures: true
        allow_force_pushes: false
        allow_deletions: false

# Enable Dependabot security features
action:
  with:
    - provider: github
      inputs:
        operation: enable_vulnerability_alerts
        owner: my-org
        repo: my-repo

# Update repository settings
action:
  with:
    - provider: github
      inputs:
        operation: update_repo
        owner: my-org
        repo: my-repo
        description: "Updated description"
        has_wiki: false
        delete_branch_on_merge: true

# Reply to a review thread
action:
  with:
    - provider: github
      inputs:
        operation: reply_to_review_thread
        owner: my-org
        repo: my-repo
        thread_id: "PRRT_abc123"
        body: "Fixed in the latest commit, thanks!"

# Create and manage labels
action:
  with:
    - provider: github
      inputs:
        operation: create_label
        owner: my-org
        repo: my-repo
        label_name: "priority/critical"
        color: "e11d48"
        label_description: "Critical priority issues"

# Add labels to an issue
action:
  with:
    - provider: github
      inputs:
        operation: add_labels_to_issue
        owner: my-org
        repo: my-repo
        number: 42
        labels:
          - bug
          - priority/critical

# Create a milestone
action:
  with:
    - provider: github
      inputs:
        operation: create_milestone
        owner: my-org
        repo: my-repo
        title: "v2.0.0"
        description: "Second major release"
        due_on: "2026-06-01T00:00:00Z"

# Add a reaction to an issue
action:
  with:
    - provider: github
      inputs:
        operation: add_reaction
        owner: my-org
        repo: my-repo
        number: 42
        reaction_content: "+1"

# Add a collaborator
action:
  with:
    - provider: github
      inputs:
        operation: add_collaborator
        owner: my-org
        repo: my-repo
        username: octocat
        permission: push

# Create a webhook
action:
  with:
    - provider: github
      inputs:
        operation: create_webhook
        owner: my-org
        repo: my-repo
        webhook_url: "https://example.com/webhook"
        webhook_events:
          - push
          - pull_request
        webhook_content_type: json

# Dispatch a workflow
action:
  with:
    - provider: github
      inputs:
        operation: dispatch_workflow
        owner: my-org
        repo: my-repo
        workflow_id: deploy.yml
        ref: main
        workflow_inputs:
          environment: production

# List workflow runs (read)
resolve:
  with:
    - provider: github
      inputs:
        operation: list_workflow_runs
        owner: my-org
        repo: my-repo
        workflow_id: ci.yml
        workflow_status: completed

# Create or update a repository variable
action:
  with:
    - provider: github
      inputs:
        operation: create_or_update_variable
        owner: my-org
        repo: my-repo
        variable_name: DEPLOY_ENV
        variable_value: production

# Create a deployment environment with reviewers
action:
  with:
    - provider: github
      inputs:
        operation: create_or_update_environment
        owner: my-org
        repo: my-repo
        environment_name: production
        wait_timer: 30
        reviewers:
          - type: User
            id: 12345

# Replace repository topics
action:
  with:
    - provider: github
      inputs:
        operation: replace_topics
        owner: my-org
        repo: my-repo
        topics:
          - go
          - cli
          - scaffolding

# Fork a repository
action:
  with:
    - provider: github
      inputs:
        operation: fork_repo
        owner: upstream-org
        repo: cool-project
        organization: my-org
        default_branch_only: true

# Create from template
action:
  with:
    - provider: github
      inputs:
        operation: create_from_template
        owner: template-org
        repo: go-template
        new_owner: my-org
        new_repo_name: my-new-service
        description: "Service from template"
        include_all_branches: false

# List check runs for a ref (read)
resolve:
  with:
    - provider: github
      inputs:
        operation: list_check_runs
        owner: my-org
        repo: my-repo
        ref: main

# List pull requests associated with a commit
resolve:
  with:
    - provider: github
      inputs:
        operation: list_commit_pulls
        owner: my-org
        repo: my-repo
        commit_sha: abc123def456

# Generic REST API call (read)
resolve:
  with:
    - provider: github
      inputs:
        operation: api_call
        endpoint: /repos/my-org/my-repo/labels
        query_params:
          per_page: 100

# Generic REST API call (write)
action:
  with:
    - provider: github
      inputs:
        operation: api_call
        endpoint: /repos/my-org/my-repo/labels
        method: POST
        request_body:
          name: bug
          color: d73a4a
          description: "Something isn't working"

For a complete end-to-end example of repo creation with rulesets and security settings, see the plugin-template solution .


go-template#

Transform data using Go text/template syntax. Supports single-template rendering (render, the default) and batch directory rendering (render-tree).

Capabilities#

transform, action

Inputs#

FieldTypeRequiredDescription
operationstring❌Operation: render (default) or render-tree
templatestring❌Go template content (required for render)
namestring❌Template name for error messages (defaults to "render-tree" for render-tree)
entriesarray❌Array of {path, content, data?, raw?} objects to render (required for render-tree). Each entry may include an optional data map, shallow-merged over the shared data for that entry only, and an optional raw bool that copies the entry verbatim.
missingKeystring❌Behavior for missing keys: default, zero, error
leftDelimstring❌Left delimiter (default: {{)
rightDelimstring❌Right delimiter (default: }})
dataany❌Additional data to merge with resolver context
forEachobject❌(render-tree only) Fan the whole entry set out once per item in a collection. { item?, index?, in } – in is the collection (array), item/index name the injected aliases (default __item/__index). Requires pathTemplate.
pathTemplatestring❌(render-tree only, required with forEach) Go template that computes each fanned-out entry’s output path. Has access to the item alias, __index, shared data, and the reserved __file* path parts (__fileDir, __fileStem, __fileName, __fileExtension, __filePath). Rendered paths must be unique across the fan-out.
rawGlobsarray❌(render-tree only) List of doublestar glob patterns matched against each entry’s full relative path. Matching entries are copied verbatim (no parse, no data). Patterns match the whole path, so *.tf matches only top-level files while **/*.tf also matches nested ones. A per-entry raw field overrides this. Max 20 patterns.
ignoredBlocksarray❌Extra literal pass-through markers (on top of the always-on built-ins). Each entry uses EXACTLY ONE mode: { start, end } (multi-line, markers preserved), { line } (single-line), or { token } (every occurrence of a literal). Built-in zero-config markers {{/* scafctl:ignore:start */}}…{{/* scafctl:ignore:end */}} (markers stripped) and # scafctl:ignore (per-line) need no declaration.

Output#

render operation: Returns the rendered template as a string.

render-tree operation: Returns an array of {path, content} objects where each content is the rendered result. Metadata includes templateName and entryCount.

Per-entry data (fan-out)#

Each entry may carry an optional data map that is shallow-merged over the shared top-level data for that entry only. On key conflicts, per-entry values win over shared data, iteration variables, and resolver context. This enables fan-out – one template rendered once per item, each with its own variables and output path – in a single resolver, without a separate forEach render step or a manual index-zip:

resolve:
  with:
    - provider: go-template
      inputs:
        operation: render-tree
        data:
          platformAppName: my-app        # shared default (merge base)
        entries:
          expr: |
            _.environments.map(env, {
              "path":    "envs/" + env.name + "/backend.tf",
              "content": _.backendTemplate.entries[0].content,
              "data":    {"environment": env}   # per-entry, wins over shared
            })

Entries without a data field render against the shared data alone, so existing render-tree usage is unaffected. A non-map data value fails with a clear error naming the entry index and path.

Tree fan-out (forEach + pathTemplate)#

Per-entry data fans out a hand-built entry list. To fan out a whole template tree – rendering every entry once per item in a collection, the way a Terraform module is instantiated once per for_each value – use forEach together with pathTemplate. This keeps the template set defined once (e.g. from a directory provider) and routes each item’s copy to a distinct output subtree:

resolve:
  with:
    - provider: go-template
      inputs:
        operation: render-tree
        entries:
          expr: '_.templateFiles.entries'   # the shared template tree
        data:
          platformAppName: my-app           # shared across every copy
        forEach:
          item: env                         # alias for the current item
          in:
            rslvr: environments             # array resolved by the spec layer
        pathTemplate: >-
          envs/{{ .env.name }}/{{ if .__fileDir }}{{ .__fileDir }}/{{ end }}{{ .__fileStem }}

Given N template entries and M items, the result is a single flat array of N x M rendered entries – ready to hand straight to the file provider’s write-tree. For each copy:

  • The current item is bound under the forEach.item alias (here env) and also under the reserved __item; the zero-based position is bound under forEach.index (if set) and __index.
  • Template data precedence is: per-entry data > fan-out aliases (item/index, __item/__index) > shared data > resolver context.
  • pathTemplate is rendered per copy with access to the item alias, __index, the shared data, and the reserved __file* path parts (__fileDir, __fileStem, __fileName, __fileExtension, __filePath) drawn from the entry’s original path. __fileStem drops the extension (e.g. a trailing .tpl), and __fileDir preserves the source subdirectory.

forEach.in is resolved by the spec layer before the provider runs, so it may be a resolver reference ({rslvr: ...}) or CEL ({expr: ...}) that yields an array. A non-array value, a missing in, or a missing pathTemplate fails with a clear error. Rendered output paths must be unique across the fan-out – a duplicate path is a hard error (it would otherwise silently collapse two copies into one). An empty collection simply produces no entries.

Verbatim passthrough (raw)#

To copy whole files through a render-tree pass unchanged – for example a fixture that is entirely Go-template-like syntax, or a GitHub Actions workflow full of ${{ ... }} – mark them raw instead of parsing them. Two mechanisms, which can be combined:

  • rawGlobs (provider-level): a list of doublestar glob patterns matched against each entry’s full relative path. This is the ergonomic choice for entries produced dynamically by the directory provider, since you cannot annotate those entries individually. Mirrors Cookiecutter’s _copy_without_render.
  • raw (per-entry bool): set on a hand-constructed entry (e.g. a CEL map). Takes precedence over rawGlobs: raw: true forces verbatim even without a glob match; raw: false forces rendering even when a glob matches.

A raw entry’s content is copied byte-for-byte: no template parsing, no delimiter handling, no ignoredBlocks processing, and any per-entry data is ignored (a warning is emitted if data was supplied). Entries without string content are still skipped with a warning, exactly as for rendered entries.

resolve:
  with:
    - provider: go-template
      inputs:
        operation: render-tree
        rawGlobs:
          - "**/*.raw"           # nested; use *.raw for top-level only
        entries:
          expr: '_.templateFiles.entries'
        data:
          rslvr: vars

Glob semantics follow doublestar: *.tf matches only a top-level main.tf, while **/*.tf also matches modules/net/main.tf. Unlike Cookiecutter, entry path values are already-resolved strings, so there is no path templating to preserve or skip.

The missing-template-dependency lint rule honors literal rawGlobs: resolver references inside raw-matched files are not flagged, since those files are never parsed. (Per-entry raw flags come from runtime entries and cannot be resolved statically, so lint filtering is glob-only.)

Ignored Blocks#

Use ignoredBlocks to bypass template rendering for specific sections. This is useful when templates contain syntax that conflicts with Go template delimiters (e.g., Terraform ${}, Helm {{ }}, GitHub Actions ${{ }}).

Start/End Mode (Multi-line)#

Define start and end markers. All content between matched markers (inclusive) is preserved:

transform:
  with:
    - provider: go-template
      inputs:
        name: terraform-config
        template: |
          resource "aws_instance" "main" {
            ami           = "{{ .ami }}"
            instance_type = "{{ .instanceType }}"
            /*scafctl:ignore:start*/
            tags = {
              Name = "${var.name}"
            }
            /*scafctl:ignore:end*/
          }
        ignoredBlocks:
          - start: "/*scafctl:ignore:start*/"
            end: "/*scafctl:ignore:end*/"

Line Mode (Single-line)#

Define a line marker. Every line containing that substring is preserved literally:

transform:
  with:
    - provider: go-template
      inputs:
        name: workflow-config
        template: |
          name: Deploy {{ .appName }}
          steps:
            - run: echo ${{ secrets.TOKEN }}  # scafctl:ignore
            - run: echo "deployed"
        ignoredBlocks:
          - line: "# scafctl:ignore"

Note: line and start/end are mutually exclusive within a single entry, but different entries can use different modes.

The content between start and end markers (including the markers themselves) passes through unchanged. For line mode, the entire line containing the marker is preserved.

Examples#

# Render a single template
transform:
  with:
    - provider: go-template
      inputs:
        name: config
        template: |
          server:
            host: {{ .host }}
            port: {{ .port }}
            env: {{ .environment }}

# Batch-render a directory of templates (render-tree)
resolve:
  with:
    - provider: go-template
      inputs:
        operation: render-tree
        entries:
          expr: '_.templateFiles.entries'
        data:
          rslvr: vars

hcl#

Process HCL (HashiCorp Configuration Language) content. Supports four operations: parse (default) extracts structured block information; format canonically formats; validate checks syntax; generate produces HCL from structured input. Accepts single files, multiple paths, or a directory of .tf files.

Capabilities#

from, transform

Inputs#

FieldTypeRequiredDescription
operationstring❌parse (default), format, validate, or generate
contentstring❌Raw HCL content to process
pathstring❌Path to a single HCL file
pathsarray❌Array of HCL file paths (merged for parse; per-file for format/validate)
dirstring❌Directory path — all .tf/.tf.json files are processed
blocksobject❌Structured block data for generate (same schema as parse output)
output_formatstring❌Generation output format: hcl (default) or json (Terraform JSON syntax .tf.json)

Source selection: For parse/format/validate, provide exactly one of content, path, paths, or dir (mutually exclusive). For generate, use blocks and optionally output_format.

Output — parse (default)#

FieldTypeDescription
variablesarrayVariable blocks (name, type, default, description, sensitive, validation)
resourcesarrayResource blocks (type, name, attributes, sub-blocks)
dataarrayData source blocks (type, name, attributes, sub-blocks)
modulesarrayModule blocks (name, source, version, attributes)
outputsarrayOutput blocks (name, value, description, sensitive)
localsmapLocal values merged across all locals blocks
providersarrayProvider configuration blocks (name, alias, attributes)
terraformobjectTerraform block (required_version, required_providers, backend, cloud)
movedarrayMoved blocks (from, to)
importarrayImport blocks (to, id, provider)
checkarrayCheck blocks (name, data, assertions)

When multiple files are parsed (paths or dir), results are merged: arrays are concatenated, locals and terraform maps are merged (last-file-wins for conflicting keys).

Output — format#

FieldTypeDescription
formattedstringThe canonically formatted HCL content (single file)
changedbooltrue if the formatter modified the content

Multi-file format returns { files: [{filename, formatted, changed}, ...], changed: bool }.

Output — validate#

FieldTypeDescription
validbooltrue if no syntax errors were found
error_countintNumber of error-level diagnostics
diagnosticsarrayDiagnostic entries with severity, summary, detail, range

Multi-file validate returns { valid: bool, error_count: int, files: [{filename, valid, error_count, diagnostics}, ...] }.

Output — generate#

FieldTypeDescription
hclstringGenerated HCL text (native HCL syntax or Terraform JSON depending on output_format)

Metadata includes output_format (hcl or json) indicating which format was produced.

Examples#

# Parse inline HCL content (operation defaults to "parse")
resolve:
  with:
    - provider: hcl
      inputs:
        content: |
          variable "region" {
            type    = string
            default = "us-east-1"
          }

# Parse an HCL file
resolve:
  with:
    - provider: hcl
      inputs:
        path: ./main.tf

# Parse all .tf files in a directory (results merged)
resolve:
  with:
    - provider: hcl
      inputs:
        dir: ./terraform

# Parse multiple specific files
resolve:
  with:
    - provider: hcl
      inputs:
        paths:
          - ./main.tf
          - ./variables.tf
          - ./outputs.tf

# Transform: parse HCL from another resolver's output
transform:
  with:
    - provider: hcl
      inputs:
        content: "{{ .resolvers.tfFile.content }}"

# Format inline HCL content
resolve:
  with:
    - provider: hcl
      inputs:
        operation: format
        content: |
          variable "region" {
          type=string
          default="us-east-1"
          }

# Format all files in a directory
resolve:
  with:
    - provider: hcl
      inputs:
        operation: format
        dir: ./terraform

# Validate HCL syntax
resolve:
  with:
    - provider: hcl
      inputs:
        operation: validate
        path: ./main.tf

# Generate HCL from structured data
resolve:
  with:
    - provider: hcl
      inputs:
        operation: generate
        blocks:
          variables:
            - name: region
              type: string
              default: us-east-1
              description: "AWS region"

# Generate Terraform JSON (.tf.json) from structured data
resolve:
  with:
    - provider: hcl
      inputs:
        operation: generate
        output_format: json
        blocks:
          variables:
            - name: region
              type: string
              default: us-east-1
          resources:
            - type: aws_instance
              name: web
              attributes:
                ami: ami-12345
                instance_type: t3.micro

http#

HTTP client for API calls with built-in pagination support for fetching data across multiple pages.

Capabilities#

from, transform, action

Inputs#

FieldTypeRequiredDescription
urlstring✅URL to request
methodstring❌HTTP method (default: GET)
headersobject❌HTTP headers
bodyany❌Request body. A string is sent verbatim; an object or array is serialized to compact JSON and Content-Type defaults to application/json when not otherwise set
timeoutint❌Timeout in seconds (max 300)
retryobject❌Retry configuration
authstring❌Auth provider (e.g., entra, github)
scopestring❌OAuth scope for authentication
paginationobject❌Pagination configuration (see below)
autoParseJsonbool❌Parse response body as JSON when Content-Type is application/json. Enables direct field access (e.g., _.result.body.items)
pollobject❌Polling configuration — re-execute request until a condition is met (see below)

Request body#

body accepts either a string or a structured value:

  • String – sent verbatim. You control the exact bytes and the Content-Type header.
  • Object or array – serialized to compact JSON. When you do not set a Content-Type header, the provider defaults it to application/json. An explicit Content-Type (matched case-insensitively) is always preserved.

Passing a structured body lets you build the payload directly from resolver values with a CEL expression, avoiding a separate serializer resolver:

# Object body built from a CEL expression -- serialized to JSON automatically
provider: http
inputs:
  url: https://api.example.com/graphql
  method: POST
  body:
    expr: '{"query": _.graphQuery, "variables": {"id": _.recordId}}'

Pagination#

The pagination input enables automatic multi-page fetching. Five strategies are supported to cover different API pagination patterns.

Pagination Fields#

FieldTypeRequiredDescription
strategystring✅One of: offset, pageNumber, cursor, linkHeader, custom
maxPagesint✅Safety limit for max pages to fetch (default: 100, max: 10000)
collectPathstring❌CEL expression to extract items from each response (e.g., body.items)
stopWhenstring❌CEL expression; if true, stop paginating (e.g., size(body.items) == 0)

CEL variables available in collectPath, stopWhen, and strategy-specific expressions:

VariableTypeDescription
statusCodeintHTTP response status code
bodyanyParsed JSON response body
rawBodystringRaw response body string
headersobjectResponse headers
pageintCurrent page number (1-based)

Strategy: offset#

Increments an offset query parameter each page.

FieldTypeDefaultDescription
limitint(required)Page size
offsetParamstringoffsetQuery parameter name for offset
limitParamstringlimitQuery parameter name for limit

Strategy: pageNumber#

Increments a page number query parameter each page.

FieldTypeDefaultDescription
pageSizeint(required)Page size
pageParamstringpageQuery parameter name for page number
pageSizeParamstringpageSizeQuery parameter name for page size
startPageint1Starting page number

Strategy: cursor#

Extracts a cursor token or next URL from the response to fetch subsequent pages.

FieldTypeDescription
nextTokenPathstringCEL expression to extract cursor from response (e.g., body.nextCursor)
nextTokenParamstringQuery parameter to set with the cursor value (required with nextTokenPath unless bodyTemplate is set)
nextURLPathstringCEL expression to extract the full next page URL (e.g., body['@odata.nextLink']). Alternative to nextTokenPath.

Use nextTokenPath + nextTokenParam for APIs that return a token. Use nextURLPath for APIs that return a full URL (e.g., Microsoft Graph @odata.nextLink).

Strategy: linkHeader#

Follows rel="next" links in the Link response header (RFC 8288). Used by GitHub, GitLab, and other REST APIs. No additional configuration needed.

Strategy: custom#

Full control using CEL expressions.

FieldTypeDescription
nextURLstringCEL expression returning the full next page URL (empty string = stop)
nextParamsstringCEL expression returning a map of query params for the next request (empty map = stop)

Body-Based Pagination (bodyTemplate)#

For APIs that paginate via the request body (GraphQL, Elasticsearch), use bodyTemplate to generate the POST body for each page instead of modifying URL query parameters.

FieldTypeDescription
bodyTemplatestringCEL expression evaluated per-page to produce the request body. Overrides the top-level body field. The expression may return a string (sent as-is) or a map/list (automatically JSON-marshaled). If the expression evaluates to null, an empty body is sent.

CEL variables available in bodyTemplate:

VariableTypeDescription
__pageintCurrent page number (starts at startPage, default 1)
__pageSizeintPage size from pageSize (pageNumber/cursor/linkHeader/custom) or limit (offset)
__offsetintCurrent offset (increments by __pageSize each page)
__cursorstringLast cursor value from nextTokenPath (cursor strategy)

When bodyTemplate is set, the URL remains unchanged across all pages — pagination state is carried entirely in the request body.

# GraphQL pagination with bodyTemplate
provider: http
inputs:
  url: https://api.example.com/graphql
  method: POST
  headers:
    Content-Type: application/json
  pagination:
    strategy: cursor
    maxPages: 10
    pageSize: 50
    nextTokenPath: "body.data.pageInfo.endCursor"
    bodyTemplate: |
      __cursor == "" ?
        '{"query":"{ items(first: ' + string(__pageSize) + ') { nodes { id } pageInfo { endCursor hasNextPage } } }"}'
      :
        '{"query":"{ items(first: ' + string(__pageSize) + ', after: \"' + __cursor + '\") { nodes { id } pageInfo { endCursor hasNextPage } } }"}'
    collectPath: "body.data.items.nodes"
    stopWhen: "body.data.items.pageInfo.hasNextPage == false"

# Elasticsearch scroll with bodyTemplate
provider: http
inputs:
  url: https://es.example.com/my-index/_search
  method: POST
  headers:
    Content-Type: application/json
  pagination:
    strategy: offset
    maxPages: 20
    limit: 100
    bodyTemplate: |
      '{"from": ' + string(__offset) + ', "size": ' + string(__pageSize) + ', "query": {"match_all": {}}}'
    collectPath: "body.hits.hits"
    stopWhen: "size(body.hits.hits) == 0"

Polling#

The poll input enables re-executing the request until a response condition is met. This is different from retry (which handles transient failures) — polling re-executes on successful responses until the content matches expectations.

FieldTypeRequiredDescription
untilstring✅CEL expression evaluated against the response. Polling stops when this returns true. Available variables: body (parsed if JSON), statusCode, headers
failWhenstring❌CEL expression that triggers immediate failure (e.g., terminal error states)
intervalstring❌Duration between polls (default: 5s). Format: 1s, 30s, 2m
maxAttemptsint❌Maximum number of poll attempts (default: 60)
# Wait for deployment to complete
resolve:
  with:
    - provider: http
      inputs:
        url: https://api.example.com/deployments/123/status
        method: GET
        auth: entra
        autoParseJson: true
        poll:
          until: 'body.status == "succeeded"'
          failWhen: 'body.status == "failed"'
          interval: 10s
          maxAttempts: 30

Output#

FieldTypeDescription
statusCodeintHTTP status code (last page when paginating)
bodyanyResponse body as string, or parsed JSON object when autoParseJson: true. When paginating with collectPath, contains JSON array of all collected items
headersobjectResponse headers (last page when paginating)
successboolWhether request succeeded (action only)
pagesintNumber of pages fetched (only when paginating)
totalItemsintTotal items collected across all pages (only when paginating)

Non-JSON and Empty Response Bodies#

body is only parsed into a structured value when autoParseJson is enabled and the response Content-Type is JSON and the body is non-empty. Otherwise body is the raw response string, and an empty response yields an empty string ("").

Some endpoints return a bare scalar as text/plain rather than JSON — for example, a Microsoft Graph $count endpoint returns a number like 42, and may return an empty body when there is nothing to count. Coercing such a body directly to a number fails on the empty string, so guard it in CEL and supply a default before converting:

resolvers:
  user_count_raw:
    resolve:
      with:
        - provider: http
          inputs:
            url: https://graph.microsoft.com/v1.0/users/$count
            headers:
              ConsistencyLevel: eventual

  user_count:
    type: int
    dependsOn: [user_count_raw]
    resolve:
      with:
        - provider: cel
          inputs:
            # Treat an empty/whitespace body as 0, then convert to int.
            expression: int(_.user_count_raw.body.trim() == "" ? "0" : _.user_count_raw.body.trim())

This keeps the generic http provider free of endpoint-specific behavior while making the numeric coercion explicit and safe at the solution level.

Examples#

# GET request
resolve:
  with:
    - provider: http
      inputs:
        url: https://api.example.com/config
        headers:
          Accept: application/json

# POST with body
provider: http
inputs:
  url: https://api.example.com/deploy
  method: POST
  headers:
    Content-Type: application/json
  body:
    expr: 'toJson({"image": _.image, "env": _.environment})'
  timeout: 60

# With retry
provider: http
inputs:
  url: https://api.example.com/status
  retry:
    maxAttempts: 3
    backoff: exponential
    initialDelay: 1s

# Authenticated GitHub API request
resolve:
  with:
    - provider: http
      inputs:
        url: https://api.github.com/user/repos
        headers:
          Accept: application/json
        auth: github
        scope: repo

# Cursor pagination (token-based)
provider: http
inputs:
  url: https://api.example.com/items
  pagination:
    strategy: cursor
    maxPages: 10
    nextTokenPath: "body.nextCursor"
    nextTokenParam: "cursor"
    collectPath: "body.items"
    stopWhen: "body.nextCursor == null"

# Cursor pagination (OData / Microsoft Graph nextLink)
provider: http
inputs:
  url: https://graph.microsoft.com/v1.0/users?$top=100
  authProvider: entra
  scope: "https://graph.microsoft.com/.default"
  pagination:
    strategy: cursor
    maxPages: 50
    nextURLPath: "body['@odata.nextLink']"
    collectPath: "body.value"

# Link header pagination (GitHub-style)
provider: http
inputs:
  url: https://api.github.com/users/octocat/repos?per_page=30
  headers:
    Accept: application/vnd.github+json
  pagination:
    strategy: linkHeader
    maxPages: 5
    collectPath: "body"

# Offset pagination
provider: http
inputs:
  url: https://api.example.com/records
  pagination:
    strategy: offset
    maxPages: 20
    limit: 50
    collectPath: "body.records"
    stopWhen: "size(body.records) < 50"

# Page number pagination
provider: http
inputs:
  url: https://api.example.com/products
  pagination:
    strategy: pageNumber
    maxPages: 10
    pageSize: 25
    pageParam: "page"
    pageSizeParam: "per_page"
    collectPath: "body.products"
    stopWhen: "size(body.products) == 0"

# Custom pagination with CEL expressions
provider: http
inputs:
  url: https://api.example.com/search?q=test
  pagination:
    strategy: custom
    maxPages: 10
    nextURL: "has(body.links) && has(body.links.next) ? body.links.next : ''"
    collectPath: "body.results"
    stopWhen: "!has(body.links) || !has(body.links.next)"

Security#

The HTTP provider enforces several security measures:

  • SSRF protection: Requests to private, loopback, and link-local addresses are blocked by default. The check runs at dial time against the resolved IP, so a hostname that resolves into private space is blocked too. To reach an on-premises endpoint, name its range in httpClient.allowedPrivateCIDRs (httpClient.allowPrivateIPs: true unblocks every private range at once and is the blunter option). Cloud metadata addresses such as 169.254.169.254 can never be exempted via direct policy settings. Proxy routing (HTTP_PROXY/HTTPS_PROXY) is disabled by default for exactly this reason – a proxied request is dialed to the proxy, not the target, so it can only be checked once against a local DNS answer before hand-off, which a proxy with different resolution (or httpClient.trustProxyResolution: true, for a target that fails to resolve locally at all) could defeat. Set httpClient.trustedProxy: true only once the configured proxy is known to enforce an equivalent egress policy itself; only then does the metadata guarantee depend on that proxy rather than on this client.
  • Response body size limit: Each response is limited to httpClient.maxResponseBodySize (default: 100 MB). This prevents denial-of-service via unbounded responses from malicious or misconfigured servers. Applies to both direct requests and each page in paginated requests.
  • Redirect validation: Every redirect hop is checked at dial time, so a redirect from a public URL into private space is refused. A maximum of 10 redirects is enforced.
  • Pagination host validation: Pagination next URLs must stay on the same hostname as the original request to prevent open redirect attacks.
  • Token security: Authentication tokens are injected via the Authorization header and are never logged. Token refresh on 401 responses is handled transparently.
# Override the default response body size limit (e.g. for large API responses)
# In config.yaml:
httpClient:
  maxResponseBodySize: 209715200  # 200 MB

identity#

Get authentication identity information without exposing tokens. Supports reading stored session metadata or minting a fresh scoped access token to inspect its claims on demand.

Capabilities#

from

Inputs#

FieldTypeRequiredDescription
operationstring✅Operation: status, claims, groups, list
handlerstring❌Auth handler name (e.g., entra, github)
scopestring❌OAuth scope for on-demand token minting. When set, claims and status mint a fresh access token for the scope and return its details instead of stored session metadata. Not supported for groups or list.

Operations#

OperationDescription
statusReturns authentication status, expiry, and identity type from stored session metadata (or scoped token when scope is set)
claimsReturns identity claims (name, email, tenant, etc.) from stored session metadata (or scoped token JWT when scope is set)
groupsReturns Entra group memberships for the authenticated user
listLists all registered auth handler names

Output#

FieldTypeDescription
operationstringThe operation that was executed
handlerstringThe auth handler that was used
authenticatedboolWhether authenticated
identityTypestringIdentity type: user or service-principal
claimsobjectToken claims (email, name, subject, tenantId, etc.)
tenantIdstringTenant ID (for Entra)
expiresAtstringToken expiration in RFC3339 format
expiresInstringHuman-readable duration until expiry
groupsarrayGroup display names (for groups operation)
handlersarrayAvailable handler names (for list operation)
scopedTokenbooltrue when the response was derived from a scoped access token
tokenScopestringThe OAuth scope the token was minted for (when scope input was set)
tokenTypestringToken type, typically Bearer (when scope input was set)
flowstringAuth flow that produced the token (when scope input was set)
sessionIdstringStable session identifier (when scope input was set)

Opaque tokens: When scope is provided and the access token is not a decodable JWT (e.g., encrypted Microsoft Graph tokens), claims will be null and a warning is added to the output. Token metadata (expiry, type) is still returned where available.

Examples#

# Check if authenticated (Entra)
resolve:
  with:
    - provider: identity
      inputs:
        operation: status
        handler: entra

# Check if authenticated (GitHub)
resolve:
  with:
    - provider: identity
      inputs:
        operation: status
        handler: github

# Get claims from stored session metadata
resolve:
  with:
    - provider: identity
      inputs:
        operation: claims
        handler: entra

# Get GitHub claims (login, name, email)
resolve:
  with:
    - provider: identity
      inputs:
        operation: claims
        handler: github

# Mint a scoped token and inspect its claims
resolve:
  with:
    - provider: identity
      inputs:
        operation: claims
        scope: api://my-app/.default

# Check scoped token status (expiry, flow, tokenType)
resolve:
  with:
    - provider: identity
      inputs:
        operation: status
        scope: https://management.azure.com/.default
        handler: entra

# List all registered auth handlers
resolve:
  with:
    - provider: identity
      inputs:
        operation: list

# Get Entra group memberships
resolve:
  with:
    - provider: identity
      inputs:
        operation: groups
        handler: entra

kubeconfig#

Kubeconfig and cluster operations: write or remove an exec-credential entry for a cluster (so kubectl/helm shell out to scafctl for auth), probe cluster reachability, detect the cluster’s auth type, or check current-user identity (whoami).

Not a general-purpose solution provider. Unlike the other official providers on this page, kubeconfig cannot be referenced from a solution’s resolve.with (requires from) or workflow.actions (requires action) – it declares a dedicated kubeconfig capability instead, and is only dispatched by the host-side pkg/kubeconfig.Manager. In practice you use it via:

  • scafctl kube login / logout / list / status – the day-to-day CLI commands that drive this provider internally to write/remove/inspect kubeconfig entries. See the CLI help (scafctl kube --help) for the supported auth flows.
  • scafctl run provider kubeconfig ... – for direct testing/debugging of a single operation outside a kube login flow (shown below).

Capabilities#

kubeconfig (dedicated capability; not from, transform, validation, or action)

Inputs#

FieldTypeRequiredDescription
operationstring✅Operation: kubeconfig_write, kubeconfig_remove, current_server, detect_auth_type, reachable, whoami
kubeconfig_pathstring❌Path to kubeconfig (empty resolves KUBECONFIG or ~/.kube/config)
cluster_namestring❌Kubeconfig cluster name
context_namestring❌Kubeconfig context name
namespacestring❌Default namespace set on the written context (kubeconfig_write only); empty omits the namespace so the context has no default
user_namestring❌Kubeconfig user name
serverstring❌Cluster API server URL
ca_datastring❌PEM-encoded cluster CA bundle; preferred over insecure_skip_tls
insecure_skip_tlsbool❌Skip TLS verification (development only)
exec_commandstring❌Exec-credential command baked into the user entry
exec_argsarray❌Exec-credential command arguments
interactive_modestring❌Exec plugin interactive mode: Never, IfAvailable, Always
provide_cluster_infobool❌Pass cluster details to the exec plugin via KUBERNETES_EXEC_INFO
install_hintstring❌Message kubectl shows when the exec command is missing from PATH
set_current_contextbool❌Set the written context as current-context
tokenstring❌Bearer token (whoami only; never cached)
audiencestring❌Token audience (accepted for contract parity; not used by this provider)

Operations#

OperationDescription
kubeconfig_writeWrites (or updates) a cluster/context/user entry in the kubeconfig, wiring the user entry to an exec-based credential plugin
kubeconfig_removeRemoves a cluster/context/user entry from the kubeconfig
current_serverReturns the API server URL for the current (or a named) context
detect_auth_typeProbes the cluster and reports its authentication type (e.g., OIDC issuer/endpoint if configured)
reachableChecks whether the cluster’s API server is reachable
whoamiReturns the identity (username, UID, groups) the current credentials resolve to

Output#

FieldTypeDescription
successboolWhether the operation succeeded
kubeconfig_pathstringPath to the kubeconfig that was read or written
context_namestringThe context name involved in the operation
serverstringCluster API server URL
reachableboolWhether the cluster was reachable (reachable operation)
removedboolWhether an entry was removed (kubeconfig_remove operation)
auth_typestringDetected auth type (detect_auth_type operation)
oidc_issuerstringOIDC issuer URL, if detected
oauth_endpointstringOAuth endpoint, if detected
usernamestringResolved username (whoami operation)
uidstringResolved user UID (whoami operation)
groupsarrayResolved group memberships (whoami operation)
statusintHTTP-style status code, where applicable
errorstringError detail, if the operation failed

Examples#

These call the provider directly via run provider (its kubeconfig capability), which is how you’d test or debug a single operation. Day-to-day kubeconfig setup goes through scafctl kube login / logout, which drive these same operations internally.

# Write a kubeconfig entry that shells out to `scafctl` for credentials
scafctl run provider kubeconfig \
  operation=kubeconfig_write \
  cluster_name=my-cluster \
  context_name=my-cluster-ctx \
  user_name=my-cluster-user \
  namespace=my-team \
  server=https://my-cluster.example.com:6443 \
  exec_command=scafctl \
  set_current_context=true

# Check whether the cluster is reachable before running kubectl
scafctl run provider kubeconfig operation=reachable context_name=my-cluster-ctx

# Detect the cluster's auth type
scafctl run provider kubeconfig operation=detect_auth_type server=https://my-cluster.example.com:6443

# Remove a previously written kubeconfig entry
scafctl run provider kubeconfig \
  operation=kubeconfig_remove \
  cluster_name=my-cluster \
  context_name=my-cluster-ctx \
  user_name=my-cluster-user

metadata#

Returns runtime metadata about the scafctl process and the currently-executing solution, including platform information. Requires no inputs – all data is gathered from the execution context and process environment. Useful for conditional logic, auditing, cross-platform solutions, and passing runtime info to templates and downstream resolvers.

Capabilities#

from

Inputs#

None. The metadata provider accepts no inputs.

Output#

FieldTypeDescription
versionobjectBuild version information
version.buildVersionstringSemantic version of the scafctl build
version.commitstringGit commit hash of the build
version.buildTimestringTimestamp of the build
argsstring[]Command-line arguments passed to scafctl
cwdstringCurrent working directory
entrypointstringHow scafctl was invoked: "cli", "api", "mcp", or "unknown". Long-lived hosts declare their own entrypoint to the plugin pool: the CLI’s mcp serve and the pkg/mcp server report "mcp", the API server reports "api". An embedder that wraps pkg/mcp gets "mcp" automatically (override via mcp.WithEntrypoint); "unknown" only appears when a custom pool-mode host neglects to declare one.
commandstringThe command path (e.g. scafctl/run/solution)
solutionobjectMetadata about the currently-running solution
solution.namestringSolution name
solution.versionstringSolution version
solution.displayNamestringSolution display name
solution.descriptionstringSolution description
solution.categorystringSolution category
solution.tagsstring[]Solution tags
solution.sourcestringSolution provenance: the local file path when run from a file, otherwise the author-declared metadata.source (e.g. a catalog or repository reference). Empty when neither is available.
osstringOperating system (runtime.GOOS): darwin, linux, windows, etc.
archstringCPU architecture (runtime.GOARCH): amd64, arm64, etc.
shellstringDetected shell name (e.g. bash, zsh, fish, pwsh, powershell, cmd.exe). On Unix, reads $SHELL. On Windows, detects PowerShell via PSModulePath and parent process inspection, then falls back to Git Bash ($SHELL) and finally %ComSpec%. Empty if not detected.

Examples#

Resolve runtime metadata:

resolvers:
  runtime-meta:
    resolve:
      with:
        - provider: metadata

Use metadata in a downstream resolver via CEL:

resolvers:
  runtime-meta:
    resolve:
      with:
        - provider: metadata
  greeting:
    dependsOn: [runtime-meta]
    resolve:
      with:
        - provider: cel
          inputs:
            expression: >-
              "Running " + _.runtime_meta.solution.name +
              " v" + _.runtime_meta.solution.version +
              " via " + _.runtime_meta.entrypoint

message#

Outputs styled terminal messages with built-in types, custom formatting via lipgloss, destination control, and respects --quiet and --no-color flags. For dynamic interpolation, use the framework’s tmpl: or expr: ValueRef on the message input — the provider does not handle templating internally. Use type: raw for machine-readable output that bypasses the 8192-character limit and writes content directly without formatting.

Capabilities#

action

Inputs#

FieldTypeRequiredDescription
messagestring✅Message text to output. Use tmpl: or expr: ValueRef for dynamic interpolation. Limited to 8192 characters unless type: raw.
typestring❌Message type: success, warning, error, info (default), debug, plain, raw
labelstring❌Contextual prefix rendered as dimmed [label] between icon and message (e.g., step 2/5). Not supported with type: raw.
styleobject❌Custom formatting that merges on top of type defaults: color (hex or named), bold, italic, icon. Not supported with type: raw.
destinationstring❌Output target: stdout (default) or stderr
newlinebool❌Append trailing newline (default: true)

Output#

FieldTypeDescription
successboolAlways true on success
messagestringRendered message text

Examples#

Built-in type styling:

resolvers:
  step1:
    resolve:
      with:
        - provider: message
          inputs:
            message: "Build succeeded"
            type: success

Custom style with icon:

resolvers:
  deploy:
    resolve:
      with:
        - provider: message
          inputs:
            message: "Starting pipeline"
            style:
              color: "#FF5733"
              bold: true
              icon: "\U0001F680"

Go template interpolation via tmpl: ValueRef:

resolvers:
  config:
    resolve:
      with:
        - provider: static
          inputs:
            value:
              appName: my-service
              version: 2.0.0
  deploy-msg:
    resolve:
      with:
        - provider: message
          inputs:
            message:
              tmpl: "Deploying {{ .config.appName }} v{{ .config.version }}"
            type: info

CEL expression via expr: ValueRef:

resolvers:
  items:
    resolve:
      with:
        - provider: static
          inputs:
            value: [a, b, c]
  status:
    resolve:
      with:
        - provider: message
          inputs:
            message:
              expr: "'Processed ' + string(size(_.items)) + ' items'"
            type: success

Raw output (machine-readable, no length limit):

actions:
  emit-index:
    steps:
      - provider: message
        inputs:
          message:
            rslvr: registry_index_json
          type: raw
          newline: false

parameter#

Access CLI parameters passed via -r flags.

Capabilities#

from

Inputs#

FieldTypeRequiredDescription
keystring❌Single-key (scalar) mode. Parameter name (exact match). Provide either key or keys; key takes precedence over keys when both are set. Mutually exclusive with all and with as: map.
keysarray of string❌Ordered list of parameter names. Default meaning: aliases for a single logical parameter (first provided via CLI wins), evaluated after key. With as: map it is instead a distinct set of parameter names read into a map.
asstring❌Read-mode discriminator for keys. Only map is supported: it reinterprets keys as a distinct set of parameter names read into a map (instead of first-match-wins aliases).
allbool❌Map mode. When true, read every supplied CLI parameter as a map. Mutually exclusive with key, keys, and as.
defaultany❌Value returned when the parameter is not provided. Only valid in single-key/alias mode; in map mode absent keys are omitted instead, so combining default with all or as: map is an error. Under auto the default keeps its authored YAML type (a quoted "false" stays the string "false", a bare false stays a bool) – inference and file:// resolution apply only to CLI values. Use an explicit type to coerce the default.
typestring❌How the value is coerced. One of auto (default), string, raw, int, float, bool, json, csv, fetch. See the table below. Only valid in single-key/alias mode; in map mode each value uses auto inference.

Specify one read mode: a single key / alias keys, keys + as: map, or all: true.

Read modes#

  • Single key (key): returns the parameter value directly, or default/an error when absent. _.environment is the scalar value.
  • Aliases (keys, no as): resolves one logical value from the first provided alias (first-match-wins), falling back to default.
  • Explicit set (keys + as: map): returns a map of the requested parameter names to their values. Requested keys not supplied via CLI are omitted from the map (not defaulted), so has(_.myBag.keyB) and _.myBag.?keyB.orValue(d) remain faithful. Each value uses auto inference.
  • All supplied (all: true): returns a map of every CLI-supplied parameter to its auto-inferred value.

In map mode the resolver value is the bare map. The set of present keys (and, in keys mode, the requested-but-absent keys) is reported in provider metadata, visible via scafctl run provider parameter ... but not in the resolver value.

type values#

ValueBehavior
autoDefault. For CLI values, infers booleans, numbers, JSON, and file:// sources, falling back to the literal string. http:///https:// values are not fetched (they stay literal strings – use fetch), and comma-separated values are not split into a list (opt in with csv). An authored default keeps its YAML type under auto (a quoted "false" stays a string, a bare false stays a bool) and is never inferred; use an explicit type to coerce a default. When the enclosing resolver declares a scalar output type (string/int/float/bool), that declared type is authoritative – the CLI value is coerced directly to it instead of being inferred and re-coerced (so -r version=2.0 on a type: string resolver stays "2.0").
stringCoerces the value to a string, stripping surrounding quotes. Use to keep a numeric-looking value (leading zeros, or a value used with CEL matches()) as a string. A resolver that already declares type: string gets this automatically under auto.
rawReturns the value untouched – no coercion or quote-stripping. A numeric YAML default stays numeric; a CLI string stays verbatim. The escape hatch to disable inference.
intForces integer parsing. Whole-number float syntax (e.g. 2.0) is accepted; a fractional (e.g. 2.5) or non-numeric value is an error.
floatForces floating-point parsing. A non-numeric value is an error.
boolForces boolean parsing, accepting only true/false (case-insensitive). Any other value is an error.
jsonParses a string value as JSON. Invalid JSON is an error; non-string values pass through unchanged.
csvSplits a comma-separated string into a list of trimmed strings. Non-string values pass through unchanged.
fetchPerforms an SSRF-guarded HTTP GET on an http:///https:// value and returns the response body. A non-URL value is an error. For anything beyond a plain GET (auth, headers, methods, retries), use the http provider.

Output#

In single-key/alias mode the output is the parameter value (typed per the parsing rules), or the default when absent. In map mode (keys + as: map, or all: true) the output is a map of present parameter names to their auto-inferred values, with absent keys omitted.

Examples#

# Get parameter with fallback
resolve:
  with:
    - provider: parameter
      inputs:
        key: environment
    - provider: static
      inputs:
        value: "dev"  # Default if not provided
# Accept the same parameter under any of several flag names (aliases).
# Whichever of -r environment / -r e / -r env was provided wins.
resolve:
  with:
    - provider: parameter
      inputs:
        keys: [environment, e, env]
        default: dev
# Read a set of distinct parameters at once into a map (opt in with as: map).
# Absent keys are omitted, so has(_.featureBag.keyB) stays faithful.
resolve:
  with:
    - provider: parameter
      inputs:
        keys: [keyA, keyB, keyC]
        as: map
# Read every supplied CLI parameter into a single map.
resolve:
  with:
    - provider: parameter
      inputs:
        all: true
# Coerce values with the "type" input. "auto" (default) infers bools,
# numbers, JSON, and file:// sources; http(s):// URLs stay literal strings
# (use "fetch" to GET them); other types force one coercion.
resolve:
  with:
    # Keep a numeric-looking ID as a string (leading zeros survive).
    - provider: parameter
      inputs:
        key: billingId
        type: string
        default: "00042"
    # Force integer parsing (a non-integer value is an error).
    - provider: parameter
      inputs:
        key: port
        type: int
        default: 8080
    # Opt in to list splitting: "a,b,c" -> [a, b, c].
    - provider: parameter
      inputs:
        key: regions
        type: csv
        default: us-east-1
    # Return the value exactly as received (no coercion, no quote-stripping).
    - provider: parameter
      inputs:
        key: token
        type: raw

Usage:

scafctl run solution -f sol.yaml -r environment=production
scafctl run solution -f sol.yaml -r environment=production

secret#

Retrieve encrypted secrets from the scafctl secrets store.

Capabilities#

from

Inputs#

FieldTypeRequiredDescription
operationstring✅Operation: get or list
namestring❌Secret name (for get)
patternstring❌Regex pattern to match names
requiredbool❌Error if not found
defaultstring❌Value when not found

Examples#

# Get secret
resolve:
  with:
    - provider: secret
      inputs:
        operation: get
        name: api-key
        required: true

# Get with default
resolve:
  with:
    - provider: secret
      inputs:
        operation: get
        name: optional-key
        default: "fallback-value"

Manage secrets via CLI:

scafctl secrets set api-key "my-secret-value"
scafctl secrets list
scafctl secrets set api-key "my-secret-value"
scafctl secrets list

sleep#

Pause execution for a specified duration.

Capabilities#

from, transform, validation, action

Inputs#

FieldTypeRequiredDescription
durationstring✅Duration (Go format: 1s, 500ms, 2m)

Examples#

# Wait between API calls
provider: sleep
inputs:
  duration: "2s"

solution#

Execute a sub-solution and return its results as a structured envelope. Supports recursive composition with circular reference detection and configurable depth limits. In from mode, only the resolver phase runs. In action mode, resolvers run first, then the workflow (if present).

Capabilities#

from, action

Inputs#

FieldTypeRequiredDescription
sourcestringYesSub-solution location: file path, catalog reference (e.g. deploy-to-k8s@2.0.0), or URL
inputsobject❌Parameters passed to the sub-solution’s parameter provider
resolversarray❌Resolver names to execute from the child solution; when empty all resolvers run
propagateErrorsbool❌Whether sub-solution failures cause a Go error (default: true)
maxDepthint❌Maximum nesting depth for recursive composition, 1–100 (default: 10)
timeoutstring❌Maximum duration for sub-solution execution (Go duration, e.g. 30s, 5m)

Output#

From capability (resolver-only):

FieldTypeDescription
resolversobjectResolver values from the sub-solution (keyed by resolver name)
statusstringOverall status: success or failed
errorsarrayResolver errors encountered during execution

Action capability (resolvers + workflow):

FieldTypeDescription
resolversobjectResolver values from the sub-solution
workflowobjectAggregate workflow status (finalStatus, failedActions, skippedActions)
statusstringOverall status: success or failed
errorsarrayResolver errors encountered during execution
successboolWhether the solution succeeded

Examples#

# Load child solution (from capability)
resolve:
  with:
    - provider: solution
      inputs:
        source: "./child-solution.yaml"
        inputs:
          environment: "production"

# Execute child solution with workflow (action capability)
action:
  with:
    - provider: solution
      inputs:
        source: "deploy-to-k8s@2.0.0"
        inputs:
          cluster: prod-east
          replicas: 3
        timeout: "5m"

# Select specific resolvers from child solution
resolve:
  with:
    - provider: solution
      inputs:
        source: "./shared/config.yaml"
        resolvers:
          - database_config
          - cache_config

# Soft failures (don't propagate errors)
resolve:
  with:
    - provider: solution
      inputs:
        source: "./optional-enrichment.yaml"
        propagateErrors: false

# Load-tolerant composition: if the child YAML is missing or malformed,
# the parent continues with a degraded envelope (status: "failed",
# errors: [{resolver: "_loader", message: "..."}]) instead of aborting.
resolve:
  with:
    - provider: solution
      inputs:
        source: "./optional-child.yaml"
        propagateErrors: false

state#

Read a resolver value that was persisted on a previous run. The value comes from the state snapshot loaded at the start of the run – i.e. the value from the prior run, before the current run overwrites it at save time. Pair with a resolver marked persist: true (or immutable: true) to reproduce the legacy “read another resolver’s previously saved value” pattern.

The key input is treated as an opaque string: it never creates a dependency edge in the resolver graph, so a resolver may safely read its own prior value without forming a self-cycle.

Capabilities#

from

Inputs#

Specify exactly one of key, keys, or all to choose the read mode.

FieldTypeRequiredDescription
operationstring❌State operation. Only get is supported (the default).
keystring❌Single-key mode. Name of the persisted resolver whose prior-run value to read. Returns the value directly.
keysarray❌Map mode. Explicit set of persisted keys to read as a map. Absent keys are omitted from the map.
allbool❌Map mode. When true, read the entire persisted snapshot as a map.
defaultany❌Only valid with key: value returned when the key has no persisted entry (e.g. on the first run). Returns null when omitted. In map mode absent keys are omitted instead, so default is rejected.

Read modes#

  • Single key (key): returns the persisted value directly, or default/null when absent. _.prior_password is the scalar value.
  • Explicit set (keys): returns a map of the requested keys to their persisted values. Requested keys with no persisted entry are omitted from the map (not defaulted), so has(_.myState.keyB) and _.myState.?keyB.orValue(d) remain faithful.
  • Whole snapshot (all: true): returns a map of every persisted resolver’s prior-run value.

In map mode the resolver value is the bare map. The set of present keys (and, in keys mode, the requested-but-absent keys) is reported in provider metadata, visible via scafctl run provider state ... but not in the resolver value. You can reconstruct the missing set in CEL with myKeys.filter(k, !has(_.myState[k])).

Examples#

resolvers:
  # Recomputes fresh every run and records its value for next time.
  db_password:
    persist: true
    resolve:
      with:
        - provider: exec
          inputs:
            command: "openssl rand -hex 16"

  # Reads the value db_password persisted on the PRIOR run.
  prior_password:
    resolve:
      with:
        - provider: state
          inputs:
            operation: get
            key: db_password
            default: ""

  # Reads several persisted keys at once as a map (absent keys omitted).
  prior_config:
    resolve:
      with:
        - provider: state
          inputs:
            operation: get
            keys: [region, tier, cluster_id]

  # Reads the entire persisted snapshot as a map.
  prior_state:
    resolve:
      with:
        - provider: state
          inputs:
            operation: get
            all: true

Within a single run, state.get("db_password") returns the prior run’s value while _.db_password returns the value produced by the current run.


static#

Return a constant value. Useful for defaults and fallbacks.

Capabilities#

from, transform

Inputs#

FieldTypeRequiredDescription
valueany✅Static value to return

Examples#

# Default fallback
resolve:
  with:
    - provider: env
      inputs:
        operation: get
        name: CONFIG_PATH
    - provider: static
      inputs:
        value: "/etc/app/config.yaml"

# Complex default
resolve:
  with:
    - provider: static
      inputs:
        value:
          timeout: 30
          retries: 3
          endpoints:
            - https://primary.example.com
            - https://backup.example.com

validation#

Validate data using regex patterns and CEL expressions.

Capabilities#

transform, validation

Inputs#

FieldTypeRequiredDescription
valuestring❌Value to validate (uses __self in transform context)
matchstring❌Regex pattern that must match
notMatchstring❌Regex pattern that must NOT match
expressionstring❌CEL expression that must be true
messagestring❌Custom error message on failure

Output#

FieldTypeDescription
validboolWhether validation passed
errorsarrayValidation error messages
detailsstringFailure details

Examples#

# Validate with regex
validate:
  with:
    - provider: validation
      inputs:
        match: "^[a-z][a-z0-9-]+$"
        message: "Name must be lowercase alphanumeric with dashes"

# Validate with CEL
validate:
  with:
    - provider: validation
      inputs:
        expression: "__self in ['dev', 'staging', 'prod']"
        message: "Environment must be dev, staging, or prod"

# Combined validation
validate:
  with:
    - provider: validation
      inputs:
        match: "^v[0-9]+\\.[0-9]+\\.[0-9]+$"
        expression: "!__self.startsWith('v0.')"
        message: "Version must be semver format and >= v1.0.0"

forEach with Providers#

The forEach clause iterates over an array, executing a provider once per element and collecting results into an output array.

Supported Phases#

PhaseSupportedforEach.inNotes
transform.withYesOptional (defaults to __self)Most common – transform an existing value
resolve.withYesRequiredNo __self in resolve phase
validate.withNo–Not supported

forEach Clause Fields#

FieldTypeRequiredDescription
itemstringNoVariable name alias for current array element. __item is always available.
indexstringNoVariable name alias for current 0-based index. __index is always available.
inValueRefResolve: Yes, Transform: NoArray to iterate over. Defaults to __self (transform only).
concurrencyintNoMaximum parallel iterations. 0 (default) means unlimited.
keepSkippedboolNoRetain nil entries for items skipped by when condition (default: false).
onErrorstringNoError handling: fail (default) or continue. Actions only; resolvers ignore this.

How It Works#

  1. The in source (or __self for transform) is evaluated to get an input array.
  2. The provider executes once per element, with __item/__index (and any custom aliases) injected into the expression context.
  3. Results are collected into an output array preserving order.
  4. If a when condition is present, items where when is false are removed from the output (unless keepSkipped: true).

Examples#

# Resolve: fan-out HTTP requests directly (no static placeholder needed)
resolve:
  with:
    - provider: http
      forEach:
        in:
          rslvr: moduleList
        item: mod
        concurrency: 10
      inputs:
        url:
          expr: '"https://registry.example.com/v1/modules/" + mod.name + "/versions"'
        method: GET
        autoParseJson: true

# Transform: double each number in an array
transform:
  with:
    - provider: cel
      forEach:
        item: num
        index: i
      inputs:
        expression: "num * 2"

# Transform: filter and keep only active users
transform:
  with:
    - provider: cel
      forEach:
        item: user
      when:
        expr: "user.active == true"
      inputs:
        expression: "user"

# Preserve index alignment with keepSkipped
transform:
  with:
    - provider: cel
      forEach:
        item: x
        keepSkipped: true
      when:
        expr: "x > 5"
      inputs:
        expression: "x"

On resolve steps, forEach.in is required because there is no __self in the resolve phase. On transform steps, forEach.in defaults to __self (the current value being transformed).

For a full walkthrough with runnable examples, see the Resolver Tutorial – Array Iteration with forEach .


Next Steps#