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
- Open the monitor in the Edit Monitor UI and fill in the URL/hostname field relevant to its type, then save.
- 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.
- 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='') ...`.
- 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
- Make the target field required in the UI when domain-expiry is enabled.
- Validate on the API boundary so a monitor without a target cannot be saved.
- Periodically scan the DB for monitors with NULL target fields.
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
- domain_expiry_unsupported_monitor_type
- domain_expiry_unsupported_is_icann
- domain_expiry_unsupported_unsupported_tld_no_rdap_endpoint
- Service Name is required.
- Invalid service name. Please use the internal Service Name (
AI-assisted analysis of louislam/uptime-kuma@6b5ea01557 (2026-08-12).
Data as JSON: /api/errors/fb11b103994775ce.
Report an issue: GitHub.