# Use Dash features (/docs/products/dash-dots/developers/dash-apis)

Your dot can borrow the phone's camera, contacts, torch and more through `this.dash`. Each group's reference lists every option:

| Group                                                                     | For                                                           |
| ------------------------------------------------------------------------- | ------------------------------------------------------------- |
| [`dash.ui`](/docs/products/dash-dots/reference/dash-api/ui)               | Alerts, and date and time pickers                             |
| [`dash.media`](/docs/products/dash-dots/reference/dash-api/media)         | Photos, files and QR codes                                    |
| [`dash.contacts`](/docs/products/dash-dots/reference/dash-api/contacts)   | A number from the person's contacts                           |
| [`dash.account`](/docs/products/dash-dots/reference/dash-api/account)     | Who the person is in Dash                                     |
| [`dash.system`](/docs/products/dash-dots/reference/dash-api/system)       | The torch, the screen, copying, sharing, dialing              |
| [`dash.payments`](/docs/products/dash-dots/reference/dash-api/payments)   | Payments in Dash's own payment sheet                          |
| [`dash.reminders`](/docs/products/dash-dots/reference/dash-api/reminders) | Notifications that bring the person back to one of your pages |
| [`dash.crypto`](/docs/products/dash-dots/reference/dash-api/crypto)       | Hashes and random values                                      |
| [`dash.utils`](/docs/products/dash-dots/reference/dash-api/utils)         | Base64 and hex                                                |

Talking to your server and taking payments have their own pages: [Network and storage](/docs/products/dash-dots/developers/network-and-files) and [Take payments](/docs/products/dash-dots/developers/payments). Some calls need a [permission](/docs/products/dash-dots/developers/security#permissions), and each section below says which.

## Use this.dash in pages

Call `this.dash` in a page's actions and hooks. When the page closes, Dash cancels any of its calls still running. Outside pages, as in `dot.ts`, use the global `dash`.

A call has no timeout unless you set one. Pass `{ timeout }`, in milliseconds, as its last argument:

```ts
const request = this.dash.network.http.request({ url }, { timeout: 10_000 });
request.cancel(); // Or stop it yourself. It rejects with `cancelled`.
```

## Handle errors

A call that fails rejects with a `DashError`, and its `code` says why:

| Code          | What happened                                      | What to do                          |
| ------------- | -------------------------------------------------- | ----------------------------------- |
| `unsupported` | The phone can't do it                              | Hide the button                     |
| `denied`      | The call isn't allowed                             | Say what the feature needs          |
| `cancelled`   | The person backed out, or the page closed          | Usually nothing                     |
| `timeout`     | Your timeout ran out                               | Offer to try again                  |
| `invalid`     | The arguments are wrong                            | Fix the call                        |
| `missing`     | Nothing is at that path in the dot's files         | Treat it as empty                   |
| `failed`      | The call couldn't finish. The phone may be offline | Show the message, and offer a retry |

A call is `denied` when the person said no to a [permission](/docs/products/dash-dots/developers/security#when-the-answer-is-no), when the permission or host isn't in `dot.json`, or when [Dash held the call back](#when-dash-holds-a-call-back).

Check a code with `isDashError`. `errorMessage` turns any error into plain words you can show:

```ts
import { errorMessage, isDashError } from '@monimesl/dot-runtime';

try {
  await this.dash.system.torch(true);
} catch (error) {
  const note = isDashError(error, 'unsupported') ? 'This phone has no torch.' : errorMessage(error);
  this.setState({ note });
}
```

Pass several codes to match any of them: `isDashError(error, 'timeout', 'failed')`. Uncaught errors are covered in [Pages and state](/docs/products/dash-dots/developers/pages-and-state#errors).

### When the person closes a picker

Closing a picker or scanner isn't a mistake, so don't show an error. `unlessCancelled` turns `cancelled` into `undefined`:

```ts
import { unlessCancelled } from '@monimesl/dot-runtime';

const date = await unlessCancelled(this.dash.ui.pickDate({ title: 'Collection day' }));
if (!date) {
  return; // The person closed the calendar.
}
```

## When Dash holds a call back

To keep people in charge of their phone, Dash refuses some calls with `denied`:

- **Too many dialogs.** A dot can open at most 6 dialogs in any 30 seconds. Dialogs are alerts, date and time pickers, photo and file pickers, the scanner, contacts, share, dial and the payment sheet.
- **Dash isn't on screen.** While the person is in another app, a dot can't open dialogs, copy text, dial, share, open a link or ask for a payment. Try again once the page's `onShow` hook runs.

## Newer Dash features

Every call on this page works in any Dash that opens your dot. When a later Dash adds a call, check with `this.dash.supports('contacts.pickPhone')` first, so your dot still works in older Dash. `monime dot dev` warns you when you forget. See [Versions](/docs/products/dash-dots/reference/dash-api/versions).

## Alerts

`dash.ui.alert` shows an alert and gives you the `id` of the button the person tapped. Leave out `actions` for a plain OK.

```ts
const { action } = await this.dash.ui.alert({
  title: 'Cancel this order?',
  actions: [
    { id: 'keep', label: 'Keep order', role: 'cancel' },
    { id: 'cancel', label: 'Cancel order', role: 'destructive' },
  ],
});
```

The roles mark the button that backs out and the one that can't be undone. An empty `actions` list fails with `invalid`. Dash ends the message with a line naming your dot: "From Plasas Kitchen".

## Dates and times

`dash.ui.pickDate` and `dash.ui.pickTime` open the phone's own calendar and clock. Dates look like `2026-10-24`, and times like `19:30`:

```ts
const date = await unlessCancelled(this.dash.ui.pickDate({ title: 'Collection day', min: '2026-10-14' }));
```

`min` and `max` limit the dates the person can pick.

## Photos, files and QR codes

In the simulator, you pick photos and files from your computer, and type a code instead of scanning it.

### Photos

`dash.media.pickPhoto` takes a photo, which needs the `camera` permission, or picks one from the library, which needs `photoLibrary`:

```ts
const photo = await unlessCancelled(this.dash.media.pickPhoto({ source: 'camera' })); // or 'library'
if (photo) {
  this.setState({ photo });
}
```

You get a JPEG of at most 2048 pixels on its longest side, with its location removed.

### Files the person chooses

`dash.media.pickFile` lets the person choose a file of up to 25 MB, like a PDF of their EDSA bill. `types` narrows the choice. It needs the `files` permission.

```ts
const file = await unlessCancelled(this.dash.media.pickFile({ types: ['application/pdf'] }));
if (file) {
  await this.dash.storage.file.save(file, `bills/${file.name}`);
}
```

Photos and files both arrive as a [file object](/docs/products/dash-dots/developers/network-and-files#file-objects).

### QR codes

`dash.media.scanCode` opens Dash's scanner and gives you the text it read. It needs the `camera` permission.

```ts
const code = await unlessCancelled(this.dash.media.scanCode());
if (code) {
  this.setState({ meter: code.text });
}
```

## Contacts

Say the person is buying airtime for a cousin in Kenema. `dash.contacts.pickPhone` lets them choose the number from their contacts, and your dot sees only that one contact. It needs the `contacts` permission.

```ts
const contact = await unlessCancelled(this.dash.contacts.pickPhone());
if (contact) {
  this.setState({ recipient: contact.name, phone: contact.phone });
}
```

The number comes as it's saved, whether `+232 76 123456` or `076 123456`.

## Sign the person in

Say your dot keeps a ferry pass for each regular. `dash.account.signIn` tells it who the person is in Dash, so they don't have to type their name and number. It needs the `account` permission, and Dash asks the person first.

```ts
// At the top of controller.ts:
// import { isDashError } from '@monimesl/dot-runtime';

// One of the page's actions
async signIn() {
  try {
    const person = await this.dash.account.signIn({ mode: 'simple' });
    this.setState({
      name: person.fullName,
      phone: person.alias,
      passId: person.accountIdToken,
    });
  } catch (error) {
    if (!isDashError(error, 'denied')) {
      throw error;
    }
    // They said no. Let them type their name and number.
    this.setState({ typeDetails: true });
  }
},
```

| Field            | What it is                                                                                                                       |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `fullName`       | Their full name on their Dash account                                                                                            |
| `alias`          | The phone number they use Dash with, such as `+232 76 123456`, or their email address if they have no number                     |
| `accountIdToken` | An ID for them in your dot. It's the same on any phone, and different in every other dot, so dots can't match up their customers |

To get an ID for this sign-in only, pass a fresh `nonce`, up to 128 characters. The same nonce always gives the same ID, so leave it out to know the person by one ID every time.

> **The ID isn't proof:** Your dot sends `accountIdToken` to your server itself, so your server can't tell it from one someone made up. Use it to recognise the person and fill in their details, not to let them into their records or their money.

`signIn` fails with `failed` when no one is signed in to Dash or Dash can't reach Monime.

## Reminders

A reminder is a notification Dash shows at a time you set, such as the morning school fees are due. When the person taps it, Dash opens your dot at the page you chose. Nothing happens until they tap.

```ts
await this.dash.reminders.set({
  key: 'fees-term-3',
  title: 'School fees are due today',
  message: 'Tap to pay for Fatmata.',
  page: '/pay?student=fatmata',
  schedule: { start: '2026-11-27', time: '08:00' },
});
```

- `key` is your own name for the reminder. Setting a reminder with the same key changes it, and `cancel(key)` removes it.
- `page` is `/` and the page's ID, with any launch options as a query. Leave it out to open your first page.
- `schedule` takes a start date and a time on the phone's clock, 09:00 if you leave it out. Add `repeat` to repeat by day, week or month, and `end` to stop on a date or after so many times. See [`ReminderSchedule`](/docs/products/dash-dots/reference/dash-api/reminders#reminderschedule).
- A dot can have 12 reminders still to come at a time.
- People can delete reminders in Dash. Call `list()` to see which are left before you show one as set.

It needs the `reminders` permission. Dash asks the first time you set a reminder. `cancel` and `list` only need the permission declared.

## Phone features

`dash.system` reaches the rest of the phone:

| Call                                   | What it does                                       |
| -------------------------------------- | -------------------------------------------------- |
| `vibrate({ style: 'success' })`        | A short buzz                                       |
| `copyText('AC-2041')`                  | Copies a payment reference or other text           |
| `share({ text, url, file })`           | Opens the share sheet                              |
| `dial('*144#')`                        | Opens the dialer with a number or USSD code ready  |
| `openUrl(url)`                         | Opens an `https` page in the browser               |
| `torch(true)`                          | Turns the torch on, or off with `false`            |
| `brightness('max')`, `keepAwake(true)` | Turns the screen all the way up, and keeps it on   |
| `info()`                               | The platform, language, time zone and Dash version |
| `delay(3000)`                          | Waits, in milliseconds. Dots have no `setTimeout`  |

For a USSD code, Dash asks the person first, and `dial` rejects with `cancelled` if they say no. Codes that set up call forwarding, such as `**21*…#`, always fail with `denied`.

For a ticket the gate scans off the screen, turn the screen up in the page's `onShow` hook and back to `'normal'` in `onHide`.

A `delay` ends when the page closes, and so does any loop that waits on it.

## Hashes, random values and base64

You'll mostly want `this.dash.crypto.randomUUID()`, which makes a good key for [sending an order once](/docs/products/dash-dots/developers/network-and-files#send-an-order-once). `dash.crypto` also has `sha256`, `sha512`, `hmac` and `randomBytes`. Use `hmac` only with a key your server issued for this person's session, [never your own API key](/docs/products/dash-dots/developers/security#keep-secrets-off-the-phone).

`dash.utils.base64` and `dash.utils.hex` turn text into base64 or hex and back, as in `dash.utils.base64.encode('Kenema')`. They're synchronous, and you can call them in a mutation too. `decode` throws on text that isn't base64 or hex. Catch it when the text comes from outside.

## Search as the person types

A search field sends `change` on every keystroke:

```xml title="pages/search/view.dml"
<Input type="search" placeholder="Find a shop" onChange="find" />
```

Two [helpers](/docs/products/dash-dots/reference/dash-api/helpers) spare your server: `debounce` waits for the person to pause, and `latest` cancels the last search when a new one starts:

```ts title="pages/search/controller.ts"
import { debounce, latest, unlessCancelled } from '@monimesl/dot-runtime';

type Shop = { id: string; name: string };
const typingPause = debounce(400);
const search = latest();

export default definePage({
  initialState: { results: [] as Shop[] },
  actions: {
    async find(_state, event: ValueEvent) {
      if (!(await typingPause(this.dash))) {
        return; // Still typing.
      }
      const url = `${env.API_URL}/shops?q=${encodeURIComponent(event.detail.value)}`;
      const answer = await unlessCancelled(search(this.dash.network.http.request<Shop[]>({ url, as: 'json' })));
      // answer is undefined when a newer search took over.
      if (answer) {
        this.setState({ results: answer.body });
      }
    },
  },
});
```
