- Discover the{" "}
- Power of Backstage{" "}
- Plugins!
-
-
- Learn from a hands-on workshop derived from our experience with
- large enterprises. This isn't theory—it's actionable expertise. Join
- a workshop loaded with best practices, insights, and advanced
- engineering.
-
- The architecture of Backstage is ingeniously designed around
- plugins. Everything from the Service Catalog, Templates, Scaffolder,
- Search and Tech Docs are all plugins! This workshop will not only
- equip you with the skills to create and manage plugins - but start
- to build an understanding of a core component of grand architecture
- underpinning Backstage. Dive into our workshop, and empower yourself
- to shape and command Backstage Plugins!
-
-
-
-
-
- Mastering Plugins: {" "}
- Improve Your Internal Developer Portal Without Limitations
-
-
- To fully harness and customize Backstage tailored to your company's
- unique needs, a deep understanding of plugin development is
- indispensable. Without mastering plugins, you're confined to a limited
- realm of possibilities in Backstage. Break through these constraints
- and unlock a world of expansive features. Embrace the power of plugins
- and propel your capabilities to new horizons. Say yes to boundless
- innovation!
-
-
-
-
-
- Confidence to Innovate
-
-
- Grasping the intricacies of plugins doesn't just give you a
- technical advantage—it fuels your creativity. With this newfound
- knowledge, you'll confidently design and implement complex features
- and functionalities, turning your innovative ideas into powerful
- features.
-
-
-
-
-
-
-
- Deeper understanding of Backstage Architecture
-
-
- Go beyond mere plugin creation, understand plugins and decode the
- core of Backstage's architecture. These are not just plugins;
- they're the fundamental building blocks that shape Backstage. As you
- master plugins, watch your understanding of the entire ecosystem
- come alive.
-
-
-
-
-
-
-
- Maximize ROI, Engagement, & Adoption
-
-
- Mastering plugin development isn't just about technical prowess—it's
- an investment that brings exponential returns. By customizing
- Backstage to your unique needs, systems and users, you open doors to
- unparalleled solutions. Don't just adapt; lead. Witness your
- investments bear fruit as you craft amazing solutions and
- experiences for your users, and organizations.
-
-
-
-
-
-
Who is this workshop for?
-
-
- Experienced Backstage Developer:{" "}
- If you've already dabbled in Backstage and built a foundational
- understanding, but feel there's more to explore—especially in plugin
- development—this workshop is your next step. We're here to refine your
- skills and elevate your mastery.
-
-
- Developers Seeking Direction:{" "}
- Perhaps you haven't ventured into creating a Backstage plugin yet, If
- the term 'plugin' sounds daunting or if you’re unsure where to begin,
- fret not! We’ve tailored segments of our workshop especially for you.
- Think of this as a guided tour, ensuring you never feel lost, and
- always feel empowered to ask, learn, and grow.
-
-
- Teams with Aspirations:{" "}
- For teams eager to amplify their collective proficiency, this workshop
- is a goldmine. Equip your developer suite with the confidence and
- competence to craft sophisticated plugins, fostering a harmonized and
- empowered team environment.
-
-
- Everyone Eager to Learn:{" "}
- Whether you’re a seasoned developer, a newbie, or someone in between,
- if you have the zeal to learn, we have the insights to share. Join us,
- and let’s embark on this enlightening journey together.
-
-
-
-
-
What you will build
-
-
- Pipeline Monitor Plugin
-
-
- In this workshop, we’ll build a Pipeline Monitor plugin to pull
- information from external services and aggregate that information
- in a database. The plugin will allow your users to customize it
- via YAML. The functionality of the YAML syntax will be
- configurable with custom functions in the backend. We’ll create a
- backend plugin engine that will use configuration from the YAML
- file to execute pipeline monitoring by polling each state of the
- pipeline, retrieving the status, and storing the status in the
- database. We’ll implement a mechanism for generating and storing
- information summaries in the database. The raw data about the
- pipeline and the status will be accessible via the backend REST
- API. We’ll write a front-end plugin that will show information
- from the database.
-
-
-
-
-
-
-
-
-
-
-
Design
-
- Dive into custom frontend components tailored for Backstage's
- APIs. Learn the boundaries between React and Backstage APIs,
- ensuring components are efficient and interactive. Understand the
- vital link between frontend design and the plugin backend.
-
-
-
-
-
-
-
-
-
Build
-
- Navigate the creation of a robust backend. Design and deploy a
- specialized database for your plugin, ensuring optimal data flow
- between frontend and backend. Master the intricacies of seamless
- data storage and retrieval.
-
-
-
-
-
-
-
-
-
Test
-
- Embrace the importance of rigorous plugin testing. Uncover best
- practices to identify and rectify vulnerabilities, ensuring your
- plugin meets the highest quality benchmarks.
-
-
-
-
-
-
-
-
-
Ship
-
- Learn to bundle, build, and ship your plugin. Ensure your creation
- is packaged for easy integration, sharing your innovation
- internally or with the broader Backstage community.
-
-
-
-
-
-
-
What you will learn
-
-
Frontend
-
-
Create frontend components that work as a page or a card.
-
Create an API client for your backend API
-
Make the API client available via an API Ref.
-
Use your custom API ref in your components.
-
Configure your API ref using Backstage API configuration.
-
Customize the frontend components of a new custom kind.
-
-
-
-
Catalog
-
-
Define a new custom kind
-
- Create a new processor that will validate your kind using Zod
-
-
Emit custom relationships using processors
-
-
-
-
Backend
-
-
- Create a new backend plugin using a new backend extension
- system.
-
-
Create a REST API backend using OpenAPI specification.
-
- Generate types from OpenAPI specification.
-
-
Store and Share types between frontend and backend.
-
Write database migrations to create a database.
-
- Write database migrations to change the database structure.
-
-
Connect the database to the REST API
-
- Write a backend engine that will execute pipeline monitoring.
-
-
-
-
-
-
Workshop Instructors
-
-
-
-
-
-
-
- Official Backstage Professional Services Partners
-
-
- Frontside is recognized as a Professional Services Partner with
- Backstage and have helped companies of all sizes develop a wide
- range of solutions leveraging Plugins.
-
-
-
-
-
-
-
-
-
- Enterprise Level Backstage Experience
-
-
- Frontside has worked with some of the largest Enterprise companies
- with extremely complex, data models, and requirements for their
- plugins and overall system of plugins. We build plugins that can
- can to Enterprise.
-
-
-
-
-
-
-
-
-
- Early-Adopters, Highly Experienced Engineers
-
-
- Frontside has been using Backstage before the 1.0-release. Our
- engineers have a wealth of experience in Backstage and Backstage
- plugins, and their underlying technologies such as React and
- Typescript.
-
-
-
-
-
-
-
-
-
- Pioneering the Backstage Ecosystem
-
-
- Frontside both independently and in collaboration with our trusted
- partners has been at the forefront of Backstage innovation.
-
-
-
-
-
-
-
-
-
-
- Core Contributors
-
-
- Frontside has been diligently studying the core mechanics, and
- where applicable have contribution code to both the core and by
- creating plugins to enhance core and overall functionality.
-
-
-
-
-
-
-
-
-
-
- Engaged and Invested Community Members
-
-
- Proactive members of various Special Interest Groups, actively
- engage on the community Discord, and are regular attendees of
- Community and Contributor Backstage Sessions. Frontside’s
- commitment isn't just superficial; they are genuinely invested in
- uplifting and supporting fellow community members.
-
-
-
-
-
-
Trusted By
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
What our clients say
-
-
-
-
-
-
-
- "I owe so much to [Frontside]...who were like professors to me —
- and [ when needed ] told me 'no'. And they needed to tell me
- 'no' [sometimes]. Thank so you very much, guys, for that.”
-
-
-
- Guilherme G., Senior Software Engineer
-
-
-
-
-
-
-
-
- "Huge, thank you, huge...for investing in me and helping me move
- forward with my knowledge of the latest technologies. It was a
- daunting task for me to learn this tech stack, and you guys
- [invested] a lot in me. I really appreciate that. I really
- [have] been able to move forward and become confident in this
- technology set because of your guys' [investment] in me!"
-
-
-
- North K., Software Engineer
-
-
-
-
-
-
-
-
- "The Frontside developers have an incredibly high level of
- technical expertise. They've built complex plug-ins from
- scratch...I consider them educators as much as individual
- contributors, because they can communicate so clearly about
- their ideas, and they have deep skills to share."
-
-
-
- Benji S., Staff Software Engineer
-
-
-
-
-
-
- Join us to elevate your Backstage expertise and transform into a
- proficient plugin developer. Discover, learn, and kick-start your path
- to master the art of crafting sophisticated plugins for Backstage in
- this comprehensive, hands-on workshop.
-
- This is just a place holder for the `/` path. However, You won't see
- this page anywhere except development because this root url is
- currently inhabited by the Gatsby-based website.
-
-
- Have a look at these sub-sites which are visible:
-
-
-
-
- );
-}
diff --git a/lib/deps.ts b/lib/deps.ts
deleted file mode 100644
index 8c5db026..00000000
--- a/lib/deps.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export {
- match,
- type MatchFunction,
- type MatchResult,
-} from "https://deno.land/x/path_to_regexp@v6.2.1/index.ts";
diff --git a/lib/html.ts b/lib/html.ts
deleted file mode 100644
index 46626b4b..00000000
--- a/lib/html.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import type { HandlerOptions, ServeHandler } from "./types.ts";
-import type { Operation } from "effection";
-
-import { toHtml } from "https://esm.sh/hast-util-to-html@8.0.4";
-import { assert } from "https://deno.land/std@0.148.0/_util/assert.ts";
-
-import { twind } from "freejack/twind.ts";
-import { URLContext } from "freejack/view.ts";
-
-export interface HtmlHandler {
- (options: HandlerOptions): Operation;
-}
-
-export const html = {
- get(handler: HtmlHandler): ServeHandler {
- return function* ({ request, params }) {
- if (request.method.toUpperCase() !== "GET") {
- return new Response("Not Found", {
- status: 404,
- statusText: "Not Found",
- });
- }
-
- yield* URLContext.set(new URL(request.url));
-
- let top = yield* handler({ params, request });
-
- assert(top.type === "element");
- twind(top);
-
- let text = `${toHtml(top)}`;
-
- return new Response(text, {
- status: 200,
- statusText: "OK",
- headers: {
- "Content-Type": "text/html",
- },
- });
- };
- },
-};
diff --git a/lib/route-recognizer.ts b/lib/route-recognizer.ts
deleted file mode 100644
index 10b481e0..00000000
--- a/lib/route-recognizer.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { default as _RouteRecognizer } from "https://esm.sh/route-recognizer@0.3.4";
-
-export interface RouteRecognizer {
- add(segments: Pattern[]): void;
- recognize(uri: string): Segment[];
-}
-
-export interface Pattern {
- path: string;
- handler: unknown;
-}
-
-export interface Segment {
- handler: unknown;
- params: Record;
-}
-
-export function createRouteRecognizer(): RouteRecognizer {
- //@ts-expect-error this library is completely untyped;
- return new _RouteRecognizer();
-}
diff --git a/lib/router.ts b/lib/router.ts
deleted file mode 100644
index a088c7de..00000000
--- a/lib/router.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const main = Symbol.for("main");
diff --git a/lib/server.ts b/lib/server.ts
deleted file mode 100644
index efaadae3..00000000
--- a/lib/server.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-import {
- type Handler,
- serve as $serve,
-} from "https://deno.land/std@0.188.0/http/server.ts";
-import { serveDir } from "https://deno.land/std@0.190.0/http/file_server.ts";
-import { action, expect, type Operation, resource, useScope } from "effection";
-import { match, type MatchFunction } from "./deps.ts";
-import type { ServeHandler } from "./types.ts";
-
-export interface FreejackServerOptions {
- serve(): Operation;
- port: number;
- dir: string;
-}
-
-export interface FreejackServer {
- hostname: string;
- port: number;
-}
-
-export function useServer(
- options: FreejackServerOptions,
-): Operation {
- return resource(function* (provide) {
- let requestHandler = yield* options.serve();
-
- let handler: Handler = async (request, info) => {
- let pathname = new URL(request.url).pathname;
- if (pathname.startsWith("/assets")) {
- return serveDir(request, { fsRoot: options.dir });
- } else {
- return await requestHandler(request, info);
- }
- };
-
- let controller = new AbortController();
- let { signal } = controller;
-
- let [done, server] = yield* action<[Promise, FreejackServer]>(
- function* (resolve) {
- let promise = Promise.resolve();
- promise = $serve(handler, {
- port: options.port,
- signal,
- onListen(s) {
- resolve([promise, s]);
- },
- });
- },
- );
-
- try {
- yield* provide(server);
- } finally {
- controller.abort();
- yield* expect(done);
- }
- });
-}
-
-export function serve(group: Record): Operation {
- return resource(function* (provide) {
- let scope = yield* useScope();
-
- let matchers: [MatchFunction, ServeHandler, string][] = [];
-
- for (let [path, handler] of Object.entries(group)) {
- matchers.push([match(path), handler, path]);
- }
-
- yield* provide(function Handler(request) {
- return scope.run(function* () {
- try {
- let url = new URL(request.url);
-
- for (let [matcher, handler] of matchers) {
- let match = matcher(url.pathname);
- if (match) {
- let { params } = match;
- return yield* handler({ params, request });
- }
- }
- } catch (error) {
- return new Response(error.stack, {
- status: 500,
- statusText: "Internal Error",
- });
- }
- return new Response("Not Found", {
- status: 404,
- statusText: "Not Found",
- });
- });
- });
- });
-}
-
-type Next = {
- type: "continue";
-} | {
- type: "response";
- response: Response;
-};
-
-import { createContext } from "effection";
-const NextContext = createContext<(next: Next) => void>("next");
-
-export function next(): Operation {
- return {
- *[Symbol.iterator]() {
- let escape = yield* NextContext;
- escape({ type: "continue" });
- },
- };
-}
diff --git a/lib/types.ts b/lib/types.ts
deleted file mode 100644
index 45312136..00000000
--- a/lib/types.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import type { Operation } from "effection";
-import type { MatchResult } from "./deps.ts";
-
-export type Params
= MatchResult
["params"];
-
-export interface HandlerOptions
{
- params: Params
;
- request: Request;
-}
-
-export interface ServeHandler {
- (options: HandlerOptions): Operation;
-}
diff --git a/lib/view.ts b/lib/view.ts
deleted file mode 100644
index 0f543c39..00000000
--- a/lib/view.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { createContext, type Operation } from "effection";
-
-export const URLContext = createContext("url");
-
-export function* useURL(): Operation {
- return yield* URLContext;
-}
-
-const Outlet = createContext("outlet");
-
-export const outlet: Operation = {
- *[Symbol.iterator]() {
- return yield* Outlet;
- },
-};
-
-export function* url(path?: string): Operation {
- let base = yield* URLContext;
- let pathname = path ?? base.pathname;
- return new URL(pathname, base.origin).toString();
-}
-
-export function* render(
- ...templates: Operation[]
-): Operation {
- // won't be necessary when we migrate to HAST
- let content = null as unknown as JSX.Element;
- for (let template of templates.reverse()) {
- yield* Outlet.set(content);
- content = yield* template;
- }
- return content;
-}
diff --git a/lib/watch.ts b/lib/watch.ts
index 6a030193..6a6a7dcb 100644
--- a/lib/watch.ts
+++ b/lib/watch.ts
@@ -39,7 +39,7 @@ await run(function* () {
ignores.some((ignore) => path.includes(ignore))
);
}),
- );
+ ).subscribe();
yield* useCommand(cmd, { args });
@@ -59,13 +59,17 @@ await run(function* () {
});
function useFsWatch(paths: string | string[]): Stream {
- return resource(function* (provide) {
- let watcher = Deno.watchFs(paths);
- try {
- let subscription = yield* stream(watcher);
- yield* provide(subscription as Subscription);
- } finally {
- watcher.close();
- }
- });
+ return {
+ subscribe() {
+ return resource(function* (provide) {
+ let watcher = Deno.watchFs(paths);
+ try {
+ let subscription = yield* stream(watcher).subscribe();
+ yield* provide(subscription as Subscription);
+ } finally {
+ watcher.close();
+ }
+ });
+ },
+ };
}
diff --git a/main.ts b/main.ts
index 1675d65c..56e1a959 100644
--- a/main.ts
+++ b/main.ts
@@ -1,16 +1 @@
-import { main, suspend } from "effection";
-
-import { useServer } from "freejack/server.ts";
-import serve from "./sitemap/sitemap.ts";
-
-await main(function* () {
- let server = yield* useServer({
- serve,
- port: 8000,
- dir: new URL(".", import.meta.url).pathname,
- });
-
- console.log(`www -> http://${server.hostname}:${server.port}`);
-
- yield* suspend();
-});
+import "./main.tsx";
diff --git a/main.tsx b/main.tsx
new file mode 100644
index 00000000..e0ac4150
--- /dev/null
+++ b/main.tsx
@@ -0,0 +1,62 @@
+import { main, suspend } from "effection";
+
+import { createRevolution, route, serveDirMiddleware } from "revolution";
+
+import { proxyRoute } from "./routes/proxy-route.ts";
+import { pluginWorkshopRoute } from "./routes/advanced-backstage-plugin-development-route.tsx";
+
+import { etagPlugin } from "./plugins/etag.ts";
+import { currentRequestPlugin } from "./plugins/current-request.ts";
+import { twindPlugin } from "./plugins/twind.ts";
+import { config } from "./twind.config.ts";
+
+await main(function* () {
+ let proxies = proxySites();
+
+ let revolution = createRevolution({
+ app: [
+ route(
+ "/workshops/advanced-backstage-plugin-development",
+ pluginWorkshopRoute(),
+ ),
+ route("/effection(.*)", proxyRoute(proxies.effection)),
+ route("/graphgen(.*)", proxyRoute(proxies.graphgen)),
+ route(
+ "/assets(.*)",
+ serveDirMiddleware({
+ fsRoot: new URL(import.meta.resolve("./assets")).pathname,
+ urlRoot: "assets",
+ }),
+ ),
+ proxyRoute(proxies.legacy),
+ ],
+
+ plugins: [
+ etagPlugin(),
+ currentRequestPlugin(),
+ twindPlugin({ config }),
+ ],
+ });
+
+ let server = yield* revolution.start();
+ console.log(`www -> http://${server.hostname}:${server.port}`);
+
+ yield* suspend();
+});
+
+function proxySites() {
+ return {
+ effection: {
+ prefix: "effection",
+ website: Deno.env.get("EFFECTION_URL") ?? "https://effection.deno.dev",
+ },
+ graphgen: {
+ prefix: "graphgen",
+ website: Deno.env.get("GRAPHGEN_URL") ?? "https://graphgen.deno.dev",
+ },
+ legacy: {
+ prefix: "",
+ website: Deno.env.get("FS_LEGACY_URL") ?? "https://frontside.netlify.app",
+ },
+ } as const;
+}
diff --git a/plugins/current-request.ts b/plugins/current-request.ts
new file mode 100644
index 00000000..158d7baa
--- /dev/null
+++ b/plugins/current-request.ts
@@ -0,0 +1,37 @@
+import type { Operation } from "effection";
+import type { RevolutionPlugin } from "revolution";
+
+import { createContext } from "effection";
+import { posixNormalize } from "https://deno.land/std@0.203.0/path/_normalize.ts";
+
+const CurrentRequest = createContext("Request");
+
+export function currentRequestPlugin(): RevolutionPlugin {
+ return {
+ *http(request, next) {
+ yield* CurrentRequest.set(request);
+ return yield* next(request);
+ },
+ };
+}
+
+export function* useCurrentRequest() {
+ return yield* CurrentRequest;
+}
+
+/**
+ * Convert a non fully qualified url into a fully qualified url, complete
+ * with protocol.
+ */
+export function* useAbsoluteUrl(path: string): Operation {
+ let normalizedPath = posixNormalize(path);
+ let request = yield* useCurrentRequest();
+
+ if (normalizedPath.startsWith("/")) {
+ let url = new URL(request.url);
+ url.pathname = normalizedPath;
+ return url.toString();
+ } else {
+ return new URL(normalizedPath, request.url).toString();
+ }
+}
diff --git a/plugins/etag.ts b/plugins/etag.ts
new file mode 100644
index 00000000..a3c4578f
--- /dev/null
+++ b/plugins/etag.ts
@@ -0,0 +1,40 @@
+import { RevolutionPlugin } from "revolution";
+import { encodeBase64 } from "https://deno.land/std@0.206.0/encoding/base64.ts";
+
+const DEPLOYMENT_ID =
+ // The same deployment will be shared by the many isolates that serve it
+ // but because pages do not change, we can use this id as the ETAG
+ Deno.env.get("DENO_DEPLOYMENT_ID") ||
+ // For local development, just create a new id every time the module is
+ // reloaded i.e. whenever the dev server restarts.
+ crypto.randomUUID();
+
+const DEPLOYMENT_ID_HASH = await crypto.subtle.digest(
+ "SHA-1",
+ new TextEncoder().encode(DEPLOYMENT_ID),
+);
+
+const ETAG = `"${encodeBase64(DEPLOYMENT_ID_HASH)}"`;
+const WEAK_ETAG = `W/"${encodeBase64(DEPLOYMENT_ID_HASH)}"`;
+
+export function etagPlugin(): RevolutionPlugin {
+ return {
+ *http(request, next) {
+ let ifNoneMatch = request.headers.get("if-none-match");
+ if (ifNoneMatch === ETAG || ifNoneMatch === WEAK_ETAG) {
+ return new Response(null, {
+ status: 304,
+ statusText: "Not Modified",
+ });
+ } else {
+ let response = yield* next(request);
+ if (!response.headers.get("etag")) {
+ let tagged = new Response(response.body, response);
+ tagged.headers.set("etag", ETAG);
+ return tagged;
+ }
+ return response;
+ }
+ },
+ };
+}
diff --git a/plugins/twind.ts b/plugins/twind.ts
new file mode 100644
index 00000000..04641f13
--- /dev/null
+++ b/plugins/twind.ts
@@ -0,0 +1,54 @@
+import type { HASTElement, RevolutionPlugin } from "revolution";
+import {
+ setup,
+ stringify,
+ tw,
+ type TwindConfig,
+ virtual,
+} from "npm:@twind/core@1.1.3";
+
+export interface TwindRevolution {
+ config: TwindConfig;
+}
+
+export function twindPlugin(options: TwindRevolution): RevolutionPlugin {
+ let { config } = options;
+
+ return {
+ *html(request, next) {
+ let html = yield* next(request);
+ let sheet = virtual();
+
+ setup(config, sheet);
+
+ visit(html);
+
+ let css = stringify(sheet.target);
+
+ let head = html.children.find((child) =>
+ child.type === "element" && child.tagName === "head"
+ ) as HASTElement | undefined;
+
+ head?.children.push({
+ type: "element",
+ tagName: "style",
+ properties: { type: "text/css" },
+ children: [{ type: "text", value: css }],
+ });
+
+ return html;
+ },
+ };
+}
+
+function visit(element: HASTElement): void {
+ let { properties: { className: classnames } = {}, children } = element;
+ if (classnames) {
+ tw(String(classnames));
+ }
+ for (let child of children) {
+ if (!!child && child.type === "element") {
+ visit(child);
+ }
+ }
+}
diff --git a/routes/advanced-backstage-plugin-development-route.tsx b/routes/advanced-backstage-plugin-development-route.tsx
new file mode 100644
index 00000000..b88e5e5a
--- /dev/null
+++ b/routes/advanced-backstage-plugin-development-route.tsx
@@ -0,0 +1,822 @@
+import type { Operation } from "effection";
+import type { JSXHandler } from "revolution";
+
+import { useAppHtml } from "./app.html.tsx";
+
+export function pluginWorkshopRoute(): JSXHandler {
+ return function* () {
+ let logoHumanitec = "../assets/client-logos/logo-humanitec.svg";
+ let logoHP = "../assets/client-logos/logo-HP-black.svg";
+ let logoIndeed = "../assets/client-logos/logo-indeed.svg";
+ let logoResideo = "../assets/client-logos/logo-resideo.svg";
+ let logoEricsson = "../assets/client-logos/logo-ericsson.svg";
+ let headerImage = "../assets/pluginWorkshopHeaderImagev2.png";
+ let benefitsImage1 = "../assets/benefitsImage1.svg";
+ let benefitsImage2 = "../assets/benefitsImage2.svg";
+ let benefitsImage3 = "../assets/benefitsImage3.svg";
+
+ let AppHtml = yield* useAppHtml({
+ title: "Frontside: Advanced Backstage Plugin Development",
+ description:
+ "Harness Backstage's Potential: Become an Advanced Plugin Developer",
+ ogImage: "/assets/pluginWorkshopHeaderImagev2.png",
+ twitterXImage: "/assets/pluginWorkshopHeaderImagev2.png",
+ author: "Frontside",
+ });
+
+ return (
+
+
+
+
+
+
+ Discover the{" "}
+ Power of Backstage{" "}
+ Plugins!
+
+
+ Learn from a hands-on workshop derived from our experience with
+ large enterprises. This isn't theory—it's actionable expertise.
+ Join a workshop loaded with best practices, insights, and
+ advanced engineering.
+
+ The architecture of Backstage is ingeniously designed around
+ plugins. Everything from the Service Catalog, Templates,
+ Scaffolder, Search and Tech Docs are all plugins! This workshop
+ will not only equip you with the skills to create and manage
+ plugins - but start to build an understanding of a core
+ component of grand architecture underpinning Backstage. Dive
+ into our workshop, and empower yourself to shape and command
+ Backstage Plugins!
+
+
+
+
+
+ Mastering Plugins: {" "}
+ Improve Your Internal Developer Portal Without Limitations
+
+
+ To fully harness and customize Backstage tailored to your
+ company's unique needs, a deep understanding of plugin development
+ is indispensable. Without mastering plugins, you're confined to a
+ limited realm of possibilities in Backstage. Break through these
+ constraints and unlock a world of expansive features. Embrace the
+ power of plugins and propel your capabilities to new horizons. Say
+ yes to boundless innovation!
+
+
+
+
+
+ Confidence to Innovate
+
+
+ Grasping the intricacies of plugins doesn't just give you a
+ technical advantage—it fuels your creativity. With this newfound
+ knowledge, you'll confidently design and implement complex
+ features and functionalities, turning your innovative ideas into
+ powerful features.
+
+
+
+
+
+
+
+ Deeper understanding of Backstage Architecture
+
+
+ Go beyond mere plugin creation, understand plugins and decode
+ the core of Backstage's architecture. These are not just
+ plugins; they're the fundamental building blocks that shape
+ Backstage. As you master plugins, watch your understanding of
+ the entire ecosystem come alive.
+
+
+
+
+
+
+
+ Maximize ROI, Engagement, & Adoption
+
+
+ Mastering plugin development isn't just about technical
+ prowess—it's an investment that brings exponential returns. By
+ customizing Backstage to your unique needs, systems and users,
+ you open doors to unparalleled solutions. Don't just adapt;
+ lead. Witness your investments bear fruit as you craft amazing
+ solutions and experiences for your users, and organizations.
+
+
+
+
+
+
Who is this workshop for?
+
+
+ Experienced Backstage Developer:{" "}
+ If you've already dabbled in Backstage and built a foundational
+ understanding, but feel there's more to explore—especially in
+ plugin development—this workshop is your next step. We're here to
+ refine your skills and elevate your mastery.
+
+
+ Developers Seeking Direction:{" "}
+ Perhaps you haven't ventured into creating a Backstage plugin yet,
+ If the term 'plugin' sounds daunting or if you’re unsure where to
+ begin, fret not! We’ve tailored segments of our workshop
+ especially for you. Think of this as a guided tour, ensuring you
+ never feel lost, and always feel empowered to ask, learn, and
+ grow.
+
+
+ Teams with Aspirations:{" "}
+ For teams eager to amplify their collective proficiency, this
+ workshop is a goldmine. Equip your developer suite with the
+ confidence and competence to craft sophisticated plugins,
+ fostering a harmonized and empowered team environment.
+
+
+ Everyone Eager to Learn:{" "}
+ Whether you’re a seasoned developer, a newbie, or someone in
+ between, if you have the zeal to learn, we have the insights to
+ share. Join us, and let’s embark on this enlightening journey
+ together.
+
+
+
+
+
What you will build
+
+
+ Pipeline Monitor Plugin
+
+
+ In this workshop, we’ll build a Pipeline Monitor plugin to
+ pull information from external services and aggregate that
+ information in a database. The plugin will allow your users to
+ customize it via YAML. The functionality of the YAML syntax
+ will be configurable with custom functions in the backend.
+ We’ll create a backend plugin engine that will use
+ configuration from the YAML file to execute pipeline
+ monitoring by polling each state of the pipeline, retrieving
+ the status, and storing the status in the database. We’ll
+ implement a mechanism for generating and storing information
+ summaries in the database. The raw data about the pipeline and
+ the status will be accessible via the backend REST API. We’ll
+ write a front-end plugin that will show information from the
+ database.
+
+
+
+
+
+
+
+
+
+
+
Design
+
+ Dive into custom frontend components tailored for Backstage's
+ APIs. Learn the boundaries between React and Backstage APIs,
+ ensuring components are efficient and interactive. Understand
+ the vital link between frontend design and the plugin backend.
+
+
+
+
+
+
+
+
+
Build
+
+ Navigate the creation of a robust backend. Design and deploy a
+ specialized database for your plugin, ensuring optimal data
+ flow between frontend and backend. Master the intricacies of
+ seamless data storage and retrieval.
+
+
+
+
+
+
+
+
+
Test
+
+ Embrace the importance of rigorous plugin testing. Uncover
+ best practices to identify and rectify vulnerabilities,
+ ensuring your plugin meets the highest quality benchmarks.
+
+
+
+
+
+
+
+
+
Ship
+
+ Learn to bundle, build, and ship your plugin. Ensure your
+ creation is packaged for easy integration, sharing your
+ innovation internally or with the broader Backstage community.
+
+
+
+
+
+
+
What you will learn
+
+
Frontend
+
+
+ Create frontend components that work as a page or a card.
+
+
Create an API client for your backend API
+
Make the API client available via an API Ref.
+
Use your custom API ref in your components.
+
+ Configure your API ref using Backstage API configuration.
+
+
+ Customize the frontend components of a new custom kind.
+
+
+
+
+
Catalog
+
+
Define a new custom kind
+
+ Create a new processor that will validate your kind using
+ Zod
+
+
Emit custom relationships using processors
+
+
+
+
Backend
+
+
+ Create a new backend plugin using a new backend extension
+ system.
+
+
+ Create a REST API backend using OpenAPI specification.
+
+
+ Generate types from OpenAPI specification.
+
+
Store and Share types between frontend and backend.
+
Write database migrations to create a database.
+
+ Write database migrations to change the database structure.
+
+
Connect the database to the REST API
+
+ Write a backend engine that will execute pipeline
+ monitoring.
+
+
+
+
+
+
Workshop Instructors
+
+
+
+
+
+
+
+ Official Backstage Professional Services Partners
+
+
+ Frontside is recognized as a Professional Services Partner
+ with Backstage and have helped companies of all sizes develop
+ a wide range of solutions leveraging Plugins.
+
+
+
+
+
+
+
+
+
+ Enterprise Level Backstage Experience
+
+
+ Frontside has worked with some of the largest Enterprise
+ companies with extremely complex, data models, and
+ requirements for their plugins and overall system of plugins.
+ We build plugins that can can to Enterprise.
+
+
+
+
+
+
+
+
+
+ Early-Adopters, Highly Experienced Engineers
+
+
+ Frontside has been using Backstage before the 1.0-release. Our
+ engineers have a wealth of experience in Backstage and
+ Backstage plugins, and their underlying technologies such as
+ React and Typescript.
+
+
+
+
+
+
+
+
+
+ Pioneering the Backstage Ecosystem
+
+
+ Frontside both independently and in collaboration with our
+ trusted partners has been at the forefront of Backstage
+ innovation.
+
+
+
+
+
+
+
+
+
+
+ Core Contributors
+
+
+ Frontside has been diligently studying the core mechanics,
+ and where applicable have contribution code to both the core
+ and by creating plugins to enhance core and overall
+ functionality.
+
+
+
+
+
+
+
+
+
+
+ Engaged and Invested Community Members
+
+
+ Proactive members of various Special Interest Groups, actively
+ engage on the community Discord, and are regular attendees of
+ Community and Contributor Backstage Sessions. Frontside’s
+ commitment isn't just superficial; they are genuinely invested
+ in uplifting and supporting fellow community members.
+
+
+
+
+
+
Trusted By
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
What our clients say
+
+
+
+
+
+
+
+ "I owe so much to [Frontside]...who were like professors to
+ me — and [ when needed ] told me 'no'. And they needed to
+ tell me 'no' [sometimes]. Thank so you very much, guys, for
+ that.”
+
+
+
+ Guilherme G., Senior Software Engineer
+
+
+
+
+
+
+
+
+ "Huge, thank you, huge...for investing in me and helping me
+ move forward with my knowledge of the latest technologies.
+ It was a daunting task for me to learn this tech stack, and
+ you guys [invested] a lot in me. I really appreciate that. I
+ really [have] been able to move forward and become confident
+ in this technology set because of your guys' [investment] in
+ me!"
+
+
+
+ North K., Software Engineer
+
+
+
+
+
+
+
+
+ "The Frontside developers have an incredibly high level of
+ technical expertise. They've built complex plug-ins from
+ scratch...I consider them educators as much as individual
+ contributors, because they can communicate so clearly about
+ their ideas, and they have deep skills to share."
+
+
+
+ Benji S., Staff Software Engineer
+
+
+
+
+
+
+ Join us to elevate your Backstage expertise and transform into a
+ proficient plugin developer. Discover, learn, and kick-start your
+ path to master the art of crafting sophisticated plugins for
+ Backstage in this comprehensive, hands-on workshop.
+