Skip to content

Contributions Reference ​

The contributes section of your manifest declares what your extension adds to BluePLM. This reference covers all contribution types with detailed examples.

Overview ​

json
"contributes": {
  "views": [...],           // UI panels and views
  "commands": [...],        // Executable commands
  "settings": [...],        // Settings pages
  "apiRoutes": [...],       // Server-side routes
  "configuration": {...}    // Extension settings schema
}

Views ​

Views add UI components to BluePLM in various locations.

View Locations ​

LocationDescriptionExample Use
sidebarMain navigation sidebarExtension main view
panelContent area panelsFile browsers, status displays
settingsSettings pageConfiguration UI
dialogModal dialogsWizards, confirmations

Schema ​

json
{
  "id": "myext.main-panel",
  "name": "My Extension",
  "icon": "box",
  "location": "panel",
  "component": "client/components/MainPanel.js",
  "when": "vault.isOpen"
}

Properties ​

PropertyTypeRequiredDescription
idstring✓Unique view identifier
namestring✓Display name
iconstringLucide icon name
locationstring✓Where to render
componentstring✓Component file path
whenstringCondition expression

When Conditions ​

ConditionDescription
vault.isOpenA vault is currently open
user.isAdminUser has admin role
platform.isWindowsRunning on Windows
platform.isMacRunning on macOS
extension.isConfiguredExtension has been configured

Examples ​

Sidebar View:

json
{
  "views": [
    {
      "id": "google-drive.sidebar",
      "name": "Google Drive",
      "icon": "cloud",
      "location": "sidebar",
      "component": "client/views/Sidebar.js"
    }
  ]
}

Panel with Condition:

json
{
  "views": [
    {
      "id": "cad-preview.panel",
      "name": "CAD Preview",
      "icon": "cube",
      "location": "panel",
      "component": "client/views/CADPreview.js",
      "when": "file.extension == '.sldprt'"
    }
  ]
}

Component Implementation ​

View components receive props with the extension API:

tsx
// client/views/MainPanel.tsx
import type { ExtensionClientAPI } from '@blueplm/extension-api'

interface ViewProps {
  api: ExtensionClientAPI
}

export default function MainPanel({ api }: ViewProps) {
  const [files, setFiles] = useState<string[]>([])
  
  useEffect(() => {
    // Load data using API
    api.storage.get<string[]>('cachedFiles').then(cached => {
      if (cached) setFiles(cached)
    })
  }, [api])
  
  async function handleSync() {
    await api.commands.executeCommand('myext.sync')
  }
  
  return (
    <div className="p-4">
      <h2 className="text-lg font-semibold">My Extension</h2>
      <button onClick={handleSync}>Sync Now</button>
      <ul>
        {files.map(f => <li key={f}>{f}</li>)}
      </ul>
    </div>
  )
}

Commands ​

Commands are executable actions available via command palette and keybindings.

Schema ​

json
{
  "id": "myext.sync-now",
  "title": "Sync Now",
  "icon": "refresh-cw",
  "keybinding": "Ctrl+Shift+S",
  "category": "My Extension",
  "when": "vault.isOpen"
}

Properties ​

PropertyTypeRequiredDescription
idstring✓Unique command identifier
titlestring✓Display name in command palette
iconstringLucide icon name
keybindingstringKeyboard shortcut
categorystringGrouping in palette
whenstringWhen command is enabled

Keybinding Format ​

Ctrl+S              // Single modifier
Ctrl+Shift+S        // Multiple modifiers
Ctrl+Alt+K          // Ctrl + Alt
Cmd+S               // macOS Command key
F5                  // Function key
Ctrl+K Ctrl+S       // Chord (two-step)

Platform-specific:

  • Windows/Linux: Use Ctrl
  • macOS: Use Cmd (or Ctrl for both platforms)

Command Categories ​

Group related commands with a category:

json
{
  "commands": [
    {
      "id": "myext.sync",
      "title": "Sync Files",
      "category": "My Extension"
    },
    {
      "id": "myext.configure",
      "title": "Configure",
      "category": "My Extension"
    }
  ]
}

In command palette appears as:

  • My Extension: Sync Files
  • My Extension: Configure

Registering Command Handlers ​

Commands declared in manifest must be registered in code:

typescript
// client/index.ts
export async function activate(context, api) {
  // Register handlers for manifest commands
  context.subscriptions.push(
    api.commands.registerCommand('myext.sync-now', async () => {
      // Handler implementation
      await performSync(api)
    }),
    
    api.commands.registerCommand('myext.configure', async () => {
      // Open configuration dialog
      await showConfigDialog(api)
    })
  )
}

Dynamic Commands ​

Register commands not in manifest (won't appear in command palette):

typescript
context.subscriptions.push(
  api.commands.registerCommand('myext.internal-action', handler, {
    hidden: true
  })
)

Settings ​

Settings pages appear in the Settings panel under your extension.

Schema ​

json
{
  "id": "myext.settings",
  "name": "My Extension",
  "description": "Configure synchronization settings",
  "icon": "settings",
  "component": "client/components/Settings.js",
  "category": "extensions"
}

Properties ​

PropertyTypeRequiredDescription
idstring✓Unique settings ID
namestring✓Display name
descriptionstringDescription text
iconstringLucide icon name
componentstring✓Settings component path
categorystringParent category

Categories ​

CategoryDescription
accountUser account settings
organizationOrganization settings
extensionsExtension settings (default)
systemSystem/app settings

Settings Component ​

tsx
// client/components/Settings.tsx
import { useState, useEffect } from 'react'
import type { ExtensionClientAPI } from '@blueplm/extension-api'

interface SettingsProps {
  api: ExtensionClientAPI
}

export default function Settings({ api }: SettingsProps) {
  const [autoSync, setAutoSync] = useState(true)
  const [interval, setInterval] = useState(300)
  const [loading, setLoading] = useState(true)
  
  useEffect(() => {
    // Load saved settings
    async function load() {
      const saved = await api.storage.get<Settings>('settings')
      if (saved) {
        setAutoSync(saved.autoSync ?? true)
        setInterval(saved.interval ?? 300)
      }
      setLoading(false)
    }
    load()
  }, [api])
  
  async function handleSave() {
    await api.storage.set('settings', { autoSync, interval })
    api.ui.showToast('Settings saved', 'success')
  }
  
  if (loading) return <div>Loading...</div>
  
  return (
    <div className="space-y-6">
      <div>
        <h2 className="text-lg font-semibold">Sync Settings</h2>
        <p className="text-gray-500">Configure automatic synchronization</p>
      </div>
      
      <div className="space-y-4">
        <label className="flex items-center gap-2">
          <input
            type="checkbox"
            checked={autoSync}
            onChange={e => setAutoSync(e.target.checked)}
          />
          <span>Enable automatic sync</span>
        </label>
        
        <div>
          <label className="block text-sm font-medium mb-1">
            Sync interval (seconds)
          </label>
          <input
            type="number"
            value={interval}
            onChange={e => setInterval(Number(e.target.value))}
            min={60}
            max={3600}
            className="border rounded px-3 py-2 w-32"
          />
        </div>
      </div>
      
      <button
        onClick={handleSave}
        className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
      >
        Save Settings
      </button>
    </div>
  )
}

API Routes ​

Server-side HTTP endpoints for your extension.

Schema ​

json
{
  "method": "POST",
  "path": "sync",
  "handler": "server/sync.js",
  "public": false,
  "rateLimit": 100
}

Properties ​

PropertyTypeRequiredDescription
methodstring✓HTTP method
pathstring✓Route path
handlerstring✓Handler file path
publicbooleanNo auth required
rateLimitnumberRequests per minute

Supported Methods ​

  • GET — Retrieve data
  • POST — Create/action
  • PUT — Replace/update
  • PATCH — Partial update
  • DELETE — Remove

Route URLs ​

Routes are mounted at /extensions/{extensionId}/:

json
{ "method": "POST", "path": "sync", "handler": "server/sync.js" }
// → POST /extensions/myext/sync

{ "method": "GET", "path": "status", "handler": "server/status.js" }
// → GET /extensions/myext/status

{ "method": "GET", "path": "files/:id", "handler": "server/file.js" }
// → GET /extensions/myext/files/123

Public Endpoints ​

Not currently reachable. public is accepted and recorded, but every extension endpoint requires a token today, so a caller without one is refused before the handler runs. Serving a handler to an anonymous caller means deciding which organization it belongs to without a credential, and the only mechanism for that was an X-Org-Id request header — which let any caller name any organization. Until an unguessable per-installation URL replaces it, do not ship a webhook receiver or OAuth callback that depends on this. The server logs a warning naming any route declared this way.

Public endpoints are intended to skip authentication. Use for webhooks:

json
{
  "method": "POST",
  "path": "webhook",
  "handler": "server/webhook.js",
  "public": true,
  "rateLimit": 10
}

Note: Public endpoints require admin approval for verified extensions.

Handler Implementation ​

See Server API Reference for handler details.

typescript
// server/sync.js
export default async function handler(api) {
  if (!api.user) {
    return api.response.error('Unauthorized', 401)
  }
  
  const { vaultId } = api.request.body
  // ... sync logic
  
  return api.response.json({ success: true })
}

Configuration ​

Typed configuration schema that auto-generates settings UI.

Schema ​

json
{
  "configuration": {
    "title": "My Extension Settings",
    "properties": {
      "propertyName": {
        "type": "string",
        "default": "value",
        "description": "Help text"
      }
    }
  }
}

Property Types ​

String ​

json
{
  "apiEndpoint": {
    "type": "string",
    "default": "https://api.example.com",
    "description": "API server endpoint"
  }
}

With Enum (Dropdown):

json
{
  "logLevel": {
    "type": "string",
    "enum": ["debug", "info", "warn", "error"],
    "enumDescriptions": [
      "All messages including debug",
      "Info and above",
      "Warnings and errors only",
      "Errors only"
    ],
    "default": "info",
    "description": "Logging verbosity"
  }
}

Number ​

json
{
  "syncInterval": {
    "type": "number",
    "default": 300,
    "minimum": 60,
    "maximum": 3600,
    "description": "Sync interval in seconds"
  },
  "maxRetries": {
    "type": "number",
    "enum": [1, 3, 5, 10],
    "default": 3,
    "description": "Maximum retry attempts"
  }
}

Boolean ​

json
{
  "autoSync": {
    "type": "boolean",
    "default": true,
    "description": "Enable automatic synchronization"
  }
}

Array ​

json
{
  "excludePatterns": {
    "type": "array",
    "items": { "type": "string" },
    "default": ["*.tmp", "*.bak", ".git/**"],
    "description": "File patterns to exclude from sync"
  },
  "allowedExtensions": {
    "type": "array",
    "items": {
      "type": "string",
      "enum": [".sldprt", ".sldasm", ".slddrw", ".step", ".iges"]
    },
    "default": [".sldprt", ".sldasm"],
    "description": "File extensions to sync"
  }
}

Object ​

json
{
  "advancedOptions": {
    "type": "object",
    "properties": {
      "batchSize": {
        "type": "number",
        "default": 100
      },
      "timeout": {
        "type": "number",
        "default": 30000
      }
    },
    "description": "Advanced sync options"
  }
}

Property Options ​

OptionTypesDescription
typeallData type (required)
defaultallDefault value
descriptionallHelp text
enumstring, numberAllowed values
enumDescriptionsstring, numberLabels for enum
minimumnumberMinimum value
maximumnumberMaximum value
itemsarrayItem schema
propertiesobjectProperty schemas
orderallDisplay order
deprecationMessageallDeprecation warning

Accessing Configuration ​

typescript
export async function activate(context, api) {
  // Load from storage
  const syncInterval = await api.storage.get<number>('syncInterval')
  const autoSync = await api.storage.get<boolean>('autoSync')
  
  // Use with defaults from manifest
  const interval = syncInterval ?? 300
  const enabled = autoSync ?? true
  
  if (enabled) {
    startAutoSync(interval)
  }
}

Deprecating Properties ​

json
{
  "oldProperty": {
    "type": "string",
    "deprecationMessage": "Use 'newProperty' instead. Will be removed in 2.0.0"
  }
}

Complete Example ​

json
{
  "contributes": {
    "views": [
      {
        "id": "cloud-sync.sidebar",
        "name": "Cloud Sync",
        "icon": "cloud",
        "location": "sidebar",
        "component": "client/views/Sidebar.js"
      },
      {
        "id": "cloud-sync.status",
        "name": "Sync Status",
        "icon": "activity",
        "location": "panel",
        "component": "client/views/StatusPanel.js",
        "when": "vault.isOpen"
      }
    ],
    "commands": [
      {
        "id": "cloud-sync.sync",
        "title": "Sync Now",
        "icon": "refresh-cw",
        "keybinding": "Ctrl+Shift+Y",
        "category": "Cloud Sync",
        "when": "vault.isOpen"
      },
      {
        "id": "cloud-sync.pause",
        "title": "Pause Sync",
        "icon": "pause",
        "category": "Cloud Sync"
      },
      {
        "id": "cloud-sync.resume",
        "title": "Resume Sync",
        "icon": "play",
        "category": "Cloud Sync"
      }
    ],
    "settings": [
      {
        "id": "cloud-sync.settings",
        "name": "Cloud Sync",
        "description": "Configure cloud synchronization",
        "icon": "cloud",
        "component": "client/components/Settings.js",
        "category": "extensions"
      }
    ],
    "apiRoutes": [
      {
        "method": "POST",
        "path": "sync",
        "handler": "server/sync.js"
      },
      {
        "method": "GET",
        "path": "status",
        "handler": "server/status.js"
      },
      {
        "method": "POST",
        "path": "webhook",
        "handler": "server/webhook.js",
        "public": true,
        "rateLimit": 20
      }
    ],
    "configuration": {
      "title": "Cloud Sync Settings",
      "properties": {
        "autoSync": {
          "type": "boolean",
          "default": true,
          "description": "Automatically sync changes"
        },
        "syncInterval": {
          "type": "number",
          "default": 300,
          "minimum": 60,
          "maximum": 3600,
          "description": "Sync check interval (seconds)"
        },
        "conflictResolution": {
          "type": "string",
          "enum": ["ask", "local", "remote", "newest"],
          "enumDescriptions": [
            "Ask for each conflict",
            "Keep local version",
            "Keep remote version",
            "Keep newest by timestamp"
          ],
          "default": "ask",
          "description": "How to resolve sync conflicts"
        },
        "excludePatterns": {
          "type": "array",
          "items": { "type": "string" },
          "default": ["*.tmp", ".git/**"],
          "description": "Patterns to exclude from sync"
        }
      }
    }
  }
}

← Server API Reference | Permissions Reference →