strapi/strapi · critical

Invalid admin url config. Make sure the url is a non-empty s

Error message

Invalid admin url config. Make sure the url is a non-empty string.

What it means

Defensive guard in getConfigUrls() that throws when `admin.url`, after lodash `_.trim`, is not a string. In practice lodash `_.trim` always returns a string (coercing numbers/objects/null), so this branch is effectively unreachable through normal config files; it exists to harden against exotic non-stringable inputs (e.g. a Symbol, which would make _.trim itself throw before this check). The message names admin config and is the companion to the server-url string check.

Source

Thrown at packages/core/core/src/configuration/urls.ts:38

  }

  if (serverUrl.startsWith('http')) {
    try {
      serverUrl = _.trim(new URL(serverConfig.url).toString(), '/');
    } catch {
      throw new Error(
        'Invalid server url config. Make sure the url defined in server.js is valid.'
      );
    }
  } else if (serverUrl !== '') {
    serverUrl = `/${serverUrl}`;
  }

  // Defines adminUrl value
  let adminUrl = _.get(adminConfig, 'url', '/admin');
  adminUrl = _.trim(adminUrl, '/ ');
  if (typeof adminUrl !== 'string') {
    throw new Error('Invalid admin url config. Make sure the url is a non-empty string.');
  }
  if (adminUrl.startsWith('http')) {
    try {
      adminUrl = _.trim(new URL(adminUrl).toString(), '/');
    } catch {
      throw new Error('Invalid admin url config. Make sure the url defined in server.js is valid.');
    }
  } else {
    adminUrl = `${serverUrl}/${adminUrl}`;
  }

  // Defines adminPath value
  let adminPath = adminUrl;
  if (
    serverUrl.startsWith('http') &&
    adminUrl.startsWith('http') &&
    new URL(adminUrl).origin === new URL(serverUrl).origin &&
    !forAdminBuild

View on GitHub (pinned to 4a4101264d)

Solutions

  1. Ensure `admin.url` is a literal string in config/admin.js (e.g. `url: '/admin'` or an absolute URL).
  2. If you set admin.url dynamically, coerce it: `url: String(value)` before it reaches Strapi's config.
  3. Avoid passing Symbol/BigInt/object-with-throwing-toString values through config.
  4. If hitting this from a plugin, read the value via `strapi.config.get('admin.url', '/admin')` and assert it is a string.

Example fix

// before
module.exports = ({ env }) => ({
  url: env.object('ADMIN_URL'), // resolves to a non-string object
});

// after
module.exports = ({ env }) => ({
  url: env('ADMIN_URL', '/admin'),
});
Defensive patterns

Strategy: type-guard

Validate before calling

const adminUrl = config.admin?.url ?? '/admin';
if (typeof adminUrl !== 'string') {
  throw new TypeError(`admin.url must be a string, got ${typeof adminUrl}`);
}

Type guard

const isStringConfig = (v: unknown): v is string => typeof v === 'string';

Try / catch

try {
  getConfigUrls(config);
} catch (e) {
  if (e instanceof Error && /admin url config.*non-empty string/.test(e.message)) {
    config.admin = { ...(config.admin ?? {}), url: '/admin' };
  } else throw e;
}

Prevention

When it happens

Trigger: `config.admin.url` is set to a value whose _.trim result is not a string. Because _.trim coerces, the realistic trigger is an upstream code path that injects a non-coercible value, or a custom config loader returning a Symbol/odd object whose toString throws. Through normal env/file config this is not expected to fire.

Common situations: Programmatically building the config object and assigning admin.url to a Symbol, BigInt, or an object with a throwing toString; a plugin or test harness mutating `strapi.config.set('admin.url', someNonString)` before getConfigUrls runs.

Related errors


AI-assisted analysis of strapi/strapi@4a4101264d (2026-08-12). Data as JSON: /api/errors/67436dfd94db024f. Report an issue: GitHub.