Treat the Asterisk engine change as a compatibility gate
Sangoma says the supported FreePBX 17 install defaults to Asterisk 22 and that Asterisk 22 does not support the Macro dialplan application. Its migration page warns that restored custom dialplan or AGI scripts using macros will not work until those calls are converted manually to Gosub. FreePBX-generated dialplan has already moved away from Macro, but that does not rewrite site-owned files or external integrations.
Record the source FreePBX and Asterisk versions, target versions, backup hash, restore transaction, installed module inventory and every custom include. A successful restore proves archive processing, not that every custom application exists or every route reached its former destination.
Inventory callers, macro contexts and hidden contracts
Search supported custom include files, Custom Destinations, AGI source and deployment tooling for Macro(, MacroIf, MacroExit, MacroExclusive, macro- contexts and MACRO_ variables. Preserve each caller, argument order, branch, destination and expected continuation before changing syntax. Do not edit FreePBX-generated files, because Apply Config can replace them.
Asterisk 20 documented Macro as entering extension s in macro-name, exposing ARG1 onward and saving the original location in MACRO_EXTEN, MACRO_CONTEXT and MACRO_PRIORITY. A mechanical name change does not recreate those hidden values, MacroExit behavior, offset continuation or exclusivity.
Translate the call target and return path explicitly
Current Asterisk documents Gosub([[context,]extension,]priority(args)) as a jump that saves a return address. Convert one caller at a time to a complete context, extension and priority, pass reviewed arguments in parentheses and end every returning branch with Return(). A fall-through, Goto() away from the subroutine or hangup is a different outcome.
Return(value) removes the last Gosub stack frame and stores the value in GOSUB_RETVAL. Preserve whether the legacy caller expected a variable side effect, an offset, a jump, a hangup or a value. Do not invent a return contract simply because the new subroutine reaches its last priority.
Replace implicit Macro variables deliberately
Gosub arguments remain available as ARG1, ARG2 and so on, but Macro location variables are not a portable substitute for the caller identity in a Gosub frame. Pass the original extension or context as explicit arguments when the business rule genuinely needs them, and document the exact owner.
Use LOCAL(name) for scratch values that must belong only to the current Gosub frame; Asterisk says the value disappears or restores its previous value after Return(). LOCAL_PEEK() can inspect a variable hidden by a specified number of frames, but an empty result can also mean the requested depth does not exist. Ordinary channel variables, inherited variables and local frame variables remain separate scopes.
Preserve FreePBX ownership and supported entry points
Keep the converted routine in an approved custom include or other site-controlled source. Point the existing FreePBX Custom Destination or authorized hook to the stable public entry point, then Apply Config and inspect the loaded dialplan. Do not paste a replacement into module-generated contexts or rename a module-owned destination merely to match the old macro label.
Draw the complete call path from inbound or internal source through the FreePBX destination, custom entry, Gosub frame, returned value or side effect and next supported destination. A subroutine that works from asterisk -rx inspection is not proof that the production route enters it on the intended channel.
Accept the migration with stack and route evidence
On an isolated restored target, load the exact custom revision, confirm no obsolete Macro application is referenced, inspect the public entry and subroutine priorities, and run a bounded matrix covering normal return, each conditional branch, invalid input, hangup and nested calls. Correlate channel, unique and linked identifiers, Gosub entry, arguments, local values, GOSUB_RETVAL, return priority and final destination.
Use StackPop() only when the intended behavior is explicitly to discard the most recent return address; it is not a generic repair for an unbalanced routine. Keep the prior target available for rollback and do not register production trunks or route public calls from an isolated restore. No PBX, restore, dialplan, channel or call was tested for this guide.
[from-custom-entry]
exten => migrate-check,1,Gosub(site-route,s,1(${EXTEN},normal))
same => n,NoOp(Subroutine result: ${GOSUB_RETVAL})
same => n,Goto(from-internal,${EXTEN},1)
[site-route]
exten => s,1,Set(LOCAL(original_extension)=${ARG1})
same => n,Set(LOCAL(mode)=${ARG2})
same => n,Return(ok)Sources & applicability
Primary references for the technical details above. Operational examples and planning checklists are VoIP.info editorial guidance.
- Sangoma: upgrading to FreePBX 17 ↗
- Sangoma: FreePBX 17 platform overview ↗
- Asterisk 20 Macro application reference ↗
- Asterisk Gosub application reference ↗
- Asterisk Gosub subroutine guide ↗
- Asterisk Return application reference ↗
- Asterisk LOCAL function reference ↗
- Asterisk LOCAL_PEEK function reference ↗
- Asterisk StackPop application reference ↗
Examples require adaptation to your topology. No live PBX or hardware testing is claimed.