octobercms/october · error · Error

getDynamicOptionsExtraData must return an object

Error message

getDynamicOptionsExtraData must return an object

What it means

loadDynamicOptions() in the Inspector control base merges extra request data from the optional control hook getDynamicOptionsExtraData(). If a custom control defines that hook but returns something that is not an object (string, number, boolean, undefined), it throws before the AJAX request is sent. The check is typeof-based, so null technically passes but contributes nothing.

Source

Thrown at modules/backend/vuecomponents/inspector/assets/js/classes/control-base.js:123

        setManagedValue: function setManagedValue(value) {
            utils.setProperty(this.obj, this.control.property, value);
        },

        refreshDisplayedValue: function refreshDisplayedValue() { },

        focusControl: function focusControl() { },

        inspectorGetTab: function inspectorGetTab() {
            return this.control.tab;
        },

        loadDynamicOptions: function loadDynamicOptions() {
            var data = Object.assign({}, $.oc.vueUtils.getCleanObject(this.parentObj), $.oc.vueUtils.getCleanObject(this.obj));
            if (typeof this.control.getDynamicOptionsExtraData === 'function') {
                const extraData = this.control.getDynamicOptionsExtraData();
                if (typeof extraData !== 'object') {
                    throw new Error('getDynamicOptionsExtraData must return an object');
                }

                Object.assign(data, extraData);
            }

            this.loadingDynamicOptions = true;

            data['inspectorProperty'] = this.control.property;
            data['inspectorClassName'] = this.serverClassName;

            var handlerAlias = this.inspectorPreferences.handlerAlias,
                optionsHandler = handlerAlias
                    ? handlerAlias + '::onInspectableGetOptions'
                    : 'onInspectableGetOptions';

            return dataLoader.requestOptions(
                    this.$el,
                    this.serverClassName,

View on GitHub (pinned to b608633a7e)

Solutions

  1. Make the hook always return an object literal — add `return {};` when there is nothing to add.
  2. Check for early returns or missing return statements in the hook.
  3. Return parsed objects, never JSON strings.

Example fix

// before
getDynamicOptionsExtraData: function () {
    if (this.contextTab) {
        return { tab: this.contextTab };
    }
}

// after
getDynamicOptionsExtraData: function () {
    if (this.contextTab) {
        return { tab: this.contextTab };
    }

    return {};
}
Defensive patterns

Strategy: type-guard

Type guard

function extraDataIsObject(value) {
    return value === null || typeof value === 'object';
}

// inside the control, normalize before returning
getDynamicOptionsExtraData: function () {
    var extra = this.computeExtra();
    return extraDataIsObject(extra) ? extra : {};
}

Prevention

When it happens

Trigger: A custom inspector control defines getDynamicOptionsExtraData() that returns a string (e.g. serialized data), a boolean, or has an early-exit path with no return (undefined); the control then triggers a dynamic-options load.

Common situations: Custom inspector controls with dynamicOptions enabled where the hook has a conditional return and no default; returning an array of pairs instead of an object; returning a JSON.stringify result instead of the parsed object.

Related errors


AI-assisted analysis of octobercms/october@b608633a7e (2026-08-21). Data as JSON: /api/errors/976fc236e7cef0c2. Report an issue: GitHub.