Skip to main content
Use a YAML action template to compose existing actions into a reusable action.

Template shape

Create a .yml file in your registry templates directory. Define inputs in expects, run actions in steps, and return the final value from returns.
type: action
definition:
  title: Post message
  description: Post a message to a Slack channel.
  display_group: Slack
  namespace: tools.slack
  name: post_message
  expects:
    channel:
      type: str
      description: The Slack channel ID.
    text:
      type: str
      description: The message text.
  steps:
    - ref: post_message
      action: tools.slack_sdk.call_method
      args:
        sdk_method: chat_postMessage
        params:
          channel: ${{ inputs.channel }}
          text: ${{ inputs.text }}
  returns: ${{ steps.post_message.result }}
  • Start every file with type: action.
  • Use tools.<integration> for the namespace.
  • Set title, description, display_group, namespace, and name in definition.
  • Define inputs in expects.
  • Build the action in steps.
  • Return the final value from returns.

Expressions

Use ${{ }} for expressions. Use inputs to read the inputs you defined in expects. Use steps to read the result of an earlier step. After a step runs, its output is available at steps.<ref>.result.
Read an input
args:
  channel: ${{ inputs.channel }}
  text: ${{ inputs.text }}
Read an earlier step result
returns: ${{ steps.post_message.result }}
Build one step from another
steps:
  - ref: lookup_user
    action: tools.slack.users.lookup_user_by_email
    args:
      email: ${{ inputs.email }}
  - ref: post_message
    action: tools.slack.chat.post_message
    args:
      channel: ${{ steps.lookup_user.result.id }}
      text: Hello
You can use:
  • inputs
  • steps
  • SECRETS
  • VARS
  • FN.*

Secrets

Declare what each template needs under definition.secrets (API keys, structured types, or OAuth). That list drives which credentials must exist for the action when the workflow runs. Use the same ${{ SECRETS... }} paths in args as in any other expression. Use the same ${{ SECRETS... }} syntax as workflow actions. Custom secrets (API keys, SSH, mTLS, CA bundles, and so on):
${{ SECRETS.<secret_name>.<KEY> }}
OAuth tokens live under <provider_id>_oauth. The key is the provider ID in uppercase plus _USER_TOKEN or _SERVICE_TOKEN:
  • authorization_code: ${{ SECRETS.<provider_id>_oauth.<PROVIDER_ID_UPPER>_USER_TOKEN }}
  • client_credentials: ${{ SECRETS.<provider_id>_oauth.<PROVIDER_ID_UPPER>_SERVICE_TOKEN }}
${{ SECRETS.slack_oauth.SLACK_USER_TOKEN }}
${{ SECRETS.google_docs_oauth.GOOGLE_DOCS_SERVICE_TOKEN }}
Optional fallback when either grant type is allowed:
${{ SECRETS.microsoft_sentinel_oauth.MICROSOFT_SENTINEL_USER_TOKEN || SECRETS.microsoft_sentinel_oauth.MICROSOFT_SENTINEL_SERVICE_TOKEN }}

Python UDFs

Declare credentials in @registry.register(..., secrets=[...]).
  • RegistrySecret: static keys; set secret_type to ssh_key, mtls, or ca_cert when needed (secret types). Optional: optional, optional_keys.
  • RegistryOAuthSecret: declares a dependency on an existing OAuth integration. Pass the provider_id and grant_type that match the integration in your workspace (a mismatch — for example declaring authorization_code when the integration uses client_credentials — will not resolve).
Read values with secrets.get("<KEY>") (key only, e.g. GOOGLE_DRIVE_USER_TOKEN), not a SECRETS. path.
from tracecat_registry import RegistryOAuthSecret, RegistrySecret, registry, secrets

api = RegistrySecret(name="exa", keys=["EXA_API_KEY"])

oauth = RegistryOAuthSecret(
    provider_id="google_drive",
    grant_type="authorization_code",
)

YAML

Under definition, set a secrets list (same validator as Python). Custom (optional secret_type for structured secrets):
definition:
  secrets:
    - name: exa
      keys: ["EXA_API_KEY"]
    - name: ansible
      keys: ["PRIVATE_KEY"]
      secret_type: ssh_key
OAuth:
definition:
  secrets:
    - type: oauth
      provider_id: microsoft_teams
      grant_type: authorization_code
The provider_id and grant_type must match an integration already configured in your workspace. Use ${{ SECRETS.<provider_id>_oauth.<TOKEN_KEY> }} in args. Set optional: true on an OAuth entry when it is not always required.

Example providers

See integrations/google_drive.py and integrations/slack_sdk.py for UDFs that use OAuth credentials. See tracecat_registry/core/ssh.py for an ssh_key secret example. For YAML templates, see microsoft_teams/send_message.yml (authorization_code), google_docs/create_document.yml (client_credentials), and microsoft_sentinel/.../get_alert_rule_template.yml (optional dual grant). See templates/tools/exa/search.yml for a simple API key template. For the full list of built-in provider IDs, see tracecat/integrations/providers/__init__.py.

Limitations

  • Template steps only support ref, action, and args.
  • Template steps run in order.
  • Template steps do not support run_if, for_each, join_strategy, start_delay, timeout, or max_attempts.
  • Templates can call tools actions, other templates, and core.script.run_python. Other platform actions are not supported inside templates.
  • If a later step fails, you do not get a final result with earlier step outputs. Keep templates short. In practice, do not build templates with more than 2 steps.

Example templates

All integrations in Tracecat are open source. Browse template actions for more examples. For concrete examples, browse the Slack templates and CrowdStrike templates.