Build interactive USSD applications using our simple, declarative domain-specific language (DSL). Define menus, actions, and navigation flows without writing code.
Quick Start
Here's a complete example of a simple banking USSD application:
Override or disable authentication for specific actions:
Code
# Override with different authaction "PublicEndpoint" { type = "http_call" url = "/public/status" auth = "none" # Disable auth for this action}action "AdminEndpoint" { type = "http_call" url = "/admin/config" auth { type = "basic" username = "admin" password = "{{secrets.admin_password}}" }}
Global Navigation Actions
New in v2.1 — Configure consistent Back and Home keys across all menus.
The global_actions block enables navigation shortcuts that work from any menu:
Code
options { http_base_url = "https://api.mybank.com/v1" global_actions { back_key = "0" # Press 0 to go back anywhere home_key = "99" # Press 99 to return to start menu }}
Property
Required
Description
back_key
No
Key that returns to the previous menu in navigation history
home_key
No
Key that returns to the application's start menu
Global actions are checked before menu-specific options, ensuring consistent behavior across all menus. See Navigation & Terminal Menus for detailed examples.
Comments
Add comments using # or //:
Code
# This is a comment// This is also a commentapp "MyApp" { shortcode = "*123#" # Inline comment version = "1.0.0" start = "Main"}
Action Chains and Actions
Action execution now happens through action chains attached to menu options or transitions. An action chain is an ordered list of inline actions, with optional error routing and transactional rollback.
Code
menu "TransferMenu" { text = "Enter amount to transfer:" option "*" { label = "Any amount" goto = "ConfirmTransfer" actions = [ { name = "normalize-amount" type = "string" op = "trim" inputs = ["{{session.input}}"] target = "amount" }, { name = "amount-range" type = "validate" source = "amount" min = 1 max = 10000 } ] error_target = "InvalidAmount" transactional = true }}
For full details on action chains, inline action fields, and error handling, see:
Updated: January 2025 - Added env.*, options.*, and secrets.* variables
Use double curly braces {{...}} to insert dynamic values:
Variable
Description
Example
{{session.msisdn}}
User's phone number
+231886123456
{{session.id}}
Current session ID
sess_abc123
{{session.shortcode}}
USSD shortcode dialed
*123#
{{session.input}}
User's last input
1
{{session.networkid}}
Mobile network identifier
orange-lr
{{session.data.*}}
Stored session data
{{session.data.amount}}
{{response.*}}
API response fields
{{response.balance}}
{{response.nested.path}}
Nested API response data
{{response.user.name}}
{{config.*}}
Application config
{{config.api_key}}
{{env.*}}
Environment variables from env block
{{env.API_KEY}}
{{options.http_base_url}}
Global HTTP base URL
https://api.example.com
{{secrets.*}}
Tenant-scoped secrets (requires secrets_engine)
{{secrets.api_key}}
{{org.id}}
Current tenant/organization ID
org_abc123
{{app.name}}
Application name
MyBankingApp
Best Practices
1. Always Provide a Back Option
Code
menu "SubMenu" { text = "Select an option:" option "1" { label = "Do something" goto = "Action" } option "0" { label = "Back" # Always include a way back goto = "MainMenu" }}
USSD screens have character limits (typically 160-182 characters). Keep messages short:
Code
# Good - concisemenu "Balance" { text = "Balance: $500.00" option "0" { label = "Back" goto = "MainMenu" }}# Avoid - too longmenu "Balance" { text = "Your current account balance as of today is five hundred dollars and zero cents. Please press 0 to return to the main menu or press 1 to view transaction history."}
5. Version Your Applications
Use semantic versioning to track changes:
Code
app "MyApp" { version = "1.0.0" # Initial release # version = "1.1.0" # New feature added # version = "2.0.0" # Breaking changes}
Navigation & Terminal Menus
New in v2.1 — Enhanced navigation controls with Back/Home actions and terminal menus.
Back Navigation
Menus can reference their parent menus to enable Back navigation. Unlike action-only cycles, menu-to-menu references are safe because they require explicit user input to traverse.
Configure consistent Back and Home keys across all menus using the global_actions block in your app options:
Code
app "BankingApp" { shortcode = "*123#" version = "2.1.0" start = "MainMenu" options { global_actions { back_key = "0" # Press 0 to go back anywhere home_key = "99" # Press 99 to return to start menu } }}menu "MainMenu" { text = "Main Menu" option "1" { label = "Services" goto = "ServicesMenu" } option "2" { label = "Settings" goto = "SettingsMenu" }}menu "ServicesMenu" { text = "Services" option "1" { label = "Balance" goto = "BalanceMenu" }}menu "BalanceMenu" { text = "Your balance: $100.00" # No explicit Back option needed - user presses 0 globally}
With global_actions configured:
Back key (0): Returns to the previous menu in navigation history
Home key (99): Returns to the application's start menu
Global actions are checked before menu-specific options, so they work consistently across all menus. When the user is already at the start menu, the Back key has no effect (nowhere to go back to).
Terminal Menus
Terminal menus end the USSD session after displaying content. There are two ways to create terminal menus:
1. Empty Options (Implicit Terminal)
A menu with no options automatically ends the session:
Code
menu "ThankYou" { text = "Thank you for using our service. Goodbye!" # No options = session ends after displaying this message}
2. Explicit Terminal Flag
Use terminal = true for menus that should end the session even if they have options defined for other purposes:
Code
menu "TransferComplete" { text = "Transfer successful!\nAmount: ${{session.data.amount}}\nRecipient: {{session.data.recipient}}" terminal = true # Session ends after displaying this message}
Property
Type
Default
Description
terminal
boolean
false
When true, the menu ends the session regardless of defined options
Terminal Search Results
Search result menus commonly use the terminal pattern to display results and end the session:
Code
menu "SearchInput" { text = "Enter phone number to search:" option "*" { label = "Search" goto = "PerformSearch" }}action "PerformSearch" { type = "http_call" url = "/api/users/search?phone={{session.input}}" on_success = "SearchResult" on_error = "SearchError"}menu "SearchResult" { text = "Search Result:\nName: {{response.name}}\nStatus: {{response.status}}" # No options = terminal, session ends after showing result}menu "SearchError" { text = "Search failed. Please try again later." terminal = true # Explicit terminal for error state}
Navigation Best Practices
Use global_actions for consistency: Configure Back/Home keys once instead of repeating in every menu.
Don't mix global and explicit Back options: If using global_actions, omit explicit Back options to avoid confusion or conflicts.
Terminal menus for confirmations: Use terminal menus for success/error messages where no further action is needed.
Test deep navigation: Ensure Back navigation works correctly through multiple menu levels.
Consider Home for deep menus: For apps with many levels, provide a Home key so users can quickly restart.
Open-Ended User Input
Tip: Use wildcard options to capture free-form user input like names, amounts, or phone numbers.
Wildcard Options
The wildcard selector (*) matches any input that doesn't match a specific option: