louislam/uptime-kuma · warning · TranslatableError

domain_expiry_unsupported_missing_target

Error message

domain_expiry_unsupported_missing_target

What it means

After the type check passes, checkSupport looks up the relevant field (url, hostname, or grpcUrl) on the monitor and reads `monitor[targetField]`. If that value is not a string or is empty, this TranslatableError is thrown. So the monitor type is fine, but the specific target cell is blank or wrong-typed (e.g. null, undefined, a number).

Source

Thrown at server/model/domain_expiry.js:218

     * @returns {(null|object)} parsed domain tld
     */
    get tld() {
        return this.parseName().publicSuffix;
    }

    /**
     * @param {Monitor} monitor Monitor object
     * @throws {TranslatableError} Throws an error if the monitor type is unsupported or missing target.
     * @returns {Promise<{ domain: string, tld: string }>} Domain expiry support info
     */
    static async checkSupport(monitor) {
        if (!(monitor.type in TYPES_WITH_DOMAIN_EXPIRY_SUPPORT_VIA_FIELD)) {
            throw new TranslatableError("domain_expiry_unsupported_monitor_type");
        }
        const targetField = TYPES_WITH_DOMAIN_EXPIRY_SUPPORT_VIA_FIELD[monitor.type];
        const target = monitor[targetField];
        if (typeof target !== "string" || target.length === 0) {
            throw new TranslatableError("domain_expiry_unsupported_missing_target");
        }

        const tld = parseTld(target);

        // It must be checked first, filter out non-ICANN domains.
        if (!tld.isIcann) {
            throw new TranslatableError("domain_expiry_unsupported_is_icann", {
                // If domain is null, use hostname as fallback for better error message.
                domain: tld.domain ?? tld.hostname ?? "EMPTY DOMAIN",
                publicSuffix: tld.publicSuffix,
            });
        }

        const publicSuffix = tld.publicSuffix;
        const rootTld = publicSuffix.split(".").pop();
        const rdap = await getRdapServer(publicSuffix);
        if (!rdap) {
            throw new TranslatableError("domain_expiry_unsupported_unsupported_tld_no_rdap_endpoint", {

View on GitHub (pinned to 6b5ea01557)

Solutions

  1. Open the monitor in the Edit Monitor UI and fill in the URL/hostname field relevant to its type, then save.
  2. If scripting, ensure the payload includes the right field: url for http/keyword/json-query/real-browser/websocket-upgrade, hostname for port/ping/dns/smtp/snmp, grpcUrl for grpc-keyword.
  3. Run a DB check for empty targets: `SELECT id,type,url,hostname,grpc_url FROM monitor WHERE type IN ('http','ping','port','dns','smtp','snmp','grpc-keyword','keyword','json-query','real-browser','websocket-upgrade') AND (url IS NULL OR url='') ...`.
  4. Validate on the API boundary before persistence so a monitor is never saved without its target.

Example fix

// before
const target = monitor[targetField];
if (typeof target !== "string" || target.length === 0) {
    throw new TranslatableError("domain_expiry_unsupported_missing_target");
}

// after — also surface which field was empty
throw new TranslatableError("domain_expiry_unsupported_missing_target", { field: targetField });
Defensive patterns

Strategy: validation

Validate before calling

const { TYPES_WITH_DOMAIN_EXPIRY_SUPPORT_VIA_FIELD } = require("../src/util");
function assertTargetPresent(monitor) {
    const field = TYPES_WITH_DOMAIN_EXPIRY_SUPPORT_VIA_FIELD[monitor.type];
    const v = monitor[field];
    if (typeof v !== "string" || v.trim().length === 0) {
        throw new Error(`Monitor ${monitor.id} (${monitor.type}) has no ${field}`);
    }
}

Type guard

function monitorHasDomainTarget(monitor) {
    const field = TYPES_WITH_DOMAIN_EXPIRY_SUPPORT_VIA_FIELD[monitor.type];
    return typeof monitor?.[field] === "string" && monitor[field].length > 0;
}

Try / catch

try {
    await DomainExpiry.checkSupport(monitor);
} catch (e) {
    if (e.msgi18n && e.message === "domain_expiry_unsupported_missing_target") {
        // prompt the user to fill in the URL/hostname field
    }
}

Prevention

When it happens

Trigger: An http monitor with an empty url; a ping monitor whose hostname is null because the row was created before the column existed; a grpc-keyword monitor with grpcUrl unset; a monitor whose url field is an object instead of a string due to a malformed API payload.

Common situations: Monitors created via the API with a missing required field; older monitors upgraded across schema versions where a new column is null; frontend bug that sent an empty string for the target.

Related errors


AI-assisted analysis of louislam/uptime-kuma@6b5ea01557 (2026-08-12). Data as JSON: /api/errors/fb11b103994775ce. Report an issue: GitHub.