Custom Code Modules
Custom Code Modules let you add your own Python functions to a VDAB app and call them from component workflows. Use them when a built-in workflow action is not quite enough: validating a form, changing component labels, copying values between controls, calculating derived values, preparing uploaded files, or coordinating several UI updates from one event.
This guide is written for VDAB builders. It focuses on how to write, link, and use modules in the app builder experience.
How Custom Modules Work
A custom module is Python code that lives in the generated app file named custom_modules.py. You edit this file from the VDAB Code tab.
Each callable module is a top-level Python function:
def my_function(ctx):
# Your code here
return {"ok": True}
You then connect that function to a workflow by adding a Call Custom Module action. When the workflow runs, VDAB calls your function and passes it a context object named ctx.
The context object gives your code a small set of safe helpers:
ctx.get_value("componentCodeName")
ctx.set_value("componentCodeName", value)
ctx.set_label("componentCodeName", "New label")
Use the component code name, not the display label. For example, a button might display Submit, but its code name might be submitButton.
Quick Start Guide
- Add components to your app.
- Give every component you want to read or update a clear code name, such as
customerName,statusFilter, orsubmitButton. - Open the Code tab.
- Select
custom_modules.py. - Add a top-level Python function that accepts
ctx. - Open the Workflow Builder.
- Select a component event, such as a button
onClickor text inputonChange. - Add a
Call Custom Moduleaction. - Choose your function from the Target function dropdown.
- Save, preview, or deploy the app.
Example:
def update_submit_button(ctx):
customer_name = ctx.get_value("customerName")
if customer_name:
ctx.set_label("submitButton", f"Submit for {customer_name}")
else:
ctx.set_label("submitButton", "Submit")
return {"customerName": customer_name}
Link this function to the onChange event of a text input with code name customerName. The function updates the label of a button with code name submitButton.
Writing custom_modules.py
VDAB can discover functions that are declared at the top level of custom_modules.py.
Valid example:
def normalize_customer_name(ctx):
value = ctx.get_value("customerName") or ""
ctx.set_value("customerName", value.strip().title())
return {"normalized": True}
Bad example, not discoverable as a workflow target:
if True:
def nested_function(ctx):
return {"ok": True}
Function naming rules:
- Start the function name with a letter.
- Use only letters, digits, and underscores.
- Do not start with an underscore.
- Do not use double underscores.
- Keep functions top-level.
- Use one function per workflow action when possible.
You can define helper functions too. If a helper is not meant to be called directly from a workflow, keep it private by starting it with “_“. Private helper names will not be valid workflow targets.
def _calculate_discount(total):
if total > 1000:
return 0.1
return 0
def apply_discount(ctx):
total = ctx.get_value("orderTotal") or 0
discount = _calculate_discount(float(total))
ctx.set_value("discountRate", discount)
return {"discount": discount}
The ctx Object
1. ctx.get_value(code_name)
Reads the current value for a component.
Common returned values:
- Text Input: string
- Dropdown: selected option string, or list of strings for multiselect
- Checkbox and Toggle: boolean
- Slider: number
- Date Picker: date value
- Time Picker: time value
- File Upload: uploaded file objects or file-related state depending on runtime
- Editable Data Window: edited table state depending on runtime
If the component code name is unknown or the value has not been set yet, the result may be None.
2. ctx.set_value(code_name, value)
Sets the value for a component by code name.
Use a value shape that matches the target component. For example, set a checkbox to True or False, a slider to a number inside its range, and a dropdown to one of its configured options.
3. ctx.set_label(code_name, label)
Overrides the visible label for a component.
This is useful for label component text, dynamic button text, contextual field labels, chart titles, table headings, upload prompts, and lightweight status messages.
Linking a Module to a Workflow
Custom modules run through workflows. They are not attached directly to component properties.
To link a module:
- Open the Workflow Builder.
- Create or select a workflow.
- Choose a trigger component.
- Choose the event for that component.
- Add an action node.
- Set the action type to
Call Custom Module. - Select a Target function from
custom_modules.py. - Connect follow-up actions using
onSuccess,onError, oralwaysroutes as needed.
The Target function dropdown is populated from top-level def declarations in custom_modules.py. If your function does not appear, check that it is not indented, commented out, misspelled, or named with an invalid identifier.
Components That Can Trigger Modules
The following VDAB components support workflow events and can trigger a Call Custom Module action:
| Component | Event | Module Use |
|---|---|---|
| Button | onClick | Run explicit user actions, update labels, copy values, validate before continuing |
| Form | onSubmit | Validate fields, normalize inputs, set status feedback |
| Text Input | onChange | Clean text, update dependent labels, populate related fields |
| Dropdown | onChange | Apply a selection to other controls, change labels, set defaults |
| Checkbox | onChange | Enable option-driven behavior, update messages, mirror boolean state |
| Toggle | onChange | Switch app modes, set defaults, update related labels |
| Slider | onChange | Calculate bands, thresholds, and derived values |
| Date Picker | onChange | Validate ranges, derive period labels, set related dates |
| Time Picker | onChange | Validate schedule inputs, build display labels |
| File Upload | onChange | Inspect uploaded file state, update status controls |
| Editable Data Window | onChange | React to edited rows, update status text, prepare downstream actions |
Components without workflow events cannot directly trigger a module. A module may still affect them if they have a code name and support the value or label update you are trying to apply.
Complete Example
This example uses a text input, dropdown, checkbox, slider, and button to build a small customer review workflow.
| Component | Code Name | Purpose |
|---|---|---|
| Text Input | customerName | Customer name |
| Dropdown | customerTier | Tier selection |
| Checkbox | includeArchived | Include archived records |
| Slider | riskScore | Risk score |
| Text Input | reviewSummary | Generated summary |
| Button | runReviewButton | Starts the review |
custom_modules.py:
def update_review_summary(ctx):
name = (ctx.get_value("customerName") or "Unknown customer").strip()
tier = ctx.get_value("customerTier") or "Standard"
include_archived = bool(ctx.get_value("includeArchived"))
score = ctx.get_value("riskScore") or 0
if score >= 80:
risk_band = "High"
elif score >= 50:
risk_band = "Medium"
else:
risk_band = "Low"
archive_text = "including archived records" if include_archived else "active records only"
summary = f"{name}: {tier} tier, {risk_band} risk, {archive_text}."
ctx.set_value("reviewSummary", summary)
ctx.set_label("runReviewButton", f"Run review for {name}")
return {
"customerName": name,
"tier": tier,
"includeArchived": include_archived,
"riskBand": risk_band,
}
def validate_and_run_review(ctx):
name = (ctx.get_value("customerName") or "").strip()
if not name:
ctx.set_label("runReviewButton", "Enter a customer name")
raise ValueError("Customer name is required.")
ctx.set_label("runReviewButton", "Review ready")
return {"ok": True, "customerName": name}
Workflow setup:
- Add
Call Custom Modulewithupdate_review_summaryto theonChangeevent forcustomerName. - Add
Call Custom Modulewithupdate_review_summaryto theonChangeevent forcustomerTier. - Add
Call Custom Modulewithupdate_review_summaryto theonChangeevent forincludeArchived. - Add
Call Custom Modulewithupdate_review_summaryto theonChangeevent forriskScore. - Add
Call Custom Modulewithvalidate_and_run_reviewto theonClickevent forrunReviewButton. - Connect the button module’s
onSuccessroute to the next real action, such asBuild SQL,Run SQL, orTrigger Toast. - Connect the button module’s
onErrorroute toTrigger Toastif you want a visible validation message.
This pattern keeps continuous UI updates separate from the final user-triggered action.

