Garphield automation API
Use the Automation API to drive the live workbench through its command registry. You can also use the embed driver for a more integrated approach to adding Garphield to your app.
This page covers direct access to the live workbench. Core concepts explains graph data and project state.
No browser available?
Section titled “No browser available?”Use the Python or R package to validate a complete project and create a URL without network I/O:
import garphield as gph
project = gph.Project.load("network.gph")url = gph.share_url(project)The R equivalent is garphield_share_url(project). Both return a URL carrying
the complete project in a compressed #p= carrier. It uses zlib/DEFLATE plus
unpadded base64url and accepts at most 262,144 encoded characters. Opening the
link runs Garphield’s normal layout when the project has no saved positions;
link generation itself does not run a layout or renderer. For a small
graph-only payload, use the documented
data: URL plus format path.
| Surface | Capability boundary |
|---|---|
| HTTP-only agents | Read documentation, LLM files, the command schema, and fetch graph files by URL. |
| Python and R | Validate, convert, fingerprint, save .gph projects, and perform project-link encoding without mounting a view. |
| Mounted browser renderer | window.garphield, WebMCP, embeds, and notebook widgets provide analysis, layout, audit, rendering, and PNG export. |
| Playwright | Automates the mounted renderer in a headless browser; it is not browser-free. |
There is currently no hosted-project or server-render endpoint. Projects above
the inline limit must be shared as .gph files.
Choose an automation surface
Section titled “Choose an automation surface”The live automation surfaces in this section all require a mounted browser
renderer. Browser-free Python and R workflows operate on project documents;
they do not provide window.garphield or WebMCP.
window.garphield is the stable, canonical automation API for same-frame
JavaScript: use it from the browser console, extensions, and Playwright tests
that can run code in the workbench frame. The remaining sections document that
API.
Compatible browser agents can instead discover page-defined garphield_*
tools through the browser-native WebMCP bridge. This is an experimental,
progressive enhancement based on the WebMCP W3C Community Group Draft: Garphield
feature-detects it, so browsers and agent harnesses without WebMCP continue to
use window.garphield unchanged. It is not the third-party webmcp.dev widget
and it adds no Garphield UI.
| WebMCP tool | Equivalent operation |
|---|---|
garphield_schema |
List registered commands. |
garphield_run |
Run a command with { id, params? }. |
garphield_get_state |
Read the serializable view state. |
garphield_apply |
Apply a state document or compressed payload. |
garphield_view |
Inspect the current drawing and projection. |
garphield_export |
Export graph text in a supported format. |
garphield_audit |
Read accessibility findings. |
garphield_quality |
Read layout-quality findings. |
garphield_get_share_url |
Get a complete share URL. |
Agents should call garphield_schema before garphield_run and use
garphield_get_state or garphield_view to inspect the result. WebMCP exposes
request/response operations only; for subscriptions and other same-frame
capabilities, use window.garphield.
Run the production workbench with Playwright
Section titled “Run the production workbench with Playwright”Playwright is a headless-browser surface, not a browser-free surface. This script opens the production Garphield workbench, waits for its mounted API, loads a supported sample, awaits the requested layout, frames final coordinates, and reads the resulting state:
import { chromium } from "playwright";
const browser = await chromium.launch({ headless: true });
try { const page = await browser.newPage(); await page.goto("https://garphield.com/", { waitUntil: "domcontentloaded", }); await page.waitForFunction( () => typeof window.garphield?.run === "function", undefined, { timeout: 30_000 }, );
await page.evaluate(async () => { await Promise.resolve( window.garphield.run("sample.load", { id: "petersen" }), ); }); await page.waitForFunction( () => window.garphield?.getState?.().dataset?.id === "petersen", undefined, { timeout: 30_000 }, );
await page.evaluate(async () => { const layout = await Promise.resolve( window.garphield.run("layout.set", { mode: "force" }), ); if (layout && !layout.ok) { throw new Error(layout.error?.message ?? "Layout mode failed"); } const settled = await Promise.resolve( window.garphield.run("layout.wait"), ); if (settled && !settled.ok) { throw new Error(settled.error?.message ?? "Layout did not settle"); } });
const id = await page.evaluate(async () => { const nodeLink = JSON.parse(window.garphield.export("nodeLink")); const id = nodeLink.nodes?.[0]?.id; if (typeof id !== "string") throw new Error("The graph has no node ID"); const selected = await Promise.resolve( window.garphield.run("selection.select", { id }), ); if (selected && !selected.ok) { throw new Error(selected.error?.message ?? "Selection failed"); } return id; }); await page.evaluate(async (id) => { const framed = await Promise.resolve( window.garphield.run("camera.frame", { nodeIds: [id], durationMs: 0, }), ); if (!framed?.ok) { throw new Error(framed?.error?.message ?? "Frame failed"); } const audit = window.garphield.audit(); const quality = window.garphield.quality(); if (!audit.ok) { throw new Error("Unsafe view"); } if (quality.verdict !== "good") { console.warn("View needs review", quality); if (quality.verdict === "poor") throw new Error("Unsafe view"); } }, id);
const state = await page.evaluate(() => window.garphield.getState()); console.log({ dataset: state.dataset.id, layout: state.layout, node: id });} finally { await browser.close();}layout.set reserves the next layout epoch before the renderer effect runs,
so an immediately following layout.wait (without an epoch parameter) waits
for that request rather than a previous settlement. The same correlation is
used after a successful graph file.load and after apply() when it changes
the graph or layout. An explicit layout.wait({ epoch }) remains supported.
Discover and run commands
Section titled “Discover and run commands”schema() returns the live command catalogue. Run the ids it returns:
Every call may be synchronous or asynchronous, so callers should use
await Promise.resolve(run(...)). Read the declared result delivery marker:
"envelope" results use the typed { ok, value } or { ok: false, error }
shape, while "raw" results are consumed directly. Void commands may return
undefined (or null on JSON transports). Unknown ids and invalid parameters
can throw synchronously; raw-command transport failures may reject the promise
or surface as an outer transport error rather than an envelope.
await Promise.resolve(window.garphield.run("sample.load", { id: "petersen" }));await Promise.resolve(window.garphield.run("layout.set", { mode: "levels" }));await Promise.resolve(window.garphield.run("layout.wait"));await Promise.resolve(window.garphield.run("selection.select", { id: "3" }));await Promise.resolve(window.garphield.run("camera.frame", { nodeIds: ["3"] }));await Promise.resolve(window.garphield.run("selection.path", { source: "3", target: "8" }));await Promise.resolve(window.garphield.run("view.fisheye", { enabled: true, magnification: 4, radiusScale: 0.45,}));selection.path respects directed links and returns the exact shortest-path
node and edge ids. It returns a validation error when no directed route exists.
view.fisheye gives agents parity with the lens controls: it enables or disables
the lens and sets its magnification and viewport-relative radius. Pointer focus
remains transient; a Playwright driver can move it with a primary click (with a
short transition), drag the inner grab ring past its breakaway threshold to
release a pinned lens, primary-drag its outer boundary to resize it, or
right-drag inside it to change magnification. Escape also releases a pinned
lens. Wheel and touchpad gestures continue to zoom the camera, even inside the
lens.
Command families
Section titled “Command families”The registry groups commands by the job they perform:
| Family | Examples |
|---|---|
| Data | Load a sample, load a URL, generate a graph. |
| Layout and view | Switch layout, relax around a selection, set graph detail, frame the camera, or configure the fisheye. |
| Channels and filters | Browse sources, bind or unbind a channel, add a filter, add or clear a transformation. |
| Selection | Select, find a directed path, grow, shrink, invert, or clear a selection. |
| History | Pin a state, jump to a point, describe the tree, or diff two points. |
| Narrative | Save a set, capture a scene, play a storyboard, or copy a storyboard link. |
Use schema() as the source of truth for current ids and parameter descriptions.
The command palette and embed capability grants use the same registry. The
Python widget reaches the renderer through its typed bridge.
file.load accepts http:, https:, and data: URLs, plus a blob: URL
created in the same browser context. Only cross-origin HTTP(S) sources need
CORS. Prefer HTTPS for hosted files because the production HTTPS site normally
cannot fetch an HTTP URL. The optional authoritative format is dot, gexf,
graphml, gml, nodeLink, compactJson, csv, edgeList, matrixMarket,
graph6, sparse6, digraph6, or gph. Garphield otherwise uses the URL
suffix and defaults an unknown or absent suffix to JSON content sniffing. MIME
type does not select the parser. Compact JSON uses node-array positions as IDs;
the graph6 family carries topology only and therefore receives numeric labels.
Build a data: URI first and then percent-encode the entire URI as the file
query value. See the
inline-loading recipes.
file.load is asynchronous and returns the typed FileLoadResultV1 descriptor
inside the normal CommandResult envelope. Await it before reading state and
branch on both transport failure and flat-table construction:
const result = await Promise.resolve( window.garphield.run("file.load", { url, format: "gexf" }),);if (!result?.ok) throw new Error(result?.error?.message ?? "Load failed");if (result.value.status === "construction-required") { throw new Error("This table needs an explicit graph construction recipe");}const settled = await Promise.resolve( window.garphield.run("layout.wait"),);if (!settled?.ok) throw new Error(settled?.error?.message ?? "Layout did not settle");For contrast, artifact.describe is declared with delivery: "raw" and
resolves directly to an ArtifactFormatV1[]; consume that array without an
ok branch. artifact.create and artifact.download likewise return raw
typed objects. Use each command’s generated result descriptor to decide which
branching rule applies instead of inferring it from kind or the command id.
status: "loaded" means the graph store has been replaced; it does not mean
layout has settled. The replacement reserves the layout epoch, so call
layout.wait immediately after the load to await that exact layout before
selecting or framing coordinates. Fetch, network, and CORS acquisition
failures return LOAD_FAILED with retryable: true. After bytes/string
acquisition, decode, parse, schema, and format failures return LOAD_FAILED
with retryable: false; the message and URL details are preserved. A request
superseded by newer graph activity returns STALE_STATE with retryable: true
and no obsolete toast.
Read and apply state
Section titled “Read and apply state”const next = structuredClone(window.garphield.getState());next.layout = "levels";next.style = { ...(next.style ?? {}), nodeSurface: "sphere",};const applied = window.garphield.apply(next);if (!applied) throw new Error("StateDoc was rejected");const settled = await Promise.resolve( window.garphield.run("layout.wait"),);if (!settled?.ok) throw new Error(settled?.error?.message ?? "Layout did not settle");const nodeLink = JSON.parse(window.garphield.export("nodeLink"));const id = nodeLink.nodes?.[0]?.id;if (typeof id !== "string") throw new Error("The graph has no node ID");const framed = await Promise.resolve( window.garphield.run("camera.frame", { nodeIds: [id], durationMs: 0 }),);if (!framed?.ok) throw new Error(framed?.error?.message ?? "Frame failed");getState() returns the serializable view document with the current camera.
apply() accepts a complete state object or compressed share payload and
returns whether validation and dispatch succeeded. A true result acknowledges
the request; it does not report completion of asynchronous generator loading.
For a generator workflow, await generator.create, confirm through getState()
that the requested dataset was adopted, then await layout.wait. A newer edit
or dataset request can supersede a generator while its module loads.
For changes dispatched synchronously, apply() reserves a layout epoch when
it changes the graph or layout; await layout.wait immediately after dispatch,
then select or frame final coordinates. Set style.nodeSurface to "flat",
"lumen", "sphere", "prism", or "sketchy"; the value is also retained
by generated view-state URLs. The optional top-level nodeSurfaceStyle field
remains accepted as a compatibility alias for older state documents.
Validate complete StateDocs against the published
StateDoc JSON Schema.
false means invalid and inert: the existing state does not change, and a
StateDoc’s file reference does not carry or replace file graph bytes. apply()
also strips positions; full positions and session restoration belong to
.gph project loading. true means validation and dispatch succeeded, not
that an asynchronous sample layout has settled. Use layout.wait when settled
geometry is required. A successful graph file.load uses the same immediate
wait contract.
The canonical layout modes are force, levels, and geo. quality remains
only a legacy input normalized to force when decoding older documents; new
commands and documents should use the canonical names.
The document’s style block contains node surface, node size, node gap, link
width, link opacity, curvature, and the fisheye’s enabled state, magnification,
and radius scale. Theme, labels, link direction, and link gradient remain
top-level for backward compatibility. The same complete style is carried by
share URLs, history pins, .gph projects, apply(), and observe() updates;
fisheye pointer focus is deliberately transient.
Subscribe to state changes:
const stop = window.garphield.observe((state) => { console.log(state.selection, state.camera);});
// Laterstop();Inspect the current drawing
Section titled “Inspect the current drawing”view() separates the saved project from the current Full or Backbone
presentation:
const view = window.garphield.view();
console.log(view.detail); // "full" or "overview"console.log(view.profile); // graph complexityconsole.log(view.rendered); // nodes and edges on canvasconsole.log(view.hiddenEdges); // shown and omitted edge countsSwitch the presentation with the matching command:
window.garphield.run("view.detail", { mode: "overview" });Node IDs and history IDs
Section titled “Node IDs and history IDs”Graph node IDs come from the node-link export’s nodes[].id values. They are
data identifiers used by selection and camera commands:
const nodeLink = JSON.parse(window.garphield.export("nodeLink"));const graphNodeId = nodeLink.nodes[0].id;await Promise.resolve( window.garphield.run("camera.frame", { nodeIds: [graphNodeId] }),);History IDs are provenance-operation IDs returned by history.tree(). They are
not graph node IDs and are the values expected by history.jump and
history.diff:
const tree = await Promise.resolve(window.garphield.run("history.tree"));const historyId = tree.nodes[0].id;await Promise.resolve( window.garphield.run("history.jump", { nodeId: historyId }),);Export and inspect
Section titled “Export and inspect”const graphml = window.garphield.export("graphml");const audit = window.garphield.audit();const quality = window.garphield.quality();const share = window.garphield.getShareString();const shareUrl = window.garphield.getShareUrl();const story = window.garphield.getStoryboardShareString();Supported export values are gexf, graphml, gml, and nodeLink.
Gate an automated result on both reports, surfacing every non-good quality verdict:
const audit = window.garphield.audit();const quality = window.garphield.quality();if (!audit.ok) throw new Error("Unsafe view");if (quality.verdict !== "good") { console.warn("View needs review", quality); if (quality.verdict === "poor") throw new Error("Unsafe view");}audit.ok is false only when a critical automated accessibility heuristic
fails; manual checks remain findings. quality().verdict is good, warn,
poor, info, or na. Reject poor, surface warn for judgment, and treat
na as ungraded rather than passed. Quality is not a certification.
API summary
Section titled “API summary”| Method | Returns |
|---|---|
schema() |
Serializable command descriptors |
run(id, params?) |
A registered command result |
getState() |
Current view state |
apply(doc) |
Whether state validated and applied |
view() |
Current Full or Backbone projection |
export(format) |
Serialized graph text |
audit() |
Accessibility findings |
quality() |
Layout-quality report |
getShareString() |
Compressed view payload |
getShareUrl() |
Full share URL for the current view |
getStoryboardShareString() |
Compressed story payload |
observe(callback) |
Unsubscribe function |
window.garphield exists while the workbench is mounted. Look it up again after
navigation rather than keeping a handle to an unmounted page.