> For the complete documentation index, see [llms.txt](https://mango-5m.gitbook.io/mango5m-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mango-5m.gitbook.io/mango5m-docs/free-resources/mango-notify/developer-guide/interactive-prompts.md).

# Interactive Prompts

Notification cards the player answers with a keybind — accept/decline flows without ever taking NUI focus.

A prompt is a notification with actions attached. It looks like every other Mango Notify card, but it carries keybind buttons and a countdown, and it calls you back with whatever the player chose.

```lua
exports.mango_notify:prompt({
    title       = 'Trade Request',
    description = 'John Doe wants to trade with you',
    icon        = 'handshake',
    duration    = 15000,
    actions = {
        { key = 'E', label = 'Accept',  style = 'success', value = 'accept'  },
        { key = 'X', label = 'Decline', style = 'danger',  value = 'decline' },
    },
}, function(result)
    -- result = the chosen action's value, or nil if it timed out
end)
```

{% hint style="success" %}
**Prompts never take NUI focus.** There is no mouse cursor and no input capture — the keys are read client-side, so the player can keep driving, running and shooting while a prompt is on screen.
{% endhint %}

## The callback

Your callback fires **exactly once**, always. It receives the chosen action's `value`, or `nil` when the prompt ended without an answer:

| Outcome                                   | Callback receives     |
| ----------------------------------------- | --------------------- |
| Player pressed one of the action keys     | that action's `value` |
| The answer window ran out                 | `nil`                 |
| Cancelled with `cancelPrompt` / `Clear()` | `nil`                 |
| Player died (with `CancelOnDeath`)        | `nil`                 |
| `mango_notify` was stopped or restarted   | `nil`                 |
| The data failed validation                | `nil`                 |

That last row matters: a prompt that is rejected for bad input still calls you back, so your script never sits waiting on a prompt that was never shown. The reason is printed to the client console.

`prompt` returns the notification id (or `nil` if it was rejected), which you can hold on to for `cancelPrompt`.

When the player answers with a key, the chip they pressed flashes and the other actions fade back before the card leaves, so an answer never looks like a timeout. Your callback fires immediately — only the card's exit waits. Tune or disable it with `PressFlash` in [Prompts](/mango5m-docs/free-resources/mango-notify/configuration/prompts.md).

## Prompt fields

`title` and `actions` are required. Everything else is optional, and every field from [Notification Fields](/mango5m-docs/free-resources/mango-notify/developer-guide/notification-fields.md) that makes sense on a prompt works here too — `subtitle`, `badge`, `badgeColor`, `image`, `metadata`, `position`, `theme`, `accent`, `width`, `sound`.

| Field         | Type   | Default                       | Notes                                                       |
| ------------- | ------ | ----------------------------- | ----------------------------------------------------------- |
| `title`       | string | —                             | **Required.** A prompt with no title is rejected.           |
| `actions`     | table  | —                             | **Required.** One or more actions, see below.               |
| `description` | string | `nil`                         | The question body.                                          |
| `icon`        | string | `fa-solid fa-circle-question` | A bare name like `handshake` resolves to Font Awesome.      |
| `duration`    | number | `Config.Prompt.Duration`      | The answer window in ms. **Must be above 0.**               |
| `id`          | string | auto                          | Pass your own to make the prompt cancellable by a known id. |

{% hint style="warning" %}
**A prompt cannot be persistent.** `duration = 0` means "never expires" everywhere else in Mango Notify, but a prompt that never resolves is a callback that never fires. `duration = 0` (or any value below it) is rejected with a console warning.
{% endhint %}

### Actions

| Field   | Type   | Default        | Notes                                                             |
| ------- | ------ | -------------- | ----------------------------------------------------------------- |
| `key`   | string | —              | **Required.** The key that triggers it, e.g. `'E'`.               |
| `label` | string | the key        | The text shown next to the key chip.                              |
| `value` | any    | `label` or key | What your callback receives. Keep these stable, not the labels.   |
| `style` | string | accent colour  | `success`, `danger`, `warning`, or `info` — colours the key chip. |
| `color` | string | from `style`   | An explicit key-chip colour. Overrides `style`.                   |

`style` is the one you usually want — it keeps every prompt on the server consistent, and a server owner can retune all four colours from `config.lua`. Reach for `color` when an action needs a colour of its own:

```lua
actions = {
    { key = 'E', label = 'Claim',  color = '#FFD700', value = 'claim'  },   -- gold, one-off
    { key = 'X', label = 'Leave',  style = 'danger',  value = 'leave'  },   -- themed
}
```

Any CSS colour works (`'#FFD700'`, `'rgb(255, 215, 0)'`, `'gold'`). An action with neither falls back to the notification's accent colour.

Duplicate keys inside one prompt, actions with no key, and keys that are not configured are each skipped with a console warning. If nothing usable is left, the whole prompt is rejected.

## Awaiting the result

Inside a coroutine, `promptAwait` returns the value directly instead of taking a callback:

```lua
CreateThread(function()
    local result = exports.mango_notify:promptAwait({
        title       = 'Sell to this dealer?',
        description = '12 × Gold Chain for 4,800$',
        icon        = 'sack-dollar',
        actions = {
            { key = 'E', label = 'Sell',  style = 'success', value = 'sell'   },
            { key = 'X', label = 'Cancel', style = 'danger', value = 'cancel' },
        },
    })

    if result == 'sell' then
        -- ...
    end
end)
```

`promptAwait` yields, so it must be called from inside a `CreateThread` or another coroutine — never from the top level of a script or from an event handler that cannot yield.

## From the server

`promptServer` does the whole round trip for you: it shows the prompt on that player's screen and calls your server-side callback with the answer. Calling scripts do not need their own client file or a reply event.

```lua
exports.mango_notify:promptServer(targetId, data, function(result)
    -- runs on the server, result = the value or nil
end)
```

The target must be one connected player — `-1` is rejected, because a prompt is a question with one answer. It returns a token string when the prompt was sent, or `false` when it was not.

{% hint style="info" %}
**The answer is validated server-side.** Mango Notify remembers which values it offered and refuses anything else, so a modified client cannot feed arbitrary data into your callback. If the player disconnects or never answers, your callback still fires with `nil` shortly after the window closes.
{% endhint %}

## Stacking and key ownership

Only one prompt listens to the keys at a time — otherwise pressing `E` would answer three questions at once.

* The **newest** prompt is the active one and holds the keys.
* Older prompts stay on screen with their keys greyed out, and their own countdowns still running.
* When the active prompt resolves, the keys pass to the next one down the stack.

`Config.Prompt.MaxActive` caps how many can be open at once. A new prompt past that cap resolves the oldest one with `nil` to make room — the player is answering the newest question, not the one from thirty seconds ago.

## Cancelling

```lua
exports.mango_notify:cancelPrompt(id)   -- resolve one prompt with nil, returns true if it was open
exports.mango_notify:cancelPrompts()    -- resolve every open prompt with nil
exports.mango_notify:IsPromptActive(id) -- is this prompt open? omit id to ask "is any prompt open?"
```

`Clear()` and `Remove(id)` also work on prompts — anything that takes a prompt card off the screen resolves its callback with `nil`.

## Examples

### Trade request

```lua
RegisterNetEvent('myscript:tradeRequest', function(fromName)
    exports.mango_notify:prompt({
        title       = 'Trade Request',
        description = ('%s wants to trade with you'):format(fromName),
        icon        = 'handshake',
        duration    = 15000,
        actions = {
            { key = 'E', label = 'Accept',  style = 'success', value = 'accept'  },
            { key = 'X', label = 'Decline', style = 'danger',  value = 'decline' },
        },
    }, function(result)
        if result == 'accept' then
            TriggerServerEvent('myscript:acceptTrade')
        elseif result == 'decline' then
            TriggerServerEvent('myscript:declineTrade')
        else
            -- nil: they never answered, tell the other player it lapsed
            TriggerServerEvent('myscript:tradeTimedOut')
        end
    end)
end)
```

### Gang invite

```lua
RegisterNetEvent('gangs:invite', function(gang, inviter)
    CreateThread(function()
        local result = exports.mango_notify:promptAwait({
            title       = 'Gang Invite',
            subtitle    = gang.label,
            description = ('%s is inviting you to the gang'):format(inviter),
            icon        = 'users',
            accent      = gang.colour,
            duration    = 20000,
            actions = {
                { key = 'E', label = 'Join',   style = 'success', value = 'join'   },
                { key = 'X', label = 'Reject', style = 'danger',  value = 'reject' },
            },
        })

        if result == 'join' then
            TriggerServerEvent('gangs:acceptInvite', gang.id)
            exports.mango_notify:Success('Welcome', ('You joined %s.'):format(gang.label))
        else
            TriggerServerEvent('gangs:declineInvite', gang.id)
        end
    end)
end)
```

### EMS callout — from the server

```lua
-- server side: offer the call to the nearest on-duty medic
local function offerCallout(medicId, call)
    exports.mango_notify:promptServer(medicId, {
        title       = 'EMS Callout',
        badge       = 'Code 3',
        badgeColor  = '#FF4B4B',
        description = call.description,
        icon        = 'truck-medical',
        duration    = 20000,
        metadata    = {
            { icon = 'fa-solid fa-location-dot', text = call.street },
        },
        actions = {
            { key = 'E', label = 'Respond',  style = 'success', value = 'respond' },
            { key = 'X', label = 'Pass',     style = 'danger',  value = 'pass'    },
            { key = 'G', label = 'En route', style = 'info',    value = 'enroute' },
        },
    }, function(result)
        if result == 'respond' or result == 'enroute' then
            assignCall(medicId, call, result)
        else
            -- 'pass', a timeout, or a disconnect: try the next medic
            offerCallout(nextMedic(call), call)
        end
    end)
end
```

## Keys and rebinding

Out of the box, `E` and `X` are the accept/decline pair, with `G` and `H` available for third and fourth actions. Because those keys clash with plenty of other scripts, they are registered as **real FiveM key bindings** — every player can rebind them under **Settings → Key Bindings → FiveM**, and the card shows whatever that player actually has bound rather than a hardcoded letter.

Any key that is not one of those slots still works, it just reads the raw control instead and cannot be rebound by the player. See [Prompts](/mango5m-docs/free-resources/mango-notify/configuration/prompts.md) under Configuration for the key tables and the rest of the settings.

{% hint style="info" %}
**Performance:** the key loop only exists while at least one prompt is open, and stops itself the moment the last one resolves. With nothing on screen, prompts cost nothing — see [Performance](/mango5m-docs/free-resources/mango-notify/reference/performance.md).
{% endhint %}
