Automations

Use presets or your own rules to change the keyboard as you type, without replacing your saved setup.

State rules apply temporary settings while their conditions match. Combine orientation, window width, appearance, language, layout, input traits, time or declared plugin state. Choose whether all conditions or any condition must match. Missing context never counts as a match for a negative condition.

Event rules run when the keyboard opens, a language or input trait changes, a plugin emits an event, or you tap a shortcut. Commands run once per event. Setting overrides last until the keyboard closes, the rule changes, or you clear session overrides.

Your saved setup comes first

Rules overlay your current saved settings. Drag them to change priority; later rules win. Turning a rule off removes its contribution. Changes you make to the saved setup remain available when an override ends. A profile action is temporary and does not select or rewrite your saved profile.

The compact preset uses 28-point key rows and 80% of your saved width, within the existing size limits. With split enabled, smaller keys leave more space between the halves and keep them near the edges. One-handed mode keeps its own alignment. Percentages always use the saved value and never compound.

Preview uses sample context that you choose. It shows matching conditions, invalid references and expected settings without running commands. The app preview is separate from the live keyboard. Add Automations to the top bar to see active overrides and run shortcut rules there.

When an event occurs

When an event occurs

  • keyboard.open
  • keyboard.close
  • keyboard.languageChanged
  • keyboard.layoutChanged
  • keyboard.profileChanged
  • keyboard.planeChanged
  • input.changed
  • time.minute
  • user.run

Actions

  • panel.close
  • plane.letters
  • plane.numbers
  • plane.symbols
  • overrides.clear
  • panel.emoji
  • panel.calculator
  • panel.conversion
  • panel.layouts
  • panel.profiles

Evaluation happens while the keyboard is active. Time rules do not run in the background, and closing events are best effort. Clink cannot identify the host app or change iOS settings. Rules keep the Membership, Full Access and user-tap requirements of the features they use. Importing grants no permissions.

The plugin contract

The optional automation object in a .clinkplugin file declares events, state, settings, commands, presets and shortcut slots. Each contribution has a stable ID, an exact schema version and localized titles. The host creates plugin/<plugin-id>/<kind>/<id> names. A plugin cannot claim a core or another plugin’s namespace.

Published state, event payloads and command arguments accept booleans, bounded numbers and declared choices. Settings can also use bounded text. State and settings use keys in the existing plugin state dictionary. Temporary setting values are restored before saving. Presets are ordinary disabled rules and require explicit selection.

Plugins ›

Hooks

HookWhen it runs
on_automation(command, args, state)on_automation(command, args, state) runs an approved local command in the existing sandbox. It can update the space-bar caption, publish declared state or emit a declared event. Text insertion, clipboard access, persistent setting changes and external requests are not permitted effects of this hook.
automation_emit(name, payload)Emit a declared event with its typed payload. The host adds the plugin namespace and schema versions; script input cannot supply a user gesture or execution ancestry.
automation_publish(name, value)Publish one declared state value for conditions. Values disappear when the keyboard session ends or the plugin becomes unavailable.
automation_run(slot)Call from a native plugin control’s action handler. The user maps the declared shortcut slot to a rule in Automations. A plugin cannot silently choose the rule or trigger a tap-only command from a timer.

Actions

  • space_text
  • automation_emit
  • automation_publish

A declared event and a safe command

This manifest fragment belongs in the automation field of a normal plugin file. Its script publishes whether the keyboard is ready, emits an event and refreshes a caption. In the editor, select that event, require the ready state and choose the caption command. No plugin is installed or enabled by reading this example.

{
  "schemaVersion": 1,
  "events": [
    {
      "id": "ready",
      "title": {
        "en": "Keyboard ready"
      },
      "version": 1,
      "payload": [
        {
          "id": "ready",
          "key": "ready",
          "title": {
            "en": "Ready"
          },
          "version": 1,
          "type": "bool",
          "choices": [],
          "minimum": 0,
          "maximum": 100,
          "defaultValue": null
        }
      ]
    }
  ],
  "state": [
    {
      "id": "ready",
      "key": "ready",
      "title": {
        "en": "Ready"
      },
      "version": 1,
      "type": "bool",
      "choices": [],
      "minimum": 0,
      "maximum": 100,
      "defaultValue": null
    }
  ],
  "settings": [
    {
      "id": "show_caption",
      "key": "show_caption",
      "title": {
        "en": "Show caption"
      },
      "version": 1,
      "type": "bool",
      "choices": [],
      "minimum": 0,
      "maximum": 100,
      "defaultValue": true
    }
  ],
  "commands": [
    {
      "id": "caption",
      "title": {
        "en": "Refresh caption"
      },
      "version": 1,
      "arguments": [],
      "behavior": "localUI",
      "requiresUserGesture": false,
      "requiresFullAccess": false
    }
  ],
  "presets": [],
  "shortcuts": [
    "run"
  ]
}
def initial():
    return {"ready": False, "show_caption": True}

def on_open(state):
    state["ready"] = True
    automation_publish("ready", True)
    automation_emit("ready", {"ready": True})
    return state

def on_automation(command, args, state):
    if command == "caption":
        space_text("Clink" if state["show_caption"] else None)
    return state

def bar_items(state):
    return [bar_button("run", "Run automation", icon="bolt.circle")]

def on_action(action, value, state):
    if action == "run":
        automation_run("run")
    return state

Share rules deliberately

Import and export JSON from Automations. Imported rules are off, get new identities and retain their referenced themes, layouts and plugins. Missing references must be restored before the rule can run. Rules are global, with optional profile scope; they are separate from profile exports and iCloud sync.

{
  "schemaVersion": 1,
  "shortcutBindings": {},
  "rules": [
    {
      "id": "landscape-split",
      "name": "Landscape split",
      "enabled": false,
      "schemaVersion": 1,
      "kind": "state",
      "trigger": "keyboard.open",
      "triggerVersion": 1,
      "cooldownSeconds": 1,
      "conditions": {
        "mode": "all",
        "items": [
          {
            "id": "landscape",
            "field": "interface.orientation",
            "comparison": "equals",
            "value": "landscape",
            "upper": null,
            "hysteresis": 0,
            "version": 1
          }
        ]
      },
      "actions": [
        {
          "id": "split",
          "kind": "set",
          "target": "layout.split",
          "value": true,
          "version": 1
        }
      ]
    }
  ]
}

Schema and limits

Version 1 supports 128 rules, 24 conditions and 32 actions per rule, with imports up to 1,048,576 bytes. The setting registry exposes 268 targets and documents 39 exclusions. Invalid configuration batches are rejected together; commands stop at the first failure without claiming rollback.

Removed, disabled or incompatible plugin contributions make dependent rules unavailable. Crashes and timeouts disable the failed contribution for the session. Commands have a 20,000-step budget and a short time limit. Bounded queues, cooldowns, chain limits and state stabilization prevent repeated events from running indefinitely.

Widgets

Clink ships three home screen widgets and one Live Activity: a profile switcher, your typing statistics, quick dictation, and the dictation session itself.

The widgets read the shared settings file directly, so they keep working with the app closed.

App Intents

Two intents ship today, and neither opens the app. Switch Keyboard Profile rewrites the shared settings from the widget, and a keyboard already on screen picks the change up at once. End Dictation reaches the microphone session from the Live Activity, launching the app in the background if it has to.

Switching to a profile you made yourself needs Membership, and that check runs inside the widget too. The profiles Clink ships are free.

Clink defines no Shortcuts phrases of its own, so there is nothing to say out loud.

Opening files

Clink registers its file types with iOS, so Files, AirDrop, Messages and any share sheet offer to open one in Clink.

Download on the App Store