Skip to content

Decorators Reference

Complete reference for @workflow, @param, and @data_provider decorators

Decorators are the foundation of Bifrost’s discovery system. They register workflows and data providers automatically when your modules are imported.

Registers an async function as a discoverable workflow.

@workflow(
name: str,
description: str,
category: str = "General",
tags: list[str] | None = None,
execution_mode: Literal["sync", "async"] = "sync",
timeout_seconds: int = 300,
retry_policy: dict[str, Any] | None = None,
schedule: str | None = None,
endpoint_enabled: bool = False,
allowed_methods: list[str] | None = None,
disable_global_key: bool = False,
public_endpoint: bool = False
)
ParameterTypeDefaultDescription
namestr-Unique workflow identifier (URL-friendly, lowercase)
descriptionstr-User-facing description
categorystr”General”Category for grouping (e.g., “User Management”)
tagslistNoneTags for filtering and search
execution_modestr”sync""sync” or “async” execution
timeout_secondsint300Maximum execution time in seconds
retry_policydictNoneRetry configuration
schedulestrNoneCron schedule for scheduled execution
endpoint_enabledboolFalseExpose as HTTP endpoint at /api/endpoints/{name}
allowed_methodslistNoneHTTP methods allowed (GET, POST, etc)
disable_global_keyboolFalseRequire workflow-specific API key
public_endpointboolFalseSkip authentication for webhooks
@workflow(
name="send_email",
description="Send an email to a user"
)
async def send_email(context, recipient: str):
# Implementation
pass

Defines a parameter for a workflow or data provider.

@param(
name: str,
type: str,
label: str | None = None,
required: bool = False,
validation: dict | None = None,
data_provider: str | None = None,
default_value: Any = None,
help_text: str | None = None
)
ParameterTypeDefaultDescription
namestr-Parameter name (must match function argument)
typestr-Field type (string, number, boolean, select, etc.)
labelstrNoneDisplay label (defaults to name)
requiredboolFalseWhether parameter is required
validationdictNoneValidation rules (pattern, min, max, etc.)
data_providerstrNoneData provider for dynamic options
default_valueAnyNoneDefault value if not provided
help_textstrNoneHelp text shown in UI
  • string - Text input
  • number - Numeric input
  • boolean - Checkbox
  • select - Single-select dropdown
  • multi_select - Multiple-select dropdown
  • date - Date picker (YYYY-MM-DD)
  • datetime - Date and time picker
  • email - Email validation
  • textarea - Multi-line text
  • file - File upload
@param(
"email",
"email",
label="Email Address",
required=True,
help_text="Enter a valid email address"
)

Available validation options:

{
"pattern": "^[a-zA-Z0-9]+$", # Regex pattern
"min": 1, # Minimum value/length
"max": 100, # Maximum value/length
"min_length": 3, # Minimum string length
"max_length": 50, # Maximum string length
"enum": ["value1", "value2"], # Allowed values
}

Registers a function that provides dynamic options for form fields.

@data_provider(
name: str,
description: str,
category: str = "General",
cache_ttl_seconds: int = 300
)
ParameterTypeDefaultDescription
namestr-Unique provider identifier
descriptionstr-Description of what data it provides
categorystr”General”Category for grouping
cache_ttl_secondsint300Cache duration in seconds (0 = no cache)
@data_provider(
name="get_active_users",
description="Get list of active users",
cache_ttl_seconds=600
)
async def get_active_users(context):
return [
{"label": "Alice Smith", "value": "alice@example.com"},
{"label": "Bob Johnson", "value": "bob@example.com"}
]
@workflow(name="create_user", description="Create a new user")
@param("email", "email", required=True) # Applied third
@param("first_name", "string", required=True) # Applied second
@param("last_name", "string", required=True) # Applied first
async def create_user(context, email: str, first_name: str, last_name: str):
pass

Parameters appear in UI in the order they’re declared (last_name, first_name, email).

Workflows return any JSON-serializable value:

@workflow(name="example")
async def example(context):
return {
"success": True,
"message": "Operation completed",
"data": [1, 2, 3]
}

✅ DO:

  • Use descriptive names and descriptions
  • Validate all parameters
  • Handle errors gracefully
  • Log important operations
  • Cache data provider results appropriately
  • Add help text for complex parameters
  • Use type hints in function signatures

❌ DON’T:

  • Use generic names like “workflow1” or “param1”
  • Forget to add descriptions
  • Return inconsistent data structures
  • Log sensitive data
  • Cache dynamic data too long
  • Ignore parameter validation
  • Create workflows without timeout constraints
from bifrost import workflow, param, data_provider, ExecutionContext
from typing import List, Dict, Any
import logging
logger = logging.getLogger(__name__)
# Data provider for departments
@data_provider(
name="get_departments",
description="Get available departments",
category="organization",
cache_ttl_seconds=600
)
async def get_departments(context: ExecutionContext) -> List[Dict[str, str]]:
"""Fetch departments from external system."""
departments = [
{"label": "Engineering", "value": "eng"},
{"label": "Sales", "value": "sales"},
{"label": "Support", "value": "support"}
]
logger.info(f"Returning {len(departments)} departments")
return departments
# Workflow using the data provider
@workflow(
name="create_user",
description="Create a new user in the system",
category="user_management",
tags=["user-creation", "onboarding"],
timeout_seconds=60
)
@param("email", "email", label="Email", required=True)
@param("name", "string", label="Full Name", required=True)
@param("department", "select", label="Department",
required=True, data_provider="get_departments")
@param("send_welcome_email", "boolean",
label="Send Welcome Email", default_value=True)
async def create_user(
context: ExecutionContext,
email: str,
name: str,
department: str,
send_welcome_email: bool = True
) -> Dict[str, Any]:
"""Create a new user with validation and notifications."""
logger.info("Creating user", extra={
"email": email,
"name": name,
"department": department
})
# Validate input
if not email.endswith("@example.com"):
raise ValueError("Email must use example.com domain")
# Create user
user_id = await create_user_in_system(email, name, department)
# Send welcome email if requested
if send_welcome_email:
await send_welcome_email_to_user(email, name)
logger.info("User created successfully", extra={
"user_id": user_id,
"email": email
})
return {
"success": True,
"user_id": user_id,
"email": email,
"name": name,
"department": department
}