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
- Add a non-empty orchardBaseUrl value (the Orchard tenant root URL) to config.json and redeploy.
- Validate config.json against IStandaloneConfigSource before deploying.
- Check for trailing whitespace-only values; the check is truthiness-based so '' fails.
- 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
- Version-control a config.json template with orchardBaseUrl filled per environment.
- Lint config.json in CI before deployment.
- Keep the config schema in sync with the bundle version.
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
- Top level JSON element must be an object. Instead
- Can't use the numeric key
- Can't use the non numeric key
- Top-level JSON element must be an object. Instead
- Could not parse the JSON document.
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)