Simple BackendLog inStart

Docs

Almost everything here is a screen you can open. The console composes what the platform needs; the file it writes for you is at the end, for when you would rather not use a browser.

Home

Until anything is live, Home is the MCP address and where to paste it, so Claude can build the rest. After that it is the live release, the preview and what failed recently.

The URL

A workspace's functions live under one prefix, and nothing else does. The console prints the exact URL above the editor, so this is mostly here to say what the shape is.

https://simplebackend.dev/t/{workspace}/{path}

Once you have added a domain of your own, the same functions answer on it without the prefix.

Functions

Your functions down the left, the editor in the middle, and a row of chips above it telling you what this one currently is — its method and path, who may call it, what it may reach, whether it is isolated, and its time limit.

Press + New function and the editor is not empty — it holds a working function you can Run immediately. This is it.

The request is {:body :headers :query :tenant :invocation-id}, and the body comes in parsed and goes back out as JSON. Note the :require: ticking a box in Settings decides what a function may reach, and the code still asks for it normally.

Note also that the save came back as a value to check, not an exception to catch. Database calls return a map, and a failed one carries :error/kind and :error/msg.

If you call something the function has not been given, the save is refused and the message names it — under the editor, rather than on the first real call.

(ns contact
  (:require [backend.db :as db]))

;; req is {:body :headers :query :tenant :invocation-id}
;; Return {:status :body} — the body is JSON on the way out.

(defn submit [req]
  (let [{:keys [name email]} (:body req)
        saved (db/save! :contact {:name name
                                  :email email})]
    (if (:error/kind saved)
      {:status 400 :body {:error (:error/msg saved)}}
      {:status 200 :body {:saved true :id (:id saved)}})))

A function's settings

The tab beside Code. Four questions, and the platform enforces every answer.

Who may call it

ChoiceWho gets throughFor
API key requiredA caller holding one of your keysA server, a webhook
PublicAnyone, rate limitedA contact form, a waitlist
Nothing externalNo URL at allQueue and cron work

Trigger

ChoiceMeans
HTTP — a URLA method and a path, called from anywhere
Schedule — on a timerRuns on a schedule; no URL
Queue — from another functionRuns when another function hands it work

What it may reach

Checkboxes. Nothing you have not ticked is available inside the function — not refused at runtime, simply not there.

CheckboxGives you
Read the databaseLook records up, count them, query them, and read the database as it was at any earlier point
Write to the databaseSave, update and delete records
Call other services over HTTPOutbound calls to any public address, or only the hosts you list if the workspace restricts them
Enqueue work for the workerHand a job to a queue function
Read secretsCredentials set under Settings › Secrets & variables, encrypted at rest

Logging, hashing and your workspace's variables (backend.vars — plain values set beside the secrets, shown in full) are always available and are not checkboxes. A name is a variable or a secret, never both.

Time limit

In milliseconds, fifteen seconds unless you change it, up to five minutes. Work that needs longer belongs behind a queue trigger, in pieces that can be retried.

The button here says Publish settings: changing who may call a function is a new release exactly like editing its code, listed and rolled back the same way.

Run

Give it an example payload and press Run. This is a draft run against the real gateway, the real sandbox and the real store, and the console labels it as such — a candidate is not a release, but it does write to your actual database.

Back comes the whole response, and the timing split three ways: sandbox start, function body, database. Then, if it wrote anything, a link straight to what it wrote, and a link to the database exactly as that run saw it.

A draft Run does not appear in the Runs log. That log is for calls to the real URL, so trying something twenty times while you get it right does not bury the one real request you are looking for.

Pages

Static pages the workspace serves itself — no function, no sandbox. A page answers only where no function is deployed, so publishing one can never take an endpoint offline. Publish makes a page live at once; Save to preview stages it at the workspace's private preview address, beside any staged functions.

A workspace does not need a single function to be worth having. Point a domain at one, publish pages, and never open the editor — that is a whole use of the product, not a lesser one.

Scripts run on the workspace's own address and on your domains. On the shared /t/ path they never do.

Data

Down the side, every kind you have stored and how many of each. In the middle, the records — and the columns are whatever attributes those records turned out to have. Nothing was declared to make any of it appear. Alongside your own fields you will find an :id, a :created-at and an :updated-at, which the platform stamps for you.

Across the top, a slider from the first write to now. Every version of every value is kept, so this is the database as it was, not a changelog. Arriving from a run drops you at that run's moment; Back to now returns you.

There is a query console, and it runs your query as a sandboxed function like any other — a query box wired straight to the store would be remote code execution wearing a nicer hat.

Fields

Each type's Fields link shows its shape. A type you never declared reads schema-on-read; declaring is opt-in and per type, and once declared the platform holds you to it on write.

Runs

Every invocation, newest first, filterable down to just the failures. For each: when, which function, how it ended, the same three-way timing split, who called it — an API key by its label, or a caller's address, or cron — and a link to the database as it was.

Kept thirty days. Your records are not aged out; this table is.

Releases

Every publish is an immutable release with a hash, and the list marks the one serving now. A release that does not validate is listed as rejected with the reason, and the previous one stays live — a bad save cannot take your workspace down.

Rolling back is a button on the row. Each release keeps its own sources, so nothing rebuilds. Export all source hands back everything you wrote, from the same screen, and Put in preview stages an older release at the preview address before you roll back.

Settings

Everything that configures the work, behind one door.

RoomHolds
MembersWho may work on this workspace, and inviting more
Sign-in & usersHow your app's people sign in, where the app runs, and who has signed in
DomainsA TXT record to prove a domain is yours and a CNAME to point it here; the certificate is issued for you
API keysFor functions set to API key required. A new key is shown once — only a hash is stored — and a label tells the Runs log which one called
Secrets & variablesValues your functions read. A secret is encrypted and hidden until revealed; a variable is shown in full
Outbound callsOpen to any public address by default; restrict it to the hosts each function lists. Private addresses are always refused
WorkspaceIts address, wipe and delete

Without the browser

Everything above is also a manifest, a CLI and an MCP tool. That lives on MCP, and the rules for writing a function are a Skill you can hand to Claude.

Open the console Connect Claude