Skip to content
WM KeyboardWM Keyboard
Accessibility

Permissions

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

There are three.

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

Most plugins declare [] and need nothing.

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.

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.

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 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 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.

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 typesNo key event API of any kind.
The text they are writing inNo API returns the field’s contents. A selection action is handed the selection the user tapped it on, and nothing else.
The clipboardNo API.
The networkNo HTTP, no sockets, no URL handling.
FilesNo paths, no filesystem.
Other apps, or the deviceNo package list, no identifiers, no sensors.
Android permissionsA plugin cannot request any; it does not have that reach.
Background workNo timers. Execution stops when the panel closes. A reminder is text the app shows, not code that runs.

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.

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.