A ready-to-use Cytoscape Web plugin with a panel, a menu action, and a context menu item. Copy this directory to scaffold a new app.
| Field | Value |
|---|---|
| Federation name | template (change this) |
| Dev server port | 5555 (change this) |
| Entry point | template@http://localhost:5555/remoteEntry.js |
Most people should use the generator instead — it does everything below and fills in the identity for you:
npm create cytoscape-app my-appThis directory is the worked example the generator's full template is built
from. To start from it by hand:
cp -r project-template my-app
cd my-app
npm install
npm run devThe dev server prints the link that installs your app into a local host:
Cytoscape Web app template — http://localhost:5555
Install it into a local host:
http://localhost:5500/?installApp=http://localhost:5555/cyweb-app.json
Start the host (cd cytoscape-web && npm run dev), open that URL, confirm the
install, and enable the app under Apps → App Settings.
You do not edit anything in the host repository. The host has accepted a
manifest URL through ?installApp= all along; the dev server serves yours at
/cyweb-app.json, generated from your package.json on every request, so it
cannot go stale when you change the port or the version.
{
"name": "@you/my-app",
"version": "0.1.0",
"description": "What your app does — shown in App Settings",
"cyweb": {
"id": "myApp",
"displayName": "My App",
"port": 6000
}
}That is your app's whole identity, and it is written once. cyweb.id is the
Module Federation container name, the CyApp.id and the id the host registers,
all at the same time — before this block those were three separate strings that
had to be kept in agreement by hand.
Pick a port nothing else uses. The examples occupy 2222, 3333, 5555, 6100 and 7000, and the host takes 5500.
Nothing. It is three lines and it reads the block above:
import { defineCyWebApp } from '@cytoscape-web/app-runtime/vite'
export default defineCyWebApp(import.meta.url)The federation wiring lives in @cytoscape-web/app-runtime, because four parts
of it are load-bearing and each fails in a way that is hard to read — an ESM
remote type, a production entry that is a sentinel rather than a URL, the
runtime plugin that resolves the host, and five shared singletons that must
match the host exactly. The comments in the file say what each one does.
Need a plugin, an alias or a define? Pass { vite: { … } }; it is merged
last. Touching a field the SDK owns fails the build and names the path.
resources→ add or remove panels and menu itemsmount()→ customize the context menu item, or add more (edge, canvas)unmount()→ clean up any event listeners you registered
Identity is already handled: id, displayName, version and description
arrive from virtual:cyweb-app-meta, which the build fills in from
package.json. Do not import package.json directly — that pulls the whole
file, devDependencies included, into your browser bundle to read one string.
components/TemplatePanel.tsx→ replace with your panel UImenuActions.ts→ replace with your menu action
project-template/
├── src/
│ ├── index.ts ← re-exports app config as default
│ ├── TemplateApp.tsx ← app config: resources and lifecycle
│ ├── contextMenus.ts ← context menu registration (Graph Traversal example)
│ ├── menuActions.ts ← apps-menu action (NetworkApi example)
│ └── components/
│ └── TemplatePanel.tsx ← right-panel component (WorkspaceApi example)
├── vite.config.ts ← three lines: defineCyWebApp(import.meta.url)
├── index.html ← remote-only stub (Vite needs an HTML entry)
├── test/appConfig.test.ts ← identity, and the shape of what ./AppConfig exports
├── tsconfig.json ← app sources (skipLibCheck: false)
├── tsconfig.node.json ← vite.config.ts
├── tsconfig.test.json ← test/
└── package.json
| File | Pattern |
|---|---|
TemplateApp.tsx |
Declarative resources[], mount() delegates to contextMenus.ts |
contextMenus.ts |
getConnectedNodes() + additiveSelect() — Graph Traversal + Selection APIs |
TemplatePanel.tsx |
useWorkspaceApi() + ApiResult<T> pattern, MUI shared singletons |
menuActions.ts |
apis.network.createNetworkFromEdgeList() from onClick(apis) |
vite.config.ts |
One call. The federation block, the runtime host resolution and the bundled-shared gate all come from @cytoscape-web/app-runtime |
package.json (cyweb block) |
The app's identity, written once and read by the build, the app config and the dev install manifest |
Right-click a node to see "Template: Select Neighbors" — it uses
getConnectedNodes() to find adjacent nodes, then additiveSelect() to
highlight them. The registration lives in src/contextMenus.ts.
To add more items, create a new function in contextMenus.ts and call it
from mount(). Items are auto-cleaned when the app is disabled.
npm run build && npx cyweb-app verifyChecks the built output against the federation contract: the ESM remote shape,
the production sentinel, the registered runtime plugin, the shared singletons,
and two artifact-hygiene rules — that package.json did not end up in your
bundle, and that build-machine paths stayed out of your chunks.
It reads this directory only, so it keeps working after you copy the app out of this repository.
npm run buildThere is no production/development flag any more. The build ships a sentinel
instead of a host URL, and the running host supplies its own address at load
time via window.__CYWEB_HOST__ — so the same artifact works against
production, a staging host, or a colleague's localhost:5500.
Verify the output before publishing:
npm run verify:federation # from the repo rootnpm run build:zipwrites <appId>-<version>.zip next to package.json — for this template,
template-1.0.0.zip. Upload that file on the App Store submission page.
npm run build does not write it; the zip is opt-in, so ordinary builds do
not leave archives lying around.
The archive holds the browser publish set, not the whole of dist/:
| Excluded | Why |
|---|---|
mf-manifest.json |
embeds absolute build-machine paths — your home directory and username |
mf-stats.json |
build metadata; nothing fetches it at runtime |
remoteEntry.ssr.js, ssrEntryLoader-*, module-runner-* |
Node-only code, unreachable in a browser |
index.html, source maps |
the host never loads them, and a Store origin should not serve developer HTML |
The list is an allowlist, applied after a set of named denies: if a future plugin version emits a file class it does not name, the build fails rather than uploading it. Classifying the new class is the fix; the failure is deliberate.
The archive also carries a generated cy-manifest.json at its root, holding
this app's identity and publication metadata so the Store does not have to ask
for them by hand. It is derived from package.json — never edit it, and never
commit one. npx cyweb-app manifest prints the same bytes without building an
archive.
Fill these in before submitting. None is needed to build or run, and packaging warns about each one it does not find:
package.json |
Becomes |
|---|---|
author |
the public author name — a display name only, never an email |
license |
the licence on the listing |
repository |
the source link; the object form's directory is kept for monorepos |
homepage |
the project's own page |
cyweb.compatibleHostVersions |
the host versions this app declares itself compatible with |
- hello-world/ — full reference app with 13 examples covering all APIs
- guides/ — App Developer Guide (getting started, architecture, registration patterns, lifecycle, troubleshooting)
- @cytoscape-web/api-types — TypeScript types for all host APIs