Skip to content

Client API Reference ​

Status: this page describes the designed sandboxed extension API, which does not run today. Only native extensions run. See what actually runs.

The Extension Client API (ExtensionClientAPI) is the primary interface for extensions running in the Extension Host. All operations are sandboxed and permission-gated.

Overview ​

typescript
export async function activate(
  context: ExtensionContext,
  api: ExtensionClientAPI
): Promise<void> {
  // api contains all available APIs
  api.ui          // UI operations
  api.storage     // Local storage
  api.commands    // Command registration
  api.workspace   // Workspace info
  api.events      // Event subscriptions
  api.telemetry   // Analytics
  api.context     // Read-only context
  api.callOrgApi  // Org API calls
  api.fetch       // External HTTP
}

Extension Context ​

The context object is passed to your activate function.

Properties ​

typescript
interface ExtensionContext {
  extensionId: string       // Your extension ID
  extensionPath: string     // Absolute path to extension files
  storagePath: string       // Path for extension data
  subscriptions: Disposable[] // Auto-disposed on deactivate
  log: ExtensionLogger      // Scoped logger
  manifest: ExtensionManifest // Your manifest
  state: ExtensionState     // Current state
}

context.subscriptions ​

Push Disposable objects here for automatic cleanup when the extension deactivates:

typescript
export async function activate(context, api) {
  // These are automatically disposed when extension deactivates
  context.subscriptions.push(
    api.commands.registerCommand('myext.cmd', handler),
    api.workspace.onFileChanged(fileHandler),
    api.events.on('vault:changed', vaultHandler)
  )
}

context.log ​

Scoped logger with extension prefix:

typescript
context.log.debug('Debug message')   // Development only
context.log.info('Info message')     // General info
context.log.warn('Warning message')  // Warnings
context.log.error('Error message')   // Errors

UI API ​

Permission required: Various ui:* permissions

ui.showToast ​

Display a brief notification message.

typescript
api.ui.showToast(
  message: string,
  type?: 'success' | 'error' | 'info' | 'warning',
  duration?: number
): void

Permission: ui:toast

Example:

typescript
api.ui.showToast('File saved!', 'success')
api.ui.showToast('Connection failed', 'error', 5000) // 5 seconds

ui.showDialog ​

Display a modal dialog and wait for user response.

typescript
api.ui.showDialog(options: DialogOptions): Promise<DialogResult>

interface DialogOptions {
  title: string
  message: string
  type?: 'info' | 'warning' | 'error' | 'confirm'
  confirmText?: string   // Default: "OK"
  cancelText?: string    // Default: "Cancel"
  dismissible?: boolean  // Click outside to close
}

interface DialogResult {
  confirmed: boolean
  dismissed: boolean
}

Permission: ui:dialog

Example:

typescript
const result = await api.ui.showDialog({
  title: 'Confirm Delete',
  message: 'Are you sure you want to delete this file?',
  type: 'confirm',
  confirmText: 'Delete',
  cancelText: 'Keep'
})

if (result.confirmed) {
  await deleteFile()
}

ui.setStatus ​

Set the extension's status indicator in the UI.

typescript
api.ui.setStatus(status: 'online' | 'offline' | 'partial' | 'checking'): void

Permission: ui:status

Example:

typescript
api.ui.setStatus('checking')
const connected = await checkConnection()
api.ui.setStatus(connected ? 'online' : 'offline')

ui.showProgress ​

Show a progress indicator while performing an operation.

typescript
api.ui.showProgress<T>(
  options: ProgressOptions,
  task: (progress: Progress, token: CancellationToken) => Promise<T>
): Promise<T>

interface ProgressOptions {
  title: string
  cancellable?: boolean
  location?: 'notification' | 'statusbar'
}

interface Progress {
  report(value: { message?: string; increment?: number }): void
}

interface CancellationToken {
  readonly isCancellationRequested: boolean
  onCancellationRequested: (callback: () => void) => Disposable
}

Permission: ui:progress

Example:

typescript
const result = await api.ui.showProgress(
  { title: 'Syncing files...', cancellable: true },
  async (progress, token) => {
    const files = await getFiles()
    
    for (let i = 0; i < files.length; i++) {
      if (token.isCancellationRequested) {
        return { cancelled: true, synced: i }
      }
      
      progress.report({
        message: `Syncing ${files[i].name}...`,
        increment: 100 / files.length
      })
      
      await syncFile(files[i])
    }
    
    return { cancelled: false, synced: files.length }
  }
)

ui.showQuickPick ​

Display a searchable selection list.

typescript
api.ui.showQuickPick(
  items: QuickPickItem[],
  options?: QuickPickOptions
): Promise<QuickPickItem | QuickPickItem[] | undefined>

interface QuickPickItem {
  label: string
  description?: string   // Shown dimmer, to the right
  detail?: string        // Shown below label
  picked?: boolean       // Pre-selected
  data?: unknown         // Custom data
  iconId?: string        // Lucide icon name
}

interface QuickPickOptions {
  title?: string
  placeholder?: string
  canPickMany?: boolean
  matchOnDescription?: boolean
  matchOnDetail?: boolean
}

Permission: ui:dialog

Example:

typescript
const selected = await api.ui.showQuickPick([
  { label: 'Production', description: 'Main environment', data: 'prod' },
  { label: 'Staging', description: 'Test environment', data: 'staging' },
  { label: 'Development', description: 'Local dev', data: 'dev' }
], {
  title: 'Select Environment',
  placeholder: 'Choose deployment target...'
})

if (selected) {
  console.log('Selected:', selected.data)
}

Multi-select:

typescript
const selected = await api.ui.showQuickPick(items, { canPickMany: true })
if (selected && Array.isArray(selected)) {
  console.log('Selected:', selected.map(s => s.label))
}

ui.showInputBox ​

Display a text input dialog.

typescript
api.ui.showInputBox(options?: InputBoxOptions): Promise<string | undefined>

interface InputBoxOptions {
  title?: string
  placeholder?: string
  value?: string           // Initial value
  valueSelection?: [number, number]  // Selection range
  password?: boolean       // Mask input
  prompt?: string          // Text above input
  validateInput?: (value: string) => string | undefined | Promise<string | undefined>
}

Permission: ui:dialog

Example:

typescript
const projectName = await api.ui.showInputBox({
  title: 'New Project',
  placeholder: 'my-project',
  prompt: 'Enter project name (lowercase, no spaces)',
  validateInput: (value) => {
    if (!value) return 'Name is required'
    if (!/^[a-z0-9-]+$/.test(value)) {
      return 'Only lowercase letters, numbers, and hyphens'
    }
    return undefined // Valid
  }
})

if (projectName) {
  await createProject(projectName)
}

Password input:

typescript
const apiKey = await api.ui.showInputBox({
  title: 'API Key',
  password: true,
  prompt: 'Enter your API key'
})

Storage API ​

Permission required: storage:local

Extension-scoped persistent storage. Data is isolated per extension.

storage.get ​

typescript
api.storage.get<T>(key: string): Promise<T | undefined>

Example:

typescript
const lastSync = await api.storage.get<number>('lastSyncTime')
const settings = await api.storage.get<Settings>('userSettings')

storage.set ​

typescript
api.storage.set<T>(key: string, value: T): Promise<void>

Values must be JSON-serializable.

Example:

typescript
await api.storage.set('lastSyncTime', Date.now())
await api.storage.set('userSettings', { theme: 'dark', autoSync: true })

storage.delete ​

typescript
api.storage.delete(key: string): Promise<void>

Example:

typescript
await api.storage.delete('cachedData')

storage.keys ​

typescript
api.storage.keys(): Promise<string[]>

Example:

typescript
const allKeys = await api.storage.keys()
// ['lastSyncTime', 'userSettings', 'cachedData']

storage.has ​

typescript
api.storage.has(key: string): Promise<boolean>

Example:

typescript
if (await api.storage.has('userSettings')) {
  // Load existing settings
}

storage.clear ​

typescript
api.storage.clear(): Promise<void>

Warning: This deletes ALL extension data. Use with caution.


Commands API ​

commands.registerCommand ​

Register a command handler.

typescript
api.commands.registerCommand(
  id: string,
  handler: (...args: unknown[]) => unknown | Promise<unknown>,
  options?: CommandOptions
): Disposable

Permission: commands:register

Example:

typescript
context.subscriptions.push(
  api.commands.registerCommand('myext.greet', (name: string) => {
    api.ui.showToast(`Hello, ${name}!`, 'info')
  })
)

commands.executeCommand ​

Execute a registered command.

typescript
api.commands.executeCommand<T>(id: string, ...args: unknown[]): Promise<T>

Permission: commands:execute

Example:

typescript
// Execute your own command
await api.commands.executeCommand('myext.greet', 'World')

// Execute another extension's command (if permitted)
const result = await api.commands.executeCommand<SyncResult>('other-ext.sync')

commands.getCommands ​

Get all registered command IDs.

typescript
api.commands.getCommands(): Promise<string[]>

Example:

typescript
const commands = await api.commands.getCommands()
// ['myext.greet', 'myext.sync', 'core.save', ...]

Workspace API ​

workspace.onFileChanged ​

Subscribe to file change events.

typescript
api.workspace.onFileChanged(
  callback: (events: FileChangeEvent[]) => void
): Disposable

interface FileChangeEvent {
  type: 'created' | 'changed' | 'deleted'
  path: string       // Relative to vault root
  vaultId: string
}

Permission: workspace:files

Example:

typescript
context.subscriptions.push(
  api.workspace.onFileChanged((events) => {
    for (const event of events) {
      console.log(`${event.type}: ${event.path}`)
      
      if (event.type === 'changed' && event.path.endsWith('.sldprt')) {
        // SolidWorks file modified
        scheduleSync(event.vaultId, event.path)
      }
    }
  })
)

workspace.getOpenFiles ​

Get currently open files.

typescript
api.workspace.getOpenFiles(): Promise<OpenFile[]>

interface OpenFile {
  path: string
  vaultId: string
  isDirty?: boolean   // Has unsaved changes
  extension?: string
}

Permission: workspace:files


workspace.getCurrentVault ​

Get the currently active vault.

typescript
api.workspace.getCurrentVault(): Promise<VaultInfo | undefined>

interface VaultInfo {
  id: string
  name: string
  localPath: string
  orgId: string
}

Example:

typescript
const vault = await api.workspace.getCurrentVault()
if (vault) {
  console.log(`Current vault: ${vault.name} at ${vault.localPath}`)
}

workspace.getVaults ​

Get all configured vaults.

typescript
api.workspace.getVaults(): Promise<VaultInfo[]>

Permission: workspace:vaults


Events API ​

events.on ​

Subscribe to application events.

typescript
api.events.on(
  event: ExtensionEvent,
  callback: (...args: unknown[]) => void
): Disposable

type ExtensionEvent =
  | 'extension:activated'
  | 'extension:deactivating'
  | 'config:changed'
  | 'auth:changed'
  | 'vault:changed'
  | 'online:changed'

Example:

typescript
context.subscriptions.push(
  api.events.on('vault:changed', (newVaultId: string) => {
    console.log('Vault changed to:', newVaultId)
    refreshExtensionState()
  }),
  
  api.events.on('online:changed', (isOnline: boolean) => {
    api.ui.setStatus(isOnline ? 'online' : 'offline')
  }),
  
  api.events.on('config:changed', () => {
    // Reload configuration
    loadSettings()
  })
)

events.emit ​

Emit custom events (prefixed with extension ID).

typescript
api.events.emit(event: string, ...args: unknown[]): void

Example:

typescript
// Only works for events prefixed with your extension ID
api.events.emit('myext.customEvent', { data: 'value' })

Network API ​

callOrgApi ​

Call the organization's API server (authenticated).

typescript
api.callOrgApi<T>(
  endpoint: string,
  options?: FetchOptions
): Promise<FetchResponse<T>>

interface FetchOptions {
  method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
  headers?: Record<string, string>
  body?: unknown
  timeout?: number
}

interface FetchResponse<T> {
  status: number
  statusText: string
  headers: Record<string, string>
  data: T
  ok: boolean
}

Permission: network:orgApi

Example:

typescript
// Call your extension's server handler
const response = await api.callOrgApi<SyncResult>('/extensions/myext/sync', {
  method: 'POST',
  body: { vaultId: 'xxx', force: true }
})

if (response.ok) {
  console.log('Synced:', response.data.fileCount)
} else {
  console.error('Error:', response.data)
}

fetch ​

Make HTTP requests to external URLs.

typescript
api.fetch<T>(url: string, options?: FetchOptions): Promise<FetchResponse<T>>

Permission: network:fetch AND declared domains in manifest

Manifest requirement:

json
"permissions": {
  "client": ["network:fetch"],
  "server": ["http:domain:api.example.com"]
}

Example:

typescript
const response = await api.fetch<WeatherData>('https://api.example.com/weather', {
  method: 'GET',
  headers: { 'Authorization': 'Bearer xxx' }
})

Note: Can only access domains declared in your manifest's permissions.


Telemetry API ​

Permission required: telemetry

Anonymous, privacy-respecting analytics.

telemetry.trackEvent ​

Track a named event.

typescript
api.telemetry.trackEvent(
  name: string,
  properties?: Record<string, string | number | boolean>
): void

Example:

typescript
api.telemetry.trackEvent('sync_completed', {
  fileCount: 42,
  duration: 1500,
  usedCache: true
})

telemetry.trackError ​

Track an error occurrence.

typescript
api.telemetry.trackError(
  error: Error,
  context?: Record<string, string>
): void

Example:

typescript
try {
  await riskyOperation()
} catch (error) {
  api.telemetry.trackError(error as Error, {
    operation: 'sync',
    vaultId: 'xxx'
  })
}

telemetry.trackTiming ​

Track a timing measurement.

typescript
api.telemetry.trackTiming(name: string, durationMs: number): void

Example:

typescript
const start = performance.now()
await longOperation()
api.telemetry.trackTiming('longOperation', performance.now() - start)

Context API ​

Read-only context information about the current session.

typescript
interface ExtensionContextInfo {
  extensionId: string
  version: string
  user: UserContext | null
  organization: OrganizationContext | null
  isOnline: boolean
  appVersion: string
  platform: 'win32' | 'darwin' | 'linux'
}

interface UserContext {
  id: string
  email: string
  displayName?: string
}

interface OrganizationContext {
  id: string
  name: string
}

Example:

typescript
const { user, organization, platform } = api.context

if (user) {
  console.log(`User: ${user.email}`)
}

if (platform === 'win32') {
  // Windows-specific logic
}

Complete Example ​

typescript
import type { ExtensionContext, ExtensionClientAPI } from '@blueplm/extension-api'

export async function activate(
  context: ExtensionContext,
  api: ExtensionClientAPI
): Promise<void> {
  context.log.info('Extension activating...')

  // Register commands
  context.subscriptions.push(
    api.commands.registerCommand('myext.sync', async () => {
      await syncWithProgress(api)
    }),
    
    api.commands.registerCommand('myext.configure', async () => {
      const endpoint = await api.ui.showInputBox({
        title: 'API Endpoint',
        placeholder: 'https://api.example.com'
      })
      if (endpoint) {
        await api.storage.set('apiEndpoint', endpoint)
        api.ui.showToast('Configuration saved', 'success')
      }
    })
  )

  // Watch for file changes
  context.subscriptions.push(
    api.workspace.onFileChanged((events) => {
      const modified = events.filter(e => e.type !== 'deleted')
      if (modified.length > 0) {
        context.log.debug(`${modified.length} files changed`)
      }
    })
  )

  // Subscribe to events
  context.subscriptions.push(
    api.events.on('vault:changed', () => {
      context.log.info('Vault changed, refreshing...')
    })
  )

  // Set initial status
  api.ui.setStatus('online')
  api.ui.showToast('Extension ready!', 'success')
}

async function syncWithProgress(api: ExtensionClientAPI): Promise<void> {
  await api.ui.showProgress(
    { title: 'Syncing...', cancellable: true },
    async (progress, token) => {
      progress.report({ message: 'Connecting...' })
      
      const vault = await api.workspace.getCurrentVault()
      if (!vault) {
        api.ui.showToast('No vault selected', 'error')
        return
      }

      progress.report({ message: 'Syncing...', increment: 50 })

      const response = await api.callOrgApi('/extensions/myext/sync', {
        method: 'POST',
        body: { vaultId: vault.id }
      })

      if (response.ok) {
        api.ui.showToast('Sync complete!', 'success')
        api.telemetry.trackEvent('sync_completed')
      } else {
        api.ui.showToast('Sync failed', 'error')
      }
    }
  )
}

export function deactivate(): void {
  // Cleanup (subscriptions are auto-disposed)
}

← Manifest Reference | Server API Reference →