This repository contains Nominal Grafana plugins for integrating with the Nominal API.
This is a production-ready Grafana data source plugin with:
- Go backend: Secure API key handling and server-side processing
- TypeScript frontend: Modern React-based query editor and configuration UI
- Full-stack integration: Backend handles API calls, frontend provides intuitive interface
- Enterprise features: Authentication, caching, and optimized performance
Note: Alternative implementations (pure TypeScript, panel plugin) are available in the
archive/directory for reference and development purposes.
For SQL setup, examples and local testing, see SQL queries.
(required for Playwright tests)
pnpm install
# Build and start development environment:
mage -v # Build Go backend
pnpm run build # Build TypeScript frontend
pnpm run server # Start Docker development environment (with pre-configured datasource)
# Run E2E tests (requires development environment):
pnpm run e2e # Run Playwright testsNote: Use
pnpm run server(development Docker) for testing - it includes pre-configured datasources that Playwright tests expect. The production build starts with unconfigured datasources and will cause test failures.
Use src/resourceRoutes.json for frontend resource paths and register their POST handlers in pkg/plugin/resource_handler.go. The Go tests check every manifest entry against the router without live credentials. Add handler tests for request and response behavior.
The Go backend tests run without live Nominal credentials by default:
go test ./pkg/...Live Nominal API checks are opt-in so normal CI and local unit tests do not depend on external data or credentials.
Run the live health-check integration test with:
NOMINAL_LIVE_TESTS=1 \
NOMINAL_API_KEY=... \
go test -count=1 ./pkg/plugin -run TestLiveNominalNOMINAL_BASE_URL is optional and defaults to https://api.gov.nominal.io/api.
For the shared local plugin .env credentials, use the staging API URL:
set -a
. ./.env
set +a
NOMINAL_LIVE_TESTS=1 \
NOMINAL_BASE_URL=https://api-staging.gov.nominal.io/api \
go test -count=1 ./pkg/plugin -run TestLiveNominalCheckHealthIntegration -vTo also run the live QueryData and SQL integration paths, point the test at staging.
The test creates a temporary asset, data scope, dataset, and numeric CSV channel,
queries that channel through the plugin, and archives the temporary asset and
dataset during cleanup.
set -a
. ./.env
set +a
NOMINAL_LIVE_TESTS=1 \
NOMINAL_BASE_URL=https://api-staging.gov.nominal.io/api \
go test -count=1 ./pkg/plugin -run 'TestLiveNominal(QueryData|SQLQuery)Integration' -vOptional query controls:
-
NOMINAL_WORKSPACE_RID: workspace for queries and temporary test resources. Required when the key has no default workspace. -
NOMINAL_QUERY_BUCKETS: bucket count, default100. -
NOMINAL_QUERY_ASSET_RID,NOMINAL_QUERY_DATA_SCOPE_NAME, andNOMINAL_QUERY_CHANNEL: use an existing query target instead of creating temporary test data. Set all three together. -
NOMINAL_QUERY_FROMandNOMINAL_QUERY_TO: RFC3339 timestamps, default to the temporary CSV range for self-provisioned data or the last 15 minutes for an existing query target. -
NOMINAL_ALLOW_DEFAULT_LIVE_WRITES=1: allows the self-provisioning query test to create temporary resources against the default production base URL. Without this, setNOMINAL_BASE_URLexplicitly for writeful live tests.
After starting the container, verify the plugin is installed:
# Check if plugin directory exists
docker exec <container-name> ls -la /var/lib/grafana/plugins/nominal-nominalds-datasource/
# Should show:
# - module.js (frontend)
# - gpx_nominal_ds_linux_amd64 (backend binary)
# - plugin.json (metadata)-
Build Output: The build creates a production-ready Grafana image containing:
- Built TypeScript frontend
- Built Go backend
- Nominal datasource plugin
-
Access deployed instance at http://localhost:3000 (credentials from your
.envfile):- Login with your configured credentials
- Go to Configuration > Data sources
- Click Add data source
- Look for Nominal in the list
For plugin catalog review setup, see:
Provisioned resources included in this repo:
- Datasource provisioning:
provisioning/datasources/datasources.yml - Dashboard provisioning provider:
provisioning/dashboards/dashboards.yml - Reviewer dashboard JSON:
provisioning/dashboards/json/nominal-review-dashboard.json
Note: Internal deployment tooling (Helm/ECR/production Docker build) is maintained in a private audit repository and intentionally omitted here.
If you already have Grafana running (cloud, self-hosted, or on-prem) and just need to install the plugin, download the pre-built plugin ZIP from GitHub Releases.
Releases are cut automatically via release-please when conventional commits land on main.
# Download the latest release (replace VERSION with the GitHub release version)
VERSION="0.11.0"
curl -L "https://github.com/nominal-io/grafana-plugin-public/releases/download/${VERSION}/nominal-nominalds-datasource-${VERSION}.zip" \
-o plugin.zip
# Extract to Grafana plugins directory
unzip plugin.zip -d /var/lib/grafana/plugins/
# Restart Grafana to load the plugin
sudo systemctl restart grafana-server
# or for Docker: docker restart <grafana-container>Release artifacts created after Grafana grants public signing include a Grafana plugin signature. Install those ZIP files normally, without configuring Grafana to allow unsigned plugins.
Older unsigned ZIP files still require Grafana to explicitly allow the plugin:
# In grafana.ini or via environment variable
[plugins]
allow_loading_unsigned_plugins = nominal-nominalds-datasource
# Or as environment variable:
# GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS=nominal-nominalds-datasourcePublic signing follows Grafana's signing and review flow. Grafana must approve the plugin for public signing before a signed release can be cut successfully.
Configure this GitHub secret before cutting a release tag:
GRAFANA_PLUGIN_ACCESS_KEY: the Grafana access policy token withplugins:writescope.
For a local signing check, build the plugin first and then run:
GRAFANA_ACCESS_POLICY_TOKEN="$GRAFANA_PLUGIN_ACCESS_KEY" \
pnpm run signThe local command maps GRAFANA_PLUGIN_ACCESS_KEY to GRAFANA_ACCESS_POLICY_TOKEN because that is the environment variable name Grafana's signing tool reads.
Do not commit tokens or generated signing output from dist/.
Signing without rootUrls is the public plugin path. Grafana approval is tied to the plugin ID, so if the plugin ID changes, the signing command may fail until the review submission is updated and Grafana approves that ID.
The release workflow also creates a GitHub provenance attestation for the signed plugin ZIP before running Grafana's release validator.
Grafana does not require the first public review submission to be signed. For the initial review, package the plugin ZIP, run the validator, and submit the plugin through Grafana's plugin publishing flow with:
- Plugin ZIP URL
- ZIP SHA1 hash
- Source code URL
- Testing guidance
- Provisioning details from REVIEW.md
After Grafana approves the plugin and grants its public signature level, cut a release tag so the release workflow can sign and publish the catalog-ready ZIP.
# Check plugin files exist
ls -la /var/lib/grafana/plugins/nominal-nominalds-datasource/
# Expected files:
# - module.js (frontend)
# - gpx_nominal_ds_linux_amd64 (backend binary for Linux)
# - gpx_nominal_ds_darwin_amd64 (backend binary for macOS Intel)
# - gpx_nominal_ds_darwin_arm64 (backend binary for macOS Apple Silicon)
# - plugin.json (metadata)Then in Grafana UI:
- Go to Configuration > Data sources
- Click Add data source
- Search for Nominal
- Configure with your Nominal API key, base URL, and (recommended) workspace RID
With Backend Plugin (Go + TypeScript): The backend plugin uses /resources/ endpoints that route through the Go backend.
-
Health Check
curl "http://localhost:3000/api/datasources/uid/{UID}/health" # {"message":"Successfully connected to Nominal API","status":"OK"}
-
Asset Search
The backend exposes a fixed set of resource endpoints:
channels,assets,datascopes,channelvariables,search-assets, andassets-by-rid. Any other path returns 404. To call other Nominal APIs, use the API key directly against the Nominal base URL.curl -s -X POST "http://localhost:3000/api/datasources/uid/{UID}/resources/search-assets" \ -H 'Content-Type: application/json' \ -d '{"query":{"type":"searchText","searchText":""},"sort":{"field":"CREATED_AT","isDescending":true},"pageSize":10}' | jq
-
Find Your Datasource
# List all datasources curl -s "http://localhost:3000/api/datasources" | jq '.[] | {id, uid, name, type, url}' # Get specific datasource by ID curl -s "http://localhost:3000/api/datasources/{ID}" | jq '{id, uid, name, url, jsonData, secureJsonFields}'
{ "id": 1, "uid": "P1E5984762EB73E39", "name": "nominal", "url": "", "jsonData": { "baseUrl": "https://api.gov.nominal.io/api" }, "secureJsonFields": { "apiKey": true } }
-
Plugin dev docs: https://grafana.com/developers/plugin-tools
-
Datasource:
- Overview https://grafana.com/developers/plugin-tools/how-to-guides/data-source-plugins/
- Basic https://github.com/grafana/grafana-plugin-examples/tree/main/examples/datasource-basic
- With backend https://github.com/grafana/grafana-plugin-examples/tree/main/examples/datasource-with-backend
- Add auth for ds plugin https://grafana.com/developers/plugin-tools/how-to-guides/data-source-plugins/add-authentication-for-data-source-plugins
-
Panel type plugin:
-
Viz types:
-
Package, Sign, Publish:
- Package https://grafana.com/developers/plugin-tools/publish-a-plugin/package-a-plugin
- Publish a plugin - signing https://grafana.com/developers/plugin-tools/publish-a-plugin/sign-a-plugin
- Publish or update a plugin https://grafana.com/developers/plugin-tools/publish-a-plugin/publish-a-plugin
- Publish a plugin FAQs https://grafana.com/developers/plugin-tools/publish-a-plugin/publish-faqs
- Plugin policies https://grafana.com/legal/plugins/#what-are-the-different-classifications-of-plugins