Skip to main content
Table of Contents
< All Topics
Print

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

  1. Add components to your app.
  2. Give every component you want to read or update a clear code name, such as customerName, statusFilter, or submitButton.
  3. Open the Code tab.
  4. Select custom_modules.py.
  5. Add a top-level Python function that accepts ctx.
  6. Open the Workflow Builder.
  7. Select a component event, such as a button onClick or text input onChange.
  8. Add a Call Custom Module action.
  9. Choose your function from the Target function dropdown.
  10. 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:

  1. Open the Workflow Builder.
  2. Create or select a workflow.
  3. Choose a trigger component.
  4. Choose the event for that component.
  5. Add an action node.
  6. Set the action type to Call Custom Module.
  7. Select a Target function from custom_modules.py.
  8. Connect follow-up actions using onSuccess, onError, or always routes 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:

ComponentEventModule Use
ButtononClickRun explicit user actions, update labels, copy values, validate before continuing
FormonSubmitValidate fields, normalize inputs, set status feedback
Text InputonChangeClean text, update dependent labels, populate related fields
DropdownonChangeApply a selection to other controls, change labels, set defaults
CheckboxonChangeEnable option-driven behavior, update messages, mirror boolean state
ToggleonChangeSwitch app modes, set defaults, update related labels
SlideronChangeCalculate bands, thresholds, and derived values
Date PickeronChangeValidate ranges, derive period labels, set related dates
Time PickeronChangeValidate schedule inputs, build display labels
File UploadonChangeInspect uploaded file state, update status controls
Editable Data WindowonChangeReact 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.

ComponentCode NamePurpose
Text InputcustomerNameCustomer name
DropdowncustomerTierTier selection
CheckboxincludeArchivedInclude archived records
SliderriskScoreRisk score
Text InputreviewSummaryGenerated summary
ButtonrunReviewButtonStarts 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:

  1. Add Call Custom Module with update_review_summary to the onChange event for customerName.
  2. Add Call Custom Module with update_review_summary to the onChange event for customerTier.
  3. Add Call Custom Module with update_review_summary to the onChange event for includeArchived.
  4. Add Call Custom Module with update_review_summary to the onChange event for riskScore.
  5. Add Call Custom Module with validate_and_run_review to the onClick event for runReviewButton.
  6. Connect the button module’s onSuccess route to the next real action, such as Build SQL, Run SQL, or Trigger Toast.
  7. Connect the button module’s onError route to Trigger Toast if you want a visible validation message.

This pattern keeps continuous UI updates separate from the final user-triggered action.