How this works
You write a snippet of C#. Whatever it returns is hosted at a public address, over HTTPS, in a few seconds. This is the whole of it, in the order you will meet it.
What a lambda is
A lambda is a snippet that returns a GenHTTP handler. The platform compiles it, loads it, and mounts whatever it returned under your own address. There is no project, no build file and no using statement. Every GenHTTP module is already imported for you.
return Content.From(Resource.FromString("hello"));
That is a complete lambda. Deployed, it answers at an address of its own named after its key, such as your-key.genhttp.run, and every request there gets the word hello.
The snippet is statements, not a class. The last thing it does is return something that can serve requests: a handler, or a builder for one.
Your first one
- 1Press Create my lambda. You get a public address and an editor key. The key is the only way back in, so keep it. Nobody can recover it for you.
- 2You land in its control center, with a small REST service already written as the first version. It is only a starting point.
- 3Hand the editor key to an agent and tell it what to build - it writes new versions through MCP. Or open Code and write it yourself: Check compiles without storing anything and tells you what the compiler thinks, file and line.
- 4Press Deploy. Now it is online. Nothing is reachable before that, and deploying again extends how long it stays.
The control center
The editor link opens a control center rather than a text box: most of the code here is written by agents, so the first thing on the screen is how your lambda is doing. The sidebar holds the lambda - whether it is online, its address, and a button when a newer version is waiting to go online - and its sections. Anything done rarely, like changing the address or deleting it, is behind the ⋯ menu there.
- Overview
- What the app is, whether it is online, how many requests it had today and how many failed, the latest change, and how much room is left.
- Documentation
- What the app is, who it is for and why, and why it is built the way it is - written by agents, kept with each version.
- Change
- Say what should be different, and the agent on this server does it while you watch. It tries the change on a draft - a copy with an address of its own - and puts it online once it works. Switch off Put it online when it is done to try the draft yourself first. It only works on your app: a request that is not about it, or that is meant to do harm, is turned down, and it says why.
- Drafts
- Changes being tried before they go online, each at an address of its own and on test data of its own. Opened, a draft has its own code, test data and logs. The section is there once there is a draft.
- Data
- What the lambda keeps while it runs, shared by every version: the database, the workspace and the secrets, each a pill of its own. Look into the tables and files, upload files, set secrets, or switch a kind on or off. The simple view shows it once the app keeps something.
- Versions
- What each version changed and what was asked for, and the difference to the one before. Deploy or roll back from here, or start a draft from any of them.
- Deployments
- What was online when, and what took it down.
- Stats
- Requests, failures, response times and the most asked-for paths, over the last hour or day.
- Logs
- Its requests, what it printed, and the stack trace of anything that went wrong, as it happens.
- Code
- Every file of a version, its code and its resources, in a tree beside the editor - with how much room they take and whether the public can reach them. Pick an older version above it to read that one. Check compiles, Save makes a version, Deploy puts it online. In a draft, Save keeps it in the draft and shows it at the draft's address.
Ctrl-Ssaves;F12goes to a declaration. - Tests
- How the app is tested automatically, with the scripts and test data for it. In the full view only.
Every section works the same way: its title, an ⓘ that explains it, its actions on the right, and - where it has more than one view - a row of pills underneath. The full view gathers the sections in groups: how people find it, where a change is made, the versions and the data, and how it runs.
The traffic and the log are held in memory, for watching rather than keeping: a restart of the server begins them again. Versions and the deployment history are stored.
Saying why
A version is the code, and optionally two notes about it: the specification, what the user wants and why, in their words where possible, and the change, one line on what the version does. They are shown beside the diff in the version history, so the why survives next to the what - for you, and for the next agent that reads the history before changing anything.
POST /api/v1/lambdas/{editorKey}/versions { "files": [ { "name": "lambda.cs", "code": "..." } ], "specification": "A guest book people can sign; entries must survive a restart", "change": "Keeps entries in the database so they survive a restart" }
Agents pass the same two fields to write_code. In Code, saving asks for the change. Both are optional; a long specification is cut at 4000 characters and a change at 500 rather than refused. A draft keeps its own two, and the version it becomes takes them over.
Documentation and tests
Every version keeps what is written about it beside its program: its documentation - what the app is, who it is for and why, and why it is built the way it is - and its tests: how to check automatically that it works, with the scripts and test data for it. Agents write them with a new lambda and keep them up to date with every change. The next agent to change the lambda reads them first, so it knows what the app is for and what has to keep working - which the code alone does not say.
- docs/product.md
- what the app is, who it is for, what people do with it and why
- docs/decisions.md
- the technical decisions, and why they were made
- tests/README.md
- how the app is tested automatically, and how to run the tests
- tests/…
- the scripts and test data the tests use
They are files of its code like any other, in the folders docs and tests: the history shows what a version changed in them, rolling back brings back the documentation that was true of that version, and a draft has a copy of its own that goes online with it. They are never compiled and never served, and count towards what a version may come to.
In the control center, Documentation shows the pages to read, and Tests how the app is tested and the files beside it; the version is picked as it is for its files. A page can be edited there too, which saves the next version. The simple view calls the documentation About and shows only what the app is for - to correct it, tell the agent.
They are written in the language you use with the agent, for whoever changes the app next - a person or an agent. Not a copy of the code: what it is for, and why.
Changing it safely
A version never changes once it is saved - which is what makes every one worth keeping: any of them can be compared with, and put back online exactly as it was. To change a lambda that people use, try the change in a draft first.
- 1Start it from any version under Versions, or let the agent start one. It is a copy of that version's code and resources, its documentation and tests among them, and of the lambda's data.
- 2Change it as often as it takes - in Code, or by asking the agent. Its preview answers at an address of its own,
/features/…/, against test data of its own. Visitors of the lambda see none of it, and nothing it writes reaches the lambda's data. - 3Put online once it is right: it becomes the next version, with its notes, and goes online. The draft goes with it - its preview and its test data.
POST /api/v1/lambdas/{editorKey}/features { "name": "Leaderboard" } PUT /api/v1/lambdas/{editorKey}/features/{feature}/files?deploy=true POST /api/v1/lambdas/{editorKey}/features/{feature}/merge { "deploy": true }
Several drafts can be worked on at once. Only one that is up to date with the newest version can go online, so that it never undoes a version saved after the draft began. When another went online first, bring its changes in - or ask the agent to - and mark the draft as up to date. Nothing goes online on its own; that is deliberate. The API calls a draft a feature, and putting it online a merge.
Code and resources
A version is any number of files, in two parts. Its code is every file but its resources: its .cs files are compiled, in any folder, as in any C# project, and every other file is kept with the version and never compiled or served: its documentation, its tests, what a front end is built from. Its resources, in resources/, are what it reads and serves while it runs - pages, scripts, styles, pictures, the database's migrations - reached from the code as Resources.
lambda.cs the snippet: what it returns is served Shelf.cs more C#, compiled beside it models/Book.cs C# in a folder, compiled the same docs/ tests/ what is written about it frontend/ whatever else - kept, never compiled or served resources/ what it reads and serves while it runs
Types do not have to sit underneath the code that uses them. In Code, press + beside the code and type a name: a .cs file is compiled beside the snippet, in the same namespace, whichever folder it is in, so nothing has to be imported to be reached. A name with no extension and no folder is taken to be C#.
var shelf = new Shelf(); return Inline.Create() .Get(() => shelf.All()) .Post((Book book) => shelf.Add(book));
public sealed class Shelf { private readonly List<Book> _books = []; public IEnumerable<Book> All() => _books; public Book Add(Book book) { _books.Add(book); return book; } } public record Book(string Title, string Author);
How the code is arranged is up to whoever writes it - a folder for types, one for the sources of a front end, one for scripts. Every .cs file is compiled into the app, so C# that is not part of it - a test, a tool of its own - does not belong in the code as a .cs file. The code and the resources of a version share one allowance of room, which the overview shows.
Serving a page
There are two ways to serve a page, and one more for what people upload beside it.
One page, written inline
Fine for something small. The page is part of the snippet.
var page = Resource.FromString(""" <!doctype html> <title>Mine</title> <h1>It works</h1> """) .Type(new ContentType("text/html; charset=utf-8")); return Content.From(page);
A folder of real files
What you want for anything with a stylesheet and a script. The files are resources of the version, and served exactly as written. Nothing compiles them.
return Layout.Create() .Add("api", api) .Add(Resources.App("site"));
Uploaded files, from the data
For what people upload or the lambda creates - pictures, documents - served beside the app. Not for the pages of the app itself: those belong in the resources, where they are versioned with the code that needs them.
return Layout.Create() .Add("api", api) .Add("uploads", Workspace.Files("uploads")) .Add(Resources.App("site"));
A front end, step by step
The second of those, in full. Every demo serves its page this way from resources/web - open demo-crud to read one. Demos are read only; their editor key is their name.
- 1In Code, press + beside the resources and type
site/index.html: it becomesresources/site/index.html. A name with a slash in it puts the file in a folder; a name with an extension is taken as the file it says it is. - 2Add
site/app.cssandsite/app.jsthe same way. Your page refers to them by name, as inhref="app.css", because the folder is the root of what gets served rather than part of the address. - 3For anything that is not text, like an image or a font, open a file in
siteand press the upload button beside the resources: it lands in the same folder. A PNG cannot be typed into a text editor, so that is the way in. - 4In
lambda.cs, serve the folder:return Layout.Create().Add(Resources.App("site"));
- 5Press Deploy.
site/index.htmlanswers at/,site/app.cssat/app.css, and any address matching no file is answered with the page, so a front end that does its own routing still works when somebody reloads on a deep link. - 6Add an API beside it and the page has something to talk to:
var api = Inline.Create().Get("notes", () => notes); return Layout.Create() .Add("api", api) .Add(Resources.App("site"));
What it is built from
Some of a lambda may be made by a build tool rather than written as it is served or compiled: compiled, bundled or generated. The version holds what the tool makes - as its resources, or as its code - and the files it makes them from are part of its code, in a folder of their own: frontend/, say, with a README that says how it is built. Your agent changes those files, runs the build where it works and saves both in the same version. This platform builds nothing.
frontend/ what it is built from, in whatever shape the tool wants frontend/README.md how it is built, and where the build goes frontend/.gitignore what the build installs or keeps for itself, left out resources/web/ what the build wrote, if it makes resources *.cs what it wrote, if it makes code # run the build where you work, then git add -A && git commit -m "…" && git push -o deploy
Like the documentation, they belong to the version: compared in the history, rolled back, copied into a draft, cloned, downloaded and published with the rest of the code - and never compiled or served. In the control center they are in Code, with every other file of the version.
What is written as it is served or compiled needs none. What a build installs or keeps for itself - node_modules, for example - is never part of a version: a .gitignore in its folder keeps it out.
A version and its data
A lambda keeps files in two places, and the editor shows them apart: Code holds the files of a version - the program - and Data holds the workspace - what the program keeps. The difference is whose they are. The files of a version belong to that version; the data belongs to the lambda, and every version shares it. Each has one allowance of room: a version's code and resources share one, and the lambda's database and workspace share the other.
| In a version | In the data | |
|---|---|---|
| what it holds | the code and the resources: the program, front end included - and its documentation, its tests and whatever it is built from | whatever the lambda writes, or somebody uploads |
| when it changes | never - a change is a new version | the moment something is written to it |
| a deploy | puts exactly these files online | never touches it |
| rolling back | brings the old files back | no effect: every version shares it |
| a draft | starts as a copy of them | works on a copy of it |
| when it goes | with old versions, past the limit | with the lambda, or when you switch it off |
| reached from code as | Resources | Workspace |
They cannot be one place. If they were, a deploy would either wipe everything your lambda had written since, or nothing could ever be removed from what it ships. A game that keeps a leaderboard wants the second; the page it serves wants the first. So the page goes in the version, and the leaderboard in the data.
Keeping records
Records - entries, accounts, orders, votes - belong in the database: a SQLite database of the lambda's own, switched on under Data. The code opens a connection with Database.GetConnection() and reads and writes it through Entity Framework Core, with a context of its own that maps the tables:
// resources/migrations/V1__Create_notes.sql: // CREATE TABLE notes (id INTEGER PRIMARY KEY, text TEXT NOT NULL); using (var connection = Database.GetConnection()) { new Evolve(connection) { Locations = [Resources.Root + "migrations"] }.Migrate(); } return Inline.Create() .Get("notes", () => { using var db = new Notes(Database.GetConnection()); return db.Entries.OrderBy(n => n.Id).Select(n => n.Text).ToList(); }) .Post("notes", (NoteInput input) => { using var db = new Notes(Database.GetConnection()); db.Entries.Add(new Note { Text = input.Text }); return db.SaveChanges(); }); record NoteInput(string Text); class Note { public long Id { get; set; } public string Text { get; set; } } // maps the table the migration made, on the connection it is handed class Notes(SqliteConnection connection) : DbContext { public DbSet<Note> Entries => Set<Note>(); protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseSqlite(connection, contextOwnsConnection: true); protected override void OnModelCreating(ModelBuilder model) => model.Entity<Note>().ToTable("notes"); }
Its tables are made by migrations: SQL files shipped with the version in resources/migrations/, applied in order by Evolve as the lambda starts - each once, so a new version only ever runs what is new. Never change a migration that was applied; a change to a table is the next file.
Like all data, the database is shared by every version, left alone by deploys and rollbacks, and a draft works on a copy of it. Under Data you see its tables and what is in them - the simple view calls them records. Download carries it along as an ordinary SQLite file.
Make a context where you need it and dispose of it, and use it synchronously - ToList and SaveChanges, not ToListAsync and SaveChangesAsync. The tables are the migrations' to make, never Entity Framework's. The demo-crud demo does all of it.
Keeping files
Workspace is a private directory your lambda may read and write: the place for files - pictures somebody uploads, a document it makes, a model it loads. Records belong in the database, and what is known about a file - who uploaded it, when - is a record too.
// uploads go into a folder of their own, served as they are Workspace.CreateFolder("photos"); return Layout.Create() .Add("photos", Workspace.Files("photos")) .Add("upload", Inline.Create().Post(async (Stream body) => { using var content = new MemoryStream(); await body.CopyToAsync(content); Workspace.WriteBytes($"photos/{Guid.NewGuid():N}.jpg", content.ToArray()); }));
There is also ReadBytes, WriteBytes, Delete, List, CreateFolder, and Tree/Files/App for serving it. Nothing else on the file system is reachable.
Keys and passwords
An API key, a password or a token belongs in the secrets, not in the code - where every version, every download and everybody reading the history would have it. The code reads one by name:
var weather = new System.Net.Http.HttpClient(); weather.DefaultRequestHeaders.Add("X-Api-Key", Secret.Read("WEATHER_API_KEY")); return Inline.Create() .Get("today", async () => await weather.GetStringAsync("https://weather.example/today"));
Switch secrets on under Data and set the value there. Once saved, it is never shown again - not to you, not to an agent; you can only replace it. The list says which names the code reads that have no value yet, and the overview asks for them. Secret.Exists says whether one is set, for code that works without it. Like all data, secrets are shared by every version, and a draft works on a copy of them.
They are stored encrypted, with a key that is not in the database. In a downloaded project, Secret.Read("NAME") reads the environment variable NAME - the values themselves stay here.
Websockets
Supported, and not an afterthought. The demo-game demo pairs players and runs every game on the server. The simplest form is three callbacks:
var room = new ConcurrentDictionary<IReactiveConnection, string>(); var socket = Websocket.Functional() .OnOpen(c => { room[c] = "someone"; return ValueTask.CompletedTask; }) .OnMessage(async (c, text) => { foreach (var other in room.Keys) { await other.WritePayloadAsync(text); } }) .OnClose((c, _) => { room.TryRemove(c, out _); return ValueTask.CompletedTask; }); return Layout.Create().Add("chat", socket);
When the page only listens - a count, a feed, a scoreboard - server-sent events are simpler: one long response the server keeps writing to, which the browser reconnects by itself. The demo-live demo sends every vote to everybody watching that way. Either way the server pushes what changed. A page that asks again every few seconds sends a request each time, whether anything changed or not, and is still late.
One thing catches everybody: a browser cannot set headers on a websocket handshake. Pass what the handler needs in the query, where it reads it from connection.Request.Header.Query, or send secrets as the first message.
What it will not let you do
Your code runs on a shared server, so some of C# is refused before it compiles: starting processes, opening sockets of your own, loading assemblies, reaching the file system outside your workspace, and reflection used to get around any of that. So is waiting for a task with .Result or .Wait() instead of awaiting it: requests run on one thread per core, and the task would have to finish on the very thread that is waiting for it.
Everything else is there, including the whole of the GenHTTP module API. If something is refused you are told which line and why, not simply that it failed.
Taking it away
Download in the editor gives you the whole thing as a .NET project: a solution you can open, dotnet run, and keep. It needs nothing but the GenHTTP package, and comes with a Dockerfile to build and run it as a container.
Your snippet becomes Project.cs, and Program.cs serves what it returns. Your other files come across exactly as you wrote them, where they were - the resources in resources, the documentation in docs, the tests in tests. Workspace and Resources become two folders beside the program, with the same methods, kept apart in a Platform folder - so nothing in your code has to change. Secret reads environment variables of the same name there; the values stay here. Database opens database/database.db, which the download carries with the records your app kept.
Worth knowing before you build anything here: what you write is yours and it leaves whole. Nothing about running it on this machine locks it to this machine.
Working on it with git
Every lambda is a git repository as well. Clone, on the overview of the control center and beside its code, has its address - the address of your editor with the name of the app after it - and git clone gives you the project Download gives you, with every version a commit of main, tagged v1, v2 and so on, and every draft a branch. Open it in your own editor, hand it to your coding agent, dotnet run it.
git clone https://genhttp.dev/editor/<editor key>/my-app.git cd my-app # change, commit git push -o deploy
Push and it is here. Each commit pushed to main becomes the next version, its first line the change it made - compiled first, and refused if it does not compile - and git push -o deploy puts it online. A branch you push becomes a draft, with its preview online at its own address; push it to main, or add -o merge to its last push, and it is the next version. What the platform puts around your code to make it a project - Program.cs, the project file, Platform - is no part of your app, so a push that changes it is refused and says why. AGENTS.md in the repository tells a coding agent the rest.
The address holds your editor key, like the address of the editor: whoever has it can push. What your app keeps - its records, its files, its keys and passwords - is never in the repository.
Publishing the code
If what you built could help somebody else, publish its code: open Open source in the control center, pick a license - MIT, unless you want another - and switch it on. Its code gets a page of its own among the open source apps, where anybody can read it, star it, download any version as the same project Download gives you, with the license beside it, or clone every version of it with git.
Every version is published, the earlier ones too, with all of its code - its documentation and its tests among it - and the change each one made. What the app keeps is never published - its records, the files it saved, the values of its keys and passwords - and neither is what you asked for in your own words, or who uses the app. Switch it off and the page is gone; its stars are kept for when you publish it again.
Everything in the code becomes public, the earlier versions included. A key or a password belongs with the keys and passwords under Data, never in the code - published or not.
Letting an agent do it
There is an MCP endpoint at /mcp. Point an agent at it and it can do everything the editor does: read the guide, read a demo in full, write files, compile them, and deploy. It is the same API underneath.
It says why as it goes - write_code takes the specification and the change - and it can look at what it deployed: read_logs answers with the lambda's recent requests, what it printed and the stack trace of anything it threw, which is how an agent finds out its code works rather than assuming it. You watch the same thing in the control center. It writes the documentation and the tests as it goes, reads them before it changes anything, and runs the tests against a draft's address before it puts the draft online. A page meant to be found gets a title, a description and an icon, and a preview for when its link is shared. At the foot of the pages it builds, it adds a small line saying they were made with GenHTTP Lambda - tell it if you would rather not have it, and it takes it out.