Most real dialplan work is string surgery. Strip a prefix, add a country code, extract a DID from a header, normalise a caller ID. Asterisk’s variable syntax handles all of it, but the substring notation with negative values confuses almost everyone the first time.
Here is the whole thing with worked examples.
Setting and Reading
exten => 100,1,Set(MYVAR=hello)
exten => 100,n,NoOp(${MYVAR})
NoOp() does nothing except print to the CLI, which makes it the workhorse of dialplan debugging. Run asterisk -rvvvvv and watch values as they change.
Channel variables are per-call. To set something globally, use the [globals] context or Set(GLOBAL(NAME)=value).
Inherit a variable across a transferred or bridged channel by prefixing with __:
exten => 100,1,Set(__CUSTOMERID=88421)
One underscore inherits one level. Two underscores inherit indefinitely. In a dialer where calls get transferred to closers, you almost always want two.
Substrings
The syntax:
${VARIABLE:offset:length}
Take EXTEN holding 12345678.
Positive Offset
Skip characters from the left.
${EXTEN:2} → 345678
${EXTEN:4} → 5678
This is what strips the VICIdial dial prefix. With EXTEN = 915551234567:
${EXTEN:1} → 15551234567
Positive Offset With Length
${EXTEN:2:3} → 345
${EXTEN:0:4} → 1234
Skip 2, take 3. Skip 0, take 4.
Negative Offset
This is the part that trips people. A negative offset counts from the end and returns everything from that point forward.
${EXTEN:-4} → 5678
${EXTEN:-2} → 78
Useful for grabbing the last four digits of an account number, or matching a DID where the carrier’s prefix varies.
Negative Offset With Positive Length
Start from the end, then take that many characters going forward.
${EXTEN:-4:2} → 56
Counts back 4 to position 5, then takes 2 characters: 56.
Negative Length
A negative length means “stop that many characters before the end.”
${EXTEN:0:-2} → 123456
${EXTEN:2:-2} → 3456
${EXTEN:0:-2} is the clean way to drop a trailing extension from a dialled string.
Concatenation
Just place variables and literals next to each other:
exten => _9X.,1,Set(OUTNUM=00${EXTEN:1})
exten => _9X.,n,NoOp(${OUTNUM})
Prepend a country code, strip a national trunk prefix:
; UK: 07911123456 → 447911123456
exten => _90X.,1,Set(OUTNUM=44${EXTEN:2})
Arithmetic
${MATH(expression,type)}
Type is int, float or hex.
exten => 100,1,Set(TOTAL=${MATH(5+3,int)}) ; 8
exten => 100,n,Set(SECS=${MATH(${MINS}*60,int)})
exten => 100,n,Set(PCT=${MATH(${ANSWERED}*100/${DIALED},float)})
Older dialplan used $[ ] expression syntax, which still works:
exten => 100,1,Set(TOTAL=$[5 + 3])
Whitespace inside $[ ] matters; $[5+3] can behave unexpectedly depending on version. MATH() is more predictable, prefer it.
Conditionals
${IF($[condition]?true_value:false_value)}
exten => _9X.,1,Set(PREFIX=${IF($[${LEN(${EXTEN:1})} = 10]?1:)})
exten => _9X.,n,Dial(SIP/mytrunk/${PREFIX}${EXTEN:1},,tTo)
That adds a 1 only when the number is 10 digits.
For branching rather than value selection, use GotoIf:
exten => _9X.,1,GotoIf($[${LEN(${EXTEN:1})} < 10]?invalid,1)
exten => _9X.,n,Dial(SIP/mytrunk/${EXTEN:1},,tTo)
exten => _9X.,n,Hangup()
exten => invalid,1,Playback(invalid)
exten => invalid,n,Hangup()
CUT — Splitting on a Delimiter
${CUT(varname,delimiter,field)}
Useful when a variable carries structured data:
exten => 100,1,Set(DATA=john|smith|5551234)
exten => 100,n,Set(FIRST=${CUT(DATA,|,1)}) ; john
exten => 100,n,Set(PHONE=${CUT(DATA,|,3)}) ; 5551234
Extracting a user from a SIP URI:
exten => _X.,1,Set(FULL=${CUT(SIP_HEADER(To),@,1)})
exten => _X.,n,Set(DID=${CUT(FULL,:,2)})
SIP_HEADER(To) returns something like <sip:15551234567@10.0.0.1>. First CUT on @ gives <sip:15551234567, second on : gives 15551234567.
For PJSIP, the equivalent is PJSIP_HEADER(read,To).
REGEX
Returns 1 or 0:
exten => _X.,1,GotoIf($[${REGEX("^1[2-9][0-9]{9}$" ${EXTEN})}]?valid,1:invalid,1)
Useful as a validity gate before handing a number to a carrier that charges for invalid attempts.
Caller ID Rewriting
Read and write with the CALLERID() function:
exten => _9X.,1,NoOp(Inbound CID: ${CALLERID(num)})
exten => _9X.,n,Set(CALLERID(num)=15551230000)
exten => _9X.,n,Set(CALLERID(name)=Acme Support)
Strip a leading + from an inbound caller ID:
exten => _X.,1,Set(CALLERID(num)=${IF($["${CALLERID(num):0:1}" = "+"]?${CALLERID(num):1}:${CALLERID(num)})})
Normalise a 10-digit inbound to 11-digit for CRM matching:
exten => _X.,1,Set(CIDLEN=${LEN(${CALLERID(num)})})
exten => _X.,n,ExecIf($[${CIDLEN} = 10]?Set(CALLERID(num)=1${CALLERID(num)}))
ExecIf() runs an application conditionally on one line, which keeps this readable.
Local Presence Dialling
A common dialer requirement — present a caller ID matching the area code you are calling:
exten => _91NXXNXXXXXX,1,AGI(agi://127.0.0.1:4577/call_log)
exten => _91NXXNXXXXXX,n,Set(NPA=${EXTEN:2:3})
exten => _91NXXNXXXXXX,n,Set(CALLERID(num)=${DB(cidmap/${NPA})})
exten => _91NXXNXXXXXX,n,ExecIf($["${CALLERID(num)}" = ""]?Set(CALLERID(num)=15551230000))
exten => _91NXXNXXXXXX,n,Dial(SIP/mytrunk/${EXTEN:1},,tTo)
exten => _91NXXNXXXXXX,n,Hangup()
Populate the AstDB from the CLI:
asterisk -rx "database put cidmap 212 12125551000"
asterisk -rx "database put cidmap 312 13125551000"
asterisk -rx "database show cidmap"
Be aware that caller ID practices are regulated in many jurisdictions, and STIR/SHAKEN attestation in the US depends on the number being one you are authorised to use. Check with your carrier before deploying anything like this.
Debugging
asterisk -rvvvvv
core set verbose 5
dialplan show default
Sprinkle NoOp() liberally while developing:
exten => _9X.,n,NoOp(EXTEN=${EXTEN} STRIPPED=${EXTEN:1} CID=${CALLERID(num)})
Then remove them once it works. Every NoOp on a high-volume dialer is a log write.
Quick Reference
| Expression | Result on 12345678 |
|---|---|
${EXTEN:1} | 2345678 |
${EXTEN:0:3} | 123 |
${EXTEN:-3} | 678 |
${EXTEN:-4:2} | 56 |
${EXTEN:0:-2} | 123456 |
${LEN(${EXTEN})} | 8 |