### `regex_match(value: Any, regex: str) -> bool`

Check if `value` matches Regular Expression `regex`.

Example:

```python
return regex_match(request.user.username, '.*admin.*')
```

### `regex_replace(value: Any, regex: str, repl: str) -> str`

Replace anything matching `regex` within `value` with `repl` and return it.

Example:

```python
user_email_local = regex_replace(request.user.email, '(.+)@.+', '')
```

### `list_flatten(value: list[Any] | Any) -> Optional[Any]`

Flatten a list by either returning its first element, None if the list is empty, or the passed in object if it's not a list.

Example:

```python
user = list_flatten(["foo"])
# user = "foo"
```

### `ak_call_policy(name: str, **kwargs) -> PolicyResult`

Call another policy with the name _name_. Current request is passed to policy. Key-word arguments
can be used to modify the request's context.

Example:

```python
result = ak_call_policy("test-policy")
# result is a PolicyResult object, so you can access `.passing` and `.messages`.
# Starting with authentik 2023.4 you can also access `.raw_result`, which is the raw value returned from the called policy
# `result.passing` will always be a boolean if the policy is passing or not.
return result.passing

result = ak_call_policy("test-policy-2", foo="bar")
# Inside the `test-policy-2` you can then use `request.context["foo"]`
return result.passing
```

### `ak_is_group_member(user: User, **group_filters) -> bool`

Check if `user` is member of a group matching `**group_filters`.

Example:

```python
return ak_is_group_member(request.user, name="test_group")
```

### `ak_user_by(**filters) -> Optional[User]`

Fetch a user matching `**filters`.

Returns "None" if no user was found, otherwise returns the [User](../users-sources/user/index.mdx) object.

Example:

```python
# Find user by username
other_user = ak_user_by(username="other_user")

# Find user by custom attribute
other_user = ak_user_by(attributes__<custom_attribute_name>="<value>")
```

### `ak_user_has_authenticator(user: User, device_type: Optional[str] = None) -> bool`

Check if a user has any authenticator devices. Only fully validated devices are counted.

Optionally, you can filter a specific device type. The following options are valid:

- `totp`
- `duo`
- `static`
- `webauthn`

Example:

```python
return ak_user_has_authenticator(request.user)
```

### `ak_create_event(action: str, **kwargs) -> None`

Create a new event with the action set to `action`. Any additional key-word parameters will be saved in the event context. Additionally, `context` will be set to the context in which this function is called.

Before saving, any data structures which are not representable in JSON are flattened, and credentials are removed.

The event is saved automatically.

Example:

```python
ak_create_event("my_custom_event", foo=request.user)
```

### `ak_create_jwt(user: User, provider: OAuth2Provider | str, scopes: list[str], validity = "seconds=60") -> str | None`:ak-version[2025.2]

:::info Requires HTTP Request
This function will only work when there is an HTTP request in the current context.
:::

Create a new JWT signed by the given `provider` for `user`.

The `provider` parameter can either be an instance of `OAuth2Provider` or the name of a provider instance as a string. Scopes is an array of all scopes that the JWT should have.

The JWT is valid for 60 seconds by default, this can be customized using the `validity` parameter. The syntax of the parameter is `hours=1,minutes=2,seconds=3`. The following keys are allowed:

    - Microseconds
    - Milliseconds
    - Seconds
    - Minutes
    - Hours
    - Days
    - Weeks

    All values accept floating-point values.

Example:

```python
jwt = ak_create_jwt(request.user, "my-oauth2-provider-name", ["openid", "profile", "email"])
```

### `ak_create_jwt_raw(provider: OAuth2Provider | str, validity = "seconds=60", **kwargs) -> str | None`:ak-version[2025.12]

Similar to [`ak_create_jwt`](#ak_create_jwtuser-user-provider-oauth2provider--str-scopes-liststr-validity--seconds60---str--none), however this function does _not_ require an HTTP request and allows for setting custom claims via `**kwargs`.

The `provider` parameter can either be an instance of `OAuth2Provider` or the name of a provider instance as a string. Scopes is an array of all scopes that the JWT should have.

The JWT is valid for 60 seconds by default, this can be customized using the `validity` parameter. The syntax of the parameter is `hours=1,minutes=2,seconds=3`. The following keys are allowed:

    - Microseconds
    - Milliseconds
    - Seconds
    - Minutes
    - Hours
    - Days
    - Weeks

    All values accept floating-point values.

Example:

```python
jwt = ak_create_jwt_raw("my-oauth2-provider-name", my_claim="my_value")
```

### `ak_send_email(address: str | list[str], subject: str, body: str = None, stage: EmailStage = None, template: str = None, context: dict = None, cc: str | list[str] = None, bcc: str | list[str] = None) -> bool`:ak-version[2025.10]

Send an email using authentik's email system.

The `address` parameter specifies the recipient email address(es). It can be:

- A single email address as a string: `"user@example.com"`
- A list of email addresses: `["user1@example.com", "user2@example.com"]`

:::info
When using multiple recipients in the `address` or `cc` fields, all email addresses will be visible to all recipients. Use `bcc` to send to multiple recipients without revealing addresses to each other.
:::

The `subject` parameter sets the email subject line.

You must provide either `body` (for plain text/HTML content) or `template` (for template rendering), but not both.

The `stage` parameter can be an `EmailStage` instance for custom email settings. If `None`, global email settings are used.

The `template` parameter specifies a template name to render. When using templates, you can pass additional context variables via the `context` parameter.

The `cc` parameter specifies email address(es) to carbon copy. Same format as `address`.

The `bcc` parameter specifies email address(es) to blind carbon copy. Same format as `address`. Recipients in `bcc` will not be visible to other recipients.

If the email is queued successfully, the function returns `True`; otherwise, it returns `False`.

Examples:

```python
# Send email with plain body to single recipient
ak_send_email("user@example.com", "Welcome!", body="Welcome to our platform!")

# Send email to multiple recipients
ak_send_email(
    ["user1@example.com", "user2@example.com", "admin@example.com"],
    "System Maintenance",
    body="Scheduled maintenance will occur tonight."
)

# Send email using a template
ak_send_email("user@example.com", "Password Reset", template="email/password_reset.html")

# Send email with custom context for template to multiple recipients
ak_send_email(
    ["user@example.com", "admin@example.com"],
    "Account Update",
    template="email/event_notification.html",
    context={
        "title": "Profile Updated",
        "body": "Your account profile has been successfully updated.",
        "key_value": {"Updated Field": "Email Address", "Date": "2025-01-01"}
    }
)

# Send email with custom email stage
ak_send_email("admin@example.com", "Report", body="Daily report", stage=my_custom_stage)

# Send email with CC
ak_send_email(
    "user@example.com",
    "Important Update",
    body="Please review this update.",
    cc="manager@example.com"
)

# Send email with multiple CC and BCC recipients
ak_send_email(
    "user@example.com",
    "Confidential Report",
    body="Attached is the quarterly report.",
    cc=["manager@example.com", "lead@example.com"],
    bcc=["audit@example.com", "compliance@example.com"]
)
```

## Comparing IP Addresses

To compare IP Addresses or check if an IP Address is within a given subnet, you can use the functions `ip_address('192.0.2.1')` and `ip_network('192.0.2.0/24')`. With these objects you can do [arithmetic operations](https://docs.python.org/3/library/ipaddress.html#operators).

You can also check if an IP Address is within a subnet by writing the following:

```python
ip_address('192.0.2.1') in ip_network('192.0.2.0/24')
# evaluates to True
```

## DNS resolution and reverse DNS lookups

To resolve a hostname to a list of IP addresses, use the functions `resolve_dns(hostname)` and `resolve_dns(hostname, ip_version)`.

```python
resolve_dns("google.com")  # return a list of all IPv4 and IPv6 addresses
resolve_dns("google.com", 4)  # return a list of only IPv4 addresses
resolve_dns("google.com", 6)  # return a list of only IPv6 addresses
```

You can also do reverse DNS lookups.

:::note
Reverse DNS lookups may not return the expected host if the IP address is part of a shared hosting environment.
See: https://stackoverflow.com/a/19867936
:::

To perform a reverse DNS lookup use `reverse_dns("192.0.2.0")`. If no DNS records are found the original IP address is returned.

:::info
DNS resolving results are cached in memory. The last 32 unique queries are cached for up to 3 minutes.
:::
