Skip to content
Open Garphield

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.

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.

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.

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.

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.

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);
});
// Later
stop();

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 complexity
console.log(view.rendered); // nodes and edges on canvas
console.log(view.hiddenEdges); // shown and omitted edge counts

Switch the presentation with the matching command:

window.garphield.run("view.detail", { mode: "overview" });

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 }),
);
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.

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.