Coder is an open source self hosted platform for cloud development environments. It lets you run secure remote workspaces on your own infrastructure and work from the IDEs your team already uses.
The Coder Backstage plugin brings those workspaces into your portal. Engineers link a Coder account with a token then browse and manage their workspaces without leaving Backstage. The plugin can map catalog entities to workspace templates so the right repo URL travels with each create action. You get a workspace list on the service page plus controls to open or create environments that match the code you are viewing.
Typical use cases are simple. Speed up day one for a new repo. Standardize tools and runtimes across teams. Cut context switching by opening an IDE for the service straight from the catalog. If you need to go beyond the bundled card the plugin exposes hooks and an API layer so you can query Coder from custom pages.
The plugin appears in the Backstage plugin catalog and continues to evolve. Planned items include OAuth support and an Open in Coder action along with examples for the Scaffolder. If you already run Coder this is a small step that gives your Backstage users direct access to ready to code environments.

Installation Instructions
These instructions apply to self-hosted Backstage only. To use this plugin on Roadie, visit the docs.
Install the frontend package
yarn --cwd packages/app add @coder/backstage-plugin-coder
Configure the proxy in app config
Edit app-config.yaml
proxy:
endpoints:
'/coder':
# Replace with your Coder access URL with a trailing slash
target: 'https://coder.example.com/'
changeOrigin: true
allowedMethods: ['GET'] # More methods will be supported soon
allowedHeaders: ['Authorization', 'Coder-Session-Token']
headers:
X-Custom-Source: backstage
Enable the proxy in the backend
New backend system
Edit packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';
import { proxyPlugin } from '@backstage/plugin-proxy-backend';
const backend = createBackend();
backend.add(proxyPlugin());
backend.start();
Old backend system
Create packages/backend/src/plugins/proxy.ts
import { createRouter } from '@backstage/plugin-proxy';
import type { PluginEnvironment } from '../types';
export default async function createPlugin(env: PluginEnvironment) {
return await createRouter({
config: env.config,
logger: env.logger,
});
}
Wire it in packages/backend/src/index.ts
import proxy from './plugins/proxy';
// inside the main bootstrap where apiRouter is defined
apiRouter.use('/proxy', await proxy(env));
Wrap the app with CoderProvider
Edit packages/app/src/App.tsx
import React from 'react';
import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components';
import { createApp } from '@backstage/app-defaults';
import { AppRouter } from '@backstage/core-app-api';
// import the provider and types from the Coder plugin
import {
CoderProvider,
type CoderAppConfig,
} from '@coder/backstage-plugin-coder';
// your existing routes and app setup above
const app = createApp({
// existing config
});
const routes = app.getRoutes();
const coderAppConfig: CoderAppConfig = {
deployment: {
accessUrl: 'https://coder.example.com',
},
workspaces: {
defaultTemplateName: 'devcontainers',
defaultMode: 'manual',
repoUrlParamKeys: ['custom_repo', 'repo_url'],
params: {
repo: 'custom',
region: 'eu-helsinki',
},
},
};
export default app.createRoot(
<CoderProvider appConfig={coderAppConfig}>
<AlertDisplay />
<OAuthRequestDialog />
<AppRouter>{routes}</AppRouter>
</CoderProvider>,
);
Use your Coder access URL. Keep a trailing slash in app-config.yaml. No slash in the provider accessUrl.
Add the workspaces card to the entity page
Edit packages/app/src/components/catalog/EntityPage.tsx
import React from 'react';
import Grid from '@material-ui/core/Grid';
import { EntityAboutCard } from '@backstage/plugin-catalog';
import { EntityLayout } from '@backstage/plugin-catalog';
import { EntitySwitch, isComponentType } from '@backstage/plugin-catalog';
import { CoderWorkspacesCard } from '@coder/backstage-plugin-coder';
const overviewContent = (
<Grid container spacing={3} alignItems="stretch">
<Grid item md={6}>
<EntityAboutCard variant="gridItem" />
</Grid>
<Grid item md={6} xs={12}>
<CoderWorkspacesCard readEntityData />
</Grid>
</Grid>
);
export const serviceEntityPage = (
<EntityLayout>
<EntityLayout.Route path="/" title="Overview">
{overviewContent}
</EntityLayout.Route>
{/* your other routes */}
</EntityLayout>
);
// export the page you use in your router
export const entityPage = (
<EntitySwitch>
<EntitySwitch.Case if={isComponentType('service')}>
{serviceEntityPage}
</EntitySwitch.Case>
{/* your other cases */}
</EntitySwitch>
);
The card reads the current entity and shows related workspaces. You can also drop the card in any grid section without readEntityData for a general list.
// General list of all user workspaces
import { CoderWorkspacesCard } from '@coder/backstage-plugin-coder';
function CoderAllWorkspaces() {
return <CoderWorkspacesCard />;
}
Optional repo settings in catalog info
Add Coder settings to your repo catalog file to control defaults for that entity
catalog-info.yaml in your repo
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: python-project
spec:
type: other
lifecycle: unknown
owner: pms
coder:
templateName: 'devcontainers'
mode: 'auto'
params:
repo: 'custom'
region: 'us-pittsburgh'
These values override the provider defaults for that entity.
Optional custom usage with hooks
You can import hooks to query the Coder API inside your app components. Wrap with CoderProvider as shown above.
import React from 'react';
import {
useCoderWorkspacesConfig,
useCoderWorkspacesQuery,
CoderAuthWrapper,
} from '@coder/backstage-plugin-coder';
export function MyWorkspacesSection() {
const workspacesConfig = useCoderWorkspacesConfig({ readEntityData: true });
const workspacesQuery = useCoderWorkspacesQuery({
coderQuery: 'owner:me',
workspacesConfig,
});
return (
<CoderAuthWrapper type="card">
<>
{workspacesQuery.isLoading && <p>Loading</p>}
{workspacesQuery.isError && <p>Failed to load</p>}
{workspacesQuery.data?.map(ws => (
<div key={ws.id}>{ws.name}</div>
))}
</>
</CoderAuthWrapper>
);
}
Things to Know
Changelog
The Coder plugin has not seen any significant changes since a year ago.
Set up Backstage in minutes with Roadie
Focus on using Backstage, rather than building and maintaining it.