{"path":"/guide","meta":{"title":"Guide","hint":"Agent orientation — read first","description":"For AI agents: the building blocks of MorphCloud, when to use each, and the behaviour an empty workspace cannot show you — connections, conditions, timers, field mapping and what a run costs. Read this before planning a build."},"control":{"resource":{},"action":{"get":{"authorized":true}},"downloadable":false,"hasNext":false,"connectable":false,"dataBearing":false,"publicContent":true,"egress":false,"billable":false,"accessGrant":false},"data":"# MorphCloud — Orientation for Agents\n\nRead this before planning a build. It is the map of what exists and *when to reach for\neach piece* — in particular the behaviour you cannot discover by browsing, because an\nempty workspace shows you nothing.\n\nEverything named here is reachable through the self-describing API: follow\n`control.resource[].linkTo` to go deeper, and read the forms in `control.action` for the\nexact verb, path and fields. Never guess or recall a path.\n\nTwo conventions apply everywhere and are worth knowing before your first write:\n\n- **Cell values are always strings** — numbers as `30`, booleans as `true`, percentages\n  as decimals (60% is `0.6`), currency as plain numbers (`50000`), datetimes as ISO-8601\n  in UTC.\n- **A form field of type `search` names an entity the server must match.** Resolve it by\n  GETting the form's own node with `formSearch:{key}={query}`, where the key comes from\n  the field's advertised `searchKeys`, then submit the matching result's key. Never\n  hand-type one. When a search depends on another select or search field of the same\n  form (the columns of the table you picked above), send that field's current value\n  beside the query as `formValue:{field}={value}`; without it the search answers \"No\n  matches\". Only select and search fields go this way. Never send a text, textarea or\n  password field as `formValue:`, because the query string is written to logs.\n- **A field that carries a `value` is preset by the server** (a child row's link to its\n  parent, say). Submit it as-is; you do not need to search for it. When the field is also\n  `readOnly`, a different value is refused.\n\n## The hierarchy\n**org or user -> workspace -> { tables, models, actions, connections }.** A workspace is\nthe unit of work *and* of isolation: it owns its own tables, actions, models and members,\nand nothing is shared across workspaces.\n\n## A table\nTyped **columns** x **rows** = **cells**. The full column-type list is in the platform\nmanifest; the ones with design consequences are `reference` (a link to a row in another\ntable), `bool`, `currency`, `percentage`, and the file types `file`, `excel`, `word`,\n`pdf`, `image` and `csv`. A file type is a contract: when an action needs a CSV, type its\ninput field `csv` and feed it from a `csv` column, so the need is visible in the design\nrather than discovered when a run fails. `file` takes anything and promises nothing.\n\n**Six system columns** are created with every table and maintained for you: `rowId`,\n`createdBy`, `createdAt`, `updatedAt`, `updatedBy`, and `actionStatus` — the last reports\nhow that row's automations went, and is the first thing to read when a pipeline looks\nwrong.\n\n**References.** A column can point at a unique column in another table, giving navigable\nparent to child relationships. Model relationships this way rather than copying ids\naround by hand. A row exposes its children, and creating a child from the parent\nauto-fills the linking reference so the new row stays attached.\n\n**Walking and building the schema graph.** Every table has a parent resource and a child\nresource. They are the schema-level edges of its reference columns: parent lists the\ntables this one points at, child lists the tables that point back. Walk them from either\nend to learn the whole shape of a workspace, and expect cycles. Posting to the parent\nresource builds a new parent in ONE request — the table, its required unique key column,\nand the reference column on this table pointing at that key. Use it when the parent does\nnot exist yet; use the table's ref resource when it already does.\n\n**Before you delete a table.** Its managed resource lists every action that reads from\nor writes to it, each tagged with the direction, and those are the actions that refuse\nthe delete. Read it first — repoint or remove what it names, rather than attempting the\ndelete to discover the blockers. The same resource creates in ONE request an action\nalready attached to the table: post a name and a type (lookup, generate and a read agent\nread the table; network, load, join and a write agent write into it), then configure\nthe rest on the action the response links to. Use it when the table is the starting\npoint; use the workspace action list for web, script and email actions, which have no\ntable of their own.\n\n**History is already there.** Every cell edit is versioned and archived, so each row\ncarries who changed which cell, when, and which action caused it. Runs are logged with\ntiming. Do not build your own audit trail.\n\n**Reading rows.** Row listings paginate and search. Filter with\n`filter:<column>[:<operator>]=<value>`, repeating the parameter to AND several columns.\nThe operators are `equal` (the default when you omit one), `not_equal`, `ilike` (also\n`contains`), `like`, `greater_than`, `greater_than_equal`, `less_than`,\n`less_than_equal`, `in`, `not_in`, `is_null` and `is_not_null`; passing several\nvalues with no operator means membership. `ilike` and `like` match a substring, `ilike`\nignoring case: send the plain text (`filter:title:ilike=smoke`), because a `%` is\nmatched as a literal character. The same filters apply to the row download, so\nan export matches what you are looking at.\n\n**Running automations on demand.** A table's execute resource fires every action wired to\nit against a named record right now, even when the normal trigger did not fire — use it\nto test or to re-run. It spends whatever those actions cost; a bulk request multiplies\nthat by the number of records, runs them in payload order, and stops at the first\nfailure, leaving records already started to finish.\n\n**Bulk spreadsheet import (the Excel ETL).** extract -> map headers -> fix errors -> load.\nA **load action** is the way in: send it a spreadsheet and it queues the import. Every\nimport aimed at a table, whatever sent it, is listed at that table's `queue` resource\nwith a derived status — `loading`, `loaded`, `parked` (something needs answering) or\n`ready`. Follow a queue entry to choose its header row, answer its date-format question,\ncorrect values, exclude rows, then load. Use it for one-off or recurring imports. It is\nnot row-by-row automation — that is what actions are for.\n\nHow an import maps columns — design the destination table for this:\n- Only **input columns** (`isInput` true) are import targets. Set `isInput` false on every\n  column an action fills, or an import can write the file's data into it.\n- A file column is mapped with no question when its header is the column's name, or when\n  an earlier load into this table mapped that header there. Every load remembers its\n  mappings for the next file, so a wrong mapping that loads is repeated.\n- Every other input column is put to the import's **AI**, one column at a time. It sees\n  the column's name and type and the file's headers with sample values — not the\n  column's description. It maps by **meaning**, so \"Money Out\" can feed \"Spend\".\n- Each column is decided alone, so one file column can feed several input columns. Give\n  the table **one column for each meaning** and let files of different layouts map into\n  it; two columns that mean nearly the same thing both get filled from one file column.\n- The AI answers each column with a file column, \"not in this file\" (the column stays\n  empty and the import goes on), or \"not sure\". **\"Not sure\" parks the whole import**,\n  on an optional column too. A queue entry that shows `ready` with no load can be\n  parked this way: read its `automation` resource, where the question shows as\n  `ABSTAINED`. Map that column on the entry's `header` resource or leave it empty, then\n  start the load from the entry's `load` resource — nothing loads it on its own.\n- The AI's tokens are charged at its model's rate on `/pricing`, on top of the load\n  action's run charge.\n\n## Actions — the nine types, and when to reach for each\nAn action is an automated behaviour, authored once at the **workspace action list** and\nmade to run by wiring it onto a table with a connection.\n\n- **agent** — one LLM call per row: structured input to structured output. Your default\n  for extract, classify, summarise, generate or rewrite. Cheap, fast, predictable.\n  **Start here for anything AI-shaped.**\n  To work on another system (an app builder, a CRM), an agent action uses an MCP server\n  from the workspace's `/mcp`: an admin, signed in as a person, connects the server there\n  (a known service, such as Lovable, from the create form's `catalog`, with no address),\n  or brings in with the create form's `server` one connected once on their own account (or their org's, at\n  the account's `mcp`), and adds the action under the server's `grant`. Each run is then offered every tool of\n  that server, named `server__tool`, and acts on the other system as the person who\n  connected it — Morphcloud cannot undo what it does there. An admin can also let the\n  workspace's Architect use a server, under the server's `architect`.\n- **lookup** — read or aggregate rows from *another* table into this row. Enrichment and\n  validation. Reads only, never writes.\n- **network** — copy rows *into* another table, triggering that table's own actions. This\n  is how multi-table pipelines are built. Read from another table with lookup; move rows\n  into one with network.\n- **web** — call an external HTTP API and map the JSON response back to columns. It can\n  send a file two ways, and the body mode chooses: in `json` and `form` a file input\n  arrives as a short-lived download link the API fetches itself; in `multipart` it is\n  uploaded as the file, in a part named by that field's body path — use `multipart`\n  whenever the API's documentation says \"file upload\" or \"multipart/form-data\". It can\n  also receive a file: when the API answers with the file itself (a CSV, a PDF), give the\n  action one file-typed output field and leave its response path unset — the whole\n  response is saved as that file. When the API answers with JSON that carries a link to\n  the file, point the file field's path at the link and it is downloaded for you. Set\n  the path to `$` to keep a JSON response itself as a file.\n- **script** — sandboxed JavaScript for deterministic per-row logic: maths, string work,\n  reshaping. No network, no filesystem. Use it instead of an agent whenever the rule is\n  exact and needs no model.\n- **email** — send a templated email as a connected Microsoft mailbox. To, Cc, subject and\n  body are templates over the inputs: a `{field}` placeholder makes the recipient or the\n  content per-row, a literal keeps it fixed.\n- **join** — deterministic one-to-many fan-out: cross an incoming row against a join\n  table's matching rows and write one combined row per match into a destination table.\n  Fire-and-forget — it returns counts, never a response — and re-firing the same input\n  writes the rows again. For AI-driven row extraction use a write agent instead. Despite\n  the name, this is not the enrichment join of spreadsheet tools: to pull values from\n  another table onto an existing row, use **lookup**.\n- **generate** — produce a file. Choose the format under `/format` first — a new action has none and cannot complete without one; it locks once a source table or template is attached:\n  `excel` matches rows in a source table into a workbook, optionally filling an uploaded\n  template whose layout, formulas and macros pass through untouched; `csv` matches rows\n  the same way and writes them as a CSV file — the format for a file another system\n  will read (dates are ISO-8601, numbers are plain, and a result over 100,000 rows is\n  refused rather than cut short). It has no template; its `/map` lists the file's\n  columns instead: which table columns, in what order, each under the header the\n  receiving system expects. With an empty `/map` the file holds every column of the\n  table, system columns included, so a file for another system always needs one; `word` renders one\n  document per run from an uploaded template's `{{tokens}}`, with no source table.\n- **load** — import a file into the action's destination table. Its `/format` is `excel`\n  (the default) or `csv`, and that choice is the type of the fixed Spreadsheet input —\n  so the input model states what the action needs, and a run that delivers the other\n  kind is refused. A source that can send both needs one load action per kind. (A CSV\n  is one sheet, so a sheet name does not apply to it.) The requester supplies the file\n  through the Spreadsheet input; every further input pins a\n  value onto a destination column for every imported row (a parent key, say), and all\n  inputs are required. The run ends when the file is accepted and queued — it returns\n  `queued`, nothing more; the destination table's queue resource carries the derived\n  progress (loading / loaded / parked / ready). One load action per destination table.\n\nIn one line: per-row AI is **agent**; read from another table is **lookup**; move rows\ninto another table is **network**; call out to the web is **web**; exact local computation\nis **script**; send mail is **email**; one row in and many out is **join**; produce a\nfile is **generate**; feed a spreadsheet into a table is **load**.\n\n## Which piece for which need\nThe wrong piece usually works on the first row and costs a rebuild later. Each entry\nnames the need, the piece that fits, and the piece it is mistaken for.\n\n- **A value from a fixed list** (a status, a category, a priority, a country, a\n  currency): a small table of the allowed values with a unique name column, and a\n  `reference` column that points at it — never a `text` column. There is no list type.\n  A reference is the only column that refuses a value outside the list, on a person's\n  edit and on an import. A text column can never be changed into a reference: you add\n  a new reference column, fill it, and delete the text one.\n- **An AI step that must pick from that list**: name the allowed values exactly in the\n  **agent**'s prompt, write its answer to a text column, and resolve that with a plain\n  **lookup** (no aggregation) into the reference column — do not map the agent's output\n  straight onto the reference. An action's output is not checked against the list, so\n  a misspelt value would be stored. A lookup that matches nothing clears the reference,\n  so an empty reference is the flag. Condition the lookup on the text being\n  `is_not_empty`: a blank match value fails the run.\n- **The same thing appears on many rows** (a customer, a product, a contract): one table\n  where it is written once, and a `reference` from every table that mentions it — not\n  its name copied as text. To show one of its values on the child row, **lookup** it;\n  do not copy it.\n- **A total or a count of child rows on the parent**: a **lookup** with `sum` or\n  `count`, matched on the child's reference column, with `isLive` on. Not a timer that\n  recomputes: a change in the child table already re-runs it.\n- **Steps one after another on a row**: connections on one table in `orderIndex` order,\n  each with a condition on what the step before wrote, and each with a done guard. A\n  later step reacts to what an earlier step has just written; an earlier step never\n  reacts to a later one in the same run — so the step that reads goes after the step\n  that writes.\n- **Different rows take different paths**: one connection per path, each with a\n  condition on the column that decides. A condition on a reference column compares the\n  referenced key as text, and case counts (`Approved` is not `approved`).\n- **An exact rule, or a judgement**: **script** when the rule can be written down\n  exactly; **agent** when it needs reading. Where both are needed, the agent reads and\n  a script decides.\n- **One row into many** (an amount split across shares, one row per recipient):\n  **join** against the table of shares or recipients, into a destination table whose\n  own connections then run on each new row. For the rows of a spreadsheet, **load**;\n  for the rows of a document, a write **agent**.\n- **Send to everyone on a list**: **join** against the recipients table, then **email**\n  on the destination table — one message per row.\n- **A spreadsheet comes in, once or every month**: a file column (`excel` or `csv`) on a\n  parent row that stands for the submission, a **load** action into the child table,\n  and a connection on the parent so that the file landing runs the import — with the\n  parent's key pinned, so every line references its submission, and a done guard on\n  Queued. Not a load run by hand, and not rows typed in. Load a parent table before\n  its children: an import whose reference values are not yet in the parent parks, and\n  it is not checked again when the parent arrives.\n- **Incoming codes that must match your lists**: where someone watches the import\n  queue, make the destination columns references — the import checks every value, its\n  own AI matches an unknown one to an existing one and remembers the match, and what it\n  cannot match parks for a person. Where nobody watches, or a wrong match is worse than\n  a missing one, land the code as text and resolve it with a lookup, as above.\n- **The same file must not load twice**: a unique column on the destination. A repeat\n  then parks the import and nothing is written; without one, the rows are added again.\n- **Ask another system and use the answer**: **web**, with its response mapped to\n  columns.\n- **Something changes that no write in MorphCloud announces** — another system, or the\n  passing of time: a timer, with a condition that stops matching once the work is\n  done. Never a timer to recompute MorphCloud's own data: every write already triggers\n  what depends on it. Give the timer a condition on a value (`equal`), which it filters\n  before it starts any work; `is_empty` alone makes it visit every row at every tick.\n  A web or email step is charged for every run, whether or not anything changed.\n- **A file goes out** (a statement, a report): **generate** writes the file into a file\n  column on the row, and an **email** later in `orderIndex`, with that column as an\n  input, attaches it (3 MB a file). Each needs its own done guard. Generate makes an\n  Excel workbook, a CSV file or a Word document — not a PDF, so do not offer one.\n- **Keep one summary row per group up to date**: **network** with `createOrUpdate` and\n  the group's key as its match column.\n- **A person must look at the exceptions**: a status reference, and a condition that\n  holds the later steps until the status changes. Record the reason on the row; do not\n  reject silently.\n\n## Connections — where behaviour is actually decided\nA connection binds a workspace action onto one table and maps that table's columns to the\naction's fields. It is not a dumb wire: almost everything about *when* and *whether* an\naction runs lives here.\n\n- **What triggers a run** — a change to the columns mapped into the action's inputs, or to\n  a column named by one of its conditions. Not \"any edit to the row\". Which columns those\n  are is decided by the mapping below.\n- **orderIndex** — the actions on a table fire in a defined order. This is how you\n  sequence a pipeline within one table.\n- **isOn** — the master switch. Pauses this binding, scheduled fires included, without\n  losing any configuration.\n- **isLive** (lookup only) — when set, changes to the *source* table re-run the lookup for\n  every row that depends on them, so enriched values stay current instead of going stale\n  until someone happens to touch the row.\n\n### Field mapping\nThe mechanic that makes a connection do anything. Two layers look alike and are not:\n\n- The **action's** input and output field lists declare *what fields exist* — their names,\n  types, and whether they are required.\n- The **connection's** input and output field lists declare *which column fills each of\n  those fields*, for this table.\n\nAn input field's column feeds the action; an output field's column receives what the\naction produced. Clearing a mapping unwires the field, and nothing flows either way.\n\nThree behaviours to know:\n\n1. **Creating a connection auto-maps every field to a column of its own name.** Where the\n   names line up that is exactly what you want. Where they do not, you get a binding that\n   looks created and is half-wired — the create response lists the fields that did not\n   land, so read it.\n2. **Mappings are late-bound by name and resolved at every run.** A field naming no real\n   column writes nothing and raises no error. Each field carries a `resolved` flag; check\n   it. The unmapped-field list comes back only when the connection is created, so\n   afterwards you confirm a binding is wired by walking its input and output field lists.\n3. **Renaming a column unwires the mappings that point at it**, deliberately: the name is\n   the key, so the old key stops resolving until you repoint it. After any rename,\n   repoint the affected fields and re-check `resolved`.\n\nThe mapping itself is a `search` field over the table's columns, so resolve it with\n`formSearch` rather than typing a column name.\n\n### Conditions — gate, and route\nConditions on the triggering row that must **all** hold for the binding to run; with none,\nit runs whenever its inputs change. The operators are `equal`, `not_equal`,\n`greater_than`, `greater_than_equal`, `less_than`, `less_than_equal`, `in`, `not_in`,\n`contains`, `starts_with`, `is_empty` and `is_not_empty`, plus four that compare a\ndate/time column against the current time: `before_now`, `after_now`, `on_or_before_now`\nand `on_or_after_now`.\n\nA condition names its column by the column's **URL slug** (`oasis-portfolio-id`), not by\nthe camelCase key the same column has in row data (`oasisPortfolioId`). Resolve it with\n`formSearch` on the condition's `column` field, as for a field mapping.\n\nThe second use is the one worth internalising: a condition can read a column that an\n*earlier* action in the pipeline wrote. So conditions do not merely gate — they **route** a\nrow to different downstream actions depending on what the previous step decided.\n\n### The done guard — a step that must run once\nA condition that reads only a step's inputs stays true after the step has succeeded, so\nevery later trigger of the row runs it again: an edit to another column, a lookup\nrefreshing, a timer. Give every step that must run once a condition its own success makes\nfalse — the **done guard**. For a step that writes back onto its own row, condition on\nthat output column being `is_empty`. A step that writes into **another** table (a write\nagent, network, join, load) changes nothing on its own row, so map one of its outputs back\nonto this row and condition on that column being `is_empty`. Use an output the platform\nsets, never one a model reports: for a write agent or a join, **Rows Written** (the rows\nthe run actually inserted — the model is never asked for it); for a network, the created\nrow's **Row ID**; for a load, **Queued**. A count a model reports can be wrong, and a\ndone guard must not depend on the model.\nWithout it, a re-run inserts the same rows again: duplicates, or a failure on a unique\ncolumn, and an agent pays for the model again each time. A connection that keeps failing\non the same row is switched off after a few failures in a row and its admins are\nmailed, but that is a safety net, not a design.\n\n### Timers — run on a schedule\nA connection can carry several schedules. The frequency is `every_minute`, `every_15_minutes`, `hourly`,\n`daily` or `weekly`. The first two are fixed intervals and land on clock boundaries;\n`daily` needs a time of day, and `weekly` needs a time of day plus one of `monday`,\n`tuesday`, `wednesday`, `thursday`, `friday`, `saturday` or `sunday`. All times are UTC.\nEditing a schedule recomputes the next run from now and never fires instantly; turning one\noff pauses it without losing it.\n\nEach fire considers **every row** of the table and runs the action on the rows whose\nconditions hold; a fire that finds no qualifying row does nothing, so an `every_minute`\ntimer with a tight condition costs almost nothing while no work is waiting. That\ncombination is the platform's polling idiom: **timer plus conditions means \"periodically\nprocess the rows that qualify\"**. You would not discover it by browsing. It is also how\nyou wait for an outside system: no action ever waits for a job to finish. One action\nstarts the job and writes its id to the row; a second, on an `every_minute` timer with a\ncondition on the status column, asks for the status; a third, conditioned on that status\nreading complete, fetches the result. **Only the waiting step gets a timer.** Every other\nstep follows from the change the step before it writes; a timer on those is one more\ntrigger for a repeat run, not a retry you need.\n\n## Composing the pieces\n**Upsert is network plus a match strategy.** A network action's match mode is `create`,\n`createIfMissing`, `updateIfExists` or `createOrUpdate`, and its match filters name the\ncolumns that form the match key — an incoming value must equal that column on an existing\nrow. **The trap: with no match filters nothing can ever match**, so the modes that need one\nonly ever insert.\n\n**Lookup shapes.** Match columns compare with `equal`, `less_than`, `less_than_equal`,\n`greater_than` or `greater_than_equal`. When several rows match, an aggregation collapses\nthem — `first`, `count`, `sum` or `average` — and a sort direction of `asc` or `desc`\ndecides which row `first` means.\n\n**Multi-table pipelines.** A network action writing into a table triggers *that* table's\nown actions, so work chains across tables. Within one table, `orderIndex` sequences the\nactions and conditions branch them.\n\n## The action lifecycle\n1. **Create** at the workspace action list — tables carry no action-creation routes. The\n   type is fixed at creation, so pick deliberately.\n2. **Configure** the fields at the action's own `input/field` and `output/field`. Every\n   type uses the same two addresses; the models are created with the action.\n3. **Attach a table**, for the types that operate on one — lookup reads from it, network\n   writes into it, join **writes its combined rows into it** (the table it fans out\n   across is attached separately at the action's join resource), an excel generate\n   reads from it, and a write-direction agent\n   inserts into it. Done once, after creation, via the action's own table resource. The\n   field lists then offer that table's columns as picks, and a network action gains its\n   match resource.\n4. **Test** it. Marking the action **complete** is what publishes it for other\n   workspaces to use — it is not a gate on wiring. A workspace can wire its own draft\n   and keep iterating on it, so build and test against a real connection rather than\n   completing early to unlock the next step.\n5. **Wire** it from the triggering table's action path, which creates the connection —\n   then map its fields, and add conditions or timers as needed.\n\n## Seeing what happened\n- The action's **log** — per-execution inputs, outputs, errors and duration. Subscribe to\n  it over WebSocket to stream entries as they arrive.\n- The action's **report** — a per-day summary over the last 30 days: how often it fired,\n  how many runs succeeded or errored, and average wait and run times. Use it to spot a\n  failing or slow day.\n- The row's own **history**, the workspace **error log**, and the `actionStatus` column on\n  every row.\n\n## What it costs\nRuns spend credits against the owning account, and a balance that cannot cover a run\nrefuses it rather than running it for free. Anything that fires actions is spending: a row\nedit, a timer, an execute, a bulk request. The usage resource breaks consumption down by\nmonth, workspace and resource — treat its per-resource credit figures as rounded\nattribution rather than an invoice, and reconcile money against the account balance rather\nthan by summing those rows. The current rates — storage per GB-month, the flat per-run\naction charge, and every AI model's per-million-token rates — are published at `/pricing`,\nreadable without an account.\n\n## Check the library before building\n`/library` is a public, view-only catalogue of actions and reference tables that owners\nhave published, organised by category and readable without an account. A listing shows the\ninputs an action expects, the outputs it returns, and what a run costs. When something\nfits, wire it onto your own table from your own workspace instead of rebuilding it; build\ncustom only when nothing published fits.\n\n## Files\nTo get a file larger than a request body into the platform, stage an asset: you get back a\none-time URL, PUT the bytes straight to it, then reference the file by its `assetId`.\nPrefer this to inlining anything above about a megabyte. A staged asset can be read in\nbounded windows through its content resource — a text-like file by byte range, a\nspreadsheet by rows, a Word document as Markdown — so you can sample a file before\ndesigning a table around its data. Images, PDFs and binaries are not readable there;\ndownload them, or attach them to a file cell. File cells also accept a public http(s) URL\nthrough the cell upload route, and the server fetches the bytes for you.\n\n## A frontend for a workspace\nA frontend on its own domain (one built with Lovable, say) signs members in as a\n**workspace app** — never with a password, never with a connector's token. As a workspace\nadmin, create the app under the workspace's `app` resource and list as return addresses\nevery address the frontend is served from; sign-in refuses, and the browser blocks, any\naddress not listed. A Lovable project has three — `id-preview--<project id>.lovable.app`,\n`preview--<name>.lovable.app` and `<name>.lovable.app` — plus any custom domain. Then read the app back: its `connect` block holds\nthe exact sign-in values and the steps to put in the frontend's code. Its tokens reach\nonly that workspace, with only the access the app was given. A Lovable project's\npublished name exists only after its first publication, so register the editor address\nfirst, and after every publication or rename add the new address to the app — until\nthen sign-in on it is refused.\n\nBuild a JavaScript frontend with the npm package `@morphcloud/client`, never with\nhand-written fetch, token or WebSocket code. Pass the app's `connect` block to\n`createMorphcloud(...)`; the client then signs members in, refreshes tokens, reads and\nwrites tables through `mc.workspace.table('<slug>')`, runs a table's actions with\n`execute` (it starts the run and returns — the results arrive in the rows) and keeps rows\nlive with `subscribe`. Every result is the envelope described above. Sign-in runs in the\nbrowser only: the server render has no member. The package's AGENTS.md is the full guide.\n","version":"4.7.1"}