Skip to content

Creating a plugin

DefKorns edited this page Oct 7, 2026 · 2 revisions

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.

How the menu works

  • optiond runs 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 options again with --commandPath. B goes back to the previous screen by itself.

1. Lay out your hmod

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.

2. Add an entry to the main screen

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 c0009 to c0080; pick a free number.
  • SUBMENU=TRUE draws the ›.
  • COMMAND_NAME is a translation key (step 5). Plain text works too.

3. Add entries to your screen

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

my_plugin/scripts/hello.sh:

#!/bin/sh
echo "Hello from My Plugin!"
uptime

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

4. Add a switch

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 state

my_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 ;;
esac

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

5. Translate it

my_plugin/lang/en-US.lang:

MY_PLUGIN=My Plugin
HELLO=Say hello
TURBO_MODE=Turbo mode

my_plugin/lang/pt-PT.lang:

MY_PLUGIN=O Meu Plugin
HELLO=Dizer olá
TURBO_MODE=Modo turbo

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

6. Show entries only when they make sense

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" ]

7. Lists the user can delete from

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_CONFIRM

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

8. Install and uninstall

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.

Field reference

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.

Clone this wiki locally