Skip to main content
One mini app can send another a message, addressed by the other app’s slug. The shell carries it. If the other app is open, the message reaches it without the screen changing. If it is closed, the shell opens it in the foreground and delivers the message once the app is listening. Whatever the receiving app’s handler returns comes back to the sender.
The receiving app handles the action:

How a message is delivered

  1. The shell checks that the target app accepts the action from your app (see Accepting messages). If it doesn’t, the answer is not_accepted and the target is never opened.
  2. The first time your app messages a given app, the shell asks the user (“Let CV talk to Notes?”). Once they allow it, your app can message that app again without asking, whatever the action. Permission is one way: Notes messaging CV is asked separately.
  3. If the target is open, the message is delivered in the background. If it is closed, the shell opens it in the foreground first.
  4. The target’s handler runs and its return value becomes your result.data.
Opening a closed app, or bringing an open one forward with focus, should follow a tap. If the user didn’t just press something, the shell asks again even for a pair they already allowed. A background app can’t open other apps.

Sending

string
required
The target app’s slug.
string
required
What you’re asking for, such as notes.create. Letters, digits, ., _ and -, up to 64 characters.
unknown
Any JSON value, up to 256 KB serialized.
boolean
default:"true"
Open the target if it is closed. With false, a closed target answers not_open.
boolean
default:"false"
Bring the target forward even when it is already open, for example so the user can pick something in it.
boolean
default:"false"
When the target answers, switch back to your app. This only happens if the target was brought forward for this message and is still on screen.
number
default:"30000"
How long to wait for the answer once the message is delivered, up to 300000. Time spent on the permission prompt or opening the target doesn’t count.
sendToApp never rejects. It resolves with one of these:
unknown
What the target’s handler returned.
AppMessageError
Why there is no answer. See Errors.
boolean
The message was delivered, but this shell can’t send an answer back (TV).

Receiving

onAppMessage(action, handler) registers a handler and returns an unsubscribe function. useAppMessage(action, handler) does the same for the lifetime of a component and always calls the latest handler. Pass '*' to handle every action that has no handler of its own. The handler receives:
{ slug: string; name?: string }
The sending app. The shell sets this, so the sender can’t impersonate another app.
string
The requested action.
unknown
The sender’s payload.
boolean
true when your app was opened to receive this message.
Return a JSON value (or a promise of one) to answer. If the handler throws, the sender gets handler_failed. Register handlers early, at startup or in the screen that handles the action. The shell holds messages for your app until the first handler is registered. A message for an action without a handler waits 5 seconds for one, so a screen that loads lazily still gets it, and is then answered no_handler.

Accepting messages

An app only receives the actions it declares. The declaration is part of the app’s record in the catalogue:
string | Record<string, string>
How the permission prompt names the action, as one string or one per language.
string[]
Only these apps (by slug) may send the action. Leave it out to accept any app.
boolean
default:"true"
With false, the action is only delivered while your app is already open.
You can’t set this from your app’s code yet. Ask the cskn team to add the actions your app accepts.

Errors

Surfaces

Check ahead of time with getAppMessagingAvailability(), which resolves { available, replies }.