Skip to main content
Scripts are methods that execute in response to user input (gestures). NVDA’s script system provides a powerful way to bind keyboard shortcuts, braille display buttons, and other input to custom functionality.

What are Scripts?

A script is a Python method that:
  • Has a name starting with script_
  • Takes a gesture parameter
  • Is bound to one or more input gestures
  • Can be assigned to a category for organization
Basic script structure
from scriptHandler import script
import ui

class MyObject(ScriptableObject):
    
    @script(
        description="Says hello to the user",
        gesture="kb:NVDA+h"
    )
    def script_sayHello(self, gesture):
        ui.message("Hello!")

What are Gestures?

A gesture is user input that can trigger a script:
  • Keyboard: kb:NVDA+t, kb:control+shift+a
  • Braille display: br(freedomScientific):leftWizWheelUp
  • Touch screen: ts:2finger_flickRight
  • Braille keyboard: bk:space+dot1+dot3
Gesture identifiers have the format:
<source>[(device)]:<key1>+<key2>+...

The Script Decorator

The modern way to define scripts uses the @script decorator from scriptHandler:
Script decorator syntax
from scriptHandler import script
import inputCore

class MyPlugin(GlobalPlugin):
    
    @script(
        # Description shown in input gestures dialog
        description="Reports the current date and time",
        
        # Category for organization
        category=inputCore.SCRCAT_MISC,
        
        # Single gesture
        gesture="kb:NVDA+shift+t",
        
        # Or multiple gestures
        gestures=["kb:NVDA+shift+t", "kb:NVDA+alt+t"],
        
        # Allow propagation to focus ancestors
        canPropagate=False,
        
        # Run even in input help mode
        bypassInputHelp=False,
        
        # Run even in sleep mode
        allowInSleepMode=False,
        
        # Resume say all after execution
        resumeSayAllMode=None,
        
        # Speak when speech mode is "on-demand"
        speakOnDemand=False
    )
    def script_sayDateTime(self, gesture):
        import datetime
        now = datetime.datetime.now()
        ui.message(f"{now.strftime('%I:%M %p, %A %B %d, %Y')}")

Decorator Parameters

description
string
required
Translatable description shown to users in the input gestures dialog
category
string
Category for grouping related scripts. Use constants from inputCore like:
  • SCRCAT_BROWSE - Browse mode
  • SCRCAT_MISC - Miscellaneous
  • SCRCAT_SPEECH - Speech
  • SCRCAT_SYSTEM - System
  • SCRCAT_CONFIG - Configuration
gesture
string
Single gesture identifier to bind
gestures
list
List of gesture identifiers to bind
canPropagate
boolean
default:"false"
Whether script applies to focus ancestor objects
bypassInputHelp
boolean
default:"false"
Whether script runs when input help is active
allowInSleepMode
boolean
default:"false"
Whether script runs in sleep mode
resumeSayAllMode
int
Say all mode to resume after script executes (from sayAll.CURSOR_* constants)
speakOnDemand
boolean
default:"false"
Whether script produces speech in “on-demand” mode

Gesture Binding Methods

Using decorator
from scriptHandler import script

class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    
    @script(
        description="Announce NVDA version",
        gesture="kb:NVDA+shift+v"
    )
    def script_announceVersion(self, gesture):
        import buildVersion
        ui.message(buildVersion.version)

Method 2: __gestures Dictionary

Using __gestures dictionary
class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    
    __gestures = {
        "kb:NVDA+shift+v": "announceVersion",
        "kb:NVDA+shift+t": "announceTime",
    }
    
    def script_announceVersion(self, gesture):
        """Announce NVDA version"""  # Used as description
        import buildVersion
        ui.message(buildVersion.version)
    
    def script_announceTime(self, gesture):
        """Announce current time"""
        import datetime
        ui.message(str(datetime.datetime.now().time()))
With __gestures, the docstring is used as the description. However, this makes it non-translatable unless you manually set the __doc__ attribute. The decorator approach is preferred.

Method 3: Dynamic Binding

Dynamic binding
class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    
    def __init__(self):
        super().__init__()
        # Bind gestures dynamically
        self.bindGesture("kb:NVDA+shift+v", "announceVersion")
    
    def script_announceVersion(self, gesture):
        import buildVersion
        ui.message(buildVersion.version)

Script Resolution Order

When a user presses a key, NVDA searches for a matching script in this order:
1

User Gesture Map

User’s custom key bindings from the input gestures dialog
2

Locale Gesture Map

Locale-specific gesture remappings
3

Braille Display Gestures

Gestures from the active braille display driver
4

Global Plugins

Scripts defined in loaded global plugins
5

App Module

Scripts in the app module for the active application
6

Tree Interceptor

Scripts in the tree interceptor (e.g., browse mode)
7

NVDA Object with Focus

Scripts on the focused NVDA Object
8

Focus Ancestors

Scripts on parent objects (if canPropagate=True)
9

Global Commands

Built-in NVDA commands
The first matching script is executed and the search stops. This allows plugins to override built-in commands by defining them earlier in the chain.

Real-World Examples

Example 1: Simple Global Command

From the NVDA Developer Guide:
Version announcement
# Version announcement plugin for NVDA
# Developer guide example 3

import globalPluginHandler
from scriptHandler import script
import ui
import buildVersion

class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    
    @script(gesture="kb:NVDA+shift+v")
    def script_announceNVDAVersion(self, gesture):
        ui.message(buildVersion.version)

Example 2: Window Information Scripts

From the NVDA Developer Guide:
Window utility scripts
# Window utility scripts for NVDA
# Developer guide example 4

import globalPluginHandler
from scriptHandler import script
import ui
import api

class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    
    @script(
        description="Announces the window class name of the current focus object",
        gesture="kb:NVDA+leftArrow"
    )
    def script_announceWindowClassName(self, gesture):
        focusObj = api.getFocusObject()
        name = focusObj.name
        windowClassName = focusObj.windowClassName
        ui.message(f"class for {name} window: {windowClassName}")
    
    @script(
        description="Announces the window control ID of the current focus object",
        gesture="kb:NVDA+rightArrow"
    )
    def script_announceWindowControlID(self, gesture):
        focusObj = api.getFocusObject()
        name = focusObj.name
        windowControlID = focusObj.windowControlID
        ui.message(f"Control ID for {name} window: {windowControlID}")

Example 3: Object-Specific Script

From the NVDA Developer Guide:
Enhanced edit field
import appModuleHandler
from scriptHandler import script
from NVDAObjects.IAccessible import IAccessible
import controlTypes
import ui

class AppModule(appModuleHandler.AppModule):
    
    def chooseNVDAObjectOverlayClasses(self, obj, clsList):
        if obj.windowClassName == "Edit" and obj.role == controlTypes.Role.EDITABLETEXT:
            clsList.insert(0, EnhancedEditField)

class EnhancedEditField(IAccessible):
    
    @script(gesture="kb:NVDA+l")
    def script_reportLength(self, gesture):
        """Report the number of characters in this edit field"""
        ui.message(f"{len(self.value)}")
This script only works when an edit field is focused because it’s defined on the EnhancedEditField class.

Example 4: Script with Multiple Gestures

Multiple gestures
class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    
    @script(
        description="Report current time",
        gestures=[
            "kb:NVDA+f12",
            "kb:NVDA+shift+t",
            "br(freedomScientific):leftWizWheelDown"
        ]
    )
    def script_reportTime(self, gesture):
        import datetime
        now = datetime.datetime.now()
        ui.message(now.strftime("%I:%M %p"))

Example 5: Script with Repeat Detection

Repeat detection
from scriptHandler import script, getLastScriptRepeatCount
import ui
import datetime

class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    
    @script(
        description="Report time/date",
        gesture="kb:NVDA+f12"
    )
    def script_reportTimeOrDate(self, gesture):
        """Report time on first press, date on second press"""
        now = datetime.datetime.now()
        
        if getLastScriptRepeatCount() == 0:
            # First press: report time
            ui.message(now.strftime("%I:%M %p"))
        else:
            # Second press: report date
            ui.message(now.strftime("%A, %B %d, %Y"))

Gesture Identifier Format

Keyboard Gestures

Keyboard gesture examples
# Format: kb[(layout)]:<modifiers>+<key>

"kb:NVDA+t"                 # NVDA key + t
"kb:control+shift+a"        # Ctrl+Shift+A
"kb:alt+f4"                 # Alt+F4
"kb(laptop):NVDA+t"         # Laptop layout specific
"kb:NVDA+shift+upArrow"     # With arrow keys
"kb:applications"           # Applications (context menu) key
"kb:escape"                 # Escape key

Braille Display Gestures

Braille display gestures
# Format: br(<driver>):<button>

"br(freedomScientific):leftWizWheelUp"
"br(alva.BC640):t3"
"br(baum):d1"
"br(brailliant):space+dot1+dot3"

Touch Screen Gestures

Touch gestures
# Format: ts:<gesture>

"ts:2finger_flickRight"
"ts:3finger_tap"
"ts:flickLeft"
"ts:tap"

Script Categories

Organize scripts into categories for the input gestures dialog:
Script categories
import inputCore

# Built-in categories
inputCore.SCRCAT_BROWSE      # Browse mode
inputCore.SCRCAT_SPEECH       # Speech
inputCore.SCRCAT_BRAILLE      # Braille
inputCore.SCRCAT_VISION       # Vision
inputCore.SCRCAT_FOCUS        # Focus
inputCore.SCRCAT_SYSTEM       # System
inputCore.SCRCAT_CONFIG       # Configuration
inputCore.SCRCAT_MISC         # Miscellaneous
inputCore.SCRCAT_KBEMU        # Keyboard emulation

# Or set on the class
class MyPlugin(globalPluginHandler.GlobalPlugin):
    scriptCategory = "My Plugin"
    
    @script(description="Do something")
    def script_doSomething(self, gesture):
        pass

Advanced Features

Propagating Scripts

Allow scripts to work on focus ancestors:
Propagating script
class MyDialog(NVDAObject):
    
    @script(
        description="Close dialog",
        gesture="kb:escape",
        canPropagate=True  # Works on focused children too
    )
    def script_closeDialog(self, gesture):
        # This runs even when a child control has focus
        ui.message("Closing dialog")
        # ... close logic

Bypassing Input Help

Bypass input help
class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    
    @script(
        description="Always-active script",
        gesture="kb:NVDA+escape",
        bypassInputHelp=True  # Runs even in input help mode
    )
    def script_emergency(self, gesture):
        # This runs even when input help (NVDA+1) is active
        ui.message("Emergency command")

Sleep Mode Scripts

Sleep mode script
class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    
    @script(
        description="Wake up NVDA",
        gesture="kb:NVDA+shift+s",
        allowInSleepMode=True  # Runs in sleep mode
    )
    def script_toggleSleep(self, gesture):
        # This can run when NVDA is in sleep mode
        pass

Gesture Pass-Through

Pass gesture to the application:
Pass-through
class AppModule(appModuleHandler.AppModule):
    
    @script(
        description="Conditional pass-through",
        gesture="kb:control+c"
    )
    def script_smartCopy(self, gesture):
        # Do custom processing first
        focusObj = api.getFocusObject()
        
        if shouldInterceptCopy(focusObj):
            # Do custom copy
            performCustomCopy()
        else:
            # Pass gesture to application
            gesture.send()

Working with Gesture Objects

The gesture parameter provides information about the input:
Gesture object
def script_example(self, gesture):
    # Gesture properties
    displayName = gesture.displayName  # Human-readable name
    identifiers = gesture.identifiers  # All matching identifiers
    
    # Send gesture to application
    gesture.send()
    
    # Check gesture type
    if gesture.isModifier:
        ui.message("Modifier key pressed")
    
    # Access keyboard-specific info
    if hasattr(gesture, 'vkCode'):
        vkCode = gesture.vkCode
        scanCode = gesture.scanCode

Unbinding Gestures

Remove gesture bindings:
Unbinding gestures
class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    
    def __init__(self):
        super().__init__()
        # Remove a gesture binding
        try:
            self.removeGestureBinding("kb:NVDA+t")
        except LookupError:
            pass  # Gesture wasn't bound
    
    # Or unbind in __gestures
    __gestures = {
        "kb:NVDA+t": None,  # Unbinds this gesture
    }

Best Practices

Script names should be clear and descriptive:
# Good
def script_announceWindowTitle(self, gesture):
    pass

# Bad
def script_dwt(self, gesture):
    pass
Write clear, translatable descriptions:
@script(
    # Good - clear and specific
    description="Reports the current date and time",
)
def script_reportDateTime(self, gesture):
    pass

# Bad - vague
# description="Does something"
Avoid conflicts with system or NVDA shortcuts:
# Good - uses NVDA key
gesture="kb:NVDA+shift+t"

# Bad - conflicts with copy
# gesture="kb:control+c"
Assign appropriate categories:
@script(
    category=inputCore.SCRCAT_MISC,
    description="My script"
)
def script_myScript(self, gesture):
    pass
Scripts should not crash:
def script_example(self, gesture):
    try:
        # Do work
        result = doSomething()
        ui.message(result)
    except Exception:
        log.exception("Error in script_example")
        ui.message("An error occurred")

Testing Scripts

Testing scripts
# 1. Enable developer scratchpad in NVDA settings
# 2. Create globalPlugins/myTest.py:

import globalPluginHandler
from scriptHandler import script
import ui
from logHandler import log

class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    
    @script(
        description="Test script",
        gesture="kb:NVDA+shift+f12"
    )
    def script_test(self, gesture):
        log.info("Test script executed")
        ui.message("Test!")

# 3. Reload plugins: NVDA menu → Tools → Reload plugins
# 4. Test the gesture
# 5. Check log: NVDA menu → Tools → View log

See Also

NVDA Objects

Define scripts on NVDA Objects

Events

React to system events

Writing Plugins

Create plugins with scripts

Architecture Overview

Understand the system design