Are "call groups" ways to limit who can call who?


I am getting confused about the call groups concept in FreePBX.

(I know pickup groups are something to let you dial a code and take a call that is ringing in a different extension, part of the same pickup group as your extension. That is not what my question is about.)

(I know ring groups are a way for you to dial a single number and have FreePBX ring a group of extensions. That is not what my question is about.)

By call groups I mean the thing defined in each Extensions properties, Advanced tab:

The help text is not very helpful, providing no clue as to what it does:

Callgroup(s) that this device is part of, can be one or more alpha/numeric callgroups, e.g. ‘1,3000-3005,sales,sales2’.

What I thought this was:

A way to limit which extensions this extension could dial. So, I was expecting that any extensions with the rs call group could only dial other extensions that listed the same call group.

I wanted to use this to effectively split the office into two groups of extensions that could not dial between them, except for a single extension that would include both groups and be able to dial every one.

I am even convinced that I verified this with tests in one installation, and things worked as planned.


But I’ve set this up in a different installation and still everyone can call everyone. It’s as if the call group field is not taking any effect.

So, do I have my concepts wrong or should I just be troubleshooting why the definition is not taking effect in a particular installation?

Thanks in advance for any help!

Those options are for Pickup Calls (**[ext#]) not to restrict calls between extensions. To accomplish that you will need a custom dialplan. I’d suggest yo look around in the forum, there are a lot of posts about it.

@spioli thanks for answering.

If that is so, can you please explain what is the difference between the two fields, Call groups and Pickup groups?

About searching the forums, I’ve done that extensively, the problem is that sometimes knowledge of a feature is required in order to know what to search to gain knowledge of that feature :sweat_smile:

If you can’t point me to a specific thread, which search terms do you suggest I use? I am not being lazy, I honestly have been getting lost in searches that don’t take me anywhere useful.

If an extension is ringing and another extension dials the Asterisk General Call Pickup code (default is *8), the call will be picked up if the extension dialing *8 is in a Pickup Group and the ringing extension is in the corresponding Call Group. The purpose is to prevent accidental pickups in large systems. It is not a restriction or security feature – by default, anyone can dial ** followed by an extension number and pick up that ringing extension.

You could do this with the commercial Class of Service module, or with a few lines of custom dialplan.

However, FreePBX is not a multi-tenant system and it would be very difficult to provide good isolation between multiple organizations using the same PBX. Several competing products have good multi-tenant support.

Also, IMO technical solutions to HR problems are generally a bad idea. If two people want to communicate and you prevent them from doing it via the PBX, they may just use their cell phones instead. You are now worse off, because you no longer have logs or recordings to monitor the activity.

Thanks for the explanation! This is not what I would call a multi-tenant situation. I guess there are so many different situations out there, naturally people will come with requirements like these. Often it’s when you have a single owner that commissions the PBX and then multiple groups using it, such as guests or renters. But in my case, it’s a single organization, but quite simply the way they work, one person is responsible for bridging everything between two quite separate teams.

It’s also a small set up, total is less than 15 extensions, and my requirements are not for actual “security”. I don’t mind if some clever tech guy finds ways to circumvent the separation (like transfers or call parking), I just need to keep basic users, doing basic usage, within the rules.

Well then, back to the technical discussion - the Class of Service seems like overkill for such a simple requirement. I would like to avoid diving into the bowels of the software (custom dialplan), but will do so if necessary.

I guess we’re talking about something like this: Restrict incoming and internal calls to certain extensions - #4 by oxon88

Question: to make the custom dialplan generic, is there a way to reference the call group that the extension is configured for, inside that custom dialplan, so that string can be used for the condition?

I believe so, but have not used it myself.

I’ve been trying to get the custom dialplan going… this is in extensions_custom.conf

exten => s,1,Noop(Entering user defined context macro-dialout-one-predial-hook in extensions_custom.conf)
exten => s,n,NoOp(Exten: ${EXTEN})
exten => s,n,Set(named=${PJSIP_ENDPOINT(${EXTEN}, named_call_group)})
exten => s,n,NoOp(Your named group: ${named})
exten => s,n,MacroExit

That isn’t working because EXTEN is just giving me an ‘s’.

But even when trying to use a constant value I couldn’t get it to work. I’ve tried several values for the first argument of PJSIP_ENDPOINT, and none of them worked. I couldn’t find any docs on how to specify the argument, and there is practically no example online of this function being used…

I tried




(this final value, I copied it from the logs, it appears when that endpoint is in a call).

Can anyone please shed some light on

  1. Which variables do I have available to me in the hook, to know who is calling who?

  2. What exactly is the PJSIP_ENDPOINT function expecting in the 1st argument?

Thanks in advance

Answering my own questions… partially

This works:


My problem was the space after the comma :exclamation: I guess using proper programming languages gives me bad habits…

About the variables: I can get the destination number inside the macro with ${CALLERID(dnid)}, and the calling number with ${CALLERID(num)}

I hope to post a full solution soon…

Answer to my original question

No, “call groups” do not limit who can call who, they are meant to work together with pickup groups to control which extensions can grab calls ringing in nearby extensions.

Ok, but let’s make them do it anyway! :rocket:

Go to Admin / Config Edit, select extensions_custom.conf and paste this bit of custom dialplan:

;this is called from /etc/asterisk/extensions_additional.conf, check other available vars there
exten => s,1,Noop(Entering user defined context macro-dialout-one-predial-hook in extensions_custom.conf)
exten => s,n,Set(grp_src=${PJSIP_ENDPOINT(${CALLERID(num)},named_call_group)})
exten => s,n,Set(grp_dest=${PJSIP_ENDPOINT(${CALLERID(dnid)},named_call_group)})
exten => s,n,Set(grp_src=${STRREPLACE(grp_src," ",)})
exten => s,n,Set(grp_dest=${STRREPLACE(grp_dest," ",)})
exten => s,n,Verbose(CUT: ${CUT(grp_src,\,,1)} ${CUT(grp_src,\,,2)} ${CUT(grp_src,\,,3)})
exten => s,n,GotoIf($[${ISNULL(${CUT(grp_src,\,,1)})}]?blocked)
exten => s,n,Set(modifiedString=${STRREPLACE(grp_dest,${CUT(grp_src,\,,1)},)})
exten => s,n,GotoIf($[${LEN(${grp_dest})} != ${LEN(${modifiedString})}]?substring_found:substring_not_found_1)

exten => s,n(substring_found),NoOp(src group FOUND in dest group list: CALL ALLOWED)
exten => s,n,MacroExit

exten => s,n(substring_not_found_1),NoOp(src group 1 <${CUT(grp_src,\,,1)}> NOT found in dest group list)
exten => s,n,GotoIf($[${ISNULL(${CUT(grp_src,\,,2)})}]?blocked)
exten => s,n,Set(modifiedString=${STRREPLACE(grp_dest,${CUT(grp_src,\,,2)},)})
exten => s,n,GotoIf($[${LEN(${grp_dest})} != ${LEN(${modifiedString})}]?substring_found:substring_not_found_2)
exten => s,n,MacroExit

exten => s,n(substring_not_found_2),NoOp(src group 2 <${CUT(grp_src,\,,2)}> NOT found in dest group list)
exten => s,n,GotoIf($[${ISNULL(${CUT(grp_src,\,,3)})}]?blocked)
exten => s,n,Set(modifiedString=${STRREPLACE(grp_dest,${CUT(grp_src,\,,3)},)})
exten => s,n,GotoIf($[${LEN(${grp_dest})} != ${LEN(${modifiedString})}]?substring_found:substring_not_found_3)

exten => s,n(substring_not_found_3),NoOp(src group 3 <${CUT(grp_src,\,,3)}> NOT found in dest group list)
exten => s,n(blocked),NoOp(No more src groups to search for: CALL BLOCKED)
exten => s,n,Playback(silence/1&cannot-complete-as-dialed&check-number-dial-again,noanswer)
exten => s,n,Hangup()

After pasting, press Save and then the red :red_square: "Apply config**.


  • this is not proper security, it can be circumvented by clever users. It’s just a basic first-line of nudging people to call the right extensions…
  • for simplicity, this only checks the first 3 groups listed as Call Groups in the calling Extension settings.
  • you will get funny behaviors if the group names include each other as a substring, so avoid that. The string comparison is simplistic so a group called Department will also match a group called AllDepartments, for example.
  • my skills writing dialplan suck, there are surely many better ways to do this


  • this is a generic solution, suitable for non-technical users. Apart from copy-pasting a bit of code, the rest of your experience is to just set call groups in the UI, and then it should “just work”.
  • Here is a rigorous description of what this does:

    Calls will only go through if one of the first three Call groups in the calling extension matches any of the Call groups in the called extension.

  • The default is to block: when there is no match, or there are no defined groups in one of the two extensions, the call gets blocked. So you’ll need to go in every extension and set call groups, or use bulk update.

Technical Explainers

  • I used the existing extension field Call Groups but you might prefer to use something else, like a custom field that only exists in the dialplan, or some other field. You have to think how this affects the call pickup functions, if you use them. My feature has nothing to do with the basic FreePBX feature - I am basically redefining it to my convenience.
  • the dialplan functions have no easy way to search for a substring in a string, so I used a trick with STRREPLACE (replacing the searched for string with an empty string) followed by a LEN comparison (if there was a replace, the string is now shorter than the original), so I know there was a match. Has some quirky edge cases, but works for the most part.
  • I didn’t test group names with spaces or punctuation or funny characters. Just go for simple group names separated by commas. Mine are ao, conv, rs
  • when testing this or troubleshooting, I recommend running a console with tail -f /var/log/asterisk/full | grep predial to see all the messages.

Thanks to everybody that helped :tada: . I am open to suggestions for improvement, obviously.

1 Like

I just noticed that some external calls are also getting routed through this hook, so I advise some testing of external calls to ensure the above code doesn’t block them.

I ended up adding a condition to ignore these checks whenever the caller ID is longer than 3 digits. Very rudimentary.

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.