Manual Getting Started Project Layout

Project Layout

elements man start/layout Read as markdown

What elements create writes, what each directory is for, and where your code goes.

The first file you likely want to edit is app/pages/home/template.ehtml, the page the user has open in their browser.

Directory layout
.elements/ # private build/server state; do not read, write, or search here AGENTS.md # agent instructions (this getting-started guide) index.ts # app entry: creates the App, registers routes, starts the server worker.ts # services setup for job and test worker processes config.jsoc # dependencies, the import map, and app config package.lock # resolved dependency lockfile (generated; don't edit by hand) config/ env/ development.env # committed; dev-safe values production.env # gitignored; production secrets app/ pages/ home/ index.ts # route handler template.ehtml # page markup and inline TypeScript style.css # page styles test.ts # page tests services.ts # per-page rpc + shared interfaces (add by hand as the page grows) errors/ not-found/ # 404 page unhandled/ # 500 page shared/ templates/ # reusable templates (layout, etc.) services/ # cross-page rpc, LiveTables, channels styles/ page.css # site-wide page baseline (imports @elements/style) email.css # site-wide email baseline vars.css # design-token overrides assets/ # favicons, images jobs/ # background jobs emails/ # email templates migrations/ # generated migration files types/ session.d.ts # SessionData augmentation (types session.get()) node_modules/

There is no root package.json or tsconfig.json. Dependencies, the import map, and TypeScript options all live in config.jsoc, and the resolved dependency set is written to package.lock (don't edit it by hand). Some folders (app/jobs/, app/emails/, app/shared/services/, app/migrations/) are created as you need them.

Hidden folders at the project root (anything starting with ., like .elements/, .git/, .claude/) are not part of your source tree. Don't grep, glob, or otherwise search inside them. Ripgrep and editor search already skip hidden folders by default; if you shell out to grep -r or find, pass --exclude-dir=.elements (grep) or -not -path '*/.elements/*' (find). Quote a glob you pass to a flag, as in grep -rn load app --include='*.ts'. zsh, the default shell on macOS, fails the whole command on an unquoted glob that matches no file, and nothing after it on the line runs.

The Entry Point

index.ts at the project root constructs the App, registers routes, and starts the server:

index.ts
import { App } from "@elements/app"; import config from "#config"; import home from "#app/pages/home"; const app = new App(); app.route("/", home); app.start(config);

worker.ts brings up the shared services (the database pool, email) that job and test worker processes need, so sql(), tx(), and email() work inside jobs and tests the same way they do in an @rpc handler. Delete it if your app has no jobs and no database-touching tests.

Creating From a Scaffold

-scaffold creates the app from a published Elements app instead of the built-in templates:

Terminal
elements create myshop -scaffold=elementscode/scaffold-ecommerce

Name the scaffold as a GitHub repository in any of its spellings (shorthand, the address bar, the clone url, the ssh remote) or as a link to a .tar.gz archive. Add @ref for a branch, a tag, or a commit.

Elements fetches the scaffold as an archive, so nothing here needs git installed and no history comes with it. The project is created first, exactly as it is without -scaffold, and the scaffold is extracted over it. AGENTS.md is rewritten with the current directives, and anything under .elements in the archive is skipped so the project keeps its own id.

A scaffold writes an app's own config.jsoc, styles, and entry point, so it applies to a new project and not over one that already has files in it. A half-applied scaffold is not a smaller app, it is a broken one. Creating the app and applying the scaffold is one step.

To publish a scaffold, push an Elements app to a public repository.