Extension reference
A dialplan. metadata.name is the number it answers for, which is why
*78 is an ordinary extension and a feature code needs no special case.
configVersion: lyno/v1
kind: Extension
metadata:
name: "200"
tenant: acme
spec:
description: Support follow-me
steps: [...]
| Key | Type | Required | Notes |
|---|---|---|---|
description | string | no | Label shown in the editor and in logs. |
steps | list | yes | Ordered steps, run top to bottom. |
Paths inside resolve against the configuration root, like every other document — see Documents and paths.
steps[]
Common keys on every step:
| Key | Type | Required | Notes |
|---|---|---|---|
id | string | no | Label used in logs. Defaults to the step type. |
type | string | yes | One of the twelve types below. |
when | object | no | Time condition gating the step. |
Type-specific keys:
| Type | Key | Required | Default | Notes |
|---|---|---|---|---|
play | file | yes | — | 8 kHz mono 16-bit PCM WAV. Answers the call if needed. |
dial | target | yes | — | See below. |
dial | timeout | no | 30s | Ring time; for followme, the per-stage fallback. |
dial | moh | no | ringback | Music-on-hold class heard instead of ringback. |
moh | moh | no | default | Class name. Runs until the caller hangs up. |
hangup | — | — | — | No fields. |
goto | extension | yes | — | A dialplan extension of this tenant. Checked for loops at startup. |
ivr | menu | yes | — | A key of the tenant's ivr_menus. |
queue | queue | yes | — | A key of the tenant's queues. |
queue | timeout | no | the queue's maxWait | Can only shorten it, never lengthen it. |
queue | moh | no | the queue's moh | Class name. |
voicemail | mailbox | no | this plan's own extension | A key of the tenant's mailboxes. |
voicemail | greeting | no | the mailbox's, then the shipped prompts | A WAV path. |
voicemail_check | mailbox | no | the authenticated caller's own box | Required in effect for a call from a trunk. |
record | action | yes | — | start, stop or toggle. |
record | confirm | no | — | A WAV played after the change. |
dnd | action | yes | — | on, off or toggle. |
dnd | confirm | no | — | A WAV played after the change. |
forward | action | yes | — | set or clear. |
forward | mode | yes | — | always or noanswer. busy is refused at startup. |
forward | collect | for set | false | Must be true when action: set. |
forward | prompt | no | — | Asks for the destination. |
forward | confirm | no | — | Played once the change has been made. |
The extension: field is the number, not the filename — every *.yaml in
the directory is read and keyed by what it declares. That is what lets a plan
answer on *78, which is how feature codes are
built.
target
target:
kind: endpoint | ring_group | followme | external
kind | Key | Required | Notes |
|---|---|---|---|
endpoint | endpoint | yes | A target reference. |
ring_group | group | yes | Name of a ring group in this tenant. |
external | number | yes | Number placed through the outbound routes. |
followme | stages | yes | At least one stage. |
stages[]
stages:
- targets: ["101"]
timeout: 15s
- targets: ["100", "external:0612345678"]
timeout: 25s
| Key | Type | Required | Notes |
|---|---|---|---|
targets | list | yes | At least one target reference. Rung in parallel. |
timeout | duration | no | Falls back to the step's timeout, then 30s. |
Stages run in order; the first answer wins and the rest are cancelled. A target that resolves to nothing at call time — an extension with no registered device — is skipped.
when
when:
days: [mon, tue, wed, thu, fri]
hours: "09:00-17:30"
dates: ["2026-12-25", "2026-12-30..2027-01-01"]
timezone: Europe/Amsterdam
not: false
| Key | Type | Format | Notes |
|---|---|---|---|
days | list | mon tue wed thu fri sat sun | Case-insensitive. |
hours | string | "HH:MM-HH:MM" | Start inclusive, end exclusive. Wraps midnight when start > end. |
dates | list | "YYYY-MM-DD" or "YYYY-MM-DD..YYYY-MM-DD" | Ranges are inclusive on both ends. |
timezone | string | IANA name | Overrides the tenant timezone for this condition. |
not | bool | Inverts the combined result. |
All configured parts must match. An omitted part is not a constraint. See
Time conditions for the evaluation details
and the traps around not.
A complete example
configVersion: lyno/v1
kind: Extension
metadata:
name: "200"
tenant: acme
spec:
description: Support follow-me
steps:
- id: greeting
type: play
file: ../sounds/welcome.wav
when:
days: [mon, tue, wed, thu, fri]
hours: "09:00-17:30"
- id: follow-me
type: dial
moh: default
target:
kind: followme
stages:
- targets: ["101"]
timeout: 15s
- targets: ["100", "external:0612345678"]
timeout: 25s
- id: nobody-home
type: play
file: ../sounds/closed.wav
- id: done
type: hangup
Validation
Every step is built and every time condition compiled at startup, so runtime never encounters bad configuration. Rejected:
- an unknown step
type; playwithoutfile;dialwithouttarget; an invalid targetkind; an unknown ring group; an emptyexternalnumber;followmewith no stages, or a stage with no targets;gotowithoutextension; agotoorivrjump to an unknown plan; a jump that forms a loop;ivrwithoutmenu, or naming a menu the tenant does not define;queuewithoutqueue, or naming a queue the tenant does not define;voicemailorvoicemail_checknaming a mailbox the tenant does not define;recordwithout a validaction;dndwithout a validaction;forwardwithout a validactionormode,forwardwithmode: busy, andforwardwithaction: setbutcollectunset;- a music-on-hold class no level defines;
- any invalid day, hour range, date, date range or timezone in a
whenblock.
Sound files are validated and cached in memory at startup as well — a play
step's file, a prompt and a confirm, and a menu's prompt files. Two are
not checked at startup: a voicemail step's greeting and a queue's
announce. A bad file there surfaces as a warning during a call.