Skip to main content

Project Directories

A project directory is a self-contained git repo that IS your service's storage. The editor reads and writes its files directly: one clean logic file per graph, positions in a sibling layout file, per-project assets and secrets, CI-able validation, and a git commit recorded at every publish.

my-service/
codefyui.project.toml manifest: name, plugin pins, default publish target
graphs/ <name>.graph.json logic (nodes/edges/params/presets, optional settings.device)
layout/ <name>.layout.json positions (reviewable, generated)
assets/images/ assets/models/ assets/data/ scaffolded empty
assets/output/ created on demand (e.g. ImageWriter)
assets/media/ created on demand (run-produced video, served by /api/media)
.env.example committed template of required secret keys
.env your secrets (gitignored, never committed)

Why the split?

graphs/<name>.graph.json holds only what changes the behavior of the graph (nodes, edges, parameters, embedded presets, and an optional settings block, for example {"device": "cuda"}, which assigns the device the graph runs on). Node positions and note geometry live in layout/<name>.layout.json. So a drag produces a diff only in layout/, and a parameter edit a diff only in graphs/ -- code review sees the logic change, not a wall of moved-pixels noise. (Known exception: SequentialModel sub-graph layer positions live inside params.layers and stay in the logic file.)

A missing layout file (or a node without a saved position) makes the editor auto-layout the graph on load and persist the result at the next save; a note missing only its geometry entry (size/binding) simply falls back to defaults -- geometry-only absence intentionally does not count as a missing layout.

A complete walkthrough

1. Create the project

cdui project init my-service
cd my-service

init scaffolds graphs/, layout/, and assets/{images,models,data}/ (empty, .gitkeep-tracked), writes .gitignore / .gitattributes / .env.example / README.md, and runs git init (no commit -- it prints the next steps). --force writes into a directory that is not empty; an existing manifest or README.md is never overwritten. assets/output/ is not created up front; it appears the first time a node (for example ImageWriter) writes to it.

2. Add a graph

Either build it in the editor (cdui start --project ., drop a Start, a GraphInput named x, and a GraphOutput named y, wire Start's trigger into GraphInput and GraphInput's value into GraphOutput, then press Ctrl/Cmd+S and name it echo), or drop this file at graphs/echo.graph.json:

{
"format_version": 1,
"name": "echo",
"description": "Echo the input string",
"nodes": [
{"id": "start", "type": "Start", "data": {"params": {}}},
{"id": "gi", "type": "GraphInput", "data": {"params": {"name": "x", "type": "string", "required": true, "default": "", "description": "text to echo"}}},
{"id": "out", "type": "GraphOutput", "data": {"params": {"name": "y", "description": "the echoed text"}}}
],
"edges": [
{"id": "t1", "source": "start", "target": "gi", "sourceHandle": "trigger", "targetHandle": "", "type": "trigger"},
{"id": "d1", "source": "gi", "target": "out", "sourceHandle": "value", "targetHandle": "value", "type": "data"}
],
"presets": [],
"settings": {"device": "cuda"}
}

settings is optional. A graph without it runs on the device the client chooses: the Settings device in the editor, or cpu for cdui run with no --device. See Device Backends.

3. Commit

git config user.name "You"
git config user.email "you@example.com"
git add -A
git commit -m "echo service"

.env is gitignored; .env.example is committed. Commit a small fetch script for large data, never the data or weights themselves. Once the server runs on the project (step 5), the editor's Source Control tab does this same staging and committing from the sidebar.

4. Validate (the CI gate)

cdui project validate .

validate initializes the FULL registry (builtin + custom + plugin nodes and presets, exactly like the server) and runs the publish pre-flight on every graph: the secret-in-graph check, contract, entry points, wiring, and node/preset validity. It also errors if .env is tracked by git, and warns (errors with --strict) on missing plugin pins. In CI, run restore then validate:

cdui project restore . # install the manifest's plugin pins by exact SHA
cdui project validate .

validate checks every graph under graphs/ and prints the checked count -- an empty graphs/ reports Validation passed (0 graphs checked) rather than a bare green. A canvas-only graph (say, a training graph that declares no GraphOutput) fails the contract gate, because every publishable graph needs at least one declared output. Either give it a legitimate output (the MNIST example project publishes its checkpoint path as a weights_path output) or validate only your publish targets:

cdui project validate . --graph serve # repeatable: --graph a --graph b

A --graph name that does not exist in graphs/ is an error, so a typo can never turn the CI gate into a vacuous pass.

Pins come from cdui project freeze .: it reads your locally-installed plugins and writes each one's exact commit SHA into codefyui.project.toml's [plugins] table (a plugin you installed as a local dev link is skipped -- there is no SHA to pin for a machine-specific path). Run it after installing or updating a plugin, and commit the manifest change before your next push:

cdui project freeze .

Freeze rewrites the manifest in place: any custom keys you have added (in [project], [publish], or your own tables) are preserved, but comments are not, and the [plugins] table is fully regenerated from what is installed.

5. Start the server on the project

cdui start --project .

The log prints Project: <abs> (git <short-sha>) and warns once, naming cdui project restore, if any pinned plugin is missing. (cdui dev --project . does the same with hot reload.)

Starting with --project is also what loads <project>/.env: plain KEY=VALUE lines (a leading export and surrounding quotes are tolerated), applied before node and plugin discovery with os.environ.setdefault semantics, so a variable already set in the shell wins. Only execution-time secrets belong there -- LLM API keys, anything a node reads from the environment at run time. CODEFYUI_* settings in the file do nothing, because the server's configuration is fixed before the file is read; set those in the shell or the systemd unit. The values are never logged, only their count, and without --project no .env is read at all.

6. Create an API key (invoke needs one)

The session token is <install dir>/.codefyui_dev/session.token for a server started by cdui start or cdui dev (default install dir ~/CodefyUI, i.e. $HOME\CodefyUI on Windows), or <CODEFYUI_USER_DATA_DIR>/session.token when that variable was exported before the launcher ran. See Graph as a Function for the other files in that directory, the platform directories a hand-launched uvicorn app.main:app uses, and why the token rotates on every restart.

PowerShell:

# payload.json: {"name": "demo"}
$token = Get-Content "$HOME\CodefyUI\.codefyui_dev\session.token"
curl.exe -s -X POST "http://127.0.0.1:8000/api/keys" `
-H "X-CodefyUI-Token: $token" -H "Content-Type: application/json" `
--data "@payload.json"

bash:

TOKEN=$(cat ~/CodefyUI/.codefyui_dev/session.token)
curl -s -X POST http://127.0.0.1:8000/api/keys \
-H "X-CodefyUI-Token: $TOKEN" -H "Content-Type: application/json" \
--data '{"name": "demo"}'

# -> {"id": 1, "name": "demo", "prefix": "cdui_xxxxxxxx", "token": "cdui_..."} (the full key is shown ONCE, in the "token" field)

7. Publish (records the git commit)

cdui project publish wraps the same publish endpoint (POST /api/apps/{slug}/publish) with a project-mode guard and automatic git provenance. Set the default target once in codefyui.project.toml:

[publish]
graph = "echo"
slug = "echo-svc"

Commit it -- an uncommitted manifest change is exactly the kind of dirty tree the next step warns about -- then publish:

git add -A && git commit -m "set publish target"
cdui project publish .
# -> Published echo-svc v1 (git 1a2b3c4)
cdui project publish . --note "first cut" # --note attaches an immutable note to the version

Publish is local-only in v1: it confirms GET /api/health reports THIS project open (so it can never record the wrong commit against foreign bytes), computes git rev-parse HEAD + git status --porcelain, and warns LOUDLY if the tree is dirty. Every publish from a git repo records git_dirty as true or false alongside the commit -- a dirty tree additionally prints the warning banner above. If git status itself fails after the commit was resolved, git_dirty is recorded as null (= unknown), never a fabricated false.

Creating the app on first publish is automatic only for the manifest's committed [publish].slug target. An explicitly passed --slug that names an app the server does not know fails with 404 app_not_found -- a typo can no longer silently mint a second app -- and the CLI points you at --create for a deliberate first publish of a new command-line slug:

cdui project publish . --graph echo --slug echo-svc --create

Remote / CI deploy is out of scope for v1. cdui project validate runs in CI, but publishing requires a local server with the project open. The named follow-up is a management-scoped, API-key publish (--url / --key).

8. Invoke

PowerShell:

# payload.json: {"inputs": {"x": "hello"}}
curl.exe -s -X POST "http://127.0.0.1:8000/api/apps/echo-svc/invoke" `
-H "Authorization: Bearer cdui_YOUR_KEY" -H "Content-Type: application/json" `
--data "@payload.json"

bash:

curl -s -X POST http://127.0.0.1:8000/api/apps/echo-svc/invoke \
-H "Authorization: Bearer cdui_YOUR_KEY" -H "Content-Type: application/json" \
--data '{"inputs": {"x": "hello"}}'

# -> {"status": "ok", "outputs": {"y": "hello"}, ...}

9. See "which commit built this"

PowerShell:

curl.exe -s "http://127.0.0.1:8000/api/apps/echo-svc/versions" -H "X-CodefyUI-Token: $token"

bash:

curl -s http://127.0.0.1:8000/api/apps/echo-svc/versions \
-H "X-CodefyUI-Token: $TOKEN"

# -> [{"version": 1, "git_commit": "1a2b...", "git_dirty": false, "active": true, ...}]

The active version's GET /api/apps/echo-svc/openapi.json info block also carries x-codefyui-git-commit and x-codefyui-git-dirty.

Migrating an existing flat graphs dir

If you followed the older "version control your graphs" recipe (a flat dir of *.json behind CODEFYUI_GRAPHS_DIR), adopt it in one command:

cdui project init my-service --adopt /path/to/old-graphs

Every *.json is copied into graphs/ and split into the logic/layout pair.

Notes and limits (v1)

  • One project per server instance (no in-editor project switcher yet).
  • DB_PATH and custom nodes stay install-global; plugins are the portable mechanism (pinned by SHA in the manifest).
  • assets/data/ is where a relative Dataset or FileReader path resolves. Files uploaded through a DATA_FILE dropdown (CSVReader, DocumentLoader, TextCorpusDataset) are install-global too -- backend/data/files, or CODEFYUI_DATA_FILES_DIR -- like DB_PATH and custom nodes.
  • CODEFYUI_MODELS_DIR, CODEFYUI_IMAGES_DIR and CODEFYUI_MEDIA_DIR relocate the model, image and run-media stores; in project mode they default to <project>/assets/models, assets/images and assets/media unless set explicitly.
  • Last-write-wins between the editor and hand-edits. Only a write made through the Source Control tab (a pull, a checkout, a stash pop, a discard) raises the "changed on disk" toast with its Reload button; an edit from any other tool is not detected. Exclude project dirs from OneDrive/Dropbox sync -- sync clients corrupt .git and race atomic renames; use a real git remote.
  • A graph written by a newer CodefyUI opens read-only (view/run allowed, Save disabled) so an older build can never drop fields it does not know. Save As is blocked by the identical guard, by design: the in-memory graph already lost those unknown fields the moment it loaded, so Save As would just write that lossy copy out under a different name.