Developing Apps for Cytoscape Web
The SDK and scaffolder are published at
0.x. An app runs inside Cytoscape Web with the host's full privileges — no sandbox, no signature verification, so nothing here is ready to carry production work. That is a property of the platform today, not of this tooling.An app runs inside Cytoscape Web with the host's full privileges — same origin, DOM, storage and network identity. There is no sandbox and no signature verification, and an app can read the user's credentials. Install only apps you trust, and understand that asking someone to install yours asks the same of them.
The API may change before
1.0. What is missing before it can be called ready is host-side: app isolation, a capability API, and artifact integrity.
Reference implementations and documentation for building apps for Cytoscape Web.
- Targets Cytoscape Web App API 1.0 (
@cytoscape-web/api-types) - Live examples
create-cytoscape-app·@cytoscape-web/app-runtime
This repository is for third-party developers who want to build apps for Cytoscape Web.
You do not need to change the host source code. Your app is loaded by the host through Module Federation (Vite). Apps can add:
- panel components in the right-side App Panel
- menu items in the Apps dropdown
- context menu actions for right-click workflows
npm create cytoscape-app my-app
cd my-app
npm run devThe dev server prints the link that installs your app into a running local host:
Cytoscape Web app myApp — http://localhost:6001
Install it into http://localhost:5500/:
http://localhost:5500/?installApp=http://localhost:6001/cyweb-app.json
Start a host (npm run dev in a cytoscape-web
checkout), open that link, confirm the install, and enable the app under
Apps → App Settings.
Or skip the host checkout entirely and develop against the shared staging
host, with your app still on localhost:
CYWEB_DEV_HOST=https://dev1.ndexbio.org/cytoscape npm run devThe printed link then points at dev1. Your browser asks once for permission to reach your own machine — click Allow; see getting-started §5d for what that prompt says and what to do if you have already dismissed it.
Needs
@cytoscape-web/app-runtime0.2.0 or newer.0.1.0ignores the variable silently — the banner still nameslocalhost:5500and the app still loads the local host, which is the only tell.
Nothing in the host repository is edited. Your dev server serves a one-entry
app manifest at /cyweb-app.json, generated from your package.json on every
request, and the host has accepted ?installApp= all along.
If it behaves like an older version, it probably is one.
npm createcan run a cached copy of the scaffolder without checking the registry, so anyone who ran it before keeps that one.npm create cytoscape-app@latest my-appnames the tag explicitly — how to tell, and how to clear it.
Your app does not hot-reload inside the host. Vite's HMR does not cross the federation boundary — that is a separate feature (
dev.remoteHmr), off by default. Your dev server rebuilds the changed module immediately; reload the host page to pick it up.
If you want to run the four example apps rather than build your own:
git clone https://github.com/cytoscape/cytoscape-web.git
git clone https://github.com/cytoscape/cytoscape-web-app-examples.git
cd cytoscape-web-app-examples && npm install && npm run dev # all four apps
cd ../cytoscape-web && npm install && npm run dev # the host, :5500Then open http://localhost:5500, Apps → App Settings, and enable one. The
dev server reads src/assets/apps.local.json, which already lists them.
Still manual, and deliberately so. The production instance loads apps from a
curated allowlist (apps.json) maintained by the core team; a self-service App
Store is designed but not built, and its launch is gated on the same host-side
isolation work as the banner above. To publish, please
contact the Cytoscape team.
npm create cytoscape-app my-app
cd my-appThat is the whole setup — it installs dependencies and leaves a project that
builds, verifies and tests as it stands. Add --yes with --id, --port and
--template to skip the prompts entirely; every prompt has a flag, so an agent
can drive it without a terminal.
Prefer to start from a worked example?
cp -r project-template my-appstill works and gives you the same shape by hand.
Then two things:
1. Your identity, in package.json. Written once, and read from there by
the build, by the app config, and by the install manifest:
{
"name": "@you/my-app",
"version": "0.1.0",
"description": "What your app does — shown in App Settings",
"cyweb": { "id": "myApp", "displayName": "My App", "port": 6001 }
}cyweb.id is the Module Federation container name, the CyApp.id and the id
the host registers, all at the same time.
2. Your app, in src/. Replace the panel and menu components, and adjust
resources in src/MyApp.tsx. vite.config.ts is three lines and needs no
edits.
Then npm run dev. It prints the link that installs your app into a running
local host:
Cytoscape Web app myApp — http://localhost:6001
Install it into http://localhost:5500/:
http://localhost:5500/?installApp=http://localhost:6001/cyweb-app.json
Nothing in the host repository is edited. The dev server serves a one-entry
manifest at /cyweb-app.json, generated from your package.json on each
request, and the host has accepted ?installApp= all along.
See project-template/README.md for details.
Every app exports one CyAppWithLifecycle object:
import { lazy } from 'react'
import { CyAppWithLifecycle } from 'cyweb/ApiTypes'
// Identity, from the `cyweb` block and the standard fields in package.json.
// Do NOT `import packageJson from '../package.json'` — that pulls the whole
// file, devDependencies included, into your browser bundle to read one string.
// `cyweb-app verify` fails a build that does.
import { description, displayName, id, version } from 'virtual:cyweb-app-meta'
export const MyApp: CyAppWithLifecycle = {
// Written once, in package.json:
// "cyweb": { "id": "myApp", "displayName": "My App", "port": 6001 }
// `id` is the Module Federation container name, this CyApp's id and the id
// the host registers, all at the same time.
id,
name: displayName,
description,
version,
apiVersion: '1.0',
// Declarative resource registration — panels and menu items
resources: [
{
slot: 'right-panel',
id: 'MyPanel',
title: 'My Panel',
component: lazy(() => import('./components/MyPanel')),
},
// An 'apps-menu' entry is plain data: the host renders the row and
// closes the dropdown itself. Put any UI behind it in a dialog opened
// from onClick via apis.dialog.open({ title, render }).
{
slot: 'apps-menu',
id: 'MyMenuItem',
label: 'My Action',
onClick: (apis) => {
apis.network.createNetworkFromEdgeList({ name: 'Example', edgeList: [['A', 'B']] })
},
},
],
// Context menus and event listeners — registered in mount()
mount(context) {
context.apis.contextMenu.addContextMenuItem({
label: 'My App: Log Node Info',
targetTypes: ['node'],
handler: (ctx) => {
const result = context.apis.element.getNode(ctx.networkId, ctx.id!)
if (result.success) console.info('Node:', result.data)
},
})
},
unmount() {
/* clean up event listeners only — context menus are auto-cleaned */
},
}| Guide | Topics |
|---|---|
| Getting Started | Scaffold, configure, register, run |
| Architecture Overview | Module Federation, type system, API layers |
| Registration Patterns | Panels, menus, context menus, upsert, batch |
| Lifecycle & Cleanup | mount/unmount, auto-cleanup, re-enable |
| Troubleshooting | Build errors, runtime errors, FAQ |
| Resource | Description |
|---|---|
| App API Reference | Complete reference for all domain APIs, ResourceApi, Event Bus, error codes, and lifecycle |
@cytoscape-web/api-types (README) |
TypeScript types package — install for IDE support |
| CHANGELOG | Version history for the types package |
| Spec | Scope |
|---|---|
| App API Specification | Full 2000-line spec for all 10 domain APIs |
| Resource Registration Specification | Slot model, lifecycle, cleanup, error boundaries |
| Registration Minimal App Example | End-to-end code walkthrough of all registration paths |
All API methods return ApiResult<T>. Always check result.success before reading result.data.
| API | Import | Purpose |
|---|---|---|
| WorkspaceApi | cyweb/WorkspaceApi |
Current network ID, workspace info, switch network |
| ElementApi | cyweb/ElementApi |
Create/delete nodes and edges, graph traversal queries |
| NetworkApi | cyweb/NetworkApi |
Create/delete networks, import CX2 |
| SelectionApi | cyweb/SelectionApi |
Read and mutate the current selection |
| ViewportApi | cyweb/ViewportApi |
Pan, zoom, fit, read/write node positions |
| TableApi | cyweb/TableApi |
Read and write node/edge attribute tables |
| VisualStyleApi | cyweb/VisualStyleApi |
Set defaults, bypasses, and mappings |
| LayoutApi | cyweb/LayoutApi |
Run layout algorithms |
| ExportApi | cyweb/ExportApi |
Export network as CX2 |
| EventBus | cyweb/EventBus |
Subscribe to host events (useCyWebEvent) |
| AppIdContext | cyweb/AppIdContext |
Per-app context (useAppContext) for resource and context menu APIs |
| ApiTypes | cyweb/ApiTypes |
TypeScript types for all of the above |
| Event | Fires when |
|---|---|
network:created |
A new network is added to the workspace |
network:deleted |
A network is removed |
network:switched |
The user navigates to a different network |
selection:changed |
Node or edge selection changes |
layout:started |
A layout algorithm begins |
layout:completed |
A layout algorithm finishes |
style:changed |
A visual style property changes |
data:changed |
Node or edge attribute data changes |
Outside React components, the same APIs are available via window.CyWebApi:
window.addEventListener('cywebapi:ready', () => {
const api = window.CyWebApi
const result = api.workspace.getCurrentNetworkId()
// ...
})Note:
window.CyWebApidoes not includeresourceor per-appcontextMenu. Those are only available insidemount()viacontext.apisor viauseAppContext().
| Example | Best for | Details |
|---|---|---|
| project-template/ | Your first app — panel, menu action, and context menu | README |
| hello-world/ | Full API coverage — 13 examples covering all APIs | README |
| network-statistics/ | Non-React — graph traversal, event-driven logging | README |
| network-workflows/ | CX2 import, Jupyter integration, menu workflows | README |
Recommended reading order: project-template → hello-world → network-statistics → network-workflows
Install the types package for IDE support:
npm install --save-dev @cytoscape-web/api-types @cytoscape-web/app-runtimeReference both packages' bundled declarations from your tsconfig.json so
TypeScript resolves the cyweb/* ambient modules and virtual:cyweb-app-meta:
{
"include": ["src/**/*"],
"compilerOptions": {
"moduleResolution": "bundler",
"types": ["@cytoscape-web/api-types", "@cytoscape-web/app-runtime/meta"]
}
}Listing them in
typesis what pulls in the ambientcyweb/*andvirtual:cyweb-app-metadeclarations — the same mechanismvite/clientuses. Do not settypeRoots: the example apps used to point it at./node_modules/@types, a directory that does not exist in a workspace, and setting it suppresses the default lookup that actually finds the types. See any of the example apps'tsconfig.jsonfor a working reference.
npm run dev # run all workspaces concurrently
npm run dev:hello-world # run one app
npm run dev:network-statistics
npm run dev:network-workflows
npm run dev:project-template
npm run build # build all workspaces
npm run deploy # build and copy each workspace's dist/ into docs/npm run verify:federation # the built dist/ has the right federation shape
npm run preflight:host -- <hostUrl> # the host publishes a usable descriptor
npm run preflight:apps -- <hostUrl> <appsBase> # the PUBLISHED apps load in that hostThe three cover different layers, and the gap between the first two is why the
third exists. verify:federation reads dist/, preflight:host reads the
host — so a fault in the serving layer between them is invisible to both. That is not
hypothetical: GitHub Pages ran this repo's docs/ through Jekyll, which drops
_-prefixed paths, and silently 404'd the _virtual_mf-* chunk every app
imports first while both other checks stayed green.
preflight:apps loads each published app through a real dynamic import()
inside a real host page, so transitive chunk fetches, CORS and MIME are the real
ones. -- --selftest proves it can still fail.
Older examples used direct store imports. They still work, but new apps should
use the App API hooks instead — they return ApiResult<T> and provide a
stable, documented contract. See
Architecture Overview → Host Exposes Reference
for the full list of legacy cyweb/*Store exposes.
| Deprecated pattern | Recommended replacement |
|---|---|
useNetworkStore |
useNetworkApi |
useTableStore |
useTableApi |
useWorkspaceStore |
useWorkspaceApi |