OrchardCMS/OrchardCore · error · Error

is missing the required "orchardBaseUrl" field.

Error message

${CONFIG_URL} is missing the required "orchardBaseUrl" field.

What it means

After successfully fetching and parsing config.json, the standalone media gallery validates that the object contains a truthy orchardBaseUrl field. If missing or empty, it throws this Error. The base URL is required to call Orchard APIs (translations, SignalR, media endpoints) from the standalone host.

Solutions

  1. Add a non-empty orchardBaseUrl value (the Orchard tenant root URL) to config.json and redeploy.
  2. Validate config.json against IStandaloneConfigSource before deploying.
  3. Check for trailing whitespace-only values; the check is truthiness-based so '' fails.
  4. If the field name changed between versions, align the config schema with the bundle version you ship.

Example fix

// before (config.json)
{ "signalrEnabled": true }
// after (config.json)
{ "orchardBaseUrl": "https://my-site.example.com", "signalrEnabled": true }
Defensive patterns

Strategy: validation

Validate before calling

// Validate config.json schema before shipping
const cfg = JSON.parse(fs.readFileSync('config.json', 'utf8'));
if (!cfg.orchardBaseUrl) throw new Error('config.json must define orchardBaseUrl');

Type guard

function isStandaloneConfigSource(v: unknown): v is IStandaloneConfigSource {
  return typeof v === 'object' && v !== null && 'orchardBaseUrl' in v && typeof (v as IStandaloneConfigSource).orchardBaseUrl === 'string' && (v as IStandaloneConfigSource).orchardBaseUrl.length > 0;
}

Try / catch

try { const src = await loadConfigSource(); }
catch (err) { if (String(err).includes('orchardBaseUrl')) { /* fix config.json and redeploy */ } throw err; }

Prevention

When it happens

Trigger: config.json exists and returns 200 with valid JSON, but the JSON lacks orchardBaseUrl or has it set to an empty string/null.

Common situations: Hand-edited config.json missing the field; a config template copied without filling in the tenant URL; config generated by an older tool version that did not emit the field.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of OrchardCMS/OrchardCore@4306c0717f (2026-09-13). Data as JSON: /api/errors/2aeaad073f65eea0. Report an issue: GitHub.

Appendix: source

Thrown at src/OrchardCore.Modules/OrchardCore.Media/Assets/media-gallery/src/standalone.ts:38

/**
 * Entry point for the **standalone** media gallery — the app hosted on its own origin against a
 * remote OrchardCore tenant. Unlike the embedded entry (main.ts), there is no Razor host to inject
 * `<media-gallery>` attributes: config comes from a fetched `config.json`, and the App is rendered
 * with an already-resolved two-origin runtime config. Auth is bearer + interactive (handled inside
 * App.vue from the injected config).
 */

const CONFIG_URL = "config.json";

async function loadConfigSource(): Promise<IStandaloneConfigSource> {
  const response = await fetch(CONFIG_URL, { cache: "no-store" });
  if (!response.ok) {
    throw new Error(`Failed to load ${CONFIG_URL}: ${response.status} ${response.statusText}`);
  }
  const source = (await response.json()) as IStandaloneConfigSource;
  if (!source.orchardBaseUrl) {
    throw new Error(`${CONFIG_URL} is missing the required "orchardBaseUrl" field.`);
  }
  return source;
}

/** Load the media gallery's UI labels from the remote Orchard tenant (the same "media-gallery" JS
 * localizations the embedded admin page renders, for the server's resolved culture). The endpoint is
 * anonymous, so this runs before authentication. Falls back to an empty set on failure. */
async function loadTranslations(apiBaseUrl: string): Promise<string> {
  try {
    // Default fetch caching: the endpoint serves Cache-Control/ETag, so repeat loads hit the
    // browser cache or revalidate to a 304 instead of re-downloading the label set every boot.
    const base = apiBaseUrl.endsWith("/") ? apiBaseUrl : `${apiBaseUrl}/`;
    const response = await fetch(`${base}api/media/localizations`);
    if (response.ok) {
      return JSON.stringify(await response.json());
    }
  } catch {
    // Endpoint unreachable — fall back to empty (labels use their built-in fallbacks where present).

View on GitHub (pinned to 4306c0717f)