Complete framework for building standalone development applications to test Backstage plugins in isolation, with built-in authentication, theming, routing, and internationalization support.
Factory function that creates a new DevAppBuilder instance for configuring and building development applications.
/**
* Creates a dev app builder for rendering plugins in development
* @returns DevAppBuilder instance for method chaining
*/
function createDevApp(): DevAppBuilder;Usage Examples:
import { createDevApp } from "@backstage/dev-utils";
// Create and configure a development app
const DevApp = createDevApp()
.registerPlugin(myPlugin)
.addPage({
element: <MyPluginPage />,
title: "My Plugin",
path: "/my-plugin"
})
.build();Utility function that detects whether React Router is in beta version by testing the behavior of route creation.
/**
* Detects if React Router is in beta version
* @returns true if React Router beta is detected, false otherwise
*/
function isReactRouterBeta(): boolean;Usage Examples:
import { isReactRouterBeta } from "@backstage/dev-utils";
// Check React Router version for conditional behavior
if (isReactRouterBeta()) {
console.log("Using React Router beta version");
// Apply beta-specific configuration
} else {
console.log("Using stable React Router version");
// Apply stable version configuration
}
// Useful for conditional imports or feature detection
const routerConfig = isReactRouterBeta()
? betaRouterConfig
: stableRouterConfig;The function works by creating a test route with an index element and checking if the resulting route object has the index property, which behaves differently between React Router versions.
Builder class that provides a fluent API for configuring development applications with plugins, pages, themes, and authentication.
/**
* Builder class for creating development applications
* Provides fluent API for configuration before building the final component
*/
class DevAppBuilder {
/** Register one or more plugins to render in the dev app */
registerPlugin(...plugins: BackstagePlugin[]): DevAppBuilder;
/** Register an API factory to add to the app */
registerApi<Api, Impl extends Api, Deps extends { [name in string]: unknown }>(
factory: ApiFactory<Api, Impl, Deps>
): DevAppBuilder;
/** Add a React node to place just inside the App Provider */
addRootChild(node: ReactNode): DevAppBuilder;
/** Add a new sidebar item without a corresponding page */
addSidebarItem(sidebarItem: JSX.Element): DevAppBuilder;
/** Add a page component along with accompanying sidebar item */
addPage(opts: DevAppPageOptions): DevAppBuilder;
/** Add an array of themes to override the default theme */
addThemes(themes: AppTheme[]): DevAppBuilder;
/** Add new sign in provider for the dev app */
addSignInProvider(provider: SignInProviderConfig): DevAppBuilder;
/** Set available languages to be shown in the dev app */
setAvailableLanguages(languages: string[]): DevAppBuilder;
/** Add translation resource to the dev app */
addTranslationResource(resource: TranslationResource): DevAppBuilder;
/** Set default language for the dev app */
setDefaultLanguage(language: string): DevAppBuilder;
/** Build a DevApp component using the resources registered so far */
build(): ComponentType<PropsWithChildren<{}>>;
/** Build and render directly to #root element, with react hot loading */
render(): void;
}Usage Examples:
import { createDevApp } from "@backstage/dev-utils";
import { myPlugin, anotherPlugin } from "./plugins";
import { myCustomApi } from "./apis";
// Full configuration example
const devApp = createDevApp()
// Register plugins
.registerPlugin(myPlugin, anotherPlugin)
// Register custom APIs
.registerApi(myCustomApi)
// Add pages with sidebar navigation
.addPage({
element: <MyPluginPage />,
title: "My Plugin",
path: "/my-plugin",
icon: MyIcon
})
.addPage({
element: <AnotherPage />,
title: "Another Page",
path: "/another"
})
// Add standalone sidebar items
.addSidebarItem(
<SidebarItem to="/external" text="External Link" icon={LinkIcon} />
)
// Configure internationalization
.setAvailableLanguages(['en', 'es', 'fr'])
.setDefaultLanguage('en')
.addTranslationResource(myTranslations)
// Add custom themes
.addThemes([lightTheme, darkTheme])
// Add authentication providers
.addSignInProvider({
id: 'github',
title: 'GitHub',
message: 'Sign in with GitHub'
})
// Add global components
.addRootChild(<GlobalErrorBoundary key="error-boundary" />);
// Build and render
devApp.render();
// Or build for manual rendering
const DevAppComponent = devApp.build();
ReactDOM.render(<DevAppComponent />, document.getElementById('root'));Configuration options for adding pages to the development app.
/**
* Configuration options for dev app pages
*/
interface DevAppPageOptions {
/** URL path for the page. Auto-generated if not provided */
path?: string;
/** React element to render for this page */
element: JSX.Element;
/** Optional child components for nested routing */
children?: JSX.Element;
/** Page title for sidebar item. No sidebar item created if not provided */
title?: string;
/** Icon for sidebar item. Defaults to BookmarkIcon if title is provided */
icon?: IconComponent;
}Usage Examples:
// Page with auto-generated path and default icon
devApp.addPage({
element: <MyComponent />,
title: "My Page" // Creates sidebar item with BookmarkIcon
});
// Page with custom path and icon
devApp.addPage({
element: <DashboardPage />,
title: "Dashboard",
path: "/dashboard",
icon: DashboardIcon
});
// Page without sidebar item (no title)
devApp.addPage({
element: <HiddenPage />,
path: "/hidden" // No sidebar item created
});
// Page with nested routing
devApp.addPage({
element: <ParentPage />,
children: <ChildRoutes />,
title: "Parent Page",
path: "/parent/*"
});The DevAppBuilder automatically provides:
addSignInProviderAppRouter and FlatRoutesAlertDisplay for global error notificationsOAuthRequestDialog for authentication flowsimport { createApiFactory, configApiRef } from "@backstage/core-plugin-api";
const customApiFactory = createApiFactory({
api: myApiRef,
deps: { configApi: configApiRef },
factory: ({ configApi }) => new MyApiImpl(configApi)
});
devApp.registerApi(customApiFactory);import { createUnifiedTheme } from "@backstage/theme";
const myCustomTheme = createUnifiedTheme({
palette: {
primary: { main: '#1976d2' },
secondary: { main: '#dc004e' }
}
});
devApp.addThemes([myCustomTheme]);const spanishTranslations = {
'en': { 'welcome': 'Welcome' },
'es': { 'welcome': 'Bienvenido' }
};
devApp
.setAvailableLanguages(['en', 'es'])
.setDefaultLanguage('en')
.addTranslationResource(spanishTranslations);The development app framework requires these peer dependencies:
{
"@backstage/app-defaults": "workspace:^",
"@backstage/core-app-api": "workspace:^",
"@backstage/core-components": "workspace:^",
"@backstage/core-plugin-api": "workspace:^",
"@backstage/integration-react": "workspace:^",
"@material-ui/core": "^4.12.2",
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0",
"react-router-dom": "^6.3.0"
}import {
BackstagePlugin,
ApiFactory,
AnyApiFactory,
AppTheme,
SignInProviderConfig,
IconComponent
} from '@backstage/core-plugin-api';
import { TranslationResource } from '@backstage/core-plugin-api/alpha';
import { ComponentType, PropsWithChildren, ReactNode } from 'react';
import { DevAppBuilder, DevAppPageOptions } from '@backstage/dev-utils';
// Backstage plugin definition
interface BackstagePlugin {
getId(): string;
provide<T>(extension: T): T;
routes: Record<string, any>;
externalRoutes: Record<string, any>;
}
// API factory for dependency injection
interface ApiFactory<Api, Impl extends Api, Deps extends { [name in string]: unknown }> {
api: { id: string };
deps: Deps;
factory(deps: Deps): Impl;
}
// Any API factory type
type AnyApiFactory = ApiFactory<any, any, any>;
// Application theme definition
interface AppTheme {
id: string;
title: string;
variant: 'light' | 'dark';
icon?: IconComponent;
Provider: ComponentType<PropsWithChildren<{}>>;
}
// Sign-in provider configuration
interface SignInProviderConfig {
id: string;
title: string;
message: string;
apiRef?: any;
}
// Translation resource for i18n
interface TranslationResource {
[languageCode: string]: {
[messageKey: string]: string;
};
}
// Development app builder class (exported as type)
class DevAppBuilder {
registerPlugin(...plugins: BackstagePlugin[]): DevAppBuilder;
registerApi<Api, Impl extends Api, Deps extends { [name in string]: unknown }>(
factory: ApiFactory<Api, Impl, Deps>
): DevAppBuilder;
addRootChild(node: ReactNode): DevAppBuilder;
addSidebarItem(sidebarItem: JSX.Element): DevAppBuilder;
addPage(opts: DevAppPageOptions): DevAppBuilder;
addThemes(themes: AppTheme[]): DevAppBuilder;
addSignInProvider(provider: SignInProviderConfig): DevAppBuilder;
setAvailableLanguages(languages: string[]): DevAppBuilder;
addTranslationResource(resource: TranslationResource): DevAppBuilder;
setDefaultLanguage(language: string): DevAppBuilder;
build(): ComponentType<PropsWithChildren<{}>>;
render(): void;
}
// Configuration options for dev app pages (exported as type)
interface DevAppPageOptions {
path?: string;
element: JSX.Element;
children?: JSX.Element;
title?: string;
icon?: IconComponent;
}
// React component types
type ComponentType<P = {}> = React.ComponentType<P>;
type PropsWithChildren<P = {}> = P & { children?: ReactNode };
type ReactNode = React.ReactNode;