Skip to content

Building extensions

Data source connectors

Build a custom Golden Reports data connector - what it does, the operations it implements, and how it gets distributed.

A data source connector teaches the platform how to read from one kind of system - a database, an API, a file format.

Connectors are written in Rust and compiled to WebAssembly. The official golden-reports-pdk crate provides typed bindings against the platform's connector ABI; you build, sign, and distribute a single .wasm binary that the Data Provider loads at runtime.

For programmatic API details (exact PDK signatures, message schemas, sandbox capabilities), see the Developer Portal. This page covers the model, the operations you implement, and the development loop.


What you'll build

A .wasm binary that implements four operations:

OperationPurpose
get_infoIdentity - connector type, version, description.
health_checkOpen the connection, do a no-op round-trip, return success/failure.
discover_schemaReturn the source's available fields and types.
fetchExecute a fetch - apply field selection, filter hints, sort hints, pagination - and return rows.

Plus a JSON Schema describing the configuration the connector accepts. The Manager renders that schema as a form when a user registers a new data source.


Why Rust and WebAssembly

Three reasons:

  • Sandboxing. A connector runs in a constrained environment with no direct access to the host's filesystem, environment, or arbitrary network. This reduces the audit burden for installing third-party connectors.
  • Resource limits. The host caps each connector's CPU and memory.
  • Distribution. A .wasm binary is one file, signed, and versioned. No per-OS builds, no language-version drift.

Rust is the supported toolchain because the PDK ships as a Rust crate.


The configuration

A connector defines its own config shape. You declare it as a Rust type (deserialised from a JSON message at runtime) and expose a corresponding JSON Schema for the Manager's form renderer.

Example fields a Postgres connector might declare: host, port, database, username, password, sslMode. The Manager uses the schema to render the form, label the fields, validate types, and mask secrets.

The connector owns its config schema, which is why adding a new connector type never requires a host code change.


Push-down hints

When the engine calls fetch, it passes:

  • The list of fields to return.
  • Filter hints - predicates the connector can apply at the source if it can.
  • Sort hints - orderings the connector can apply at the source if it can.
  • A pagination hint - offset/limit the connector can apply at the source if it can.

A capable connector translates these into a more efficient native query (a SQL WHERE clause, a REST query-string parameter). A simple connector ignores them - the engine post-filters, post-sorts, and post-paginates for correctness regardless.

Push-down is always an optimization, never a correctness contract. Implementing it badly is fine; not implementing it is fine; the worst that happens is your connector is slower than it could be.


Returning rows

A fetch result is a list of column definitions followed by a list of row values. Each value is typed (String, Int32, Int64, Float, Bool, DateTime, Date, Decimal, Bytes, Json, or Null).

Two things to know:

  • Decimals are strings. Floating-point representation would silently lose precision for currency, scientific data, etc. The engine treats decimal strings as exact.
  • Column definitions come first. Always set the columns before populating rows - the engine streams the column metadata in the first batch.

Sandboxing - what your connector can and can't do

The WebAssembly sandbox gives connectors a small set of host-exposed capabilities: HTTP fetch (with per-connector URL allow-lists configurable by the host), outbound socket calls for source types that need them (such as databases), and standard runtime calls for time, randomness, and JSON parsing. Filesystem access and environment-variable access are not part of the sandbox.

This is why customers can install third-party connectors with reduced audit overhead. Install only connectors from sources you trust.


The development loop

A typical iteration:

  1. Build your crate with the WebAssembly target.
  2. Run the connector against a local instance of the host's runtime to verify the ABI calls work.
  3. Add the .wasm to a side-loaded plugin directory in your local data provider.
  4. Register a data source in the Manager using your connector's type.
  5. Test discovery, health check, and fetch from the Manager and from an authored report.

Unit tests run against the connector functions directly. Integration tests should use real sources (a Postgres container, a stub HTTP server) - connector logic is straightforward to test in isolation.


Distribution

Two paths to a customer:

  1. Marketplace publication. Submit your .wasm plus manifest to the Marketplace. The platform signs the bundle and serves it from the public CDN. Customers install through Manager → Marketplace.
  2. Side-loaded private connector. For internal use, customers can upload the bundle directly to their workspace's plugin registry.

Either way, the bundle is verified before the data provider loads it. See Extensibility model for the trust model.


Versioning

Connectors declare a semver version. The engine checks compatibility before invoking:

  • Major mismatch → rejected with CONNECTOR_VERSION_MISMATCH.
  • Minor mismatch (loaded > requested) → accepted; assume backward compatibility.
  • Patch mismatch → always accepted.

Be honest with major bumps - if you change the config shape, that's a major. If you add a new optional field, that's a minor. If you fix an off-by-one in pagination, that's a patch.


Operational considerations

  • Connection pooling. The host doesn't pool for you. If your source benefits from connection reuse (database keep-alive, HTTP keep-alive), pool inside the connector - it's the right layer because keys depend on the connection config, not on the engine.
  • Timeouts. The host passes a deadline. Honor it. Cancellation is the right reaction to a deadline exceeded.
  • Error messages. Return human-readable messages but never leak credentials. The host sanitizes again before returning to the API, but defense in depth is good.
  • Memory. WebAssembly has a hard memory limit set by the host. If your source supports streaming, return partial batches - don't load entire result sets into memory.

Next

Previous
Block plugins