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

Plugin types​

Importing plugins​

Import eprints 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
Cosmik Backlinksnetwork.cosmik.card
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": ["eprint.indexed"]
}
}

Base classes​

Plugins extend one of three base classes:

// General purpose plugin
class MyPlugin extends BasePlugin {
async onEprintIndexed(eprint: Eprint): Promise<void> {
// React to new eprints
}
}

// Import external eprints
class MyImporter extends ImportingPlugin {
async fetchEprints(): AsyncIterable<ImportedEprint> {
// Yield eprints 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​

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('eprint.indexed', async (event) => {
await this.enrichEprint(event.eprint);
});

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​