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 validateruns 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_PATHand custom nodes stay install-global; plugins are the portable mechanism (pinned by SHA in the manifest).assets/data/is where a relativeDatasetorFileReaderpath resolves. Files uploaded through a DATA_FILE dropdown (CSVReader,DocumentLoader,TextCorpusDataset) are install-global too --backend/data/files, orCODEFYUI_DATA_FILES_DIR-- likeDB_PATHand custom nodes.CODEFYUI_MODELS_DIR,CODEFYUI_IMAGES_DIRandCODEFYUI_MEDIA_DIRrelocate the model, image and run-media stores; in project mode they default to<project>/assets/models,assets/imagesandassets/mediaunless 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
.gitand 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.