Gravity Gravity Docs

Features

What actually ships in Gravity, grouped the way the codebase is organized. See Architecture for how these fit together, and Backend & Adhara for what's Gravity's job versus what needs a backend.

Server & runtime

  • FastAPI — router-based routing (app/routes/*.py), Depends()-based dependency injection for auth (app/deps.py), Jinja2 server-side rendering, a factory-based app config (create_app()). Modern async-native Python, applied consistently — no Node.js, no build step.
  • Configurable server mode — plain Uvicorn for platforms that already autoscale per-instance (Cloud Run, Container Apps), or Gunicorn supervising Uvicorn workers for a standalone server/VM where nothing else respawns a crashed process. One env var (GRAVITY_SERVER_MODE) picks the mode; see Deployment.
  • JSON command API/api/message routes typed JSON commands (blog_new_post, contact_add, event_register, …) through a schema-validated dispatcher (app/messaging/) instead of one bespoke endpoint per action.
  • File upload — multipart upload through the same pluggable ContentStore every other piece of content uses; lands in a local volume by default, a cloud bucket in production.

Deployment

  • Four targets, one commandmake deploy PLATFORM=vercel|gcp|cloudflare|azure. Vercel needs no Docker (fastest first deploy); the other three build and push a container image. Every script auto-generates a SECRET_KEY and auto-provisions that platform's recommended storage backend when run non-interactively (by an agent), so make deploy is genuinely one-shot.
  • make push-repo — creates a GitHub or GitLab repository and pushes your code, detecting and safely routing around the case where origin still points at Gravity's own shared template repo. VERCEL_LINK_GIT=1 on top of make deploy PLATFORM=vercel connects that repo in Vercel so future pushes trigger an automatic rebuild.
  • Docker + Compose — one Dockerfile, one docker-compose.yml wiring together the app, port mapping, and persistent volume mounts for data/auth/uploads/certs.
  • install.sh — a single curl | bash gets a local instance running natively, no Docker required, with status/stop/logs/update controls that never prompt — designed to be driven by an AI agent as easily as a human terminal.

Design system

  • CSS design tokens — colors, fonts, spacing, radii, and shadows live in one :root {} block per theme (public/themes/<theme>/main.css). Change the tokens, change the entire look.
  • Five swappable themesgravity (dark navy + gold, the default and fallback), clarity (Notion-inspired light), photo (editorial/photography), resonance (music-artist, audio-reactive hero), stripe (CSS + one page — the rest falls back to gravity). Switch with GRAVITY_TEMPLATE; a theme missing a given template transparently falls back to gravity's copy instead of 404ing.
  • Server-rendered, no build step — all HTML is Jinja2, rendered server-side. No hydration, no JS framework, no bundler. Fast first paint, full accessibility, and nothing to compile before a page reflects a change.
  • Mobile responsive — every layout collapses cleanly; no per-page media-query work required.

Auth & admin

  • JWT-backed admin auth, gated on the Adhara SDK in real deployments — see Authentication for the full require_admin/require_portal flow, including the local GRAVITY_DEV_ADMIN=1 bypass for testing without an Adhara account.
  • Admin dashboard — manage blog posts, events, contacts, and users from one interface; public and admin routes are cleanly separated APIRouters.
  • Customer portal — a separate auth system (/portal/*, require_portal) with silent session refresh, courses, community, resources, and certificates — Adhara-backed, with its own local dev bypass (GRAVITY_DEV_PORTAL=1).

The Visual Editor

  • Block-based page content — pages are {id, type, fields} sections validated against schema/blocks/*.json, with a draft/published split so edits never go live until explicitly published.
  • Three ways to edit — inline click-to-edit, a full drag-and-configure Builder, or asking an AI assistant to edit the underlying JSON directly. See Visual Editor design for the full model.
  • Theme Tweaks — the same draft/publish pattern applied to CSS custom-property overrides (colors, shadows, opacity) layered on top of a theme's stylesheet, rather than editing it — a build-time customization tool, gated separately from normal admin access.

Storage — the ContentStore abstraction

  • Six backends, one interface — local JSON files, Google Cloud Storage, AWS S3, Azure Blob, Cloudflare R2, and Vercel Blob all implement the same read/write/describe/write_media/list/delete contract (app/stores/base.py). Every deploy script can offer to provision the platform's own native option and default to it.
  • Local by default — with nothing configured, blog, events, contacts, the link-in-bio page, and even the customer portal all work immediately against local JSON. No database, no signup, no persistence guarantee — see Backend & Adhara for exactly where that stops being enough.

Adhara integration

  • Two tiers, both optional — an API key unlocks blog, events, forms, and newsletter over Adhara's REST API; the Adhara SDK additionally unlocks admin login, Adhara Commerce checkout, and portal auth. Everything degrades gracefully when unconfigured instead of crashing.
  • Configurable edge caching for Adhara-backed content (the blog, to start) — a standard Cache-Control header (app/caching.py) lets Vercel's and Cloudflare's edge networks serve a cached response instead of re-fetching from Adhara on every request, with the TTL and stale-while-revalidate window configurable via GRAVITY_EDGE_CACHE_TTL/GRAVITY_EDGE_CACHE_SWR. Never applied to an authenticated admin's own view of the same page. See Deployment → Edge caching for what each platform actually needs to honor it.
  • Full picture, including what it costs and what building your own backend instead actually involves: Backend & Adhara.

SEO & branding

  • Generated sitemap.xml and robots.txt (app/controllers/seo.py) — static pages are discovered directly from the app's own router table (deduped by controller, so the ///home//index//index.html aliases collapse to one entry), with published blog posts, listed events, and every docs page enumerated separately. A new page shows up with zero extra step the moment its route is registered.
  • Title/description/Open Graph/Twitter cards/canonical URL/favicon, computed once per request (app/theme.py::_global_context()) and available to every theme — a page that sets nothing still gets a complete, page-specific set (including a correct og:url/canonical per page, not the same URL repeated on every page). Override per page with the normal {% block title %}/{% block description %}/ {% block og_image %} pattern.
  • One branding source of truth (app/content.py::default_header(), editable live via the Visual Editor's Site Settings) — brand name, tagline, logo, and social-share image are all data, not strings baked into a template, so rebranding this framework for a different site doesn't mean editing five themes' HTML.
  • Full checklist for adding a new page (what's automatic, what to set by hand, how to keep a page out of the sitemap): AGENTS.md at the repo root.