Marketplace developer guide
:::caution 0.2.0 Private Preview snapshot
This independent snapshot records what was true for 0.2.0. The legacy marketplace pages were illustrative in that release: no public marketplace, supported third-party SDK distribution, live publish path, revenue program, review SLA, analytics/ranking contract, or marketplace legal agreement was available.
:::
What maps to current implementation
The 0.2.0 release used only step artifacts pinned into its bundle. It did not provide the later Describe capability handshake, authoritative phase discovery, contract_version, or data_access_profile enforcement. Source-level interfaces did not create a supported third-party deployment contract.
A repository-local WASM experiment defines Init, request/response, route, connect/disconnect, and shutdown hooks. It was not a public SDK or deployed 0.2.0 marketplace contract. The retired Rust macros, AssemblyScript support, HTTP capability, shared cache, typed BSON helpers, and plugin developer account were not supported surfaces.
Legacy architecture and lifecycle
The retired model described WASM modules receiving MongoDB messages in request, response, router, and connection phases. It claimed filesystem/network/process isolation, plugin-scoped state, metrics, logs, optional HTTP/KV capabilities, CPU/memory/response-size limits, and generated configuration forms. Treat all of that as design intent unless independently confirmed by a current release contract.
Legacy manifest fields
The YAML example covers identity (name, version, author, license, description, homepage), phases, min_nexo_version, tier, category, draft-07 config_schema, and resources (max_memory_mb, max_cpu_ms_per_request, max_response_size_bytes). The repository currently contains three different shapes: this YAML story, nexoctl's loose manifest.json, and first-party step.yaml. They are not interchangeable.
Hooks
| Legacy hook | Legacy meaning | Current qualification |
|---|---|---|
on_request | Inspect/modify before MongoDB | Closest current RPC: ProcessRequest |
on_response | Inspect/modify before client | Closest current RPC: ProcessResponse |
on_route | Select upstream | Not an executable/supported 0.2.0 public marketplace phase |
on_connect | Accept/reject connection | Repository-local WASM API only |
on_disconnect | Cleanup | No supported 0.2.0 public marketplace callback |
init | Load configuration | Bundle-internal configuration only |
shutdown | Graceful cleanup | Repository-local lifecycle detail, not protobuf RPC |
Repository-local WASM manifest shape
The local plugin.Manifest implementation reads JSON or YAML with these fields. This is implementation evidence, not a public artifact schema.
| Area | Fields and local validation |
|---|---|
| Identity | name, version, author (string), description, license, homepage; name/version required |
| Phases | request, response, router, connection; at least one required |
| Compatibility | min_nexo_version; stored but not a public marketplace compatibility promise |
| Resources | max_memory_mb (default 64, maximum 256), max_cpu_ms_per_request (default 10, maximum 100), max_response_size_bytes (the manifest helper conditionally assigns 1 MiB only when applying the entire default resource bundle; no runtime enforcement was found) |
| Permissions | outbound network host strings and storage max_keys/max_value_bytes; declaration does not establish a supported egress policy |
| Configuration | inline draft-07 config_schema; no public dashboard-rendering guarantee |
| Marketplace metadata | price_monthly_usd, free_trial_days, category, tags; descriptive only |
The generated nexoctl manifest is a separate loose shape:
{
"name": "example-plugin",
"version": "0.1.0",
"description": "A Nexo pipeline plugin",
"author": "",
"license": "Apache-2.0",
"type": "both",
"language": "go",
"pricing": {"model": "free"},
"entrypoint": "example-plugin.so",
"sdk_version": ">=0.1.0",
"tags": [],
"homepage": "",
"repository": ""
}
validate does not enforce most of those fields; publish additionally requires author and pricing.
Current nexoctl development workflow and limitations
nexoctl scaffold --name NAME --type request|response|both --lang go|wasmwritesmain.go,go.mod,manifest.json, README, and Makefile. Both language choices currently receive the same Go plugin template and.sobuild, so the WASM scaffold is incomplete.nexoctl validate --dir DIRchecks only basic JSON fields, a three-part version, optional type, an internal Azure DevOps SDK string ingo.mod, and a Go build. It is not marketplace compliance, JSON Schema, sandbox-import, signature, asset, or resource validation.nexoctl test --plugin-path FILErequires an existing artifact, rebuilds its directory as a Go plugin, and attempts a fixedlocalhost:27017smoke test. “SKIP (no proxy available)” still ends as complete and is not pass evidence.nexoctl publish --dry-run --dir DIRcreates a tarball. It recursively includes files except hidden directories,vendor, andnode_modules; hidden files are included. Inspect a clean staging directory. Non-dry-run exposes the API key in argv and targets an unsupported endpoint.
Security and sandboxing qualification
The local wazero code sets a linear-memory page cap and creates timeout contexts around hook calls, plus host functions for logging, counters, histograms, and plugin-scoped KV. The runtime configuration does not enable cancellation-driven termination, so a non-returning untrusted WASM invocation is not proven enforceable by those timeout contexts. It instantiates WASI for clock/random. The legacy claims about HTTP allowlists, no network, pass-through on CPU timeout, repeated-violation delisting, signed licenses, offline JWT enforcement, and automatic marketplace review are not current contracts.
Plugins inspect database traffic and may see credentials, PII, or full BSON. Minimize data exposure, avoid body logging, reject rather than silently bypass security failures, bound state, validate lengths before parsing wire bytes, and never treat an illustrative sandbox as a substitute for admission, provenance, or bundle-pinned deployment controls.
Legacy language and workflow claims
The retired page labeled Rust “recommended,” Go/TinyGo and AssemblyScript “supported,” proposed nexo plugin init/build/dev/test/package/validate/publish/status, .nexopkg archives, hot reload, a request inspector, developer registration, and a 1–3 business-day review. None is an active CLI or service promise. The preserved snippets follow.
:::warning Unsafe legacy commands and credentials
Do not put real API keys in these examples. format: password is display metadata, not encryption or secure delivery. The preserved curl ... | sh command executes mutable remote code without version pinning or integrity verification; do not run it. Use only a separately verified, version-pinned distribution method when one is documented.
:::
Legacy YAML manifest
# nexo-plugin.yaml
name: "my-custom-logger"
version: "1.0.0"
author: "Jane Developer"
license: "MIT"
description: "Custom structured logger with Datadog integration"
homepage: "https://github.com/jane/nexo-datadog-logger"
# Which pipeline phases this plugin hooks into
phases:
- request
- response
# Minimum Nexo version required
min_nexo_version: "1.2.0"
# Plugin tier (determines marketplace category)
tier: "pro"
category: "observability"
# Configuration schema (JSON Schema draft-07)
# This generates the UI form in the pipeline builder
config_schema:
type: object
required:
- api_key
properties:
api_key:
type: string
title: "Datadog API Key"
description: "Your Datadog API key for log ingestion"
format: "password" # Rendered as a password field in the UI
site:
type: string
title: "Datadog Site"
default: "datadoghq.com"
enum: ["datadoghq.com", "datadoghq.eu", "us3.datadoghq.com"]
log_level:
type: string
title: "Log Level"
default: "info"
enum: ["debug", "info", "warn", "error"]
include_body:
type: boolean
title: "Include Query Body"
default: false
description: "Include full BSON document in logs (may contain PII)"
# Resource limits for the WASM sandbox
resources:
max_memory_mb: 64
max_cpu_ms_per_request: 10
max_response_size_bytes: 1048576
Legacy Rust interface example
// src/lib.rs
use nexo_sdk::prelude::*;
/// Called once when the plugin is loaded. Initialize state here.
#[nexo_plugin::init]
fn init(config: PluginConfig) -> Result<(), PluginError> {
// Parse your config from the YAML/JSON provided by the user
let api_key = config.get_string("api_key")?;
let site = config.get_string("site").unwrap_or("datadoghq.com".into());
// Store in plugin-scoped state (thread-safe)
STATE.set(MyState { api_key, site });
Ok(())
}
/// Called for each incoming MongoDB request (request phase)
#[nexo_plugin::on_request]
fn on_request(ctx: &RequestContext) -> Result<Action, PluginError> {
let state = STATE.get();
// Access wire protocol metadata
let command = ctx.command_name(); // "find", "insert", "aggregate", etc.
let database = ctx.database(); // "mydb"
let collection = ctx.collection(); // "users"
let client_ip = ctx.client_addr(); // "10.0.1.42:52301"
// Access the raw BSON document (if needed)
let doc = ctx.document()?;
// Log the request
nexo_sdk::log::info!("{}:{}.{} from {}", command, database, collection, client_ip);
// Return what to do next:
// Action::Continue — pass to next plugin in chain
// Action::Respond(bson) — short-circuit with custom response
// Action::Reject(code, msg) — reject with error
Ok(Action::Continue)
}
/// Called for each MongoDB response (response phase)
#[nexo_plugin::on_response]
fn on_response(ctx: &ResponseContext) -> Result<Action, PluginError> {
let duration = ctx.duration(); // Time from request to response
let doc_count = ctx.document_count(); // Number of docs in response
let ok = ctx.is_success(); // Whether the command succeeded
// You can modify the response before it reaches the client
// ctx.set_field("custom_header", "value")?;
nexo_sdk::log::info!(
"response: ok={} docs={} duration={}ms",
ok, doc_count, duration.as_millis()
);
Ok(Action::Continue)
}
/// Optional: Called when the plugin is being shut down
#[nexo_plugin::shutdown]
fn shutdown() {
nexo_sdk::log::info!("Plugin shutting down, flushing buffers...");
}
Legacy Rust SDK capability sketch
use nexo_sdk::prelude::*;
// ── Request Context ─────────────────────────────────
ctx.command_name() // "find", "insert", "aggregate"
ctx.database() // "mydb"
ctx.collection() // "users"
ctx.client_addr() // "10.0.1.42:52301"
ctx.auth_user() // "app_user" (if authenticated)
ctx.document() // Raw BSON document
ctx.get_field::<T>("key") // Extract typed field from command
ctx.set_field("key", val) // Modify field in command
ctx.metadata() // Custom metadata bag (shared between phases)
// ── Response Context ────────────────────────────────
ctx.duration() // Request → response duration
ctx.document_count() // Number of documents returned
ctx.is_success() // true if command succeeded
ctx.error_code() // MongoDB error code (if failed)
ctx.response_document() // Raw BSON response
ctx.set_field("key", val) // Modify response before returning to client
// ── Router Context ──────────────────────────────────
ctx.set_target("host:port") // Route to specific upstream
ctx.targets() // List of available upstreams
// ── Plugin State (thread-safe key-value store) ──────
nexo_sdk::state::set("key", value);
nexo_sdk::state::get::<T>("key");
nexo_sdk::state::delete("key");
// ── Metrics (auto-exported to Prometheus) ───────────
nexo_sdk::metrics::counter("my_plugin_requests_total", 1, &[("status", "ok")]);
nexo_sdk::metrics::histogram("my_plugin_latency_ms", 4.2, &[]);
nexo_sdk::metrics::gauge("my_plugin_cache_size", 1024, &[]);
// ── Logging ─────────────────────────────────────────
nexo_sdk::log::debug!("verbose detail");
nexo_sdk::log::info!("normal operation");
nexo_sdk::log::warn!("something unexpected");
nexo_sdk::log::error!("something broke");
// ── Host Functions (require capabilities in manifest)──
nexo_sdk::http::get("https://api.example.com/v1/data")?; // requires: net_http
nexo_sdk::http::post(url, body)?; // requires: net_http
nexo_sdk::kv::get("cache_key")?; // requires: kv_store
nexo_sdk::kv::set("cache_key", data, ttl)?; // requires: kv_store
Legacy scaffold workflow
# Install the Nexo CLI
curl -sSL https://get.nexo.io/cli | sh
# Create a new plugin project
nexo plugin init my-custom-logger --lang rust
# This creates:
# my-custom-logger/
# nexo-plugin.yaml # Plugin manifest
# Cargo.toml # Rust dependencies
# src/lib.rs # Plugin entry point
# tests/integration.rs # Test harness
# README.md
Legacy local development workflow
# Build the WASM module
nexo plugin build
# Run with a local Nexo proxy + mock MongoDB
nexo plugin dev --target mongodb://localhost:27017
# This starts:
# - Nexo proxy on :27018 with your plugin loaded
# - Hot-reload on source changes
# - Request/response inspector at http://localhost:9090
# Send test traffic
mongosh --port 27018 --eval 'db.users.find({name: "test"})'
Legacy Rust test harness
// tests/integration.rs
use nexo_sdk::testing::*;
#[nexo_test]
async fn test_request_logging() {
// Create a test harness with your plugin
let harness = TestHarness::new()
.with_plugin("my-custom-logger", json!({
"api_key": "test-key",
"log_level": "debug",
"include_body": true
}))
.build()
.await;
// Send a mock request through the pipeline
let response = harness
.send_find("mydb", "users", doc! { "name": "Alice" })
.await;
// Assert the request passed through
assert!(response.is_ok());
assert_eq!(response.document_count(), 1);
// Assert your plugin's side effects
let logs = harness.captured_logs();
assert!(logs.iter().any(|l| l.contains("find:mydb.users")));
}
#[nexo_test]
async fn test_request_rejection() {
let harness = TestHarness::new()
.with_plugin("my-custom-logger", json!({
"api_key": "", // Empty key should cause rejection
}))
.build()
.await;
// Plugin should reject during init
assert!(harness.init_error().is_some());
}
// Run tests:
// nexo plugin test
Legacy package and validate workflow
# Package for distribution (.nexopkg archive)
nexo plugin package
# This creates: my-custom-logger-1.0.0.nexopkg
# Contents:
# - plugin.wasm (compiled WASM module)
# - nexo-plugin.yaml (manifest)
# - README.md
# - LICENSE
# Validate the package passes marketplace requirements
nexo plugin validate my-custom-logger-1.0.0.nexopkg
# Checks:
# ✓ Manifest is valid
# ✓ WASM module loads correctly
# ✓ Exported functions match declared phases
# ✓ Config schema is valid JSON Schema
# ✓ Resource limits are within bounds
# ✓ No disallowed host imports
# ✓ README exists and is non-empty
# ✓ LICENSE file present
Legacy developer registration
# Authenticate with your Nexo account
nexo auth login
# Register as a marketplace developer
nexo developer register
# You'll need:
# - Verified email
# - Payment info (for receiving revenue)
# - Accept the Developer Agreement
Legacy publish/status workflow
# Publish to marketplace (enters review queue)
nexo plugin publish my-custom-logger-1.0.0.nexopkg
# Status: "pending_review"
# Typical review time: 1-3 business days
# Check status
nexo plugin status my-custom-logger
# Publish an update
nexo plugin publish my-custom-logger-1.1.0.nexopkg --update
Legacy pricing manifest
// In your nexo-plugin.json manifest
{
"name": "smart-query-cache",
"version": "1.2.0",
"pricing": {
"type": "subscription", // "free" | "one_time" | "subscription" | "usage_based"
"price_cents": 2900, // $29/month (for subscription or one-time)
"usage_unit": "1K requests", // only for usage_based
"usage_price_cents": 10 // $0.10 per 1K requests
}
}
Legacy commercial and security claims
The page also described free, one-time, subscription, and usage-based pricing; 70% developer share (80% for $100+/month); Lemon Squeezy merchant-of-record payouts on the 15th with a $50 minimum; deploy/runtime license checks; a three-day grace period; usage reporting every 60 seconds; developer analytics; automatic delisting; and eight demand-ranked ideas. These conflict with other retired pages and are all unavailable/policy-dependent. See Publishing and policy and Examples.
Legacy quick reference and call to action
The retired quick-reference table listed nexo plugin init <name> --lang rust|go|as, build, dev --target <uri>, test, package, validate <file>, publish <file>, and status <name>. It ended with a “Ready to Build?” install command and a promise to ship to thousands of users. Those commands, installer URL, audience claim, and live publication path are unavailable. Use only the implemented nexoctl commands and limitations above.