App shapes
Publish a Flask app
Write a normal Flask app, use Ellivate's persistence SDK instead of sqlite or local files, and run `ellivate publish`. Sign-in is handled for you — never write a login route.
The shortest possible Flask app
Here's a working Flask app that publishes cleanly on Ellivate. Save it as app.py:
from flask import Flask, render_template_string, request, redirect
import ellivate
app = Flask(__name__)
@app.route("/")
def home():
items = ellivate.kv.get("shared:items") or []
return render_template_string("""
<h1>Our grocery list</h1>
<ul>{% for item in items %}<li>{{ item }}</li>{% endfor %}</ul>
<form method="post" action="/add">
<input name="item" autofocus>
<button>Add</button>
</form>
""", items=items)
@app.route("/add", methods=["POST"])
def add():
items = ellivate.kv.get("shared:items") or []
items.append(request.form["item"])
ellivate.kv.set("shared:items", items)
return redirect("/")
And a requirements.txt:
flask>=3.0That's the whole app. No database, no sign-in, no deploy config. Run ellivate publish and you get a shared grocery list at <you>.ellivate.ai/grocery.
What Ellivate does for you at publish time
1. Writes the SDK file
The publish pipeline drops ellivate_client.py into your project root. That's what provides ellivate.kv.get(...) and ellivate.kv.set(...). You never install it via pip — it's written fresh on every publish so it stays in sync with the backend.
2. Injects the auth gate
Because your app is private by default, Ellivate writes ellivate_auth.py and injects init_ellivate_auth(app) right after your app = Flask(__name__) line. That module:
- Adds a
/ellivate-handoffroute so the shell can hand your app a viewer identity. - Gates every other route behind a valid viewer cookie — anonymous visitors get a 401 and the shell redirects them to sign in.
- Stashes the viewer's identity so the SDK's
kvcalls know *who* is reading or writing, enabling per-viewer data scopes.
3. Rewrites persistence
If your code used sqlite3, a JSON file, or shelve for user data, the storage translator rewrites it to the Ellivate SDK. If your data model is too complex to auto-rewrite (relational joins, full-text search), publish fails loudly with a Class 1 blocker — we won't silently smuggle sqlite into a container that'll get wiped on the next deploy.
4. Binds to the right port and host
Railway containers bind to the PORT env var on 0.0.0.0. Flask's default app.run() binds to localhost — Ellivate rewrites any development-mode launch blocks during publish.
Working with the SDK
Key prefixes and data scopes
Keys you pass to ellivate.kv can start with an explicit prefix to control data scope:
# Shared across everyone with access to this app
ellivate.kv.set("shared:items", [...])
# Private to this viewer (each user has their own copy)
ellivate.kv.set("personal:settings", {...})
# Public — readable by anonymous visitors
ellivate.kv.set("public:template", {...})Unprefixed keys get classified automatically by the publish pipeline — items, list, and entries-style keys default to shared; user settings and preferences default to personal. Classification happens once at publish time; runtime reads honor whatever the classifier decided.
Reading with fallback
items = ellivate.kv.get("shared:items") or []Missing keys return None, not an error. Design for empty states — first-time visitors always see one.
Writing safely
# Read, mutate, write — the KV is eventually consistent;
# prefer whole-value writes over partial patches
items = ellivate.kv.get("shared:items") or []
items.append(new_item)
ellivate.kv.set("shared:items", items)
# Delete a key
ellivate.kv.delete("shared:items")
# List all keys matching a prefix
keys = ellivate.kv.list("personal:")Knowing who the viewer is
Sometimes you want to show the signed-in user's name, or route differently based on who they are. The SDK exposes the current viewer:
import ellivate
@app.route("/")
def home():
viewer = ellivate.viewer()
if viewer:
return f"Hi, {viewer.username}"
return "Welcome"Viewer shape is { id, email, username, verified }. Returns None for anonymous visitors on requiresAuth=false apps.
Sharing with specific people
Once the app is live, open it in your Ellivate dashboard and click Share. You can:
- Make it public — anyone with the URL sees it in their dashboard if they're signed in.
- Invite by email — paste a list of emails. Anyone already on Ellivate gets the app in their dashboard; anyone not on Ellivate gets a signup link that drops them into your app on first load.
- Pick a data mode — either everyone sees the same shared data (one grocery list for the household) or each invitee gets their own copy (each user has their own task list).
Things to avoid in Flask code
- sqlite3 for user data. Works locally, wiped on every redeploy. Use
ellivate.kvinstead. - Local file writes. Same reason. If the data is user-scoped, it belongs in KV.
- Flask-Login / Flask-Auth / OAuth libraries. Sign-in is handled by the Ellivate shell. Adding a login route fights the platform and will fail the publish gate.
- Hardcoded
localhostURLs. Use relative paths for routes on your own app. For calls to other Ellivate apps, use their vanity URL.
What's next
- The Ellivate contract — the full list of what's enforced at publish time, so you know what to expect.
- Bring your code — if you haven't run your first publish yet.