Skip to content

Forms Concept

Understanding forms in Bifrost

Forms provide a user-friendly interface for executing workflows. They:

  • Collect user input with validation
  • Display dynamic dropdown options
  • Show/hide fields conditionally
  • Execute workflows on submission
  • Display results
User fills form → Form validates input → Workflow executes → Results displayed

Form lifecycle:

  1. User navigates to form
  2. Form loads with fields
  3. Data providers populate dropdowns
  4. User fills fields
  5. Form validates input
  6. Form submits to workflow
  7. Workflow executes
  8. Results shown to user

Form fields collect different types of input:

  • Text: Short text input
  • Textarea: Long text
  • Email: Email with validation
  • Number: Integer or decimal
  • Checkbox: Boolean true/false
  • Select: Single choice dropdown
  • Multi-select: Multiple choice
  • Date: Date picker
  • File Upload: Attach files

Each field has:

  • Label (display text)
  • Validation rules
  • Help text
  • Default value

Populate dropdowns dynamically:

@data_provider(name="get_departments")
async def get_departments(context):
return [
{"label": "Engineering", "value": "eng"},
{"label": "Sales", "value": "sales"}
]

Used in form builder:

  1. Add select field
  2. Choose data provider
  3. Dropdown populates automatically

Show/hide fields based on other values:

// Show only if checkbox checked
context.field.is_manager === true;
// Show for specific role
context.field.role === "admin";
// Multiple conditions
context.field.role === "admin" && context.field.department === "engineering";

Every form executes a workflow:

  • Form collects parameters
  • Submits to workflow
  • Displays workflow result
# Form submits to this workflow
@workflow(name="create_user", description="Create user")
@param("email", type="email", required=True)
@param("name", type="string", required=True)
async def create_user(context, email, name):
return {"user_id": "123"}

Form fields → Workflow parameters:

Form:

{
"fields": [
{ "name": "email", "type": "email" },
{ "name": "department", "type": "select" }
]
}

Workflow:

@workflow(name="create_user")
@param("email", type="email", required=True)
@param("department", type="string", required=True)
async def create_user(context, email, department):
return {"user_id": "123"}

Field names must match parameter names.

Cascade selections:

# First dropdown
@data_provider(name="get_departments")
async def get_departments(context):
return [{"label": "Sales", "value": "sales"}]
# Second dropdown depends on first
@data_provider(name="get_users_by_dept")
@param("department", type="string", required=True)
async def get_users_by_dept(context, department):
users = await fetch_users(department)
return [{"label": u.name, "value": u.id} for u in users]

User selects department → User dropdown updates.

To do this, you would have a required field, but only show it under certain conditions. There’s currently now way to always show a field but sometimes make it required.

Forms validate as user types:

  • Email format
  • String length
  • Number ranges
  • Custom patterns
  • Forms can be scoped globally or to an organization
  • Forms can be assigned to roles or be available to any authenticated user

For example, if you have a global form, you could assign it to no one and then only your Platform Admins would have access. You could also assign it to “Human Resources” and then only your Human Resource users in your tenants.

  1. Keep Simple: Minimize fields, max clarity
  2. Use Data Providers: Dynamic options allow you create one form and customize options per tenant
  3. Add Help Text: Guide users with tooltips
  4. Validate Early: Use field validation + workflow validation
  5. Test Flow: Verify all visibility rules work