BookStackApp/BookStack · error · Error

Can't find options for dropdown menu

Error message

Can't find options for dropdown menu

What it means

The dropdown helper keeps a Map of HTMLElement -> HandleDropdownParams tracking registered dropdown menus and their behavior. getOptions(menu) throws when an event fires for a menu element that was never registered in that map. The framework treats unknown dropdown elements as a programming error rather than silently no-oping.

Source

Thrown at resources/js/wysiwyg/ui/framework/helpers/dropdowns.ts:119

    }

    protected openDropdown(menu: HTMLElement): void {
        const {toggle, showAside, onOpen} = this.getOptions(menu);
        menu.hidden = false;
        positionMenu(menu, toggle, Boolean(showAside), this.isRTL);

        this.openDropdowns.add(menu);
        menu.addEventListener('mouseover', this.onMenuMouseOver);

        if (onOpen) {
            onOpen();
        }
    }

    protected getOptions(menu: HTMLElement): HandleDropdownParams {
        const options = this.dropdownOptions.get(menu);
        if (!options) {
            throw new Error(`Can't find options for dropdown menu`);
        }

        return options;
    }

    /**
     * Add handling for a new dropdown.
     */
     public handle(options: HandleDropdownParams) {
        const {menu, toggle, showOnHover} = options;

        // Register dropdown
        this.dropdownOptions.set(menu, options);

        // Configure default events
        const toggleShowing = (event: MouseEvent) => {
            menu.hasAttribute('hidden') ? this.openDropdown(menu) : this.closeDropdown(menu);
        };

View on GitHub (pinned to 18f8469a1c)

Solutions

  1. Ensure every dropdown menu element is registered through the helper's setup/add path before its open/close/toggle handlers can fire.
  2. If you rebuild or replace the dropdown DOM, re-register the new element with the helper.
  3. Verify only one dropdown helper instance is in play — handlers must query the same instance that registered the menu.
  4. Unregister/cleanup elements on teardown so handlers and options are removed together.

Example fix

// before
const menu = document.createElement('div');
dropdowns.toggle(menu); // getOptions(menu) throws: never registered

// after
const menu = document.createElement('div');
dropdowns.addDropdown(menu, { /* HandleDropdownParams */ });
dropdowns.toggle(menu); // safe
Defensive patterns

Strategy: validation

Validate before calling

// Verify a menu is registered with the helper before invoking its behavior
function isRegisteredDropdown(helper: DropdownHelper, menu: HTMLElement): boolean {
    return helper.hasOptions(menu); // or track registered elements in a local Set
}
if (!isRegisteredDropdown(dropdowns, menu)) {
    dropdowns.addDropdown(menu, options); // register first
}
dropdowns.toggle(menu);

Try / catch

try {
    dropdowns.toggle(menu);
} catch (e) {
    if (e instanceof Error && e.message.includes("Can't find options for dropdown menu")) {
        // menu element was stale or never registered: re-register or skip
    } else { throw e; }
}

Prevention

When it happens

Trigger: An onOpen/onClose/toggle/showAside handler fires for a dropdown DOM element with no entry in dropdownOptions — e.g. the menu element was created or replaced outside the registration path, the dropdown was unregistered before a late close event, or a second helper instance's handlers query a map they never populated.

Common situations: Custom dropdown buttons wired without going through the register flow; DOM elements rebuilt after registration (stale element references); a close event arriving after teardown removed the options.

Related errors


AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02). Data as JSON: /api/errors/93647c2a6f9dd8bb. Report an issue: GitHub.