Getting Started With Asterisk Dialplan

Overview

Asterisk dialplan is basically blocks of blocks of lists of things to do.

The first type of block is the Context. A Context is a container of extensions.

The second type of block is an extension. An extension will have a list of actions.

Here's a basic example that will be discussed more below:

[default]
exten => 1000,1,NoOp(A call has arrived at extension 1000 in default)
exten => 1000,2,NoOp(This particular extension is for a simple prompt playback)
exten => 1000,3,NoOp(We should probably answer the call now)
exten => 1000,4,Answer()
exten => 1000,5,Playback(weasels-eaten-phonesys)
exten => 1000,6,Hangup()
exten => 1001,1,NoOp(A call has arrived at extension 1001 in default)
exten => 1001,2,NoOp(We will send this to a phone registered as 1001)
exten => 1001,3,Dial(SIP/1001)
exten => 1001,4,Hangup()

In the example above, we have a single context called 'default', with two extensions in it, '1000' and '1001'. A context starts with the name of the context surround by square brackets [] on it's own line, everything that follows, up to the start of another context, is within that context.

Contexts can typically include two types of items, extensions, or includes of other contexts. An extension line starts with the word 'exten' followed by '=>', then the extension being configured, the priority of the line, the dialplan application command and any parameters, i.e.

exten => <extension>,<priority>,<dialplan application>(<parameters>)

When Asterisk is handling a call it will look for the extension in the defined context and start at priority 1, after it has finished with priority 1, it will increment, 2, 3 etc. until the call ends, it gets to a priority that doesn't exist, or, it is told to go somewhere else.

Looking at what is configured here, for extension 1000, we have the following at each priority:

  1. Execute the 'NoOp' dialplan application with the text between brackets as the parameter. NoOp does nothing, but, you can see it in the Asterisk CLI when the dialplan is being executed, which can be a useful aid in following dialplan flow, it also can help to document your dialplan.
  2. The same as 1, but with different text.
  3. The same as 1, again with different text.
  4. Execute the 'Answer' dialplan application, this instructs Asterisk to answer the call, prior to this point, even though Asterisk is processing the dialplan, the caller would still hear ringing and their call would not be active. In this example, there are open and close brackets following Answer, this isn't strictly required, but, it's a good habit. Answer can take an optional parameter, a number of milliseconds to wait before continuing after answering the call, in certain circumstances, this could be useful to use, e.g. in the event that we have a problem with an audio path being slow to come up, a prompt played immediately after Answer could have it's start not heard by the caller.
  5. Execute the 'Playback' dialplan application, this tells Asterisk to play the specified file/s, multiple files can be included separated/joined by an ampersand (&). Playback will be default answer the call if it is not already answered, though includes options to either skip playback if not answered, or try to playback without answering the call. Playback doesn't give any option to skip past a prompt, rewind, fast forward, etc. it will just play the entire specified audio with no interruptions. The file/s specified must not specify the file extension, Asterisk will select the best format if multiple formats exists, but, it must exist in the current language path if not specified as a full path and filename.
  6. Execute the 'Hangup' dialplan application, this tells Asterisk to hang up the call, it takes an optional cause code as a parameter, otherwise it will default to the current channel status. More on cause codes later.

Looking at what is configured here for extension 1001, we have the following at each priority:

  1. Execute the 'NoOp' dialplan application with the text between brackets as the parameter. NoOp does nothing, but, you can see it in the Asterisk CLI when the dialplan is being executed, which can be a useful aid in following dialplan flow, it also can help to document your dialplan.
  2. The same as 1, but with different text.
  3. Execute the 'Dial' dialplan application, this instructs Asterisk to place an outbound call to be bridged with the current inbound call. This is the basis of how Asterisk connects multiple call legs together typically. Dial is the most complex dialplan application, though here, in it's simplest form, the one required parameter is a destination in the format of Technology/Resource, in this case, the technology is 'SIP' and the resource is '1001'.
  4. Execute the 'Hangup' dialplan application, this tells Asterisk to hang up the call, it takes an optional cause code as a parameter, otherwise it will default to the current channel status. More on cause codes later.

Modifying the dialplan - insert a line

In this first change, we want to change the behaviour of extension 1000 in the example above. Currently, it answers the call, plays the prompt 'weasels-eaten-phonesys' once, then hangs up on the caller. We want to change it so that the prompt is played forever, or at least, for as long the the caller is on the call.

The first thing we do in this case, is insert a line between priority 5 and 6 for extension 1000, on this line, we will be telling Asterisk to go back to playing the prompt again after it finishes playing the prompt, giving us the following dialplan for extension 1000:

exten => 1000,1,NoOp(A call has arrived at extension 1000 in default)
exten => 1000,2,NoOp(This particular extension is for a simple prompt playback)
exten => 1000,3,NoOp(We should probably answer the call now)
exten => 1000,4,Answer()
exten => 1000,5,Playback(weasels-eaten-phonesys)
exten => 1000,6,Goto(5)
exten => 1000,7,Hangup()

A couple of things you can see here, first, the line we inserted has been given priority '6', which is needed for it to run after priority '5', but, we already had a priority '6', as this is now after the new priority '6', we have had to increase it to priority '7'. We can't have multiple lines with the same priority. Secondly, we have used a new dialplan application, in this case 'Goto', Goto can take up to 3 parameters separated by commas, context, extension and priority, this is documented as Goto([[context,]extension,]priority), note, the square brackets indicate optional parameters. In our case we have passed a single parameter, as the only non-optional parameter is priority, our use means we have said to go to priority 5 in the current extension in the current context.

You may have noticed, if we are always going back to the prompt playback at priority 5 when we get to priority 6, we will never get to priority 7 to hangup, this is true, in this particular case, the hangup is not required, but, it is a best practice to end a flow with a hangup to prevent unexpected behaviour in the future if changes are made later, e.g. if the goto was removed.

Now, with this change, our requirement of just playing the prompt in a loop have been satisfied, but, it was a bit of a pain to have to modify the priority on our hangup, though not as much of a pain as if our dialplan went on for hundreds of lines more, manual renumbering all those lines would be horrendous.

Thankfully, Asterisk has a solution, at least since version 1.2, the 'n' priority. The 'n' priority is a short cut in dialplan configuration that means next priority. When Asterisk reads in the configured dialplan, if it finds a priority of 'n', it will add 1 to the previous priority it read for that extension in the context being processed and store that value in it's internal representation of the dialplan. When Asterisk is running, it keeps the dialplan in memory, and in that copy, there is no 'n' priority, it still uses sequentially numbered priorities, the 'n' is just a configuration shortcut to make your life easier.

So, lets see what our dialplan for extension 1000 would look like with the 'n' priority:

exten => 1000,1,NoOp(A call has arrived at extension 1000 in default)
exten => 1000,n,NoOp(This particular extension is for a simple prompt playback)
exten => 1000,n,NoOp(We should probably answer the call now)
exten => 1000,n,Answer()
exten => 1000,n,Playback(weasels-eaten-phonesys)
exten => 1000,n,Goto(???)
exten => 1000,n,Hangup()

So, now it's nice and simple, no needing to keep track of the priorities and ensuring they are always sequential, but, we have a problem... How do we tell goto where to go now, without knowing what priority Asterisk will come up with when it parses the configuration? Again, Asterisk has us covered, labels... Labels allow us to label a priority and use that defined label to refer to the priority, rather than by number, this has the advantage that the label will work even if the priority changes value and the label can be more meaningful when reading the dialplan, making it easier to understand and easier to follow, so, lets add a label of 'playprompt' and see how the dialplan looks now:

exten => 1000,1,NoOp(A call has arrived at extension 1000 in default)
exten => 1000,n,NoOp(This particular extension is for a simple prompt playback)
exten => 1000,n,NoOp(We should probably answer the call now)
exten => 1000,n,Answer()
exten => 1000,n(playprompt),Playback(weasels-eaten-phonesys)
exten => 1000,n,Goto(playprompt)
exten => 1000,n,Hangup()

OK, there we go, our dialplan is now functional again, but it's easier to see where our goto will jump to and we don't have to mess around specifying the priorities manually, except for priority 1.

This is all great, but, having to type 'exten => 1000,' on every line could get a little tiring, and if we wanted to use this extension as the basis of another extension, or, if we wanted to change the extension to say '2000', we have to change every single line, that would not be much fun. Enter 'same =>'. same allows you to simplify your dialplan writing by only having to define the extension once at the top of the block, now, we'll see how that looks with our sample extension 1000.

exten => 1000,1,NoOp(A call has arrived at extension 1000 in default)
same => n,NoOp(This particular extension is for a simple prompt playback)
same => n,NoOp(We should probably answer the call now)
same => n,Answer()
same => n(playprompt),Playback(weasels-eaten-phonesys)
same => n,Goto(playprompt)
same => n,Hangup()

So, lots of changes to insert a single line in this case, but, now we are in a great position of we need to make changes in future.

Modifying the dialplan - limiting a loop

In the previous section we modified extension 1000 to play our prompt forever, but, what if we don't actually want it to play forever? What if we only want it to play 3 times? Well, we could just remove the loop and put three separate Playback lines, which in some situation may be the simplest and best approach, but, that won't teach much here, so, what could we do that would leave the loop in place, but, limit the number of times it's run to 3? Lets have a look:

exten => 1000,1,NoOp(A call has arrived at extension 1000 in default)
same => n,NoOp(This particular extension is for a simple prompt playback)
same => n,NoOp(We should probably answer the call now)
same => n,Answer()
same => n,Set(count=0)
same => n(playprompt),Playback(weasels-eaten-phonesys)
same => n,Set(count=$[${count}+1])
same => n,GotoIf($[${count}<3]?playprompt)
same => n,Hangup()

Here, we see two new dialplan applications and an introduction to variables.

On the first new line, in bold, the 'Set' dialplan application is called with a parameter of 'count=0', what this does is initialise a new channel variable with the name 'count' and the value '0'. Variables are values you can set that will be remembered for the life of the channel. If you need a variable to also be passed to sub-channels, you can prefix the name with one underscore '_', or if you want it to be inherited by all child channels forever, two underscores '__', but this is beyond what we are looking at right now.

To use a channel variable, you need to prefix the name with a dollar sign ($) and wrap it in curly brackets, e.g. ${variable_name} will be replaced with the value of the variable called 'variable_name' when executed in the dialplan. It is better to not use upper case variable names as all built in variables and functions are in upper case.

In the event that we want to perform arithmetic on a variable, or otherwise evaluate a condition, we need to tell asterisk to evaluate by using a dollar sign ($) prefix and wrapping the content in square brackets. The contents of the square brackets will be evaluated by Asterisk first before executing the application.

The second new line says the set the value of count to be the current value of count, plus one. The count+1 part is wrapped in square brackets with a dollar prefix to tell Asterisk to evaluate that first, and count is specified as a channel variable, wrapped in curly brackets with a dollar prefix, i.e. ${count}, when evaluated the first time, the count variable would have a value of 0 as initialised, so the evaluation would calculate 0+1, which comes out at 1, and so the count variable would be set to 1.

The last modified line changes our execution of the Goto dialplan application into the GotoIf dialplan application. GotoIf takes takes a condition parameter, typically in square brackets with a dollar prefix for evaluation, followed by a question mark, and a destination if the condition is true, optionally followed by a destination if the condition is false, split by a colon. If a destination for a false condition is not specified, processing just continues to the next priority in the event the condition is not true. In this case, we check to see if our count variable is less than 3.

If we follow through the logic, we would see the following behaviour with our loop after answering the call:

  1. First time in, before the prompt, our count gets set to 0.
  2. We play the prompt.
  3. The count gets incremented by 1 to 1.
  4. We check if count is less than 3, as it is true that it is, we go to the playprompt label.
  5. We play the prompt.
  6. The count gets incremented by 1 to 2.
  7. We check if count is less than 3, as it is true that it is, we go to the playprompt label.
  8. We play the prompt.
  9. The count gets incremented by 1 to 3.
  10. We check if count is less than 3, as this is no longer true, it is equal to 3, we go to the next priority.
  11. We hangup the call

Pattern Matching

So far, we've only looked at a couple of static examples for extensions, but, you don't need to make all extensions static, you can match extensions using patterns and then do something common for anything that matches.

Looking back at our example extension 1001 and bringing it up to date with what we have learned so far, we have the following:

exten => 1001,1,NoOp(A call has arrived at extension 1001 in default)
same => n,NoOp(We will send this to a phone registered as 1001)
same => n,Dial(SIP/1001)
same => n,Hangup()

Now, we want to extend this to allow calls to extension 1001 to 1009 to call through to their respective SIP phones, this is what we get:

exten => _100Z,1,NoOp(A call has arrived at extension ${EXTEN} in default)
same => n,NoOp(We will send this to a phone registered as ${EXTEN})
same => n,Dial(SIP/${EXTEN})
same => n,Hangup()

We see three new items here:

  1. The extension has been prefixed by an underscore '_', this tells asterisk to treat th extension as a pattern rather than a static value.
  2. The '1' at the end of the extension has been replaced with 'Z', this one of a number of special characters in pattern matching that equates to digits 1-9, others are:
    • X - matches digits 0-9
    • N - matches digits 2-9
    • Z - matches digits 1-9 as already mentioned
    • . - matches any digit or character
    Additionally, ranges can be specified wrapped in square brackets, e.g.:
    • [123] - digits 1, 2 & 3
    • [1-4] - digits 1-4
    • [1-9] - digits 1-9, equivalent to 'Z'
    • [a-m0-5] - lower case letters a to m or digits 0-5
    • [ABC123] - upper case letters A, B or C, or digits 1, 2 or 3
  3. We are using a variable that we didn't set - in this case, one of the built in variables, note it is all upper case. The EXTEN variable contains the active extension, useful in this case because we are doing pattern matching and need to know which actual extension was dialled to send the call to the right phone.

Dial Status & Voicemail

If you can't answer the phone, because you aren't at your phone, or because you're on a call, it is typical that a caller will be given an opportunity to leave a message. Let's add voicemail to our extension 1001-1009:

exten => _100Z,1,NoOp(A call has arrived at extension ${EXTEN} in default)
same => n,NoOp(We will send this to a phone registered as ${EXTEN})
same => n,Dial(SIP/${EXTEN},60)
same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy)
same => n,GotoIf($["${DIALSTATUS}" = "NOANSWER"]?noanswer)
same => n,NoOp(${EXTEN} got dialstatus ${DIALSTATUS} - hangupcause ${HANGUPCAUSE} - treating as NOANSWER)
same => n(noanswer),NoOp(${EXTEN} did not answer or had an error)
same => n,Voicemail(${EXTEN},u))
same => n,Hangup(19)
same => n(busy),NoOp(${EXTEN} is busy)
same => n,Voicemail(${EXTEN},b))
same => n,Hangup(17)

For this to actually work, you also need to add users in your voicemail.conf, but, as we are only looking at dialplan here, we'll look at that another time.

Here, the first change is that we add an additional parameter on our 'Dial', the first additional parameter is timeout, i.e. how long we'll wait for the call to be answered. As we want the call to go to voicemail if it's not answered, we need to set this value to something reasonable, in this case, 60 refers to seconds, so, 1 minute. The default dial timeout is 136 years, which might be a little long for a caller to wait.

Again we are using GotoIf, but, this time evaluating a new variable 'DIALSTATUS' for being equal to 'BUSY' or 'NOANSWER'. As voicemail supports having separate outgoing messages for if you are busy on the phone or didn't answer, we need to differentiate between the two. Now, there are actually a number of additional possible values for 'DIALSTATUS', though in this case we want them to get the unavailable outgoing message from voicemail, so we allow the call the 'fall through' if it doesn't match the two main values we are interested in, though using NoOp to be able to see what value we did get and also capturing another system variable 'HANGUPCAUSE'.

The main part comes next, executing the Voicemail application itself. Voicemail takes two parameters, the mailbox, which in this case we pass the extension, and some character options, here 'u' means to play the unavailable message and 'b' means to play the busy message. That's all there is to sending a call to voicemail.

To keep things tidy, we Hangup after, Voicemail could come back with the caller after they have left a message, so we need to ensure we handle the call properly, i.e. end it. You may have noticed here that we are passing a numeric parameter to Hangup, this is the Hangup Cause Code, what we would see in the 'HANGUPCAUSE' variable, in this case, 19 means 'User Alerted No Answer' and 17 means 'Busy'. A useful resource for these cause codes is available here.