Logic & Behavior
Overview
The Code Editor in the multimodal platform empowers you to transform a static UI into a dynamic experience. Your Python code runs in the browser (via Pyodide), allowing you to respond to user actions, manipulate component values, and integrate with external APIs—without needing a separate backend for lightweight logic.

On the left-hand side of the editor, you’ll find:
- A live preview of your form, where you can see how your interface behaves in real time.
- A variables panel, allowing you to define reusable values accessible within your code.
- A component inspector: Right-click any component in the preview to reveal its type and ID. You can copy the component’s ID directly from this menu for use in your code.
If your form uses visibility permissions, you can preview it from different perspectives by selecting a specific role and item status. The form will adjust dynamically to reflect the user experience for that configuration.
On the right-hand side, you’ll see the code editor. This is where you write custom Python logic, interact with components, use predefined variables, and build functionality specific to your annotation workflows.
Using Pyodide
The Code Editor leverages Pyodide, a Python runtime compiled to WebAssembly (Wasm). This enables Python scripts to run client-side, in the user’s browser. While it’s incredibly flexible, keep in mind:
- Limited Library Support
You can’t install every Python package by default, though many popular libraries are available or can be installed dynamically withpyodide_js
. - Performance Constraints
Pyodide handles small to medium tasks well but may struggle with heavy data processing or other compute intensive tasks. For larger workflows, see the Orchestrate Documentation.
Installing packages
To load external Python packages in the code editor, use the pyodide_js module:
import pyodide_js
async def load_libraries():
await pyodide_js.loadPackage('some_package')
import some_package
# Now you can use some_package
Note: Packages are installed during the first run of each session. If you switch between items within the editor during the same session, the packages remain in place and are not redownloaded. However, closing and reopening the editor starts a new session, which will trigger a fresh installation.
Base Code Structure
When you create or update a form in the UI Builder, the platform automatically generates a base Python file named __init__.py
. This file includes:
- Component Path Definitions
- Mappings for each component’s unique ID or group path.
- Event Handler Stubs
- Predefined functions (
on_<component_id>_<event_type>
) for standard component events.
- Predefined functions (
You can freely extend __init__.py
with custom logic, variables, or imports. If you need a more modular approach, you can add additional Python files and import them into __init__.py
.
File Management
Files are a way for you to manage and sort your form’s code without maintaining it all in one compacted place. You can categorize different areas of your code into separate files, then simply import the desired data within to the file that requires it.
To create a new file:
- Click the “+” icon at the top of the Code Editor.
- Enter a filename in the dialog (e.g.,
helpers.py
). - Click Create.
To import a specific variable, function, or class from one file to another, include this import sequence in the destination file:
from helpers import calculate_sum
Run
To test your code after editing, you must click Run to update the form preview.
Regenerate
After your form's base code is generated, when you make changes to your form in the UI builder, the code editor will be modified accordingly. Clicking Regenerate will replace the code with an initial state based on the updated form UI. If there are any lines of custom code that you want to keep, make sure to have a copy of them with you before regenerating the code editor.
Event Handlers
Event handlers are special functions that allow you to attach custom behavior to user interactions and system events within the Code Editor. These functions follow a strict naming convention:
def on_<component_id>_<event_type>(path, ...):
# Your code here
Where:
component_id
is the unique ID of the UI element.event_type
is one of the supported interaction or lifecycle events listed below.
Supported Component Events
Value Change (change
)
change
)Triggered when the value of a component changes (e.g., typing in a text field).
Signature:
def on_<component_id>_change(path, value):
# Your custom code
return
path
: The component’s path (['component_id']
or['group_id', row_index, 'component_id']
).value
: The new value of the component.
Button Click (click):
Triggered when a button is clicked.
Signature:
def on_<component_id>_click(path):
# Custom logic
return
path
: Path to the clicked button
Web Component Message (message):
Triggered when a WebComponent sends a message using postMessage.
Signature:
def on_<component_id>_message(path, value):
# Your custom code
return
path
: Path to the WebComponentvalue
: Message sent from the component
Group Row Deleted (deleted):
Triggered when a row is removed from a Group component (e.g., clicking the ❌ icon).
Signature:
def on_<group_id>_deleted(path):
# Your custom code
return
path
: Path to the deleted row
Group Selected
Triggered when a row is selected.
def on_<group_id>selected(path):
# Your custom code
return
path
: Path to the selected row
Lifecycle Hooks
In addition to direct component event handlers, the editor provides lifecycle hooks for major item actions:
post_hook()
- Called when an item is closed in the editor.before_save_hook()
- Called immediately before saving. Useful for final validations or data transformations.on_saved_hook()
- Called right after an item is saved.before_status_change_hook()
- Called before an item’s status changes (e.g., from “InProgress” to “QualityCheck”).on_status_changed_hook()
- Called after the item’s status has changed.
Use these hooks to enforce consistency checks, log events, or trigger custom actions when items are saved or statuses change.
Interacting with Components
Component Paths
Every component is identified by a path, which depends on whether it’s standalone or nested in Groups:
- Standalone:
['component_id']
- Grouped:
['group_id', <row_index>, 'component_id']
- Nested Groups:
['group_1', <row_index_1>, 'group_2', <row_index_2>, 'component_id']
Note
use ‘-1’ as
row_index
to get the last row.
Built-in Functions
In the code editor, you can access a set of built-in helper functions for interacting with components, sending messages, and manipulating UI state. These are available via the sa
module:
import sa
All functions are accessed through the sa.
namespace:
Component Interaction
-
sa.changeTab(path: list[tuple[str, int]], index: int)
Switches the selected tab in aTab
component based on the provided path and tab index. -
sa.deleteRow(path: list[tuple[str, int]])
Deletes a row in aGroup
component at the specified path. The path must include the row index. -
sa.enable(path: list[tuple[str, int]])
Enables a component (e.g., input, button) located at the given path. -
sa.disable(path: list[tuple[str, int]])
Disables a component at the given path. -
sa.getGroupLength(path: list[tuple[str, int]]) -> int
Returns the number of repeated rows in aGroup
component. -
sa.repeatRow(path: list[tuple[str, int]], index: int) -> list[tuple[str, int]]
Appends a new row to a group at the specified path and returns the path of the newly created row.
Value & State Access
-
sa.getValue(path: list[tuple[str, int]]) -> Any
Returns the current value of a component at the given path. -
sa.setValue(path: list[tuple[str, int]], value: Any)
Sets the value of the component. The value type must match the component's expected format:str
,int
,float
,list[str]
,tuple[int, float]
, etc. -
sa.setLoading(state: bool, path: list[tuple[str, int]])
Sets the loading state of aButton
component.
Payloads & Web Communication
-
sa.getPayload(name: str) -> Any
Retrieves the contents of a JSON file attached to the current item by name. -
sa.postMessageToWebComponent(path: list[tuple[str, int]], data: Any)
Sends data to a customWebComponent
.
User Messaging
Displays a message on the bottom-left of the interface. Useful for user feedback or error handling.
sa.showError(message: str)
– Displays an error message.sa.showInfo(message: str)
– Displays an informational message.sa.showSuccess(message: str)
– Displays a success message.sa.showWarning(message: str)
– Displays a warning message.
Best practices
- Backup Custom Code
- Always save or copy your code elsewhere before regenerating the base code. The regeneration process can overwrite changes in init.py.
- Organize Files
- Keep business logic, helper functions, and main event handlers in separate files for clarity and maintainability.
- Test Frequently
- Use the Run button in the Code Editor to verify changes in real-time. Log or print outputs to the console to debug.
- Avoid Long-Running Tasks
- Pyodide runs in the main UI thread. Heavy operations can freeze the interface. For large computations, consider using Orchestrate.
- Leverage Lifecycle Hooks
- Validate data, run housekeeping tasks, or log user actions at key points in the annotation process (before/after save, on status change, etc.).
Next steps
- Learn About Custom Components: Explore how to build or embed custom HTML/JS interfaces.
- Discover Orchestrate: For tasks that exceed Pyodide’s constraints, see our orchestration system for containerized Python scripts.
- Review Advanced Examples: Check out the Code Editor Examples for a guided walkthrough on event handlers, group manipulation, and more.
Updated 40 minutes ago