# Permissions

> The three permissions a plugin can declare, and what they grant.

URL: https://wmkeyboard.pages.dev/plugins/permissions/
Section: Plugins

There are three.

```json title="plugin.json (excerpt)"
"permissions": ["storage", "reminders", "selection"]
```

Most plugins declare `[]` and need nothing.

## `storage`

Gives you `wm.storage`: a small string-to-string map that belongs to your plugin
alone.

```python
wm.storage.set("items", json.dumps(items))
items = json.loads(wm.storage.get("items", "[]"))
```

- Local to the device. It cannot leave, because the interpreter has no network
  API for it to leave through.
- Local to your plugin. No other plugin can read it.
- Deleted when the user uninstalls you, and clearable by them any time from
  Tools / Plugins / Manage plugins / your plugin. An
  update to your plugin leaves it alone, so a saved list survives a new version.
- 128 keys, 64 characters per key, 8 KB per value, 64 KB in total.

`set` raises `ValueError` when you are over quota. Catch it and say something.
Do not lose the user's data quietly.

## `reminders`

Gives you `wm.reminders`: ask the app to show a line of text in a notification
at a time you set.

```python
due = wm.timestamp(2026, 12, 24, hour=9)
wm.reminders.set("item:7", due, "Buy milk")
wm.reminders.cancel("item:7")
```

- The app shows it, not your script. It keeps the id, the time and the text,
  and when the time comes it posts the notification. No plugin code runs in the
  background, and none runs when it fires.
- The title is always your plugin's name. You choose the words under it, never
  the name, so a reminder cannot pretend to come from the system or another app.
- Tapping it opens your plugin's page in
  Tools / Plugins / Manage plugins / your plugin, which
  lists every pending reminder with a button to cancel them all.
- Nothing shows while plugins, or your plugin, are turned off, or while the user
  has the app's notifications off.
- 64 pending, 200 characters of text, up to ten years ahead.

## `selection`

Lets your plugin add buttons to the bar over selected text, listed under
`actions` in the manifest. A manifest with actions must declare it, as one with
this permission must have actions.

```python
def transform(text, action):
    return text.upper()
```

- Your script sees text only when the user selected it and tapped one of your
  buttons. It never sees what they type, the rest of the field, or a password
  field: the bar does not appear on one.
- Each tap runs in a fresh interpreter, discarded when `transform` returns. Only
  what you write to `wm.storage` stays, and storage has no way off the device.
- The user sees your action names before they install, and can switch each one
  off in Advanced / Selection actions.

[Selection actions](https://wmkeyboard.pages.dev/plugins/api-reference/#selection-actions) has the
manifest fields.

Declared permissions are shown to the user *before* they install, on the addon
page and in the file-import confirmation. There is no runtime prompt. With no
way for data to leave the device, a prompt would have nothing to protect, and a
prompt for something harmless mostly teaches people to dismiss prompts without
reading them.

## What you cannot ask for

This is not a list of things that need a permission. It is a list of things with
no API at all:

| | |
|---|---|
| What the user types | No key event API of any kind. |
| The text they are writing in | No API returns the field's contents. A [selection action](#selection) is handed the selection the user tapped it on, and nothing else. |
| The clipboard | No API. |
| The network | No HTTP, no sockets, no URL handling. |
| Files | No paths, no filesystem. |
| Other apps, or the device | No package list, no identifiers, no sensors. |
| Android permissions | A plugin cannot request any; it does not have that reach. |
| Background work | No timers. Execution stops when the panel closes. A reminder is text the app shows, not code that runs. |

Adding `"permissions": ["network"]` to your manifest enables nothing. It makes
the app refuse to install your plugin, because the app will not install
something whose capabilities it cannot explain to the user. That refusal is
deliberate: it is also how an old app version safely declines a plugin built for
a newer one.

## Getting text without reading it

The replacement for the text APIs is the user, and it is less limiting than it
sounds.

**In:** draw a `ui.input`. The user types into it, or taps **Paste** beside it to
drop in whatever they already copied. You are told the contents through
`input_changed`. They can see exactly what they gave you.

**Out:** draw a `ui.output`. The keyboard puts an **Insert** button under it, and
tapping that puts your text where they are writing.

So the flow for "encrypt the message I just wrote" is: select it, copy it, open
your plugin, tap Paste, tap Encode, tap Insert. Six taps instead of two, and in
exchange nothing on the keyboard can read anyone's messages.

**Both at once:** a [selection action](#selection). The user selects the text
and taps your button on the bar, and what you return replaces it. Two taps, and
still only the text they chose.

## Why it is this short

A keyboard sees everything its user types. Third-party code that could read that
text *and* reach the network is a keylogger with extra steps. No consent dialog
makes that untrue, because nobody can audit what a script does with a permission
after they grant it.

Google Play's Device and Network Abuse policy takes the same view of
interpreted code an app loads at run time, and names Python among its examples:
such code "must not allow potential violations of Google Play policies". That is a question about
what is *possible*, not about what is consented to. The only answer that holds
is not to build the capability.

[Security](https://wmkeyboard.pages.dev/plugins/security/) covers how that is enforced.
