Dialplan Contexts, Extensions, and Priorities
Dialplan Contexts, Extensions, and Priorities
Asterisk's dialplan looks like a config file, but it runs like a script. Calls come in, hit a matching extension, and execute a sequence of steps. Three concepts hold the whole thing together: contexts, extensions, and priorities.
On this page
Contexts
A context is a named block in the dialplan. Square brackets, a name, and everything below it until the next context starts.
[internal]
exten => 100,1,Answer()
same => n,Playback(hello-world)
same => n,Hangup()
[from-trunk]
exten => s,1,Answer()
same => n,Playback(tt-weasels)
same => n,Hangup()
Why bother with separate blocks? Security. A phone in [internal] can only reach extensions defined there. It has no visibility into [from-trunk]. This matters because your internal context probably allows outbound dialing, and you absolutely do not want anonymous inbound callers to have access to that.
Contexts also keep things organized. As the dialplan grows, you end up with [internal], [from-trunk], [after-hours], [outbound], and so on. Each one handles a distinct part of the call flow.
Assigning phones to contexts
Each PJSIP endpoint has a context line that determines where its calls land:
[200]
type = endpoint
context = internal
Extension 200 dials a number, Asterisk looks inside [internal] for a match and nowhere else.
Jumping between contexts
Goto() moves a call from one context to another:
[internal]
exten => _9NXXNXXXXXX,1,Goto(outbound,${EXTEN},1)
[outbound]
exten => _9NXXNXXXXXX,1,Dial(PJSIP/trunk/${EXTEN:1})
User dials 9 followed by a phone number from [internal], the call lands in [outbound] where the pattern match strips the 9 and sends it out the trunk. Keeps outbound routing logic in its own context instead of cluttering up the internal one.
The includes directive
You can also make one context's extensions available in another:
[internal]
include => features
[features]
exten => *98,1,VoicemailMain(${CALLERID(num)}@default)
Now phones in [internal] can dial *98 even though it lives in [features]. Useful for shared stuff like voicemail access, conference rooms, or parking lots.
One warning: be careful what you include where. If you include a context with outbound dialing into a context that handles inbound trunk calls, you have just created a toll fraud vulnerability.
Extensions
An extension matches a dialed number to a set of instructions. Asterisk compares what was dialed against each extension in the current context, finds the best match, and runs it.
[internal]
exten => 200,1,Dial(PJSIP/200,30)
exten => 201,1,Dial(PJSIP/201,30)
exten => 300,1,ConfBridge(main)
Pattern matching
You are not going to hardcode every possible phone number someone might dial. Asterisk has pattern matching for this, triggered by the _ prefix:
[outbound]
; Match 10-digit North American numbers
exten => _NXXNXXXXXX,1,Dial(PJSIP/trunk/${EXTEN})
; Match 11-digit with leading 1
exten => _1NXXNXXXXXX,1,Dial(PJSIP/trunk/${EXTEN})
; Match 3-digit internal extensions
exten => _2XX,1,Dial(PJSIP/${EXTEN},30)
The pattern characters:
| Character | Matches |
|---|---|
X |
Any digit 0-9 |
Z |
Any digit 1-9 |
N |
Any digit 2-9 |
[15-7] |
Digit 1, 5, 6, or 7 |
. |
One or more of any character (wildcard) |
! |
Wildcard; causes the matching process to complete as soon as it can unambiguously determine that no other matches are possible |
When multiple patterns could match, Asterisk picks the most specific one. An exact match like exten => 200 always beats a pattern like exten => _2XX.
Special extensions
A few extension names are reserved:
| Extension | Purpose |
|---|---|
s |
"Start." Incoming calls with no DID land here |
i |
"Invalid." Fires when the caller presses something that matches nothing |
t |
"Timeout." Fires when WaitExten() times out |
h |
"Hangup." Runs after the call disconnects, good for cleanup |
a |
"Assistant." When someone presses * inside VoicemailMain() |
If you are building an IVR and forget the i and t handlers, callers who press the wrong button or wait too long will hear nothing. Just dead air. Always define both.
Priorities
Priorities are the steps inside an extension. They run in order, top to bottom.
exten => 200,1,Answer()
exten => 200,2,Playback(hello-world)
exten => 200,3,Hangup()
The same => and n shorthand
Nobody manually numbers priorities anymore. If you insert a step in the middle, you have to renumber everything after it. Use same => n instead:
exten => 200,1,Answer()
same => n,Playback(hello-world)
same => n,Hangup()
same refers to the previous extension. n auto-increments the priority number. Start the first one at 1, then use n for everything after.
Labels
You can name a priority so you can jump to it later:
exten => 200,1,Answer()
same => n(greeting),Playback(welcome)
same => n,Goto(200,greeting)
The label greeting lets Goto() reference that step by name. If you rearrange priorities later, the label still works.
Conditional branching
GotoIf() is how the dialplan makes decisions:
exten => 200,1,Answer()
same => n,Set(OPEN=${IFTIME(09:00-17:00|mon-fri|*|*?1:0)})
same => n,GotoIf($[${OPEN}=1]?open:closed)
same => n(open),Playback(office-is-open)
same => n,Goto(reception,s,1)
same => n(closed),Playback(office-is-closed)
same => n,Voicemail(200@default,u)
same => n,Hangup()
The syntax is GotoIf(condition?true_label:false_label). Between labels, Goto(), and GotoIf(), the dialplan has enough control flow to handle most routing logic without external scripts.
A working example
A small-office dialplan tying all of this together:
[internal]
include => features
; Internal extensions
exten => _2XX,1,Dial(PJSIP/${EXTEN},30)
same => n,Voicemail(${EXTEN}@default,u)
same => n,Hangup()
; Outbound via 9 prefix
exten => _9NXXNXXXXXX,1,Set(CALLERID(num)=5551234567)
same => n,Dial(PJSIP/trunk/${EXTEN:1})
same => n,Hangup()
[features]
; Voicemail access
exten => *98,1,VoicemailMain(${CALLERID(num)}@default)
same => n,Hangup()
; Echo test
exten => *99,1,Answer()
same => n,Echo()
same => n,Hangup()
[from-trunk]
; Inbound calls from SIP provider
exten => s,1,Answer()
same => n,Background(press-1-for-sales&press-2-for-support)
same => n,WaitExten(5)
exten => 1,1,Dial(PJSIP/200&PJSIP/201,30)
same => n,Voicemail(200@default,u)
same => n,Hangup()
exten => 2,1,Dial(PJSIP/202,30)
same => n,Voicemail(202@default,u)
same => n,Hangup()
exten => i,1,Playback(invalid)
same => n,Goto(s,1)
exten => t,1,Dial(PJSIP/200,30)
same => n,Voicemail(200@default,u)
same => n,Hangup()
The two main contexts are isolated from each other. [internal] is for employee phones with outbound access. [from-trunk] handles outside callers and has its own IVR with i and t handlers so nobody gets stuck in silence. Shared features like voicemail live in their own context and get pulled in with include.
_2XX matches any three-digit extension starting with 2. _9NXXNXXXXXX matches 9 followed by a 10-digit number, and ${EXTEN:1} strips the leading 9 before sending to the trunk.
For Voicemail() and VoicemailMain() to work, matching mailboxes must exist in voicemail.conf. After editing extensions.conf or voicemail.conf, reload the dialplan from the CLI:
asterisk -rx "dialplan reload"
asterisk -rx "voicemail reload"
Debugging
When calls do the wrong thing, or nothing at all, connect to the CLI:
asterisk -rvvv
Make a call and watch. Asterisk prints each priority as it runs. You can also dump the parsed dialplan for a specific context:
asterisk -rx "dialplan show internal"
If an extension you wrote is missing from that output, either it is in a different context or it has a syntax error that prevented loading.
What to read next
- Dialplan Applications reference
- Dialplan Functions reference
- The
Goto(),GotoIf(),Gosub(), andDial()reference entries
User Notes
Know a tip or gotcha for this topic? Share it below and help others.
Contribute a note
Share a tip, gotcha, or practical example. Keep it under 2000 characters. No questions (use the Asterisk community forums for support). Wrap code in backticks.