Skip to main content

Plugin system overview

Chive's plugin system allows external integrations for metadata enrichment, author verification, and backlink tracking. Plugins run in isolated sandboxes with declared permissions.

Architecture

The plugin system uses a hybrid approach:

  • TSyringe for dependency injection
  • EventEmitter2 for event hooks
  • isolated-vm for execution isolation
PluginManager
├── PluginLoader (dynamic loading from manifests)
├── PluginRegistry (discovery and listing)
├── PluginEventBus (event publication)
├── PermissionEnforcer (security limits)
└── IsolatedVmSandbox (execution isolation)

Plugin types

Importing plugins

Import preprints from external archives:

PluginSourceMethod
arXivarxiv.orgOAI-PMH + API
LingBuzzlingbuzz.netRSS + scraping
Semantics Archivesemanticsarchive.netWeb scraping
PsyArXivpsyarxiv.comOSF API
OpenReviewopenreview.netAPI v2

Enrichment plugins

Add metadata from external sources:

PluginData provided
Semantic ScholarCitation counts, SPECTER2 recommendations
OpenAlexConcepts, institution affiliations
CrossRefDOI metadata, references
WikidataEntity linking, multilingual labels

Verification plugins

Verify author identity:

PluginVerification method
ORCIDProfile works matching
RORInstitution verification

Track references from ATProto apps:

PluginSource app
Bluesky Backlinksapp.bsky.feed.post
Semble BacklinksSemble notes
Leaflet BacklinksLeaflet annotations
WhiteWind BacklinksWhiteWind research notes

Creating plugins

See Creating plugins for a step-by-step guide.

Plugin manifest

Every plugin declares its metadata and permissions:

{
"id": "pub.chive.plugin.example",
"name": "Example Plugin",
"version": "1.0.0",
"description": "What this plugin does",
"author": "Your Name",
"license": "MIT",
"entrypoint": "dist/index.js",
"permissions": {
"network": {
"allowedDomains": ["api.example.com"]
},
"storage": {
"maxSize": "50MB"
},
"hooks": ["preprint.indexed"]
}
}

Base classes

Plugins extend one of three base classes:

// General purpose plugin
class MyPlugin extends BasePlugin {
async onPreprintIndexed(preprint: Preprint): Promise<void> {
// React to new preprints
}
}

// Import external preprints
class MyImporter extends ImportingPlugin {
async fetchPreprints(): AsyncIterable<ImportedPreprint> {
// Yield preprints from external source
}
}

// Track ATProto backlinks
class MyBacklinkTracker extends BacklinkTrackingPlugin {
readonly collection = 'com.example.post';

async extractBacklinks(record: unknown): Promise<Backlink[]> {
// Extract Chive references from records
}
}

Plugin lifecycle

1. Load: PluginLoader reads manifest and validates
2. Initialize: Plugin.initialize(context) called
3. Active: Plugin responds to events
4. Shutdown: Plugin.shutdown() called for cleanup

Initialization

Plugins receive a context with dependencies:

async initialize(context: PluginContext): Promise<void> {
this.logger = context.logger;
this.cache = context.cache;
this.http = context.httpClient;
this.eventBus = context.eventBus;
}

Event hooks

Plugins subscribe to lifecycle events:

this.eventBus.on('preprint.indexed', async (event) => {
await this.enrichPreprint(event.preprint);
});

this.eventBus.on('system.shutdown', async () => {
await this.flushCache();
});

Security model

Permission enforcement

Plugins declare required permissions in their manifest. The system blocks unauthorized operations:

// Allowed: Domain in manifest
await this.http.get('https://api.example.com/data');

// Blocked: Domain not in manifest
await this.http.get('https://other-site.com/data');
// Throws: PermissionDeniedError

Resource limits

ResourceDefault limit
Network domains20 max
Storage100 MB max
Event hooks50 max
Execution time30 seconds per operation

Isolation

Plugins run in isolated-vm sandboxes:

  • No access to Node.js internals
  • No file system access
  • No process spawning
  • Memory limits enforced

Configuration

Plugin behavior can be configured via environment variables:

VariableDefaultDescription
PLUGIN_DIR./pluginsPlugin directory
PLUGIN_CACHE_TTL86400Cache TTL in seconds
PLUGIN_MAX_CONCURRENCY5Parallel plugin operations

Next steps