---
title: "Devframe Definition"
description: "One defineDevframe call returns a portable DevframeDefinition any adapter consumes."
canonical_url: "https://devfra.me/guide/devframe-definition"
---
# Devframe Definition

> One defineDevframe call returns a portable DevframeDefinition any adapter consumes.

One `defineDevframe` call returns a portable `DevframeDefinition` any [adapter](https://devfra.me/adapters) consumes.

## Minimal definition

```ts
import { defineDevframe, defineRpcFunction } from 'devframe'
import * as v from 'valibot' // npm i valibot

export default defineDevframe({
  id: 'my-tool',
  name: 'My Tool',
  version: '1.0.0',
  packageName: 'my-tool',
  importMetaUrl: import.meta.url,
  homepage: 'https://github.com/me/my-tool',
  description: 'A one-line summary of what the tool does.',
  icon: 'ph:gauge-duotone',
  setup(ctx) {
    // A scoped context auto-namespaces ids with your devframe `id`.
    const my = ctx.scope('my-tool')

    // Register your RPC functions, shared state, etc. here.
    my.rpc.register(defineRpcFunction({
      name: 'hello', // stored as `my-tool:hello`
      type: 'static',
      jsonSerializable: true,
      handler: () => ({ message: 'hello' }),
    }))
  },
})
```

## Definition fields

`id`, `name`, `version`, `packageName`, `homepage`, `description`, and `setup` are required; pass `importMetaUrl: import.meta.url` so [remote assets](https://devfra.me/guide/client-assets) and declared [services](https://devfra.me/guide/services#wire-services) resolve against the devframe's own dependencies. The remaining fields cover display (`icon`, [Icon values](https://devfra.me/references/node-api#icon-values)), mounting (`basePath`, `duplicationStrategy`, `capabilities`), what the devframe consumes and serves (`services`, `clientAssets`, `rpc.snapshot`), and [CLI defaults](#cli-options) (`cli`). Every field is listed in the [Node-Side API reference](https://devfra.me/references/node-api#definition-fields).

### Sourcing metadata from `package.json`

Import metadata from `package.json` (`name` → `packageName`; devframe `name` is a separate display label):

```ts
import pkg from '../package.json' with { type: 'json' }

export default defineDevframe({
  id: 'my-tool',
  name: 'My Tool', // display label
  version: pkg.version,
  packageName: pkg.name,
  importMetaUrl: import.meta.url,
  homepage: pkg.homepage,
  description: pkg.description,
  setup(ctx) { /* … */ },
})
```

### Resolving against the devframe's own dependencies

`importMetaUrl` resolves companion packages against the devframe's **own** dependencies:

```ts
export default defineDevframe({
  /** …metadata as above */
  importMetaUrl: import.meta.url,
  /**
   * Served from the locally installed `my-tool--assets`, resolved via
   * `importMetaUrl`; it works under pnpm's strict layout with zero network.
   */
  clientAssets: { package: `${pkg.name}--assets`, version: pkg.version },
  /** Imported from `my-tool`'s own dependency graph. */
  services: [
    { package: '@scope/my-service', version: pkg.version }
  ],
  setup(ctx) { /* … */ },
})
```

For remote assets, `importMetaUrl` is the default `resolveFrom`; a per-source value wins; `resolveFrom: null` opts out.

### Serving the UI with `clientAssets`

```ts
import { fileURLToPath } from 'node:url'

export default defineDevframe({
  /** …metadata as above */
  importMetaUrl: import.meta.url,
  /**
   * A local build resolved from the module; it works from source and the
   * published package.
   */
  clientAssets: fileURLToPath(new URL('../dist/spa', import.meta.url)),
  setup(ctx) { /* … */ },
})
```

For assets you host yourself, call `ctx.views.hostStatic` in `setup`; see [Client Assets](https://devfra.me/guide/client-assets#programmatic-hosting-from-setup).

### Runtime flags

`ctx.mode` (`'dev'`/`'build'`) gates runtime-specific work:

```ts
defineDevframe({
  id: 'my-tool',
  name: 'My Tool',
  setup(ctx) {
    if (ctx.mode === 'build') {
      // Static-only work, baked into the RPC dump.
      ctx.rpc.addFunctions(staticFunctions)
    }
    else {
      // Dev-mode wiring, file watchers, etc.
      watchProject(ctx)
    }
  },
})
```

The CLI dev server sets `mode: 'dev'`; `createBuild`, `'build'`.

## The setup context

`setup(ctx)` receives a `DevframeNodeContext`:

```ts
interface DevframeNodeContext {
  readonly cwd: string
  readonly workspaceRoot: string
  readonly mode: 'dev' | 'build'

  host: DevframeHost // runtime abstraction (mountStatic / resolveOrigin / getStorageDir)
  rpc: RpcFunctionsHost // register + broadcast + sharedState
  views: DevframeViewHost // static file hosting (`hostStatic`)
  diagnostics: DevframeDiagnosticsHost
  agent: DevframeAgentHost // expose tools + resources to coding agents
  services: DevframeServicesHost // typed cross-devframe service registry
  staticConfig: Partial<DevframeConnectionConfigsRegistry> // this context's own ConnectionMeta.configs

  scope: (id) => DevframeScopedNodeContext // namespaced view (preferred)
}
```

### Cross-devframe services

`ctx.services` is a typed, namespaced registry: one devframe exposes a capability, others consume it ([Cross-Devframe Services](https://devfra.me/guide/services)).

```ts
ctx.services.provide('my-plugin:sources', sources)

ctx.services.whenAvailable('my-plugin:sources', (sources) => {
  sources.register(/* ... */)
})
```

### Static connection configs

`ctx.staticConfig` is this context's own `ConnectionMeta.configs`: read-only boot-time data from the connection handshake; write it during `setup(ctx)`. Contrast `ctx.scope(id).settings` (mutable, synced).

```ts
declare module 'devframe/types' {
  interface DevframeConnectionConfigsRegistry {
    'my-plugin': { featureFlag: boolean }
  }
}

ctx.staticConfig['my-plugin'] = { featureFlag: true }
```

### Storage scopes

`ctx.host.getStorageDir(scope)` places persisted state in three classes: `workspace` is committable and team-shared (saved presets, config), `project` is per-checkout (caches, personal settings), and `global` is per-user (auth tokens, machine-wide prefs). Placements are in the [Node-Side API reference](https://devfra.me/references/node-api#storage-scopes).

`ctx.scope(id)` returns a namespace-scoped view ([Scoped Context](https://devfra.me/guide/scoped-context)) auto-prefixing every RPC id, shared-state key, and streaming channel, plus a persisted `settings` store (`project`/`global` scopes use the matching storage classes).

Hosted adapters can augment `ctx`, e.g. the [`vite` adapter](https://devfra.me/adapters/vite)'s dock, command, message, and terminal hosts.

## CLI options

`cli` sets CLI-adapter defaults and plugs flags/commands into CAC:

```ts
defineDevframe({
  id: 'my-tool',
  name: 'My Tool',
  clientAssets: './client/dist', // built SPA served as the UI
  cli: {
    command: 'my-tool', // binary name; default: the `id`
    port: 9876, // preferred port; default: 9999
    portRange: [9876, 10000], // forwarded to get-port-please
    random: false, // forwarded to get-port-please
    host: 'localhost', // default host; --host overrides
    open: true, // auto-open the browser on dev start; embeds the current OTP so the tab lands authenticated
    configure(cli) { // contribute capability flags/commands
      cli
        .option('--my-flag <value>', 'Tool-specific flag')
    },
  },
  setup(ctx, { flags }) {
    // `flags` carries the parsed cac bag: the built-in flags
    // (`--port`, `--host`, `--open`, `--no-open`) and anything you added
    // in `configure`.
  },
})
```

Beyond the fields shown, `random` prefers a random open port and `auth: false` disables the WS trust flow for localhost-only, single-user tools. Every field is listed in the [Node-Side API reference](https://devfra.me/references/node-api#cli-options).

## Multiple runtimes, one definition

Wire the definition into multiple adapters from one file:

```ts
import { createPluginFromDevframe } from '@vitejs/devtools-kit/node'
import { createBuild } from 'devframe/adapters/build'
import { createCac } from 'devframe/adapters/cac'

const myDevframe = defineDevframe({
  id: 'my-tool',
  name: 'My Tool',
  setup() {}
})

// 1. Standalone CLI:
await createCac(myDevframe).parse()

// 2. Offline snapshot:
await createBuild(myDevframe, { outDir: 'dist-static' })

/** 3. Mount into a host framework (Vite DevTools shown; others can implement equivalents): */
export const myPlugin = () => createPluginFromDevframe(myDevframe)
```

## What's next

- [Adapters](https://devfra.me/adapters): deployment targets
- [RPC](https://devfra.me/guide/rpc): register node-side functions
- [`vite` adapter](https://devfra.me/adapters/vite): mount into a host framework


## Sitemap

See the full [sitemap](https://devfra.me/sitemap.md) for all pages.
