outline/outline · error · Error

Must be a valid url

Error message

Must be a valid url

What it means

The IsUrl decorator validates that a value is a well-formed URL with http/https protocol required, and (in cloud-hosted mode) a TLD required. A value missing the protocol, using a non-http scheme, or lacking a TLD in cloud mode throws at save time.

Source

Thrown at server/models/validators/IsUrl.ts:21

import env from "@server/env";

/**
 * A decorator that validates that a string is a valid HTTP(S) url. A top-level
 * domain is only required when cloud hosted, allowing self-hosted installations
 * to use internal hostnames.
 */
export default function IsUrl(target: object, propertyName: string) {
  return addAttributeOptions(target, propertyName, {
    validate: {
      validUrl(value: string) {
        if (
          !isURL(value, {
            protocols: ["http", "https"],
            require_protocol: true,
            require_tld: env.isCloudHosted,
          })
        ) {
          throw new Error("Must be a valid url");
        }
      },
    },
  });
}

View on GitHub (pinned to 935a44d4c0)

Solutions

  1. Prefix the value with https:// (or http:// for local) before saving.
  2. In self-hosted deployments where localhost is valid, ensure isCloudHosted is false.
  3. Sanitize/normalize URL input client-side using the URL constructor.

Example fix

// before
user.avatarUrl = 'example.com/avatar.png';

// after
user.avatarUrl = 'https://example.com/avatar.png';
Defensive patterns

Strategy: validation

Validate before calling

let normalized = value.trim();
if (!/^https?:\/\//i.test(normalized)) normalized = `https://${normalized}`;
try { new URL(normalized); } catch { throw new ValidationError('Must be a valid url'); }

Type guard

const isHttpUrl = (v: string): v is string => {
  try { const u = new URL(v); return u.protocol === 'http:' || u.protocol === 'https:'; } catch { return false; }
};

Prevention

When it happens

Trigger: Saving a URL column (e.g. avatar URL, integration webhook) with a value like 'example.com/path' (no protocol), 'ftp://x', or 'http://localhost' while isCloudHosted is true (no TLD).

Common situations: Users pasting URLs without https://; self-hosted dev values pointing at localhost while the cloud-hosted flag is on; webhook URLs with custom schemes.

Related errors


AI-assisted analysis of outline/outline@935a44d4c0 (2026-08-12). Data as JSON: /api/errors/a91fdf0598bda748. Report an issue: GitHub.