> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tracecat.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Python UDFs

> Build custom Python actions for Tracecat: declare inputs and outputs, package dependencies, and expose user-defined functions to workflows and agents.

Tracecat makes it easy to turn your Python scripts into actions for agents and workflows. All you need is:

* A single Python decorator, `@registry.register`
* `from typing import Annotated`
* `from typing_extensions import Doc`

## Quick start

1. Add a Python file to your custom registry package.
2. Register one async function with `@registry.register(...)`.
3. Define typed inputs, read secrets with `secrets.get(...)`, and return JSON-serializable data.

```python theme={null}
from typing import Annotated
from typing_extensions import Doc

from tracecat_registry import registry, RegistrySecret, secrets


# (Optional) Define secrets used in the function
secret_name = RegistrySecret(
    name="secret_name",
    keys=["SECRET_NAME"],
    optional_keys=["OPTIONAL_SECRET_NAME"],
)


# Register the function as a Tracecat UDF
@registry.register(
    default_title="Say goodbye secretly",
    description="This is a function that says goodbye",
    display_group="Greetings",
    namespace="integrations.greetings",
    # (Optional) Define secrets used in the function
    secrets=[secret_name],
)
async def say_goodbye_secretly(
    name: Annotated[str, Doc("The name to say goodbye to")],
):
    secret = secrets.get("SECRET_NAME")
    # We're returning the secret for demonstration only.
    # Do not do this in your own functions!
    return {"message": f"Goodbye, {name}! Secret: {secret}"}
```

## What to define

* Use `tools.<integration>` for the namespace.
* Set `default_title`, `description`, `display_group`, `doc_url`, and `namespace` in `@registry.register(...)`.
* Define action inputs with type hints and `Field(..., description=...)`.
* Define required credentials with `RegistrySecret(...)` and pass them in `secrets=[...]`.
* Read secret values with `secrets.get(...)`.
* Return plain dicts, lists, strings, numbers, or booleans.

## Declaring secret types

By default, secrets are `custom` (arbitrary key-value). If your action requires an SSH key, mTLS certificate, or CA certificate, set `secret_type` so the credentials UI renders the correct form.

| `secret_type` | Required `keys`                          | Description                       |
| ------------- | ---------------------------------------- | --------------------------------- |
| `"custom"`    | Any                                      | Generic key-value pairs (default) |
| `"ssh_key"`   | `["PRIVATE_KEY"]`                        | SSH private key (PEM)             |
| `"mtls"`      | `["TLS_CERTIFICATE", "TLS_PRIVATE_KEY"]` | mTLS client certificate and key   |
| `"ca_cert"`   | `["CA_CERTIFICATE"]`                     | CA certificate bundle             |

### Examples

```python theme={null}
# SSH key secret — renders an SSH private key textarea in the UI
ssh_secret = RegistrySecret(
    name="ansible",
    keys=["PRIVATE_KEY"],
    secret_type="ssh_key",
)

# mTLS secret — renders certificate + key textareas
mtls_secret = RegistrySecret(
    name="mtls",
    keys=["TLS_CERTIFICATE", "TLS_PRIVATE_KEY"],
    secret_type="mtls",
    optional=True,
)

# CA certificate secret — renders a certificate textarea
ca_cert_secret = RegistrySecret(
    name="ca_cert",
    keys=["CA_CERTIFICATE"],
    secret_type="ca_cert",
    optional=True,
)

# Custom secret (default) — renders key-value inputs
api_secret = RegistrySecret(
    name="slack",
    keys=["SLACK_BOT_TOKEN"],
)
```

`secret_type` uses snake\_case everywhere — Python declarations, the API, and the UI all use the same values (`ssh_key`, `ca_cert`, `mtls`, `custom`).

Use the same `${{ SECRETS... }}` syntax as workflow actions.

Custom secrets (API keys, SSH, mTLS, CA bundles, and so on):

```yaml theme={null}
${{ 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 }}`

```yaml theme={null}
${{ SECRETS.slack_oauth.SLACK_USER_TOKEN }}
${{ SECRETS.google_docs_oauth.GOOGLE_DOCS_SERVICE_TOKEN }}
```

Optional fallback when either grant type is allowed:

```yaml theme={null}
${{ 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](/automations/core-concepts/secrets)). 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.

```python theme={null}
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):

```yaml theme={null}
definition:
  secrets:
    - name: exa
      keys: ["EXA_API_KEY"]
    - name: ansible
      keys: ["PRIVATE_KEY"]
      secret_type: ssh_key
```

OAuth:

```yaml theme={null}
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`](https://github.com/TracecatHQ/tracecat/blob/main/packages/tracecat-registry/tracecat_registry/integrations/google_drive.py) and [`integrations/slack_sdk.py`](https://github.com/TracecatHQ/tracecat/blob/main/packages/tracecat-registry/tracecat_registry/integrations/slack_sdk.py) for UDFs that use OAuth credentials. See [`tracecat_registry/core/ssh.py`](https://github.com/TracecatHQ/tracecat/blob/main/packages/tracecat-registry/tracecat_registry/core/ssh.py) for an `ssh_key` secret example.

For YAML templates, see [`microsoft_teams/send_message.yml`](https://github.com/TracecatHQ/tracecat/blob/main/packages/tracecat-registry/tracecat_registry/templates/tools/microsoft_teams/send_message.yml) (`authorization_code`), [`google_docs/create_document.yml`](https://github.com/TracecatHQ/tracecat/blob/main/packages/tracecat-registry/tracecat_registry/templates/tools/google_docs/create_document.yml) (`client_credentials`), and [`microsoft_sentinel/.../get_alert_rule_template.yml`](https://github.com/TracecatHQ/tracecat/blob/main/packages/tracecat-registry/tracecat_registry/templates/tools/microsoft_sentinel/alert_rules/get_alert_rule_template.yml) (optional dual grant). See [`templates/tools/exa/search.yml`](https://github.com/TracecatHQ/tracecat/blob/main/packages/tracecat-registry/tracecat_registry/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`](https://github.com/TracecatHQ/tracecat/blob/main/tracecat/integrations/providers/__init__.py).

## Example actions

You can browse all open-source integrations in Tracecat in [tools actions](https://github.com/TracecatHQ/tracecat/tree/main/packages/tracecat-registry/tracecat_registry/integrations). Use [template actions](https://github.com/TracecatHQ/tracecat/tree/main/packages/tracecat-registry/tracecat_registry/templates/tools) to see how actions are composed, and use [core actions](https://github.com/TracecatHQ/tracecat/tree/main/packages/tracecat-registry/tracecat_registry/core) to inspect built-in implementations.
For concrete examples, browse the [Slack](https://github.com/TracecatHQ/tracecat/blob/main/packages/tracecat-registry/tracecat_registry/integrations/slack_sdk.py), [AWS Boto3](https://github.com/TracecatHQ/tracecat/blob/main/packages/tracecat-registry/tracecat_registry/integrations/aws_boto3.py), and [FalconPy](https://github.com/TracecatHQ/tracecat/blob/main/packages/tracecat-registry/tracecat_registry/integrations/crowdstrike_falconpy.py) integrations.
