Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/signalwire/freeswitch/llms.txt

Use this file to discover all available pages before exploring further.

FreeSWITCH can run scripts written in several programming languages directly inside the media server process. Embedded scripting is used to implement dynamic IVR flows, custom routing logic, database lookups, REST API calls, and any other call behavior that goes beyond what static XML dialplan extensions can express. Scripts run in-process and have full access to the FreeSWITCH session API, letting them answer calls, play audio, collect DTMF, query databases, and transfer callers — all from within a single script file.
Lua is the recommended embedded scripting language for FreeSWITCH. It has the largest community of FreeSWITCH users, the most examples in the wild, the lowest memory footprint, and the most stable API binding. Unless you have a specific reason to use another language, start with Lua.

Supported Languages

ModuleLanguageRuntime
mod_luaLuaLua 5.x (bundled)
mod_python3PythonPython 3.x (system)
mod_v8JavaScriptGoogle V8 engine
mod_perlPerlSystem Perl
mod_javaJavaJVM via JNI
mod_managedC# / .NETMono or .NET
mod_basicBASICEmbedded BASIC interpreter
All language modules expose the same underlying C++ CoreSession object through language-specific SWIG-generated bindings. This means the session API (answer(), streamFile(), getDigits(), transfer(), etc.) is identical across languages — only the syntax differs.
mod_lua embeds the Lua 5.x interpreter directly in the FreeSWITCH process. Scripts have access to the freeswitch global module (which wraps CoreSession and other APIs) as well as all standard Lua libraries. The module is defined in src/mod/languages/mod_lua/mod_lua.cpp.

Running Lua Scripts

There are two ways to invoke a Lua script from the dialplan:
  • lua — Runs a Lua script in the context of a call. The script receives the active session and can interact with the call synchronously.
  • luarun — Runs a Lua script in a background thread, independent of any specific call. Used for scheduled tasks, background processing, or triggering outbound calls.
<!-- Run a Lua script as a dialplan application (handles the call) -->
<action application="lua" data="/etc/freeswitch/scripts/ivr_main.lua"/>

<!-- Run a Lua script in the background (non-blocking, no session) -->
<action application="luarun" data="/etc/freeswitch/scripts/notify_crm.lua"/>

Session API Reference

The session object is the primary interface for controlling a call from within Lua:
MethodDescription
session:answer()Answer the call
session:preAnswer()Send early media (183 Session Progress)
session:hangup([cause])Hang up the call with an optional hangup cause string
session:streamFile(path)Play an audio file to the caller
session:getDigits(max, term, timeout)Collect DTMF digits
session:playAndGetDigits(min, max, tries, timeout, term, file, invalid, regex, var)Play and collect in one call
session:transfer(ext, dialplan, context)Transfer the call to a new extension
session:setVariable(name, value)Set a channel variable
session:getVariable(name)Get the value of a channel variable
session:say(text, module, type, method)Speak text using a language module
session:recordFile(path, limit, silence_threshold, silence_hits)Record audio to a file
session:ready()Returns true if the call is still active

Example: Lua IVR Script

The following Lua script answers a call, plays a greeting, collects a single digit, and routes the call based on the input:
-- /etc/freeswitch/scripts/ivr_main.lua

-- Answer the inbound call
session:answer()

-- Brief pause to let the call settle
session:sleep(500)

-- Play the main menu prompt and collect 1 digit (timeout: 5 seconds)
local digits = session:playAndGetDigits(
  1,                                      -- min digits
  1,                                      -- max digits
  3,                                      -- max tries
  5000,                                   -- timeout (ms)
  "#",                                    -- terminators
  "ivr/ivr-please_enter_ext.wav",         -- prompt file
  "ivr/ivr-that_was_an_invalid_entry.wav",-- invalid input file
  "\\d",                                  -- regex to validate input
  "destination_number"                    -- variable to store result
)

freeswitch.consoleLog("INFO", "Caller pressed: " .. tostring(digits) .. "\n")

if session:ready() then
  if digits == "1" then
    session:transfer("3000", "XML", "default")
  elseif digits == "2" then
    session:transfer("3001", "XML", "default")
  else
    session:streamFile("ivr/ivr-no_valid_response.wav")
    session:hangup()
  end
end

Running an API Command from Lua

You can execute any FreeSWITCH API command from Lua using the freeswitch.API() object:
local api = freeswitch.API()
local result = api:execute("sofia", "status")
freeswitch.consoleLog("INFO", result .. "\n")

XML Handler / Dialplan from Lua

Lua can also register as an XML binding, generating dynamic dialplan XML responses to FreeSWITCH’s XML lookup requests. This is configured in lua.conf.xml via the xml-handler-script parameter.

mod_python3

mod_python3 embeds the Python 3 interpreter in FreeSWITCH (the default Python module since replacing mod_python). Python scripts are invoked from the dialplan using the python application. The module is defined in src/mod/languages/mod_python3/mod_python3.c.

Invoking Python Scripts

<!-- Run a Python module (must be importable by the Python path) -->
<action application="python" data="myivr"/>

<!-- Pass arguments to the module -->
<action application="python" data="myivr arg1 arg2"/>
The named module must expose a handler(session, args) function. FreeSWITCH calls this function with the active CoreSession object.

Example: Python 3 IVR Script

# /etc/freeswitch/scripts/myivr.py
import freeswitch

def handler(session, args):
    """Entry point called by mod_python3."""
    session.answer()
    session.sleep(500)

    digits = session.playAndGetDigits(
        1,                                        # min digits
        1,                                        # max digits
        3,                                        # max tries
        5000,                                     # timeout (ms)
        "#",                                      # terminator
        "ivr/ivr-please_enter_ext.wav",           # prompt
        "ivr/ivr-that_was_an_invalid_entry.wav",  # bad input sound
        "\\d",                                        # regex to validate input
        "destination_number",                     # variable name
        0,                                        # digit timeout
        "",                                       # transfer on failure
    )

    freeswitch.consoleLog("INFO", f"Caller pressed: {digits}\n")

    if not session.ready():
        return

    if digits == "1":
        session.transfer("3000", "XML", "default")
    elif digits == "2":
        session.transfer("3001", "XML", "default")
    else:
        session.streamFile("ivr/ivr-no_valid_response.wav")
        session.hangup()
mod_python3 uses SWIG-generated Python bindings (freeswitch.py and _freeswitch.so) that are installed alongside the module. The CoreSession class provides the same methods as the Lua session object — answer(), hangup(), streamFile(), getDigits(), transfer(), etc.

mod_v8

mod_v8 embeds the Google V8 JavaScript engine, allowing call control scripts written in JavaScript (ECMAScript 5+). The API surface mirrors the Lua and Python bindings.

Invoking JavaScript Scripts

<action application="javascript" data="/etc/freeswitch/scripts/ivr.js"/>

Example: Basic V8 Script

// /etc/freeswitch/scripts/ivr.js

session.answer();
session.sleep(500);

var digits = session.playAndGetDigits(
  1, 1, 3, 5000, "#",
  "ivr/ivr-please_enter_ext.wav",
  "ivr/ivr-that_was_an_invalid_entry.wav",
  "\\d", "destination_number"
);

if (session.ready()) {
  if (digits === "1") {
    session.transfer("3000", "XML", "default");
  } else {
    session.hangup();
  }
}

mod_v8 Configuration

Configure the JavaScript script path and startup scripts in conf/autoload_configs/v8.conf.xml. The startup-script parameter can load a global JavaScript file at FreeSWITCH startup.

Calling Scripts from the Dialplan

All embedded language modules expose their scripts via dialplan <action> elements. The general pattern is:
<!-- Lua -->
<action application="lua"
  data="/etc/freeswitch/scripts/my_script.lua"/>

<!-- Lua with arguments (accessible as argv[1], argv[2], etc.) -->
<action application="lua"
  data="/etc/freeswitch/scripts/my_script.lua arg1 arg2"/>

<!-- Python 3 -->
<action application="python" data="my_module"/>

<!-- JavaScript (V8) -->
<action application="javascript"
  data="/etc/freeswitch/scripts/my_script.js"/>

<!-- Perl -->
<action application="perl"
  data="/etc/freeswitch/scripts/my_script.pl"/>
By default, FreeSWITCH looks for Lua scripts relative to the FreeSWITCH base directory. You can configure additional search paths by adding them to the lua_path parameter in conf/autoload_configs/lua.conf.xml:
<configuration name="lua.conf" description="Lua Configuration">
  <settings>
    <param name="lua-path" value="/etc/freeswitch/scripts/?.lua"/>
    <!-- Optional: run a script at FreeSWITCH startup -->
    <!-- <param name="startup-script" value="startup.lua"/> -->
  </settings>
</configuration>

ESL as an Alternative to Embedded Scripting

For some use cases, using the Event Socket Layer (ESL) from an external process is a better choice than embedding a script inside FreeSWITCH:
ScenarioEmbedded ScriptingESL (External)
Simple IVR logic✅ Simpler, lower latency
Complex business logicWorks, but limited by scripting env✅ Full access to all libraries/frameworks
Crash isolation❌ A crash can affect FreeSWITCH✅ External process is isolated
Long-running daemons❌ Not intended for persistent threads✅ Natural fit
Multi-server orchestration❌ One FreeSWITCH instance only✅ Control multiple FS instances
Debugging / testing❌ Harder to unit test✅ Easy to test independently
ESL client libraries are available for Python, Node.js, Ruby, Go, Java, and others. For inbound call control via ESL, use the socket dialplan application to hand off a call from FreeSWITCH to an external program over a TCP connection. See ESL Overview for details.

Build docs developers (and LLMs) love