Create a Plugin
Build your first bizSupply plugin — from scaffolding to registration — using the Python SDK.
This guide walks you through creating your first bizSupply plugin. By the end, you will have a working Classification plugin that you can register with the platform and use in a pipeline.
Install the SDK
The bizSupply SDK is distributed as a Python package. Install it with pip:
pip install bizsupply-sdkThe SDK requires Python 3.10 or later. Verify your installation:
python -c "import bizsupply_sdk; print(bizsupply_sdk.__version__)"
="color:#5c6370;font-style:italic"># Output: 1.4.2Plugin Types
bizSupply supports four plugin types. Choose the one that matches your use case.
| Type | Base Class | When to Use |
|---|---|---|
| Source | SourcePlugin | You need to ingest documents from an external system (email, cloud storage, API, FTP). |
| Classification | ClassificationPlugin | You need to determine the type of a document (invoice, contract, receipt, etc.). |
| Extraction | ExtractionPlugin | You need to pull structured fields from a document based on an ontology. |
| Aggregation | AggregationPlugin | You need to normalize, enrich, or combine data across multiple documents. |
Critical Requirements
All plugins must satisfy these requirements. Violating any of them will cause registration or execution failures.
| Requirement | Details | What Happens If Violated |
|---|---|---|
| Single required method | Each plugin type has exactly one method you must implement (e.g., classify for Classification). | Plugin fails validation at registration time. |
| Return the correct type | classify() must return a string, extract() must return a dict, fetch_documents() must return a list. | Runtime error — the pipeline stage fails and the job enters a partial or failed state. |
| No side effects on platform state | Plugins must not directly modify documents, pipelines, or other platform resources. Return data and let the platform handle persistence. | Undefined behavior — data corruption is possible. |
| Handle errors gracefully | Raise PluginError for expected failures. Unhandled exceptions cause the entire job to fail. | Job enters failed state instead of partial — retryable documents are lost. |
| Idempotent execution | Running the same plugin on the same document twice must produce the same result. The platform may retry failed documents. | Duplicate or inconsistent extracted data. |
| No blocking I/O in the main thread | Use the provided async helpers (e.g., prompt_llm) for any LLM calls or external API requests. Do not use time.sleep() or synchronous HTTP clients. | Worker thread starvation — other documents in the same job are delayed. |
Quick Start
Choose Your Plugin Type
For this guide, we will create a Classification plugin. This plugin will analyze a document and determine whether it is an invoice, a purchase order, or an unknown type.
Scaffold with the CLI
The SDK includes a scaffolding CLI that generates a plugin project with the correct structure:
bizsupply scaffold classification my-classifier
cd my-classifier/This creates the following structure:
my-classifier/
├── plugin.py # Your plugin implementation
├── plugin.yaml # Plugin metadata and configuration
├── requirements.txt # Python dependencies
└── tests/
└── test_plugin.py # Unit testsValidate Your Plugin
Before registering, validate that your plugin meets all requirements:
bizsupply validate ./plugin.py
="color:#5c6370;font-style:italic"># ✓ Plugin class found: MyClassifier
="color:#5c6370;font-style:italic"># ✓ Base class: ClassificationPlugin
="color:#5c6370;font-style:italic"># ✓ Required method implemented: classify
="color:#5c6370;font-style:italic"># ✓ Return type annotation: str
="color:#5c6370;font-style:italic"># ✓ Plugin metadata valid
="color:#5c6370;font-style:italic"># All checks passed.Plugin Structure
Here is a complete Classification plugin that uses the LLM service to classify documents:
from bizsupply_sdk import ClassificationPlugin, PluginError
class InvoiceClassifier(ClassificationPlugin):
"""Classifies documents as invoice, purchase_order, or unknown."""
# Plugin metadata
name = "invoice-classifier"
version = "1.0.0"
description = "Classifies financial documents using LLM analysis."
# Configurable parameters (set via pipeline config)
confidence_threshold: float = 0.8
def classify(self, document) -> str:
"""
Analyze the document content and return a document type string.
Args:
document: A Document object with properties:
- content: str (extracted text content)
- filename: str
- mime_type: str
- metadata: dict
Returns:
A string representing the document type (e.g., "invoice").
"""
# Guard: ensure we have content to classify
if not document.content or len(document.content.strip()) == 0:
raise PluginError(
"Document has no extractable text content.",
retryable=False,
)
# Build the classification prompt
prompt = f"""Analyze the following document and classify it as one of:
- invoice
- purchase_order
- receipt
- contract
- unknown
Document filename: {document.filename}
Document content:
{document.content[:4000]}
Respond with ONLY the document type, nothing else."""
# Call the LLM via the platform service
result = self.prompt_llm(prompt)
doc_type = result.strip().lower()
# Validate the result
valid_types = {"invoice", "purchase_order", "receipt", "contract", "unknown"}
if doc_type not in valid_types:
return "unknown"
return doc_typeRegister Your Plugin
Once validated, register the plugin with the platform:
curl -X POST https://api.bizsupply.com/v1/plugins \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "invoice-classifier",
"type": "classification",
"version": "1.0.0",
"description": "Classifies financial documents using LLM analysis.",
"module_path": "invoice_classifier.plugin.InvoiceClassifier",
"config_schema": {
"confidence_threshold": {
"type": "number",
"default": 0.8,
"description": "Minimum confidence score to accept a classification."
}
}
}'The API returns the registered plugin with its assigned ID:
{
"id": "plg_a1b2c3d4",
"name": "invoice-classifier",
"type": "classification",
"version": "1.0.0",
"status": "active",
"created_at": "2026-01-15T10: 30: 00Z"
}Common Mistakes
These are the most frequent errors when developing plugins.
1. Returning the wrong type from classify()
# WRONG — returning a dict instead of a string
def classify(self, document) -> str:
return {"type": "invoice", "confidence": 0.95}
# CORRECT — return only the document type string
def classify(self, document) -> str:
return "invoice"2. Modifying the document object directly
# WRONG — mutating the document
def extract(self, document, fields) -> dict:
document.status = "extracted" # Don't do this!
return {"vendor": "Acme"}
# CORRECT — return the extracted data, let the platform handle state
def extract(self, document, fields) -> dict:
return {"vendor": "Acme"}3. Using synchronous HTTP instead of platform services
# WRONG — blocking synchronous HTTP call
import requests
def classify(self, document) -> str:
resp = requests.post("https://my-llm.com/classify", json={"text": document.content})
return resp.json()["type"]
# CORRECT — use the platform's LLM service
def classify(self, document) -> str:
result = self.prompt_llm(f"Classify this document: {document.content[:4000]}")
return result.strip().lower()Next Steps
- Read the Plugin Interface Specification for the full API reference, including all available service methods.
- Create an Ontology to define the fields your Extraction plugin will populate.
- Create a Pipeline to connect your plugin with sources and ontologies.
- Test your plugin locally using the SDK test harness: bizsupply test ./plugin.py --document sample.pdf