Hive is a fully open-source schema registry, analytics, metrics and gateway for GraphQL federation and other GraphQL APIs.
@graphql-hive/laboratory is Hive's embeddable, in-browser GraphQL IDE (the "Lab"): an editor and
runner for GraphQL operations, in the spirit of GraphiQL. It powers the Laboratory page inside the
Hive Console and can be embedded into any page that talks to a GraphQL endpoint.
Pre-1.0. The public API (see
LaboratoryApi) is still evolving and can change between patch releases.
- Query builder: click schema fields/arguments to build the operation
- Schema explorer with search (list and tree modes)
- Collections (saved operations) and request history
- Schema documentation pane (opt-in via
enableDocs), reachable from Builder rows and editor hovers - Preflight scripts: run JavaScript before a request in a sandboxed Web Worker
- Environment variables with
{{variable}}interpolation - Renders a federation query plan when a server includes one in the response
extensions(extensions.queryPlan), e.g. Hive Router or Hive Gateway - Plugin system for adding tabs, command-palette entries and preflight APIs
This package is storage- and transport-agnostic. It exposes state as props and reports changes via callbacks; the host decides where data lives.
- Hive Console (
packages/web/app) embeds the<Laboratory>React component directly intarget-laboratory-new.tsxand wires the callbacks to the Hive GraphQL API andlocalStorage. - Hive Gateway serves the Lab as its GraphiQL replacement via
@graphql-hive/render-laboratory(../render-laboratory), which wraps this package's UMD bundle plus the Monaco workers into a self-contained HTML page forgraphql-yogaservers. - Hive Router embeds this package's UMD bundle (
dist/hive-laboratory.umd.js) directly at build time, generating a static page that calls theHiveLaboratory.renderLaboratory()global.
pnpm add @graphql-hive/laboratoryTwo entry points are exported from src/index.tsx:
renderLaboratory(el, props) mounts the Lab into a DOM node and wires all state to
localStorage for you. This is the quickest way to embed it:
import { renderLaboratory } from '@graphql-hive/laboratory'
renderLaboratory(document.getElementById('root')!, {
defaultEndpoint: 'https://example.com/graphql'
})<Laboratory /> is the React component when you want to own persistence. Every piece of state
follows the same controlled/uncontrolled contract: a defaultX prop seeds the initial value and an
onXChange callback fires whenever it changes.
import { Laboratory } from '@graphql-hive/laboratory'
function LabPage() {
return (
<Laboratory
theme="dark"
defaultEndpoint={endpoint}
onEndpointChange={setEndpoint}
defaultCollections={collections}
onCollectionsChange={saveCollections}
defaultHistory={history}
onHistoryChange={saveHistory}
// ...tabs, operations, env, preflight, settings, plugins
/>
)
}The full prop surface (endpoint, collections, operations, history, tabs, env, preflight, settings,
tests, plugins, plus granular onXCreate/onXUpdate/onXDelete callbacks and a permissions
object) is defined by LaboratoryApi. Treat that interface
as the source of truth rather than this README.
operations is the pool of documents; tabs is what is open in the tab bar. They are separate
stores, so a host that seeds one must seed the other: an operation with no tab pointing at it is
unreachable, and an operation tab whose data.id matches no operation renders an empty tab. Seed
defaultOperations, defaultTabs and defaultActiveTabId together, with ids that line up. See
dev/operations.ts for a worked example.
Pass a permissions object to gate actions per resource (preflight, collections,
collectionsOperations) with read/create/update/delete flags. Gating is applied in the UI
(controls are hidden/disabled) with a backstop in the operations logic; anything unspecified
defaults to allowed.
enableDocs adds a documentation icon to the left rail, opening a schema browser in the same slot
as Collections and History. It is off unless you pass it. Builder rows get an "Open in Docs" context
menu entry, and the GraphQL editor hover gets an "Open in Docs" link (the Lab serves that hover
itself when docs are on, instead of monaco-graphql).
The prop also decides whether introspection requests descriptions, since nothing else renders them.
That only reaches introspection the Lab performs itself: if you pass defaultSchemaIntrospection,
build it with descriptions or the pane will have nothing to show. introspectionFromSchema includes
them by default, so the usual introspectionFromSchema(buildSchema(sdl)) needs no extra options.
The Lab bundles its own styles and injects them into its shadow root, so there is no CSS file to import. It is client-side only (it uses Web Workers, Shadow DOM and Monaco), so mount it in the browser rather than during server-side rendering.
pnpm dev # Vite dev server on http://localhost:5173
pnpm build # library build (ES + CJS) and UMD build
pnpm lint # eslintpnpm dev mounts the Lab via src/main.tsx / index.html, a thin
harness that seeds collections, operations and tabs from dev/ on every load and persists
nothing, so a reload is always the same known state. No backend is required: the Vite dev server
mounts a mock GraphQL endpoint at the same origin (see
dev/mock-graphql.ts), which the harness points at by default.
Tests run from the monorepo root (this package has no test script):
# from the repo root
pnpm vitest run packages/libraries/laboratoryEach feature is a useX hook (state + actions) under src/lib, paired with a component
under src/components/laboratory. All the hooks are composed into one
context in laboratory.tsx and consumed via
useLaboratory().
| Feature | Hook (src/lib) |
UI (src/components/laboratory) |
|---|---|---|
| Endpoint + schema introspection | endpoint.ts |
(implicit) |
| Operations + run/abort | operations.ts, operations.utils.ts |
operation.tsx, builder.tsx |
| Collections | collections.ts |
collections.tsx |
| History | history.ts |
history.tsx, history-item.tsx |
| Preflight scripts | preflight.ts |
preflight.tsx |
| Environment variables | env.ts |
env.tsx |
| Settings | settings.ts |
settings.tsx |
| Tabs | tabs.ts |
tabs.tsx |
| Query plan | query-plan/ |
flow.tsx |
Because the Lab is embedded into pages it does not control, it renders inside a Shadow DOM
(ShadowRootContainer in laboratory.tsx) and injects
its CSS (Tailwind v4) plus Monaco's CSS inline. This gives two-way isolation from the host page's
styles. A consequence worth knowing: Radix portals (dropdowns, tooltips, dialogs) must target the
Lab's container (exposed on the context) rather than document.body, or they render unstyled.
The UI is built on shadcn/Radix primitives (src/components/ui), Monaco, and
@tanstack/react-form.
A plugin can add tabs, command-palette commands, and objects injected into the preflight sandbox.
See LaboratoryPlugin for the shape and
src/plugins/target-env.tsx for a worked example (the Target
Environment plugin used by Hive Console). Register plugins via the plugins prop.
This package is published to npm via Changesets from the monorepo:
- Add a changeset from the repo root:
pnpm changeset(pick@graphql-hive/laboratory, choose the bump, write the summary that becomes the changelog entry). - Opening a PR publishes an
alphasnapshot to npm for testing. - Merging to
mainaccumulates changes into an "Upcoming Release Changes" PR. - Merging that PR versions the package and publishes it to npm.
See CHANGELOG.md for release history.
Licensed under the MIT License.