S3 Viewer adds an S3 browser to your Backstage portal. It gives you a page to explore buckets and objects in a simple table. You can view object details. You can preview small images. You can download files when allowed. Requests go through your Backstage backend so auth and permissions stay in place.
The plugin fits day to day work. Use it to inspect build artifacts, logs, static docs, and media stored in S3. Open a bucket and click through folders like a file explorer. Copy a deep link to share a specific path or object with a teammate. Keep focus in one place instead of jumping to external consoles.
Under the hood it ships a frontend and a backend. The backend exposes endpoints to list buckets, fetch metadata, preview content, and stream or download objects. The UI reads from those endpoints and renders a clean view of what is inside S3. It also supports generating shareable URLs for the exact item you are viewing. These choices make the plugin useful for support work, incident triage, and reviews during delivery.
S3 Viewer is listed in the Backstage plugin marketplace as a way to visualize S3 buckets and their contents in file explorer style. The code lives in a public GitHub repo and sees active updates.
Installation Instructions
These instructions apply to self-hosted Backstage only.
Install the frontend package
# from your Backstage root
yarn --cwd packages/app add @spreadshirt/backstage-plugin-s3-viewer
Add the page route
// packages/app/src/App.tsx
import { S3ViewerPage } from '@spreadshirt/backstage-plugin-s3-viewer';
// inside your <FlatRoutes> in the returned App component
// other routes
<Route path="/s3-viewer" element={<S3ViewerPage />} />
Add a sidebar link
// packages/app/src/components/Root/Root.tsx
import { SidebarItem } from '@backstage/core-components';
import { SiAmazons3 } from 'react-icons/si';
// inside <SidebarPage>
<SidebarItem icon={SiAmazons3} to="s3-viewer" text="S3 Viewer" />
Install the backend package new backend system
# from your Backstage root
yarn --cwd packages/backend add @spreadshirt/backstage-plugin-s3-viewer-backend
Register the backend plugin new backend system
// packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';
const backend = createBackend();
// other backend plugins
backend.add(import('@spreadshirt/backstage-plugin-s3-viewer-backend'));
backend.start();
Configure S3 connections
Add this to your app config. Adjust names endpoints regions and secrets for your setup.
# app-config.yaml
s3:
bucketLocatorMethods:
- type: config
platforms:
- endpoint: http://endpoint-one.com
name: endpoint-one-name
region: us-east-1
accessKeyId: ${ENDPOINT_ONE_ACCESS_KEY}
secretAccessKey: ${ENDPOINT_ONE_SECRET_KEY}
- endpoint: http://endpoint-two.com
name: endpoint-two-name
region: us-east-1
accessKeyId: ${ENDPOINT_TWO_ACCESS_KEY}
secretAccessKey: ${ENDPOINT_TWO_SECRET_KEY}
- type: radosgw-admin
platforms:
- endpoint: http://radosgw-endpoint.com
name: radosgw-endpoint-name
region: us-east-1
accessKeyId: ${RADOSGW_ACCESS_KEY}
secretAccessKey: ${RADOSGW_SECRET_KEY}
- type: iam-role
platforms:
- endpoint: http://iam-endpoint.com
name: iam-endpoint-name
region: us-east-1
allowedBuckets:
- platform: endpoint-one-name
buckets:
- allowed-bucket-one
- allowed-bucket-two
- platform: radosgw-endpoint-name
buckets:
- other-allowed-bucket
- platform: iam-endpoint-name
buckets:
- another-bucket-name
bucketRefreshSchedule:
frequency: { minutes: 30 }
timeout: { minutes: 1 }
Set the cookie on sign in for preview and download
The UI fetches images and downloads through the browser. Set the cookie after sign in so those requests are authorized.
// packages/app/src/App.tsx
import React from 'react';
import { createApp } from '@backstage/app-defaults';
import { SignInPage } from '@backstage/core-components';
import { useApi, IdentityApi } from '@backstage/core-plugin-api';
import { S3ApiRef } from '@spreadshirt/backstage-plugin-s3-viewer';
const app = createApp({
components: {
SignInPage: props => {
const s3ViewerApi = useApi(S3ApiRef);
return (
<SignInPage
{...props}
provider={{
id: 'guest',
title: 'Guest',
message: 'Login as a guest',
}}
onSignInSuccess={async (identityApi: IdentityApi) => {
props.onSignInSuccess(identityApi);
await s3ViewerApi.setCookie();
}}
/>
);
},
},
});
export default app;
No extra backend code is needed for this step when using the new backend system. The backend plugin provides the endpoint used by setCookie.
Add a permission policy for S3 viewer
Decide what users can do. Here is a simple example that allows listing but denies object download. Wire this in your permission backend as you do for other policies.
// packages/backend/src/plugins/permission.ts
import {
isPermission,
isResourcePermission,
type PolicyDecision,
type PolicyQuery,
AuthorizeResult,
type PermissionPolicy,
} from '@backstage/plugin-permission-node';
import {
S3_VIEWER_RESOURCE_TYPE,
s3ViewerPermissions,
} from '@spreadshirt/backstage-plugin-s3-viewer-common';
import { type BackstageIdentityResponse } from '@backstage/plugin-auth-node';
export class CustomPolicy implements PermissionPolicy {
async handle(
request: PolicyQuery,
_user?: BackstageIdentityResponse,
): Promise<PolicyDecision> {
if (isResourcePermission(request.permission, S3_VIEWER_RESOURCE_TYPE)) {
if (isPermission(request.permission, s3ViewerPermissions.s3ObjectDownload)) {
return { result: AuthorizeResult.DENY };
}
return { result: AuthorizeResult.ALLOW };
}
return { result: AuthorizeResult.ALLOW };
}
}
You can also use conditional decisions to filter visible buckets. Use helpers from the common package if you need that.
Optional refresh API setup
You can trigger a bucket refresh on demand with a service token.
Static token example
# app-config.yaml
backend:
auth:
externalAccess:
- type: static
options:
token: ${S3_API_REFRESH_TOKEN}
subject: s3-viewier-api-test-token
accessRestrictions:
- plugin: s3-viewer
Plugin to plugin token example
// inside a backend module
const { token } = await auth.getPluginRequestToken({
onBehalfOf: await auth.getOwnServiceCredentials(),
targetPluginId: 's3-viewer',
});
// call POST /api/s3-viewer/buckets/refresh with Authorization Bearer <token>
About the old backend system
Current versions of this plugin support the new backend system only. If your app still runs the old backend system, migrate to the new backend system before installing this plugin.
Changelog
This changelog is produced from commits made to the S3 Viewer plugin since 3 months ago, and based on the code located here. It may not contain information about all commits. Releases and version bumps are intentionally omitted. This changelog is generated by AI.
Breaking changes
- Change the stream route format to support relative refs in html. The endpoint now sits in the path. The key can include escaped and plain slashes. This can break callers that use the old stream path. #168 merged 3 months ago
Features
- Allow viewing html files in the browser. Stop sending attachment for html. #158 merged 3 months ago
- Add an API to refresh buckets on demand with a service token. Useful for service to service calls. #160 merged 3 months ago
- Improve paging. Use a fixed total so you can move past the second page and jump to the last page. #167 merged 3 months ago
- Support relative refs in html when streaming objects. #168 merged 3 months ago
Bug fixes
- Respect object content encoding metadata. Browsers now render gzipped html correctly. #163 merged 3 months ago
- Fix html content type check logic. #165 merged 3 months ago
- Do not force the download attribute on links. Rely on the presence of a response header instead. #162 merged 3 months ago
Documentation
- Add docs for the new s3 api. Update changeset notes. #164 merged 3 months ago
Maintenance
- Bump Backstage dependencies to version 1.43.2. No code changes in the plugin. #177 merged 2 weeks ago
- Bump Backstage to the latest. Align other packages with upstream. Replace momentjs with luxon. Address security issues. #175 merged 1 month ago
- Bump to the latest Backstage. Address security alerts. Update the test backend plugin to match the new major. #171 merged 2 months ago
Set up Backstage in minutes with Roadie
Focus on using Backstage, rather than building and maintaining it.