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.0

That'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-handoff route 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 kv calls 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.kv instead.
  • 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 localhost URLs. Use relative paths for routes on your own app. For calls to other Ellivate apps, use their vanity URL.

What's next