Permissions
The three permissions a plugin can declare, and what they grant.
There are three.
"permissions": ["storage", "reminders", "selection"]Most plugins declare [] and need nothing.
storage
Section titled “storage”Gives you wm.storage: a small string-to-string map that belongs to your plugin
alone.
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 WM Keyboard 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.
reminders
Section titled “reminders”Gives you wm.reminders: ask the app to show a line of text in a notification
at a time you set.
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 WM Keyboard 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
Section titled “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.
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
transformreturns. Only what you write towm.storagestays, and storage has no way off the device. - The user sees your action names before they install, and can switch each one off in WM Keyboard Advanced Selection actions.
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
Section titled “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 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. |
Getting text without reading it
Section titled “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. 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
Section titled “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 covers how that is enforced.
