Project Structure
The packaged z8 new starter uses the following directories. Authentication routes, domain services, and extra APIs are application additions, not automatically generated features.
myapp/
main.py # app_builder, app, and asgi_app
pyproject.toml # uv/Python project
requirements.txt # pip-compatible dependencies
package.json
package-lock.json
vite.config.ts
tsconfig.json
endpoints/
views/
index.py # /
about.py # /about
api/
hello.py # /api/hello/
templates/
base.jinja
pages/
index.jinja
about.jinja
server-components/ # Jinja macros
content/
index.yaml
about.yaml
src/
css/app.css # Tailwind entry
ts/
app.ts # Page module loader
pages/
index.ts
about.ts
ui-components/ # Solid custom elements
utils/
static/
css/output.css # Tailwind output
js/.vite/manifest.json # Vite manifest
js/assets/ # Built JS assets
Dockerfile
docker-compose.yml
Views and routes
The default builder scans endpoints.views for View subclasses. A Python file without a View class does not become a page route.
endpoints/views/index.py → /
endpoints/views/about.py → /about
endpoints/views/products/index.py → /products
endpoints/views/products/detail.py → /products/detail
A view's path class attribute can override its URL, including Starlette path parameters. Its template filename is selected explicitly in self.render(); it is not inferred from the URL.
API classes
The default builder scans endpoints.api for API subclasses. Module paths determine mount paths, and @API.endpoint defines paths inside each mount:
endpoints/api/hello.py → /api/hello
endpoints/api/admin/reports.py → /api/admin/reports
A decorator path of "/" gives an endpoint with a trailing slash. See API Endpoints for the shared-instance contract and custom routing.
Templates and page content
templates/base.jinja provides the outer HTML. Page templates normally extend it, and server-components contains reusable Jinja macros.
View.render() derives a page_id from the view module. For endpoints.views.app.dashboard, the ID is app.dashboard; content is looked up as content/app/dashboard.json, .yaml, then .yml. The first existing format wins. Missing content gives an empty page_content mapping; malformed content raises an error.
The generated layout puts data-page="{{ page_id }}" on body. The TypeScript entry reads that attribute, converts dots to directories, and imports the matching file under src/ts/pages.
Frontend files
The generated starter uses Solid and solid-element, with vite-plugin-solid. It does not use React by default. Import custom elements from the page module that needs them; an optional pages/common.ts can hold imports needed across pages.
Vite builds JavaScript into static/js; Tailwind separately builds static/css/output.css. The /static mount is added only when that directory exists. See Interactive Islands.
Application logic
You may add an app/ directory for domain logic, repositories, or services. Its shape is your application's choice; Z8ter does not discover or register all service objects automatically.
Path resolution
from pathlib import Path
import z8ter
z8ter.set_app_dir(Path("/absolute/path/to/myapp"))
print(z8ter.BASE_DIR)
print(z8ter.VIEWS_DIR)
print(z8ter.TEMPLATES_DIR)
print(z8ter.STATIC_PATH)
print(z8ter.API_DIR)
print(z8ter.TS_DIR)
The app root uses an explicit set_app_dir() value first, then Z8TER_APP_DIR, then the working directory. The CLI sets it to the current working directory.
Low-level build_routes_from_pages(package_or_path) and build_routes_from_apis(package_or_path) accept custom scan roots. To use them, compose the resulting routes yourself; calling them does not change the default AppBuilder scan directories. Ensure custom modules can be imported.