Repository navigation
Creating a plugin
Any hmod can add entries to the Options Menu, or a whole screen of its own. A plugin is just files: no compiling, no code changes in the menu.
This page builds a small example plugin, My Plugin, step by step. The full field reference is at the end.
-
optiondruns in the background and waits for the button combo, then starts/etc/options_menu/options. - Every screen is a commands folder: one small text file per entry. The main screen is
/etc/options_menu/commands/. - Entries run shell commands, usually scripts from the screen's scripts folder.
- A screen with its own commands folder is opened by running
optionsagain with--commandPath. B goes back to the previous screen by itself.
my_plugin/
├── install
├── uninstall
├── readme.md
└── etc/options_menu/
├── commands/
│ └── c0090_MyPlugin ← entry on the main screen
├── images/
│ └── preview_my_plugin.png ← 200×140 icon (optional)
└── my_plugin/
├── commands/ ← your screen
│ ├── c0010_Hello
│ └── c0020_Turbo
├── scripts/
│ ├── hello.sh
│ └── turbo.sh
└── lang/
├── en-US.lang
└── pt-PT.lang
Keep everything of yours under /etc/options_menu/my_plugin/, so nothing clashes with the menu or other plugins. Only the entry on the main screen goes in the shared commands/ folder.
etc/options_menu/commands/c0090_MyPlugin:
COMMAND_NAME=MY_PLUGIN
COMMAND_TYPE=EXTERNAL
RESTART_UI=FALSE
SUBMENU=TRUE
COMMAND_STR=usleep 50000 && %options_path%/options --commandPath %options_path%/my_plugin/commands/ --scriptPath %options_path%/my_plugin/scripts/ --title "MY_PLUGIN" &
PREVIEW_IMAGE=/etc/options_menu/images/preview_my_plugin.png- The number in the file name sets the position: entries are sorted by name. The built-in main screen uses
c0009toc0080; pick a free number. -
SUBMENU=TRUEdraws the ›. -
COMMAND_NAMEis a translation key (step 5). Plain text works too.
An entry that runs a script and shows its output — my_plugin/commands/c0010_Hello:
COMMAND_NAME=HELLO
COMMAND_TYPE=INTERNAL
COMMAND_STR=sh %script_dir%/hello.shmy_plugin/scripts/hello.sh:
#!/bin/sh
echo "Hello from My Plugin!"
uptimeINTERNAL shows the output in the menu's console, and the user presses B to go back. Use EXTERNAL for commands that draw on the screen themselves or launch another app.
An entry with STATE_STR is drawn as an on/off switch — my_plugin/commands/c0020_Turbo:
COMMAND_NAME=TURBO_MODE
COMMAND_TYPE=INTERNAL
COMMAND_STR=sh %script_dir%/turbo.sh toggle
STATE_STR=sh %script_dir%/turbo.sh statemy_plugin/scripts/turbo.sh:
#!/bin/sh
flag=/etc/options_menu/my_plugin/turbo_on
case "$1" in
state) [ -f "$flag" ] && echo on || echo off ;;
toggle) if [ -f "$flag" ]; then rm -f "$flag"; else touch "$flag"; fi ;;
esacSTATE_STR is only read to draw the switch: the first line it prints is the state (1, on, y, yes or true mean on). COMMAND_STR does the actual switching.
my_plugin/lang/en-US.lang:
MY_PLUGIN=My Plugin
HELLO=Say hello
TURBO_MODE=Turbo modemy_plugin/lang/pt-PT.lang:
MY_PLUGIN=O Meu Plugin
HELLO=Dizer olá
TURBO_MODE=Modo turboEvery /etc/options_menu/<folder>/lang/ with an en-US.lang is loaded at startup. Missing keys fall back to English, and a key with no translation at all is shown as is.
Language codes: en-US, fr-FR, de-DE, es-ES, it-IT, nl-NL, pt-PT, ru-RU, ja-JP.
| Field | Shown only when |
|---|---|
USB_ONLY=TRUE |
A USB/SD drive is mounted |
ENABLE_IF=<shell condition> |
The condition exits 0 |
ENABLE_IF runs with /etc/preinit sourced and script_init called, so you can use $rootfs, $mountpoint, $sftype (nes, snes, hvcj...) and $sfregion:
ENABLE_IF=[ "$sftype" = "snes" ]
ENABLE_IF=[ -d "$rootfs/etc/my_plugin_backups" ]For a list built at runtime (saved files, backups...), have a script write one command file per item into a folder and then open that folder as a screen. Give each item a DELETE_STR: holding B on it asks for confirmation and runs it.
COMMAND_NAME=backup_2026-10-07
COMMAND_TYPE=INTERNAL
COMMAND_STR=sh /etc/options_menu/my_plugin/scripts/restore.sh backup_2026-10-07
DELETE_STR=rm -rf "/media/data/my_backups/backup_2026-10-07" && sh /etc/options_menu/my_plugin/scripts/list.sh
DELETE_CONFIRM_KEY=DELETE_BACKUP_CONFIRMEnding DELETE_STR by rebuilding and reopening the list refreshes it after the delete. Network → Restore Wifi Config works this way (network/scripts/restore_settings.sh).
hakchi copies your etc/ into the console when the hmod is installed. Your uninstall script runs on removal: delete anything your scripts created at runtime (flags, generated lists, backups on NAND). hakchi removes the files it installed by itself.
Test on the console, then open the menu: your entry is there. No restart needed.
One file per entry, named cNNNN_Name. One FIELD=value per line, no spaces around =, case sensitive.
| Field | Description |
|---|---|
COMMAND_NAME |
Label, or a translation key |
COMMAND_TYPE |
INTERNAL shows the output in the menu's console; EXTERNAL runs the command with the menu paused |
COMMAND_STR |
Single-line command. Use a script for anything longer |
RESTART_UI |
External only. FALSE leaves the console's UI paused; resume it with /bin/sh /etc/options_menu/scripts/ResumeUI.sh when you're done |
IGNORE_INTERRUPT |
TRUE: B doesn't interrupt an internal command while it runs |
USB_ONLY |
TRUE: only shown with a USB/SD drive mounted |
ENABLE_IF |
Only shown if this shell condition exits 0 |
SUBMENU |
TRUE: draws ›. Ignored on switches |
CHILD |
TRUE: indents the entry under the one above it |
STATE_STR |
Prints the state; makes the entry a switch |
DELETE_STR |
Run when the user holds B on the entry and confirms |
DELETE_CONFIRM_KEY |
Translation key for that confirmation (default: "Delete this item?") |
PREVIEW_IMAGE |
PNG shown in the detail panel, scaled to fit. 200×140 matches the built-in icons |
In COMMAND_STR, DELETE_STR and STATE_STR:
| Variable | Replaced with |
|---|---|
%options_path% |
The folder with the options binary (/etc/options_menu) |
%script_dir% |
The current screen's --scriptPath
|
options arguments:
| Argument | Description |
|---|---|
--commandPath <folder> |
Commands folder for the screen |
--scriptPath <folder> |
Scripts folder (%script_dir%) |
--title <key> |
Screen title, a translation key or plain text |
The grid fields PREVIEW_NEAREST, PREVIEW_SQUARE, PREVIEW_GRID_COLS, PREVIEW_FIT_CONTAIN and PREVIEW_HIDE_LABEL are only used by apps built on the shared framework with a grid layout; the menu itself always shows a list. PREVIEW_IMAGE_X/Y/WIDTH/HEIGHT from 1.3.4 are accepted and ignored.
UI colors (/etc/options_menu/theme.cfg) are described in the README.