Skip to main content
Fymo ships a family of code generators under fymo generate. One command writes the controller, template, remote module, tests, and route wiring for a feature, and every file it writes is ready to run before you’ve edited a line. One idea underpins all of them: generated code is plain app code. Templates are inert text Fymo never imports at runtime; the only runtime coupling is the same auto-discovery that picks up files you write by hand. Once generated, the files are yours to edit, and Fymo will never touch them again.

Pages

A controller, a template, and the route wired into fymo.yml. The injection contract is honest: Fymo edits your config only when the routes block still matches the shape the scaffold produces, verified by reparsing the result. When you’ve restructured the file, the page files are still generated and the exact line to add is printed instead:
Never a half-write, never a silent skip.

Remote modules

A typed remote module with in-memory stand-in rows, plus a test file driving it through fymo.testing’s signed_in and acting_as. When the project has no tests/conftest.py yet, one is generated too, so pytest works from any directory.

Resources

The flagship. A resource is a page and a remote module generated together, wired as full CRUD:
The emitted API speaks grammatical English. The collection function keeps the plural, everything addressing one row speaks singular, and the TypedDict takes the singular name:
The singularizer handles the non-trivial cases too. fymo generate resource courses emits get_course and a Course TypedDict, not get_course_ or a mangled stem. Route wiring uses a resources: entry in fymo.yml, which is what makes both /posts and /posts/<id> exist through the Router’s resources expansion. The index page renders the list through the typed $remote client, shows a create form to signed-in visitors, and links each title to the detail page, where edit and delete controls appear only on rows the visitor owns. A co-located Item.svelte renders one row. The generated test file carries nine tests: the seed row lists and fetches, an unknown id raises NotFound, the owner can update and delete, someone else’s row reads as missing, and anonymous mutations get 401. Two authorization conventions in the generated code are deliberate, and worth keeping when you edit it:
  1. The author comes from the authenticated identity, never client input. create_post stamps current_uid() on the row; there is no author parameter to forge.
  2. Ownership mismatches answer NotFound, never Forbidden. A 403 would confirm the id exists to someone who shouldn’t know that. The generated helper’s own comment says it plainly:

Without auth

In a project with no app/auth/ (scaffolded with fymo new --no-auth, or before you’ve run fymo generate auth), the resource generator emits a read-only variant instead of silently generating guards that could never pass:
The page renders the list and detail views with no create form and no owner controls. The upgrade path is exactly what the message says: generate auth, then regenerate the resource with --force.

Components and layouts

Component names are PascalCase, matching how you’ll import them.
A layout for a section with no pages yet is dead code, so the generator refuses and tells you what to do first:

Broadcasts

The generated channel is working code, not a stub: a typed payload, a subscribe-time guard, and the client-side usage documented in the file’s own docstring:
See Jobs and broadcasts for how channels, guards, and publishing fit together.

Conflicts and previews

Every generator refuses loudly by default when a target file exists, naming each one:
Three flags cover the rest:
  • --force overwrites.
  • --dry-run lists every path (would create / would update) and writes nothing.
  • --diff prints a unified diff of what would change and writes nothing.

Destroying

fymo destroy page|remote|resource <name> is the safe inverse:
Generate followed by destroy leaves the tree byte-identical to where it started, fymo.yml included. The safety rule: destroy deletes only files still byte-identical to a pristine render of the current templates. Anything modified since generation makes the whole operation refuse, all-or-nothing:
The route entry is un-injected only when the reparsed file equals the old mapping minus exactly that one entry, the same never-a-half-write contract as injection.
Coverage matches what the CLI’s own help says: destroy handles page, remote, and resource. Generated component, layout, and broadcast files are single plain files, delete those by hand.

Template overrides

Every generator renders from a packaged template tree, and a project can override any of it. A file at .fymo/templates/<same relative path> wins over the packaged version, with identical tokens and identical conflict behavior. This applies to fymo generate auth too. fymo generate templates publishes the packaged tree into your project for editing:
It writes 25 template files under .fymo/templates/ (page/, remote/, resource_page/, component/, layout/, broadcast/, auth/). The project scaffold itself is excluded, since fymo new runs outside any project. Edit one and the next generate picks it up. Add a line to .fymo/templates/page/controller.py.tmpl, run fymo generate page teamcheck, and the generated controller opens with your line:
Delete an override file and the packaged template is back in charge. Overrides are per-project and version-controlled with the rest of your app, so a team convention travels with the repo.

Auth

fymo generate auth predates the rest of the family and has its own page. It scaffolds app-owned identity code into app/auth/ (password by default, --clerk and --skeleton variants), and it honors the same .fymo/templates/ overrides and conflict flags as everything here. See Authentication.