withastro/astro · error · Error

Apps must default export an object with an id, and a name.

Error message

Apps must default export an object with an id, and a name.

What it means

Dev toolbar apps registered through an integration can be declared as a string path. In that form the toolbar imports the entrypoint module and requires its default export to be an object with at least id and name (plus lifecycle methods such as init). This error means the module at the entrypoint loaded fine but its default export is not an object or is missing id/name.

Solutions

  1. Make the entrypoint default-export an app object: export default { id: 'my-app', name: 'My App', init(ctx) {} }
  2. Check the dev toolbar API docs for your Astro version — the expected export shape changed across majors
  3. If the implementation lives in another file, re-export it as the default: export { default } from './impl.js'

Example fix

// before — src/toolbar/my-app.ts
export default function app() {
	console.log('runs');
}

// after
export default {
	id: 'my-app',
	name: 'My App',
	init(ctx) {
		console.log('runs', ctx);
	},
};
Defensive patterns

Strategy: validation

Validate before calling

// dev-time sanity check for a string-form toolbar app entrypoint
const app = (await import('./my-app.js')).default;
if (typeof app !== 'object' || app === null || !app.id || !app.name) {
	throw new TypeError('Toolbar app entrypoint must default-export { id, name, ... }');
}

Type guard

const isToolbarApp = (v: unknown): v is { id: string; name: string } =>
	typeof v === 'object' && v !== null && typeof (v as any).id === 'string' && typeof (v as any).name === 'string';

Prevention

When it happens

Trigger: Registering a toolbar app as a string entrypoint whose module default-exports a function, class, or nothing; default-exporting { name: 'X' } without id; the entrypoint re-exporting from another module that has no default export.

Common situations: Copying toolbar app examples from older Astro docs where the default export was a component factory; splitting an app across files and forgetting to re-export the app object as default.

Related errors


AI-assisted analysis of withastro/astro@52e6c34790 (2026-08-18). Data as JSON: /api/errors/6a274a24aebb7c16. Report an issue: GitHub.

Appendix: source

Thrown at packages/astro/src/toolbar/vite-plugin-dev-toolbar.ts:96

										`safeLoadPlugin(${JSON.stringify(
											plugin,
										)}, async () => (await import(${JSON.stringify(
											typeof plugin === 'string' ? plugin : plugin.entrypoint.toString(),
										)})).default, ${JSON.stringify(
											typeof plugin === 'string' ? plugin : plugin.entrypoint.toString(),
										)})`,
								)
								.join(',')}]));
						};

						async function safeLoadPlugin(appDefinition, importEntrypoint, entrypoint) {
							try {
								let app;
								if (typeof appDefinition === 'string') {
									app = await importEntrypoint();

									if (typeof app !== 'object' || !app.id || !app.name) {
										throw new Error("Apps must default export an object with an id, and a name.");
									}
								} else {
									app = appDefinition;

									if (typeof app !== 'object' || !app.id || !app.name || !app.entrypoint) {
										throw new Error("Apps must be an object with an id, a name and an entrypoint.");
									}

									const loadedApp = await importEntrypoint();

									if (typeof loadedApp !== 'object') {
										throw new Error("App entrypoint must default export an object.");
									}

									app = { ...app, ...loadedApp };
								}

								return app;

View on GitHub (pinned to 52e6c34790)