Products · Developer guides
Context Stores
Keep state in a flow without a server: session context (sc) for one session, recurring context (rc) for a subscriber's next visits.
A USSD session is a short run of screens, and each answer arrives on its own. To show the right next screen, a flow has to remember things: what the subscriber has answered, what it worked out from those answers, and what it knew about them from their last visit.
Many flows can do all of this with no server of their own: a school fees payment, a savings group check-in, a market levy or a short survey. Context stores are how. They let a flow keep its own named values, during a session and from one session to the next, without an exchange or a dynamic page. Call your server only when the data lives in your own system, such as an account balance.
A flow already has each answer as a page value, $(page.ID.value). Context stores add what page values can't do: they hold values you work out and name yourself, and they can outlast the session.
Session Context (sc)
Session context is the flow's scratch pad for one session. Use it for values you work out along the way and need on later pages: a fee total, the option the subscriber picked, or a flag that decides whether to skip a page.
{
"session_context": {
"mutations": {
"fee_total": "$(page.select_term.value * page.student_count.value)"
}
}
}A later page reads it as $(sc.fee_total), for example to confirm the amount before the payment.
Session context is gone when the subscriber hangs up or the session times out. It has no fixed number of keys. Text saved as-is can be up to 100 characters.
Recurring Context (rc)
Recurring context is the subscriber's saved state in this flow. It's kept from one session to the next, so a returning subscriber doesn't answer the same questions again. Use it for what the flow should remember about them: their name, their meter number, the student they last paid fees for, or how many times they've checked in.
{
"recurring_context": {
"mutations": {
"student_name": "$(strings.title(page.enter_student_name.value))"
}
}
}On their next visit, $(rc.student_name) is already set, and the page that asks for it can be skipped.
- Each subscriber has their own recurring context in each flow. Other subscribers and other flows can't see it.
- Each time the subscriber starts a session in this flow, it's kept for another 90 days. After 90 days away, it's gone.
- It holds up to 16 keys per subscriber. Text can be up to 100 characters, also after
$(...)is filled in. - A key keeps the type it was first saved with. See Value Types in Recurring Context.
Which One to Use
Use sc unless a value needs to be there the next time the subscriber dials. Keys for both are 1 to 33 characters: letters, digits and underscores, not starting with a digit.
Saving Values
Values are saved by mutations in a page's on_done:
{
"on_done": {
"action": "next",
"session_context": {
"mutations": {
"chosen_term": "$(page.select_term.label)",
"fee_total": "$(page.select_term.value * page.student_count.value)"
}
},
"recurring_context": {
"mutations": {
"has_paid_before": true,
"student_name": "$(strings.title(page.enter_student_name.value))"
}
}
}
}A mutation value is one of:
- a plain value:
true,42,3.5,"Form 2"ornull - a
$(...), whose result is saved:"$(page.enter_amount.value)"
Mutations run before the flow moves on, so the next page can read them. A display page's mutations run when it is shown. To save values before the first screen, put them on a side_effect page. The full order is in Flow Definition.
The subscriber's phone number
If a saved value is the subscriber's own phone number, Monime saves a masked copy. The exception is a number taken straight from a get_subscriber_msisdn page: "$(page.ID.value)", where ID is that page. A key saved that way keeps the full number, and so does a key copied straight from it.
Reading Values
Read saved values anywhere $(...) works, for example in a label:
{
"id": "welcome_back",
"component": {
"type": "display",
"label": "Welcome back. Your last payment was for $(rc.student_name)."
}
}A key that was never saved, or was cleared, gives empty text in a label and null inside an expression. A misspelt key behaves the same way, so test both paths. Inside $(...), every saved value reads back as text; see Expressions.
Value Types in Recurring Context
The first save of an rc key sets its type:
| Value saved | Type |
|---|---|
true or false | true/false |
42 | Whole number |
3.5 | Decimal number |
"Form 2" | Text |
"$(...)" | Text |
null | Clears the key |
Later saves are converted to that type when they can be. "5" saved to a whole-number key is stored as 5, and "true" saved to a true/false key is stored as true. A value that can't be converted, such as "Form 2" into a whole-number key, stops the flow with an error.
To keep a counter as a number, save a number first, then update it with an expression:
{ "recurring_context": { "mutations": { "visit_count": 0 } } }{ "recurring_context": { "mutations": { "visit_count": "$(rc.visit_count + 1)" } } }Clearing Values
Save null to clear a key:
{
"on_done": {
"action": "next",
"recurring_context": {
"mutations": { "student_name": null }
}
}
}A cleared rc key no longer counts toward the 16-key limit.
Example: Returning Subscribers
A check-in flow for a Makeni savings group. It asks for a name only on the first visit, then counts visits.
{
"version": "1.0",
"pages": [
{
"id": "collect_display_name",
"component": {
"type": "prompt",
"data_type": "text",
"label": "Welcome to Makeni Savings Group.\nEnter your name",
"min_length": 2,
"max_length": 40
},
"options": {
"skip": "$(!strings.is_blank(rc.display_name))"
},
"on_done": {
"action": "next",
"recurring_context": {
"mutations": {
"display_name": "$(strings.title(page.collect_display_name.value))",
"visit_count": 0
}
}
}
},
{
"id": "count_visit",
"component": { "type": "side_effect" },
"on_done": {
"action": "next",
"recurring_context": {
"mutations": { "visit_count": "$((rc.visit_count ?? 0) + 1)" }
}
}
},
{
"id": "checkin_done",
"component": {
"type": "display",
"label": "Thanks $(strings.split_index(rc.display_name, ' ', 0)). This is check-in number $(rc.visit_count)."
}
}
]
}First visit
rc.display_name is blank, so collect_display_name asks for a name. It saves the name and sets visit_count to 0. count_visit adds 1, and checkin_done shows "check-in number 1".
Later visits
The name is saved, so collect_display_name is skipped. count_visit adds 1 each time.
After 90 days away
The subscriber hasn't started a session in this flow for more than 90 days, so the values are gone. The subscriber is asked for their name again, and the count starts over.
Good Practice
- Keep values small. Text is limited to 100 characters. Keep larger records on your server and fetch them with an exchange.
- Don't save secrets. Context stores are not a secure store. Never save PINs, passwords or keys.
- Plan for 16
rckeys. If you need more, keep the data on your server. - Expect
rcto expire. Write flows that still work when a returning subscriber's values are gone.
For naming keys, see Authoring Conventions.