Writing a HarborClient Plugin

Featured

HarborClient ships with a plugin system that lets you extend the desktop HTTP client with installable packages. Plugins can add settings panels, sidebar views, themes, HTTP hooks, and more — all packaged as a .hcp file (a normal ZIP archive with a HarborClient-specific extension).

If you have not used HarborClient yet, start with my earlier post on HarborClient as a Postman alternative. This tutorial walks through building a small plugin from scratch: a request logger that writes every outbound HTTP request and response to the terminal as a JSON object — one line per event, ready to pipe into jq or any log aggregator.

Plugins are long-lived extensions. That is different from request scripts, which run once per send inside the same SES sandbox. Plugins install through Settings → Plugins, declare permissions up front, and stay active until you disable them. Full API reference lives in the plugin development docs.

Prerequisites

  • HarborClient ≥ 1.7.0
  • pnpm, TypeScript, and esbuild
  • @harborclient/sdk as a dev dependency

Scaffold the project

The fastest way to start is the official skeleton. It creates a plugin directory with the expected layout and build scripts:

npx @harborclient/plugin-skeleton@latest
cd my-plugin
pnpm install

For this tutorial we only need a main entry — no React UI — so a minimal layout looks like this:

request-logger/
├── manifest.json
├── package.json
├── src/
│   └── main.ts
└── dist/
    └── main.js          # produced by esbuild

Add a package.json with esbuild build targets. HarborClient does not ship a plugin SDK runtime; you bundle everything yourself:

{
  "name": "request-logger",
  "version": "1.0.0",
  "type": "module",
  "devDependencies": {
    "@harborclient/sdk": "^0.4.3",
    "esbuild": "^0.25.0",
    "typescript": "^5.0.0"
  },
  "scripts": {
    "build": "esbuild src/main.ts --bundle --outfile=dist/main.js --format=esm --platform=neutral",
    "dev": "esbuild src/main.ts --bundle --outfile=dist/main.js --format=esm --platform=neutral --watch",
    "pack": "pnpm build && zip -r ../request-logger.hcp manifest.json README.md dist"
  }
}

manifest.json

Every plugin needs a manifest at the package root. Ours is main-only: no renderer entry, no contributes block, and a single http permission for send hooks:

{
  "id": "com.example.request-logger",
  "name": "Request Logger",
  "author": "Acme Inc.",
  "version": "1.0.0",
  "engines": { "harborclient": ">=1.7.0" },
  "main": "dist/main.js",
  "permissions": ["http"]
}

The id is a reverse-DNS identifier that namespaces storage and updates. The main field points at the bundled entry that runs in HarborClient's SES-hardened utilityProcess — the same infrastructure used for request scripts, but with a broader plugin API.

src/main.ts — JSON logging

Main entries export an activate(hc) function. Import MainPluginContext from @harborclient/sdk, register HTTP hooks, and push each returned disposable onto hc.subscriptions so the host cleans up automatically when the plugin is disabled.

The official docs include a request logger example that prints human-readable lines. We will emit structured JSON instead — one object per console.log call, which is easy to parse downstream:

import type { MainPluginContext } from '@harborclient/sdk';

function logEvent(payload: Record<string, unknown>): void {
  console.log(JSON.stringify(payload));
}

export function activate(hc: MainPluginContext): void {
  hc.subscriptions.push(
    hc.http.onBeforeSend((request) => {
      logEvent({
        event: 'request',
        timestamp: new Date().toISOString(),
        method: request.method,
        url: request.url,
        headers: request.headers,
        ...(request.body ? { body: request.body } : {}),
      });
    })
  );

  hc.subscriptions.push(
    hc.http.onAfterSend((request, response) => {
      logEvent({
        event: 'response',
        timestamp: new Date().toISOString(),
        method: request.method,
        url: request.url,
        status: response.status,
        statusText: response.statusText,
        headers: response.headers,
        ...(response.body ? { body: response.body } : {}),
      });
    })
  );
}

A few things worth noting:

  • onBeforeSend receives a mutable request snapshot. You can change method, URL, headers, or body before the request goes out. We only read fields here.
  • onAfterSend fires once the response arrives, with both the request that was sent and the serialized response (status, headers, body).
  • The host exposes console inside the utilityProcess sandbox, so output appears in the terminal where you launched HarborClient — not inside the app window.
  • Main-only plugins activate as soon as they are enabled. You do not need to open a settings panel first.

After you send a GET https://httpbin.org/get request with the plugin enabled, your terminal might show:

{"event":"request","timestamp":"2026-06-25T14:32:01.123Z","method":"GET","url":"https://httpbin.org/get","headers":{"Accept":"*/*"}}
{"event":"response","timestamp":"2026-06-25T14:32:01.891Z","method":"GET","url":"https://httpbin.org/get","status":200,"statusText":"OK","headers":{"content-type":"application/json"},"body":"{\n  \"url\": \"https://httpbin.org/get\"\n}"}

Build and run

Bundle the entry, then load the folder into HarborClient for development:

pnpm build   # one-off build to dist/main.js
pnpm dev     # rebuild on every save

The recommended dev loop uses two terminals:

  1. Terminal 1 — run pnpm dev in your plugin directory.
  2. Terminal 2 — run HarborClient from a checkout (pnpm dev) or launch an installed build.
  3. In the app, open Settings → Plugins → Load unpacked… and select your plugin folder.
  4. Enable the plugin, send a request, and watch JSON lines appear in Terminal 2.

HarborClient watches manifest.json and entry files for unpacked plugins. When you save a rebuild, the host debounces briefly, calls deactivate(), and re-runs activate(hc) — no manual reload required during development.

For day-to-day work on the same plugin, you can also register the path at startup:

HARBOR_PLUGINS_DEV=~/projects/request-logger pnpm dev

Package for distribution

When you are ready to share the plugin, zip the manifest, README, and dist/ output with a .hcp extension:

pnpm pack
# or manually:
zip -r request-logger.hcp manifest.json README.md dist

Install the .hcp file from Settings → Plugins. HarborClient unpacks it under your application data directory, validates the manifest, and shows the requested permissions before enabling.

Built-in logging vs. a plugin

HarborClient also supports verbose request logging via -vv / --very-verbose on the command line. A plugin logger runs whenever the plugin is enabled and lets you choose any format — JSON lines, CSV, filtering by host, redacting headers — without restarting the app with different flags.

Where to go next

This plugin has no UI, but HarborClient plugins can also contribute settings panels, sidebar views, request tabs, themes, and toolbar actions. The renderer overview covers React integration with installReact(hc.react), and the examples section includes an audit tab and a Solarized theme.

For the full reference — manifest fields, permissions, IPC, storage, and packaging — see the plugin development docs.