---
title: "wf create"
section: "CLI"
framework: WebFluid
package: webfluid
version: "1.0.0b3"
stage: "beta"
released: "August 17, 2026"
canonical: "https://docs.webfluid.dev/latest/cli/create.md"
html: "https://docs.webfluid.dev/latest/cli/create"
audience: ai-agent
format: markdown
---

# wf create


Three scaffolders: `project` writes a whole opinionated project, `app` writes an app config
interactively, `additive` writes a feature module. Everything the other pages describe by hand is
what these produce.


## `wf create project <name>`

```bash
wf create project myapp
wf create project myapp --skip-frontend        # -sf: no Node, no htmx/Alpine download, no package.json
wf create project myapp --skip-defaults        # -sd: no fluid/ structure, no .gitignore
wf create project myapp --babel-fallback       # -bf: also extract + compile .po catalogs
```

Refuses to run if the target exists and is non-empty.

### What it writes

```text
myapp/
├── main.py                  # prepare_fluid(), includes the routers
├── package.json             # npm workspace           (unless -sf)
├── vite.config.js           # the orchestrator         (unless -sf)
├── .gitignore
├── additives/
└── fluid/
    ├── config.py            # @register_config Config(MyConfig)
    ├── api/
    │   ├── __init__.py      # api_router, prefix /api
    │   ├── health.py        # a ready handler
    │   └── v1/__init__.py   # v1 router, prefix /v1
    ├── app/                 # HTML routes — only for htmx / none
    │   ├── __init__.py      # app_router (HTMLResponse)
    │   └── index.py
    ├── frontend/            # Vite workspace — only for type vite
    ├── models/  schemas/  services/  events/  utils/
    ├── static/
    │   ├── css/tailwind_raw.css
    │   ├── img/
    │   └── js/
    └── templates/
        └── index.html       # extends fluid_base.html
```

The frontend answer changes two things:

| Answer          | Effect                                                                                                                                                                                          |
|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `vite`          | Creates `fluid/frontend` from a create-vite template, **removes `fluid/app`** (the Vite index owns `/`, unless you answered no to `register_index`), and `main.py` includes only the API router |
| `htmx` / `none` | Keeps `fluid/app` with a ready `index.html`, and `main.py` includes both routers                                                                                                                |

### The generated config

```python
# fluid/config.py
from webfluid.core.config import register_config

# The MyConfig class is our convention for developing public git repos.
try: from fluid._my_config import MyConfig
except ImportError:
    class MyConfig: pass


@register_config(10)
class Config(MyConfig):
    APP_CONFIG = {
        "title": "myapp",
        "version": "1.0.0"
    }
    APP_FRONTEND = { "type": "htmx", "alpine": True }
```

`fluid/_my_config.py` is in the generated `.gitignore`. Shared settings live in `config.py` and go
into git; anything local or private lives in `_my_config.py` and never does. The config builder walks
the MRO, so a key only `MyConfig` defines is picked up as if written in `Config`, and a key in both
belongs to `Config`. A clone without the file still starts.

> **RULE** — Adopt this pattern in every project. It is the only mechanism the framework offers for keeping non-secret local overrides out of a public repository, and it costs three lines.

### The generated `.gitignore`

Uses a custom `[folders]` / `[files]` section header format:

```text
[folders]
.vscode/  .idea/  .venv/  __pycache__/  node_modules/
app_configs/  app_services/  additives/  migrate/
translations/  dist/  logs/

[files]
messages.pot  tailwind.css  _my_config.py  package-lock.json  *.db
```

> **WARNING** — additives/ and app_configs/ are gitignored by default. That is deliberate — Additives are installed artefacts and app configs carry secrets — but it means git status shows nothing after you edit an Additive in place. Verify those changes by reading files or compiling them, not by git status. If an Additive is genuinely part of this repository, remove that line or track it as a submodule.

## `wf create app <name>`

```bash
wf create app prod
wf create app prod --secret-length 64        # -sl, default 32 (bytes of token_hex)
```

Interactive. It:

1. generates `SECRET_KEY = token_hex(32)`,
2. asks which `EXT_*` extensions to enable, and writes **all eight** with `1`/`0`,
3. asks which `WF_*` features to enable, and writes **all six**,
4. discovers Additives in `additives/`, asks which to enable, and runs each selected one's
   `configure()` so its own `setup` questions land in the file,
5. writes `SECURITY_SECRET = token_hex(32)` if `EXT_SECURITY` was selected,
6. asks for `DATABASE_URI` and `REDIS_URI`,
7. asks for `MAIL_USERNAME` / `MAIL_PASSWORD` if `EXT_MAIL` was selected.

Refuses to overwrite an existing config file.

Every file the scaffolders write, `app_configs/<name>.ini` included, is explicitly UTF-8 since
`1.0.0b3`. `wf run` and `wf migrate` read configs through `utils.core.read_config`, which tries UTF-8
first and falls back to the locale encoding, so configs written by an older release keep working.

> **WARNING** — A config written before 1.0.0b3 on a non-UTF-8 console still reads back correctly only on the machine that wrote it: the fallback recovers the bytes with the local encoding, which is a different encoding elsewhere. Rewrite affected configs once, or keep their values ASCII and move secrets behind the *_FILE indirection.

## `wf create additive <id>`

```bash
wf create additive portal
```

Interactive. It asks for version, name, description, author, whether it is a base or extends one,
the frontend type, and the required extensions — then writes:

```text
additives/portal/
├── __init__.py        # Additive(...), before_enable registering api v1 (+ index)
├── manifest.json      # everything you answered
├── .gitignore
├── api/{__init__,health}.py, api/v1/__init__.py
├── app/{__init__,index}.py      # dropped for a vite frontend
├── models/ schemas/ services/ events/ utils/
├── static/css/tailwind_raw.css, static/img, static/js
├── templates/index.html         # unless a vite frontend
└── frontend/                    # for a vite frontend
```

Notes on what it decides for you:

- The **id is sanitised** through `safe_string()`; if that changes it, you are asked to confirm.
  `fluid` is rejected outright.
- `requires.wf` is pinned to `>=1.0.0b3` — the framework version you are running.
- Extending a base adds `import_base("<id>")` to `__init__.py` **and** the dependency to
  `requires.additives`.
- A Vite frontend runs `npm install -w additives/<id>/frontend` for you.

The generated `.gitignore` also decides what ships: `wf ocean publish` builds the archive from the
working directory minus `.git`, `__pycache__`, `node_modules`, editor folders, compiled files **and**
everything the `.gitignore` excludes (nested ones included, negations honoured).

## Rules for agents

> **RULE** — Prefer wf create project over hand-building the tree. The generated layout is what wf migrate, the Alembic env.py, the npm workspace glob and every example in these docs assume — a hand-rolled variation costs you those.

> **RULE** — The scaffolders are interactive and have no non-interactive mode. Without a TTY, run wf create project <name> --skip-frontend (which asks nothing) and write app_configs/<name>.ini yourself. The .ini format is in config/app-config.md.

> **RULE** — Never regenerate over an existing project. All three scaffolders refuse a non-empty target, which is the correct behaviour — to add something, write the file by hand following the shapes on these pages.

## Next

- [`cli/run.md`](/latest/cli/run.md) — running what you just generated.
- [`config/app-config.md`](/latest/config/app-config.md) — the file `wf create app` writes.
- [`additives/intro.md`](/latest/additives/intro.md) — what `wf create additive` produces.


---

## Navigation


- [Overview](/latest/.md) — what WebFluid is, how to navigate these docs, release state and known bugs
- [Getting Started](/latest/get-started.md) — install, minimal app, the layout the framework expects

**Configuration**
- [App configs](/latest/config/app-config.md) — `app_configs/<app>.ini`, switches, `*_FILE` secrets, `[dev]`
- [Config classes](/latest/config/config-class.md) — `register_config`, `DefaultConfig`, every framework key

**Extensions**
- [Introduction](/latest/ext/base.md) — `FluidExtension`, entry points, `expand_fluid`
- [Scheduling](/latest/ext/scheduling.md) — APScheduler `AsyncIOScheduler`
- [SQLAlchemy](/latest/ext/sqlalchemy.md) — `Model`, executors, binds, sessions
- [Migrate](/latest/ext/migrate.md) — Alembic wrapper, `prepare_fluid`, single/multi-db
- [Mail](/latest/ext/mailman.md) — SMTP, sync/async, batching, attachments
- [Babel](/latest/ext/babel.md) — gettext, domains, locale selection, formatters
- [Security](/latest/ext/security.md) — users, gates, CSRF, tokens, OAuth, bearer grants
- [Events](/latest/ext/events.md) — signals, events, queries, browser socket
- [Cache](/latest/ext/cache.md) — redis / legacy backends
- [JWTManager](/latest/ext/jwt.md) — encode/decode, rotation, revocation

**Frontend (surface)**
- [Introduction](/latest/surface/tooling.md) — feature switches, `fluid_base.html`, Tailwind, themes
- [Integration](/latest/surface/frontend.md) — `APP_FRONTEND`, htmx, Vite workspaces
- [Template resolution](/latest/surface/jinja.md) — loader order, namespaces, overriding

**Additives**
- [Introduction](/latest/additives/intro.md) — manifest, routers, enabling
- [Base Additives](/latest/additives/base.md) — `type: base`, `import_base`, extension semantics
- [Interaction between](/latest/additives/contract.md) — events/queries as contracts, requirements, packaging

**Framework utility**
- [Lifecycle](/latest/utils/lifecycle.md) — `mix()`, startup/shutdown hooks, graceful stop
- [Runtime](/latest/utils/runtime.md) — `FluidContext`, request hooks, themes, proxy, rate limits
- [Logging](/latest/utils/logging.md) — the log factory, levels, files, additive attribution

**CLI**
- [Introduction](/latest/cli/wf.md) — command tree, extension CLIs
- [Create](/latest/cli/create.md) — `wf create project/app/additive`
- [Run](/latest/cli/run.md) — `wf run`, flags, debug and interactive mode
- [Ocean](/latest/cli/ocean.md) — search, install, publish

**Reference**
- [Overview](/latest/ref.md) — package layout, stability contract
- [Core](/latest/ref/core.md) — `Fluid`, config, context, `core.ext`, constants
- [Extensions](/latest/ref/extensions.md) — the battery API surface
- [Surface](/latest/ref/surface.md) — `Frontend`, Node and Tailwind tooling
- [Additives](/latest/ref/additives.md) — `Additive`, `Router`, `Manifest`, registry helpers
- [Utils](/latest/ref/utils.md) — helpers, logging, exceptions