IPComms
PricingAboutBlog
Tutorial February 18, 2026 25 min read

Asterisk Dialplan Tutorial — From Basics to Advanced Routing

Master Asterisk's extensions.conf from the ground up. Learn contexts, pattern matching, variables, essential applications, and build complete inbound and outbound routes with IPComms SIP trunks.

1. What is a Dialplan?

The dialplan is the heart and brain of every Asterisk PBX. Defined in extensions.conf, it controls how every single call is handled — from the moment a call arrives or a user dials a number, to the moment it is answered, transferred, recorded, or hung up.

Think of the dialplan as a set of instructions that tells Asterisk: "When someone dials this number, do these things in this order." Without a dialplan, Asterisk cannot process a single call. Every inbound call from your IPComms SIP trunk, every internal extension-to-extension call, and every outbound call to the PSTN flows through the dialplan.

Three Core Concepts

Contexts

Logical groupings that separate different call flows. Contexts provide security by isolating groups of extensions from each other.

Extensions

Named or numbered entries within a context. Each extension defines a set of actions to execute when that extension is matched.

Priorities

The sequential order in which actions (applications) are executed within an extension. Priority 1 always runs first.

File Location: The dialplan is stored at /etc/asterisk/extensions.conf. After editing, reload it with dialplan reload from the Asterisk CLI without restarting the service.

2. Dialplan Structure

Every dialplan follows a consistent structure. Understanding the syntax is essential before you can build any call routing logic.

Contexts

Contexts are defined inside square brackets. They group related extensions together and act as security boundaries. A channel entering Asterisk is assigned to a specific context, and it can only access extensions within that context (unless explicitly included).

; Define a context for internal extensions
[internal]

; Define a context for inbound calls from your SIP trunk
[from-ipcomms]

; Define a context for outbound calls
[outbound]

Extensions and Priorities

Within each context, you define extensions using the exten => keyword. Each extension has a name/number, a priority, and an application to execute:

; Syntax: exten => name,priority,Application(arguments)

[internal]
exten => 100,1,Answer()
exten => 100,2,Playback(hello-world)
exten => 100,3,Hangup()

The "same =>" Shorthand

Manually numbering priorities is tedious and error-prone. Modern Asterisk dialplans use same => with the n (next) priority to auto-increment:

[internal]
exten => 100,1,Answer()
 same => n,Playback(hello-world)
 same => n,Hangup()

; This is identical to writing priorities 1, 2, 3
; The "n" means "next priority" and auto-increments

The [general] Section

The top of extensions.conf typically contains a [general] section with global settings:

[general]
static = yes          ; Prevent dialplan from being overwritten by "dialplan save"
writeprotect = no     ; Allow CLI modifications for testing
clearglobalvars = no  ; Preserve global variables on reload

Best Practice: Always use the same => n shorthand instead of hard-coded priority numbers. It makes your dialplan easier to edit — you can insert or remove lines without renumbering everything.

3. Pattern Matching

Pattern matching lets you write a single extension that handles an entire range of dialed numbers. Instead of creating one extension for every possible phone number, you define patterns that Asterisk matches against. Patterns always begin with an underscore (_).

Pattern Characters

CharacterMatchesExample
XAny digit 0-9_XXX matches 000-999
ZAny digit 1-9_ZXXX matches 1000-9999
NAny digit 2-9_NXXXXXX matches 7-digit local
[ranges]Specific digit range_[2-5]XX matches 200-599
. (dot)One or more of any character_011. matches any international number
! (bang)Zero or more of any character_011! matches 011 and anything after

Common Pattern Examples

; US 11-digit dialing (1 + area code + number)
exten => _1NXXNXXXXXX,1,NoOp(US Long Distance: ${EXTEN})

; US 10-digit dialing (area code + number, no leading 1)
exten => _NXXNXXXXXX,1,NoOp(US Local: ${EXTEN})

; International dialing (011 + country code + number)
exten => _011.,1,NoOp(International: ${EXTEN})

; 4-digit internal extensions (1000-9999)
exten => _XXXX,1,NoOp(Internal Extension: ${EXTEN})

; 3-digit extensions starting with 1, 2, or 3
exten => _[1-3]XX,1,NoOp(Extension: ${EXTEN})

; Toll-free numbers (1-800, 1-888, 1-877, 1-866, 1-855, 1-844, 1-833)
exten => _1[8][0-8][0-9]NXXXXXX,1,NoOp(Toll-Free: ${EXTEN})

Important: The dot (.) wildcard is greedy and matches everything. Use it carefully to avoid routing calls to unintended destinations. Prefer specific patterns like _1NXXNXXXXXX over _1. whenever possible.

4. Variables & Functions

Variables let you store and manipulate data during call processing. Asterisk supports channel variables (per-call), global variables (system-wide), and built-in functions that return dynamic values.

Built-in Channel Variables

; ${EXTEN} - The dialed extension (the number the caller dialed)
exten => _X.,1,NoOp(Dialed number: ${EXTEN})

; ${CALLERID(num)} - Caller's phone number
 same => n,NoOp(Caller number: ${CALLERID(num)})

; ${CALLERID(name)} - Caller's name (CNAM)
 same => n,NoOp(Caller name: ${CALLERID(name)})

; ${CHANNEL} - Current channel name
 same => n,NoOp(Channel: ${CHANNEL})

; ${UNIQUEID} - Unique call identifier
 same => n,NoOp(Call ID: ${UNIQUEID})

Setting Variables with Set()

; Set a channel variable (available only during this call)
exten => 100,1,Set(DEPARTMENT=sales)
 same => n,NoOp(Department is ${DEPARTMENT})

; Set a global variable (available to all channels)
 same => n,Set(GLOBAL(BUSINESS_HOURS)=true)

; Modify the caller ID before sending the call out
 same => n,Set(CALLERID(num)=12025551234)
 same => n,Set(CALLERID(name)=My Company)

Useful Dialplan Functions

; String manipulation - extract area code from 10-digit number
exten => _NXXNXXXXXX,1,Set(AREA_CODE=${EXTEN:0:3})
 same => n,NoOp(Area code: ${AREA_CODE})

; String length
 same => n,Set(DIGITS=${LEN(${EXTEN})})
 same => n,NoOp(Number has ${DIGITS} digits)

; Conditional expression with IF()
 same => n,Set(ROUTE=${IF($["${AREA_CODE}" = "212"]?newyork:default)})

; Check if an extension exists in a context
 same => n,GotoIf($[${DIALPLAN_EXISTS(internal,${EXTEN},1)}]?internal,${EXTEN},1)

; Regular expression matching
 same => n,GotoIf($[${REGEX("^1[89][0-9]{2}[0-9]{7}$" ${EXTEN})}]?tollfree,${EXTEN},1)

String Slicing: Use ${EXTEN:offset:length} to extract substrings. For example, ${EXTEN:1} strips the first digit (useful for removing a leading "1" from 11-digit numbers), and ${EXTEN:0:3} gets the first three digits.

5. Essential Applications

Applications are the actions that Asterisk performs at each priority step. Here are the applications you will use most frequently when building dialplans.

Call Control

; Answer() - Pick up the channel (answer the call)
exten => 100,1,Answer()

; Hangup() - End the call
 same => n,Hangup()

; Wait() - Pause for a specified number of seconds
 same => n,Wait(2)

; NoOp() - No Operation, used for logging/debugging
 same => n,NoOp(This message appears in the CLI)

Audio Playback

; Playback() - Play a sound file (caller cannot interrupt)
exten => 200,1,Answer()
 same => n,Playback(welcome)
 same => n,Playback(vm-goodbye)
 same => n,Hangup()

; Background() - Play a sound file (caller CAN press digits to interrupt)
exten => 300,1,Answer()
 same => n,Background(main-menu)
 same => n,WaitExten(5)       ; Wait 5 seconds for input

Dial()

The Dial() application is the workhorse of Asterisk. It rings one or more channels and connects the caller to whoever answers.

; Dial a single PJSIP extension with 30-second timeout
exten => 100,1,Dial(PJSIP/100,30)

; Dial an external number via IPComms SIP trunk
exten => _1NXXNXXXXXX,1,Dial(PJSIP/${EXTEN}@ipcomms-endpoint,60)

; Ring multiple extensions simultaneously (ring group)
exten => 200,1,Dial(PJSIP/100&PJSIP/101&PJSIP/102,30)

; Dial with options: t=allow transfer by called, T=by caller, r=ringing
exten => 100,1,Dial(PJSIP/100,30,tTr)

Flow Control

; Goto() - Jump to a different context, extension, or priority
exten => 100,1,Goto(internal,200,1)

; GotoIf() - Conditional branching
; Syntax: GotoIf(condition?true_destination:false_destination)
exten => 100,1,GotoIf($["${CALLERID(num)}" = "12025551234"]?vip,${EXTEN},1:standard,${EXTEN},1)

; GotoIf with label names (easier to read)
exten => 100,1,GotoIf($["${BUSINESS_HOURS}" = "true"]?open:closed)
 same => n(open),Dial(PJSIP/100,30)
 same => n,Hangup()
 same => n(closed),Playback(closed)
 same => n,Voicemail(100@default,u)
 same => n,Hangup()

Voicemail

; Voicemail() - Send caller to a voicemail box
; u = "unavailable" greeting, b = "busy" greeting
exten => 100,1,Dial(PJSIP/100,30)
 same => n,Voicemail(100@default,u)
 same => n,Hangup()

; VoicemailMain() - Let users check their voicemail
exten => *97,1,Answer()
 same => n,VoicemailMain(${CALLERID(num)}@default)
 same => n,Hangup()

Queue()

; Queue() - Place caller into an ACD queue
exten => 400,1,Answer()
 same => n,Queue(support,,,,300)  ; 300-second max wait time
 same => n,Voicemail(400@default,u)
 same => n,Hangup()

Dial String Format: When routing calls through an IPComms SIP trunk, use the format PJSIP/${EXTEN}@ipcomms-endpoint where ipcomms-endpoint is the endpoint name defined in your pjsip.conf.

6. Building an Outbound Route

This is a complete, production-ready outbound routing context that handles all North American call types through your IPComms SIP trunk. It includes 10-digit local, 11-digit long distance, toll-free, international, and emergency calling.

Complete Outbound Dialplan

; ==============================================
; Outbound Routes via IPComms SIP Trunk
; ==============================================

[outbound-ipcomms]

; --- Emergency Calls (911) ---
; ALWAYS route emergency calls first, no caller ID manipulation
exten => 911,1,NoOp(EMERGENCY CALL from ${CALLERID(num)})
 same => n,Dial(PJSIP/911@ipcomms-endpoint,300)
 same => n,Hangup()

exten => 9911,1,Goto(outbound-ipcomms,911,1)

; --- US/Canada 11-digit (1NXXNXXXXXX) ---
exten => _1NXXNXXXXXX,1,NoOp(Outbound US/CA 11-digit: ${EXTEN})
 same => n,Set(CALLERID(num)=YOUR_DID_NUMBER)
 same => n,Dial(PJSIP/${EXTEN}@ipcomms-endpoint,60)
 same => n,NoOp(Dial status: ${DIALSTATUS})
 same => n,Hangup()

; --- US/Canada 10-digit (NXXNXXXXXX) - prepend 1 ---
exten => _NXXNXXXXXX,1,NoOp(Outbound US/CA 10-digit: ${EXTEN})
 same => n,Set(CALLERID(num)=YOUR_DID_NUMBER)
 same => n,Dial(PJSIP/1${EXTEN}@ipcomms-endpoint,60)
 same => n,NoOp(Dial status: ${DIALSTATUS})
 same => n,Hangup()

; --- Toll-Free Numbers (1-800, 1-888, 1-877, 1-866, 1-855, 1-844, 1-833) ---
exten => _1[8][0-8][0-9]NXXXXXX,1,NoOp(Outbound Toll-Free: ${EXTEN})
 same => n,Set(CALLERID(num)=YOUR_DID_NUMBER)
 same => n,Dial(PJSIP/${EXTEN}@ipcomms-endpoint,60)
 same => n,NoOp(Dial status: ${DIALSTATUS})
 same => n,Hangup()

; --- International Calls (011 + country code + number) ---
exten => _011.,1,NoOp(Outbound International: ${EXTEN})
 same => n,Set(CALLERID(num)=YOUR_DID_NUMBER)
 same => n,Dial(PJSIP/${EXTEN}@ipcomms-endpoint,120)
 same => n,NoOp(Dial status: ${DIALSTATUS})
 same => n,Hangup()

; --- 7-digit local dialing (if your area still supports it) ---
; Prepend 1 + your local area code
exten => _NXXXXXX,1,NoOp(Outbound 7-digit local: ${EXTEN})
 same => n,Set(CALLERID(num)=YOUR_DID_NUMBER)
 same => n,Dial(PJSIP/1YOUR_AREA_CODE${EXTEN}@ipcomms-endpoint,60)
 same => n,NoOp(Dial status: ${DIALSTATUS})
 same => n,Hangup()

; --- Catch-all for unmatched patterns ---
exten => _X.,1,NoOp(Unmatched outbound pattern: ${EXTEN})
 same => n,Playback(ss-noservice)
 same => n,Hangup()

Replace Placeholders: Change YOUR_DID_NUMBER to your actual IPComms DID (e.g., 12025551234) and YOUR_AREA_CODE to your local area code (e.g., 202). The ipcomms-endpoint must match the endpoint name in your pjsip.conf.

Connecting Internal Extensions to Outbound

Your internal extensions need access to the outbound context. Use the include statement to grant access:

; In your internal context, include the outbound routes
[internal]
include => outbound-ipcomms

; Internal extension 100
exten => 100,1,Dial(PJSIP/100,30)
 same => n,Voicemail(100@default,u)
 same => n,Hangup()

; Now users in the [internal] context can dial external numbers

7. Building an Inbound Route

Inbound calls from your IPComms SIP trunk arrive in the context defined in your pjsip.conf endpoint configuration (typically from-ipcomms). The dialed number (your DID) arrives as the extension (${EXTEN}), which you can match to route calls to the right destination.

Route by DID Number

; ==============================================
; Inbound Routes from IPComms SIP Trunk
; ==============================================

[from-ipcomms]

; --- Main office number - route to IVR ---
exten => 12025551000,1,NoOp(Inbound to main number: ${EXTEN} from ${CALLERID(num)})
 same => n,Answer()
 same => n,Wait(1)
 same => n,Goto(ivr-main,s,1)

; --- Sales DID - route to sales queue ---
exten => 12025551001,1,NoOp(Inbound to sales DID: ${EXTEN})
 same => n,Answer()
 same => n,Queue(sales,,,,300)
 same => n,Voicemail(601@default,u)
 same => n,Hangup()

; --- Support DID - route to support queue ---
exten => 12025551002,1,NoOp(Inbound to support DID: ${EXTEN})
 same => n,Answer()
 same => n,Queue(support,,,,300)
 same => n,Voicemail(602@default,u)
 same => n,Hangup()

; --- Direct DID for extension 100 ---
exten => 12025551100,1,NoOp(Inbound direct DID for ext 100)
 same => n,Dial(PJSIP/100,30)
 same => n,Voicemail(100@default,u)
 same => n,Hangup()

; --- Direct DID for extension 101 ---
exten => 12025551101,1,NoOp(Inbound direct DID for ext 101)
 same => n,Dial(PJSIP/101,30)
 same => n,Voicemail(101@default,u)
 same => n,Hangup()

; --- Catch-all for any other DID ---
exten => _X.,1,NoOp(Inbound call to unrouted DID: ${EXTEN})
 same => n,Answer()
 same => n,Playback(tt-allbusy)
 same => n,Hangup()

Simple IVR (Auto Attendant)

; ==============================================
; IVR Menu for Main Number
; ==============================================

[ivr-main]
exten => s,1,NoOp(IVR Main Menu)
 same => n,Answer()
 same => n(menu),Background(custom/main-menu)  ; "Press 1 for sales, 2 for support..."
 same => n,WaitExten(5)

; Press 1 for Sales
exten => 1,1,Goto(from-ipcomms,12025551001,1)

; Press 2 for Support
exten => 2,1,Goto(from-ipcomms,12025551002,1)

; Press 0 for Operator
exten => 0,1,Dial(PJSIP/100,30)
 same => n,Voicemail(100@default,u)
 same => n,Hangup()

; Invalid input - replay menu
exten => i,1,Playback(invalid)
 same => n,Goto(ivr-main,s,menu)

; Timeout - replay menu
exten => t,1,Goto(ivr-main,s,menu)

DID Format: IPComms delivers inbound calls with the DID in 11-digit E.164 format (e.g., 12025551000). Match this exact format in your dialplan, or use pattern matching like _1NXXNXXXXXX for a catch-all.

8. Advanced Techniques

Once you have the basics down, these advanced techniques let you build sophisticated, production-grade call routing logic.

Time-Based Routing with GotoIfTime()

Route calls differently based on day and time. The syntax is GotoIfTime(times,days_of_week,days_of_month,months?destination):

[from-ipcomms]
exten => 12025551000,1,NoOp(Checking business hours)
; Mon-Fri 8:00 AM to 6:00 PM = business hours
 same => n,GotoIfTime(08:00-18:00,mon-fri,*,*?open)
; Saturday 9:00 AM to 1:00 PM = limited hours
 same => n,GotoIfTime(09:00-13:00,sat,*,*?open)
; Everything else = closed
 same => n,Goto(closed)

 same => n(open),Queue(sales,,,,300)
 same => n,Voicemail(600@default,u)
 same => n,Hangup()

 same => n(closed),Answer()
 same => n,Playback(custom/office-closed)
 same => n,Voicemail(600@default,u)
 same => n,Hangup()

Include Statements

The include directive lets one context inherit the extensions of another. This is how you give internal users access to outbound dialing:

; Build a layered permission system

[local-calls]
exten => _NXXNXXXXXX,1,Dial(PJSIP/1${EXTEN}@ipcomms-endpoint,60)

[long-distance]
include => local-calls
exten => _1NXXNXXXXXX,1,Dial(PJSIP/${EXTEN}@ipcomms-endpoint,60)

[international]
include => long-distance
exten => _011.,1,Dial(PJSIP/${EXTEN}@ipcomms-endpoint,120)

; Assign contexts to endpoints in pjsip.conf:
; Standard users:   context = long-distance
; Executives:       context = international
; Lobby phones:     context = local-calls

GoSub() for Reusable Logic

GoSub() calls a subroutine and returns to the calling point. This replaces the deprecated Macro() application:

; Define a subroutine to set outbound caller ID
[sub-set-callerid]
exten => s,1,NoOp(Setting outbound caller ID)
 same => n,Set(CALLERID(num)=12025551000)
 same => n,Set(CALLERID(name)=My Company)
 same => n,Return()

; Call the subroutine before dialing out
[outbound-ipcomms]
exten => _1NXXNXXXXXX,1,GoSub(sub-set-callerid,s,1)
 same => n,Dial(PJSIP/${EXTEN}@ipcomms-endpoint,60)
 same => n,Hangup()

exten => _011.,1,GoSub(sub-set-callerid,s,1)
 same => n,Dial(PJSIP/${EXTEN}@ipcomms-endpoint,120)
 same => n,Hangup()

Chaining Contexts with Goto()

Use Goto() to chain contexts together for multi-step call processing:

; Pre-processing context: log the call, then route
[inbound-preprocess]
exten => _X.,1,NoOp(Pre-processing inbound call)
 same => n,Set(CDR(userfield)=inbound)
 same => n,Set(__ORIGINAL_DID=${EXTEN})
 same => n,Goto(from-ipcomms,${EXTEN},1)

; The call continues in [from-ipcomms] with all variables intact
; Double underscore (__) makes the variable inherit to new channels

AEL2 vs Traditional Dialplan

Asterisk also supports AEL2 (Asterisk Extension Language 2), a syntax that resembles a programming language with curly braces and if/else blocks. It is defined in extensions.ael instead of extensions.conf:

// AEL2 example (extensions.ael) - equivalent to the traditional syntax
context from-ipcomms {
    12025551000 => {
        NoOp(Inbound call);
        Answer();
        if (${BUSINESS_HOURS} = "true") {
            Queue(sales,,,,300);
        } else {
            Voicemail(600@default,u);
        }
        Hangup();
    }
}

Recommendation: Stick with the traditional extensions.conf syntax for production systems. It has the widest community support, the most documentation, and all Asterisk tutorials and examples use it. AEL2 is less commonly used and can be harder to debug.

9. Debugging Your Dialplan

Even experienced Asterisk administrators make dialplan mistakes. These tools and techniques will help you quickly identify and fix issues.

CLI Debugging Commands

# Connect to the Asterisk CLI
sudo asterisk -rvvv

# Set verbose level to see dialplan execution
core set verbose 5

# View the entire loaded dialplan
dialplan show

# View a specific context
dialplan show from-ipcomms

# View a specific extension in a context
dialplan show 12025551000@from-ipcomms

# Reload the dialplan after editing extensions.conf
dialplan reload

Using NoOp() for Logging

Sprinkle NoOp() statements throughout your dialplan to trace call flow in the CLI:

[from-ipcomms]
exten => _X.,1,NoOp(=== INBOUND CALL ===)
 same => n,NoOp(DID: ${EXTEN})
 same => n,NoOp(CallerID: ${CALLERID(num)} / ${CALLERID(name)})
 same => n,NoOp(Channel: ${CHANNEL})
 same => n,Answer()
 same => n,NoOp(Call answered, routing to extension)
 same => n,Dial(PJSIP/100,30)
 same => n,NoOp(Dial result: ${DIALSTATUS})
 same => n,Hangup()

With verbose mode on, the CLI output will show each step as the call progresses:

-- Executing [12025551000@from-ipcomms:1] NoOp("PJSIP/...", "=== INBOUND CALL ===")
-- Executing [12025551000@from-ipcomms:2] NoOp("PJSIP/...", "DID: 12025551000")
-- Executing [12025551000@from-ipcomms:3] NoOp("PJSIP/...", "CallerID: 15551234567 / John Smith")
...

Common Mistakes and Fixes

MistakeSymptomFix
Forgetting Answer()Audio plays but caller hears nothing, or call dropsAdd Answer() before Playback() or Queue(). Dial() does not require it.
Wrong context in pjsip.confInbound calls get "not found" or go to wrong destinationVerify the context = line in your PJSIP endpoint matches your dialplan context name
Overlapping patternsCalls route to unexpected extensionsAsterisk matches the most specific pattern first. Use dialplan show to check ordering
Missing same => indentationSyntax errors on reloadEnsure same => lines have a leading space and follow an exten => line
Using exten = instead of exten =>Extension overwrites instead of adding priorityAlways use => (arrow) not = (equals) for extension definitions
Forgetting to reloadChanges do not take effectRun dialplan reload from the CLI after every edit to extensions.conf

Pro Tip: Use core set verbose 5 during testing, but reduce it in production to avoid excessive logging. Verbose output shows every application executed for every call, which can fill up logs quickly on a busy system.

Ready to Route Calls with IPComms?

Put your new dialplan skills to work with IPComms SIP trunking. Get reliable call routing, competitive per-minute rates, and free DID numbers to test your setup.

Related Articles