# How Extensions Are Manifested and Loaded in OpenWork: A Technical Deep Dive

> Explore how OpenWork manifests and loads extensions. Discover dynamic ESM bundle imports, runtime registration, and policy-gated capabilities within the OpenWork technical deep dive.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: deep-dive
- Published: 2026-08-06

---

**OpenWork discovers extensions through a server-generated JSON manifest, dynamically imports each extension's ESM bundle at runtime, and registers their capabilities into a global `openworkContext` object that is gated by configurable desktop policies.**

In the `different-ai/openwork` codebase, extensions are first-class plugins that augment the desktop client with new UI panels, commands, and provider integrations. Understanding exactly how extensions are manifested and loaded in OpenWork requires tracing the lifecycle from server-side manifest generation through client-side dynamic import and policy enforcement.

## The Three-Phase Extension Lifecycle

OpenWork treats extension loading as a structured pipeline with distinct discovery, registration, and enforcement phases.

### Phase 1: Manifest Discovery on the Server

At workspace startup, the server aggregates available extensions from three distinct sources: built-in extensions shipped with the application, locally installed plugins found under `.opencode/plugins/`, and marketplace extensions enabled for the organization. The core logic resides in [`apps/server/src/extensions-export.js`](https://github.com/different-ai/openwork/blob/main/apps/server/src/extensions-export.js), which exports the `exportExtensions` function. This utility scans the local plugin directory for `.ts` and `.js` files, extracts the `manifest` export from each, and merges these entries with the built-in catalog to produce a unified JSON manifest.

### Phase 2: Loading and Runtime Registration

The desktop client fetches this manifest via a GET request to `/workspace/:id/extensions/export`. For each entry in the manifest, the client performs a dynamic import using `import(entryPointUrl)`, where the URL may be a local `file://` path or an HTTPS URL for marketplace extensions. The imported module must export a `registerExtension` function, which receives the global `openworkContext` defined in [`packages/types/src/openwork-context.ts`](https://github.com/different-ai/openwork/blob/main/packages/types/src/openwork-context.ts). This context maintains an internal registry of type `kind: "extensions"` (among other kinds) and exposes methods like `registerPanel()` and `registerCommand()` that inject the extension's capabilities into the OpenWork runtime.

### Phase 3: Policy Enforcement and UI Presentation

Before rendering extensions in the *Extensions* settings panel or command palette, the client consults desktop policies defined in [`packages/types/src/den/desktop-policies.ts`](https://github.com/different-ai/openwork/blob/main/packages/types/src/den/desktop-policies.ts). These policies include `"extensions"` (master toggle) and `"localExtensions"` (local installation permission). If a policy disables a specific class of extensions, the UI omits those cards and the runtime marks them as disabled, effectively sandboxing unauthorized functionality.

## Server-Side Manifest Generation

The `exportExtensions` function in [`apps/server/src/extensions-export.js`](https://github.com/different-ai/openwork/blob/main/apps/server/src/extensions-export.js) serves as the single source of truth for extension availability. It recursively scans the `.opencode/plugins/` directory, validates discovered plugin files, and constructs a manifest array containing metadata such as `id`, `name`, `description`, and `entry` points. This manifest is cached and served to clients upon workspace initialization, ensuring that the client has a complete catalog before attempting any dynamic imports.

## The OpenWork Context and Registration API

The `openworkContext` object acts as the dependency injection container for extensions. According to [`packages/types/src/openwork-context.ts`](https://github.com/different-ai/openwork/blob/main/packages/types/src/openwork-context.ts), extensions register themselves by calling context methods that populate internal maps for panels, commands, and MCPs. The context also tracks diagnostic state via `expectedBranch` values such as `"extensions-only"`, which indicates when the UI should limit functionality to extension-specific workflows. Registration is synchronous and atomic; once an extension calls `registerExtension()`, its capabilities are immediately available to the command palette and control pane without requiring an application restart.

## Security and Policy Gating

Security is enforced at the policy layer defined in [`packages/types/src/den/desktop-policies.ts`](https://github.com/different-ai/openwork/blob/main/packages/types/src/den/desktop-policies.ts). Administrators can disable local extension management entirely using the policy `"Allow users to install and manage extensions locally."` or hide built-in extensions using `"Allow users to see and use OpenWork's built-in extensions..."`. These boolean flags are evaluated in the UI layer before rendering extension cards, ensuring that policy violations never reach the execution phase. End-to-end tests in [`evals/specs/skills-local.slow.test.ts`](https://github.com/different-ai/openwork/blob/main/evals/specs/skills-local.slow.test.ts) verify that policy-compliant extensions like **OpenWork Browser** are correctly loaded and listed, while restricted extensions remain hidden.

## Implementing a Local Extension

Developers can create local extensions by placing TypeScript files in the `.opencode/plugins/` directory. Each extension must export a `manifest` object and a `registerExtension` function.

```typescript
// .opencode/plugins/hello-world-extension.ts
export const manifest = {
  id: "hello-world",
  name: "Hello World",
  description: "Shows a greeting panel",
  entry: "hello-world-extension.js",
};

export async function registerExtension(openworkContext) {
  // Register a new panel in the control pane
  openworkContext.registerPanel({
    id: "hello-world-panel",
    title: "Hello World",
    render: () => <div>Hello, OpenWork!</div>,
  });

  // Expose a command to open the panel
  openworkContext.registerCommand({
    id: "hello-world.show",
    title: "Show Hello World",
    action: () => openworkContext.openPanel("hello-world-panel"),
  });
}

```

When the desktop client initializes, the server's `exportExtensions` includes this plugin in the manifest. The client dynamically imports the entry point, invokes `registerExtension` with the global context, and the panel and command become instantly available. Flow tests in [`evals/flows/extensions-sidebar-and-header.flow.ts`](https://github.com/different-ai/openwork/blob/main/evals/flows/extensions-sidebar-and-header.flow.ts) validate this end-to-end integration.

## Summary

- **Manifest Discovery**: The server aggregates extensions from built-in catalogs and `.opencode/plugins/` via [`apps/server/src/extensions-export.js`](https://github.com/different-ai/openwork/blob/main/apps/server/src/extensions-export.js).
- **Dynamic Loading**: The client fetches the manifest and performs `import()` on each extension's ESM bundle, registering capabilities into `openworkContext`.
- **Policy Enforcement**: Desktop policies in [`packages/types/src/den/desktop-policies.ts`](https://github.com/different-ai/openwork/blob/main/packages/types/src/den/desktop-policies.ts) gate visibility and execution, preventing unauthorized extensions from loading.
- **Hot Reload**: Local extensions can be added to `.opencode/plugins/` and loaded without restarting the application, triggering a manifest refresh and dynamic registration.

## Frequently Asked Questions

### What file format does OpenWork use for extension manifests?

OpenWork expects each extension to export a JavaScript object named `manifest` containing `id`, `name`, `description`, and `entry` properties. This manifest is consumed by the server's `exportExtensions` function and validated before being sent to the client as JSON.

### Where does OpenWork look for locally installed extensions?

The server scans the `.opencode/plugins/` directory within the user's workspace for files ending in `.ts` or `.js`. These paths are resolved and included in the extension manifest generated by [`apps/server/src/extensions-export.js`](https://github.com/different-ai/openwork/blob/main/apps/server/src/extensions-export.js).

### How does OpenWork handle security for third-party extensions?

Security is enforced through desktop policies defined in [`packages/types/src/den/desktop-policies.ts`](https://github.com/different-ai/openwork/blob/main/packages/types/src/den/desktop-policies.ts). Administrators can disable local extension installation or hide specific extension categories, ensuring that only vetted marketplace extensions or built-in plugins are visible in the UI and executable by the runtime.

### Can extensions be loaded without restarting the OpenWork desktop app?

Yes. When a user adds a new plugin to `.opencode/plugins/`, the server detects the change and updates the extension manifest. The client can then dynamically import the new module and call its `registerExtension` function against the live `openworkContext`, making the new functionality available immediately without an application restart.