Developers
Write the plugin. The platform does the rest.
A plugin is a small TypeScript project: one declaration, a Bun server and React screens. The platform signs every call, keeps the state and mounts the screens in the Portal.
A plugin in one command.
bun forge new <name> scaffolds a typed declaration, a Bun/TypeScript server, React 19 + Tailwind 4 screens, fixtures and a Playwright test. On every change, CI proves that a fresh scaffold builds, passes its tests and packs into an image.
import { definePlugin } from '@forge-plugins/server/define';
import { z } from 'zod';
export const plugin = definePlugin({
name: 'notes',
version: '0.1.0',
displayName: { cs: 'Poznámky', en: 'Notes' },
screens: { main: { title: { cs: 'Poznámky', en: 'Notes' } } },
state: {
schema: z.looseObject({ note: z.string().max(2000).optional() }),
maxBytes: 16_384,
},
ops: {
saveNote: {
privileges: ['TASKS'],
task: true,
effects: true,
timeoutMs: 5_000,
input: z.object({ note: z.string().max(2000) }),
},
},
});What it writes
plugin.ts- The declaration: screens, state, datasets and operations.
fixtures.ts- The synthetic task the harness and the test start from.
server/src/handlers.ts- Exactly one handler for each operation.
web/src/screens/main.tsx- The screen, built on the plugin UI kit.
web/e2e/<name>.spec.ts- The Playwright test, run against the dev harness.
import type { Handlers } from '@forge-plugins/server';
import type { plugin } from '../../plugin';
export const handlers = {
saveNote: (input, ctx) => ({
state: { ...ctx.state, note: input.note },
}),
} satisfies Handlers<typeof plugin>;An effect operation is a pure reducer: it returns the next state, and the platform commits it.
Declare once, typed everywhere.
One plugin.ts with zod schemas types the server’s handlers and the screens’ call() and stream(). Change a schema and the compiler shows every place that has to follow.
Typed end to end
Handlers receive typed input and a context shaped by the operation’s flags. Screens get typed state and a typed call for every operation.
The manifest is rendered
bun forge render writes manifest.json and the platform’s registry from plugin.ts. Nobody writes a manifest by hand.
Drift is blocked
bun forge doctor fails when a manifest or the registry no longer matches its declaration, when an operation has no handler, or when the built screens outgrow their budget.
Develop without the platform running.
bun forge dev <name> runs the web build in watch mode, the plugin’s real server and a harness that answers like the platform: signed calls, state written by compare-and-set, and the screen mounted exactly as the Portal mounts it.
The harness bar switches the screen, the user’s privileges, read-only mode, the theme and the language. The Playwright test runs against the same harness.

The declaration, field by field.
What plugin.ts declares and the platform enforces. The rendered manifest is checked against the versioned contract before the platform accepts it.
name- Lowercase and hyphenated, up to 40 characters. It becomes the plugin’s address on the platform.
version- The plugin’s semantic version.
displayName- The title users see, in Czech and English.
screens- Each screen a process designer can bind to a task form, titled in both languages. The element name is derived, never declared.
stateschemais the zod schema of the plugin’s one state document,maxBytesits size cap, up to 1 MiB. The platform checks every write against both.datasets- Business datasets the operations may read. They arrive with the call and are read with the user’s own rights.
ops- The operations the platform exposes for the plugin. Each one declares:
privileges- At least one; the user must hold one of them.
task- The operation runs against one task.
context- What the call carries: the state, the read-only submission, datasets.
effects- The reply’s state replaces the plugin state, committed by compare-and-set.
stream- The reply streams as server-sent events.
timeoutMs- The time budget per attempt.
input / output- zod schemas for what goes in and what comes back. They type the handler and the screen’s call.
The bun forge commands.
bun forge new <name>- Scaffolds a typed plugin and registers it: declaration, server, screens, fixtures and a Playwright test.
bun forge render- Writes the manifest and the registry from
plugin.ts. With--checkit only compares, for CI. bun forge dev <name>- Runs the plugin on the dev harness, mounted exactly as the Portal mounts it, and rebuilds as you save.
bun forge doctor- The one gate CI runs: rendered files match, every server boots with one handler per operation, and the built screens stay within their budget and the CSS contract.
Tell us the process that's costing you.
Bring one real process to the demo. We describe it to Studio together and you watch it take shape on a working platform — not on slides.