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 ships a single, authoritative C++ ESL implementation in libs/esl/ and uses SWIG to generate language bindings from the interface definition in libs/esl/ESL.i. Every binding exposes the same two classes — ESLconnection and ESLevent — with identical method names (adjusted for each language’s conventions), so code written for one language translates directly to any other. The bindings are available for Python 3, Lua, Ruby, Perl, Java, and .NET/C#, plus the original C++ API for native development.
Connect and Send a Command
The following examples all do the same thing: connect to FreeSWITCH on 127.0.0.1:8021, authenticate with ClueCon, run the status API command, and print the response body.
Python 3
Lua
Perl
Ruby
Java
.NET / C#
# libs/esl/python3/single_command.py (adapted for Python 3)
import ESL
con = ESL.ESLconnection('127.0.0.1', '8021', 'ClueCon')
if con.connected():
e = con.api('status')
print(e.getBody())
else:
print("Not connected")
Install the Python 3 binding by building it in libs/esl/python3/:cd /usr/src/freeswitch/libs/esl
make pymod
The resulting ESL.py and _ESL.so must be on your PYTHONPATH.-- libs/esl/lua/single_command.lua
require("ESL")
local command = arg[1]
table.remove(arg, 1)
local args = table.concat(arg, " ")
local con = ESL.ESLconnection("localhost", "8021", "ClueCon")
local e = con:api(command, args)
print(e:getBody())
Build the Lua binding:cd /usr/src/freeswitch/libs/esl
make luamod
#!/usr/bin/perl
# libs/esl/perl/single_command.pl
require ESL;
my $command = shift;
my $args = join(" ", @ARGV);
my $con = new ESL::ESLconnection("127.0.0.1", "8021", "ClueCon");
my $e = $con->api($command, $args);
print $e->getBody();
Build the Perl binding:cd /usr/src/freeswitch/libs/esl
make perlmod
#!/usr/bin/ruby
# libs/esl/ruby/single_command.rb
require 'ESL'
HOST = '127.0.0.1'
PORT = '8021'
PASSWORD = 'ClueCon'
command = ARGV.join(" ")
con = ESL::ESLconnection.new(HOST, PORT, PASSWORD)
e = con.sendRecv('api ' + command)
puts e.getBody
Build the Ruby binding:cd /usr/src/freeswitch/libs/esl
make rubymod
// Using libs/esl/java/org/freeswitch/esl/ESLconnection.java
import org.freeswitch.esl.ESLconnection;
import org.freeswitch.esl.ESLevent;
public class SingleCommand {
public static void main(String[] args) throws Exception {
ESLconnection con = new ESLconnection("127.0.0.1", "8021", "ClueCon");
if (con.connected() != 0) {
ESLevent e = con.api("status", "");
System.out.println(e.getBody());
con.disconnect();
} else {
System.err.println("Not connected");
}
}
}
Build the Java binding:cd /usr/src/freeswitch/libs/esl
make javamod
This produces libesljni.so (the JNI native library) and the .class/.jar files in libs/esl/java/.// Using libs/esl/managed/ESLconnection.cs (SWIG-generated)
using System;
class SingleCommand {
static void Main(string[] args) {
// ESLconnection(host, port, password)
var con = new ESLconnection("127.0.0.1", "8021", "ClueCon");
if (con.Connected() != 0) {
ESLevent e = con.Api("status", "");
Console.WriteLine(e.GetBody());
con.Disconnect();
} else {
Console.Error.WriteLine("Not connected");
}
}
}
Note that the .NET binding uses PascalCase method names (Api, GetBody, Connected, Disconnect) because the SWIG interface file renames them for .NET conventions via %rename directives in ESL.i.Build the managed binding:cd /usr/src/freeswitch/libs/esl
make managedmod
Building the Bindings
All language bindings are generated from the single SWIG interface file libs/esl/ESL.i, which includes esl_oop.h containing the canonical C++ class definitions. SWIG reads this file and emits a language-specific wrapper (esl_wrap.cpp for the C/C++ glue, plus a language module file) that is then compiled and linked against libesl.
# Build all bindings at once
cd /usr/src/freeswitch/libs/esl
make
# Or build individual language modules
make pymod # Python 3 → ESL.py + _ESL.so
make luamod # Lua → ESL.so
make perlmod # Perl → ESL.pm + ESL.so
make rubymod # Ruby → ESL.rb + ESL.so
make javamod # Java → libesljni.so + .class files
make managedmod # .NET/C# → ESLmanaged.dll
Pre-built packages exist for some distributions. On Debian/Ubuntu, freeswitch-mod-python3 and freeswitch-mod-lua provide the modules as system packages, which is simpler than building from source. Check the SignalWire FreeSWITCH packages for your distribution.
Python 3: Event Loop Example
This pattern is taken directly from libs/esl/python3/events.py. It subscribes to all events and prints each one as it arrives:
#!/usr/bin/env python3
# Adapted from libs/esl/python3/events.py
import ESL
con = ESL.ESLconnection("localhost", "8021", "ClueCon")
if con.connected():
# Subscribe to every event FreeSWITCH emits
con.events("plain", "all")
while True:
e = con.recvEvent()
if e:
# serialize() returns the full event as a plain-text string
print(e.serialize())
For selective subscriptions combine events() with filter():
import ESL
con = ESL.ESLconnection("localhost", "8021", "ClueCon")
if con.connected():
# Only CHANNEL_ANSWER, DTMF, and CHANNEL_HANGUP events
con.events("plain", "CHANNEL_ANSWER DTMF CHANNEL_HANGUP")
while con.connected():
e = con.recvEventTimed(500) # 500 ms timeout
if not e:
continue # timeout — do other work here
name = e.getHeader("Event-Name")
uuid = e.getHeader("Unique-ID")
if name == "CHANNEL_ANSWER":
print(f"Call answered: {uuid}")
elif name == "DTMF":
digit = e.getHeader("DTMF-Digit")
print(f"DTMF {digit} on call {uuid}")
elif name == "CHANNEL_HANGUP":
cause = e.getHeader("Hangup-Cause")
print(f"Hangup {uuid}: {cause}")
Python 3: Outbound Server
This pattern is taken from libs/esl/python3/server.py. FreeSWITCH connects to this server for each call that hits the socket dialplan application:
#!/usr/bin/env python3
# Adapted from libs/esl/python3/server.py
import socketserver
import ESL
class ESLRequestHandler(socketserver.BaseRequestHandler):
def handle(self):
print(f"{self.client_address} connected!")
fd = self.request.fileno()
con = ESL.ESLconnection(fd)
print(f"Connected: {con.connected()}")
if con.connected():
info = con.getInfo()
uuid = info.getHeader("unique-id")
print(f"Call UUID: {uuid}")
# Answer and play a file
con.execute("answer", "", uuid)
con.execute("playback", "/ram/swimp.raw", uuid)
# Bind and serve forever — one thread per connection
server = socketserver.ThreadingTCPServer(("", 8040), ESLRequestHandler)
server.serve_forever()
Perl: Subscribing to Events
From libs/esl/perl/events.pl — iterates through every header on each received event:
#!/usr/bin/perl
require ESL;
my $con = new ESL::ESLconnection("localhost", "8021", "ClueCon");
$con->events("plain", "all");
while ($con->connected()) {
my $e = $con->recvEvent();
if ($e) {
my $h = $e->firstHeader();
while ($h) {
printf "Header: [%s] = [%s]\n", $h, $e->getHeader($h);
$h = $e->nextHeader();
}
}
}
Ruby: Outbound Server with DTMF
From libs/esl/ruby/server3.rb — a forking outbound server that handles DTMF during playback:
#!/usr/bin/ruby
require "ESL"
require 'socket'
include Socket::Constants
bind_address = "127.0.0.1"
bind_port = 8086
socket = Socket.new(AF_INET, SOCK_STREAM, 0)
socket.bind(Socket.sockaddr_in(bind_port, bind_address))
socket.listen(5)
puts "Listening on #{bind_address}:#{bind_port}"
loop do
client_socket, _addr = socket.accept
pid = fork do
@con = ESL::ESLconnection.new(client_socket.fileno)
info = @con.getInfo
uuid = info.getHeader("UNIQUE-ID")
@con.sendRecv("myevents")
@con.sendRecv("divert_events on")
@con.execute("answer", "")
@con.execute("playback", "/usr/local/freeswitch/sounds/music/8000/suite-espanola-op-47-leyenda.wav")
while @con.connected
e = @con.recvEvent
next unless e
name = e.getHeader("Event-Name")
break if name == "SERVER_DISCONNECTED"
if name == "DTMF"
digit = e.getHeader("DTMF-Digit")
puts "DTMF: #{digit}"
@con.execute("transfer", "99355151") if digit == "9"
end
end
puts "Connection closed"
end
Process.detach(pid)
end
Java: Full Connect Example
import org.freeswitch.esl.ESLconnection;
import org.freeswitch.esl.ESLevent;
public class ESLDemo {
public static void main(String[] args) throws Exception {
ESLconnection con = new ESLconnection("127.0.0.1", "8021", "ClueCon");
if (con.connected() == 0) {
System.err.println("Failed to connect");
return;
}
// Subscribe to all events
con.events("plain", "all");
// Run a background originate
ESLevent job = con.bgapi(
"originate",
"sofia/internal/1001@pbx.example.com &echo",
""
);
String jobUuid = job.getHeader("Job-UUID");
System.out.println("Job UUID: " + jobUuid);
// Receive events in a loop
for (int i = 0; i < 10; i++) {
ESLevent e = con.recvEventTimed(2000);
if (e != null) {
System.out.println("Event: " + e.getHeader("Event-Name"));
String body = e.getBody();
if (body != null && !body.isEmpty()) {
System.out.println("Body: " + body);
}
}
}
con.disconnect();
}
}
ESL is not thread-safe at the connection level. Create one ESLconnection per thread — do not share a single connection across multiple threads. If you need to multiplex events across threads, use an inbound connection in a dedicated reader thread and dispatch events to worker threads via a queue.