Not yet assessed
Review the original instructions and requested permissions before installing.
No security review is available for this catalog entry yet.
Build Base44 apps in cloud sandboxes with auto-synced backend resources and remotely configured connectors.
Develop a Base44 app remotely inside Base44's cloud sandbox using your own agent — no local checkout and no deploy/push commands. The implementation is remote: writing a resource file into the sandbox is what ships it (backend functions, realtime actors, entities, and agents all auto-sync from the file you write), and OAuth connectors are set up against the remote app via MCP tools or the projectless `base44 connectors` CLI. This skill is the place for learning what you can author in the sandbox, how backend functions, actors, entities, and agents are structured, and how to connect a connector without a local filesystem. Triggers on 'develop my Base44 app remotely', 'no local files', 'cloud sandbox', 'create an entity/agent remotely', 'add realtime/multiplayer/presence remotely', 'connect a connector remotely', 'bring my own agent', or any work editing a Base44 app inside a sandbox.
Review the original instructions and requested permissions before installing.
No security review is available for this catalog entry yet.
How clearly the skill guides your agent, how complete its workflow is, and how you can check the outcome.
No quality assessment is available for this catalog entry yet.
Original instructions from the publisher’s SKILL.md
# Base44 in the Cloud Sandbox
Author Base44 app code **inside Base44's cloud sandbox** with your own coding agent. There is no local checkout: you read, write, and run files through the sandbox tools (over MCP or the `base44 sandbox` CLI), and the platform builds and deploys from what you write.
For **how to connect** to the sandbox (MCP endpoint or the `base44 sandbox` CLI, the `read_file` / `write_file` / `edit_file` / `run_command` / `grep` / `list_directory` / `create_checkpoint` tools — which the CLI exposes under shorter names (`sandbox read` / `sandbox write` / `sandbox edit` / `sandbox run` / `sandbox grep` / `sandbox ls` / `sandbox checkpoint`), the edit→preview→verify loop, persistence, and concurrency), use the **`base44-remote-dev`** skill. This skill covers **what you can author and how** once you are connected.
> **Check these references first.** This skill and its siblings (`base44-remote-dev`, `base44-sdk`) are the source of truth — consult them before searching the web. See [Reference order & the complete README](#reference-order--the-complete-readme).
## ⚡ The mental model: writing the file *is* the deploy
You are working on a **remote** app, not a local checkout. The project-level CLI workflow does **not** apply — never run `base44 deploy`, `base44 functions deploy`, `base44 actors deploy`, `base44 actors delete`, `base44 ... push`, `base44 create`, or `base44 scaffold`. They assume a local project and a manual deploy step that does not exist here.
Instead: **as soon as you write a resource file into the sandbox — a backend function, an actor, an entity, or an agent — the platform deploys/syncs it from there.** Your write is auto-committed (~5s debounce) and goes live. You do not run, and must not wait for, any `deploy` / `push` command.
**One exception — connectors.** OAuth connectors aren't authored as files; they're set up against the remote app by its id, either with the MCP connector tools or with the dedicated, projectless `base44 connectors` commands (which take `--app-id` and need no local project). See [Connectors](#connectors-oauth-integrations) below.
You *may* still use `run_command` (`sandbox run` in the CLI) for ordinary checks (e.g. `npm run build`, `npx tsc --noEmit`, `npm run lint`) and preview — that is verification, not deployment. See the edit→preview→verify loop in `base44-remote-dev`.
## What you can author today
| Resource | Status in the sandbox |
|----------|-----------------------|
| **Backend functions** (`base44/functions/`) | ✅ Supported — write the files; they deploy from the sandbox. |
| **Actors** (`base44/actors/`) | ✅ Supported — write `entry.ts`; the actor deploys from the sandbox. Deleting the entry file tears it down. |
| **Entities** (`base44/entities/`) | ✅ Supported — write the `.jsonc` schema file; it auto-syncs. No `entities push`. |
| **Agents** (`base44/agents/`) | ✅ Supported — write the `.jsonc` config file; it auto-syncs. No `agents push`. |
| **Frontend code** (`src/…`) | ✅ Supported — edit normally; HMR/preview reflects it. Use the **`base44-sdk`** skill for SDK API usage. |
| **Connectors** (OAuth integrations) | ✅ Supported — set up via the connect flow below (MCP tools or `base44 connectors`), **not** by writing files. |
## Backend functions
Backend functions live in `base44/functions/`, one directory per function (kebab-case name). In the sandbox you only need to create the **`entry.ts`** file directly under `base44/functions/<name>/` — **no `function.jsonc` is required** (the sandbox infers the function from the directory; the config file is ignored in this mode):
```
base44/functions/
process-order/
entry.ts
```
Entry file — `export default` an async request handler, and use the `npm:` prefix for npm packages:
```typescript
import { createClientFromRequest } from "npm:@base44/sdk";
export default async function (req) {
const base44 = createClientFromRequest(req); // inherits the caller's auth
const { orderId } = await req.json();
const order = await base44.entities.Orders.get(orderId);
return Response.json({ success: true, order });
}
```
Conventions:
- **Kebab-case** directory and function name; entry typically `entry.ts`.
- `createClientFromRequest(req)` for a client in the caller's auth context; `base44.asServiceRole.…` for admin-level operations.
- Read secrets with `secrets.get("KEY")` from `base44:runtime` (`import { secrets } from "base44:runtime"`; configured in app settings).
- Return with `Response.json(body, { status })`; handle errors and set appropriate status codes.
That's enough to author functions correctly. For deeper detail and more examples (service role, secrets, common mistakes), see the `base44-cli` skill's reference: [`functions-create.md`](../base44-cli/references/functions-create.md) — but **ignore its "Deploying Functions" / CLI sections** and its **`function.jsonc`** guidance, which assume a local project and do not apply in the sandbox (here you only write `entry.ts`).
> **Calling the function from the frontend:** `base44.functions.invoke(name, data)` returns the **raw axios response** — your function's JSON is on **`.data`** (`const result = res.data`), not the top-level object, and it **throws on non-2xx** (error body at `err.response.data`). See the `base44-sdk` skill's [`functions.md`](../base44-sdk/references/functions.md) for details.
## Actors (realtime)
Actors are stateful realtime server rooms over WebSockets — one live instance per room id, shared by everyone connected to that id. Reach for one when users interact **live in one shared session**: multiplayer, collaborative boards/docs, presence and live cursors, in-room chat, live auctions. A page that merely lists records live does not need an actor (`base44.entities.Thing.subscribe()` covers that).
One folder per actor in `base44/actors/`, containing `entry.ts`. Just write the file — it deploys; **don't run `base44 actors deploy` or `deploy`.** Write **plain JavaScript**, exactly like a backend function — no type annotations; the file is `.ts` only because that is the entry contract. There is no test tool for an actor (it serves WebSockets, not requests) — verify it in the preview.
```
base44/actors/
ChatRoom/
entry.ts
```
```javascript
// base44/actors/ChatRoom/entry.ts
import { Actor } from "base44:runtime/actors"; // the only import that resolves the base class
export default class ChatRoom extends Actor {
async handleStart() {
// Runs on every wake — instance fields are lost when the room hibernates.
this.history = (await this.storage.get("history")) ?? [];
}
handleConnect(conn) {
conn.send({ type: "history", messages: this.history }); // this client only
}
async handleMessage(conn, msg) {
if (msg?.type !== "message" || typeof msg.text !== "string") return; // validate everything
const authorUserId = conn.identity?.type === "authenticated" ? conn.identity.userId : null;
const entry = { authorUserId, text: msg.text.slice(0, 2000) };
this.history = [...this.history, entry].slice(-100);
await this.storage.put("history", this.history);
this.broadcast({ type: "message", ...entry }); // the whole room
}
handleClose(conn) {}
}
```
Conventions:
- **PascalCase** folder name — it becomes a JavaScript class binding, so `[A-Za-z_][A-Za-z0-9_]*` only (no `-`, `.`, `/`), no JS reserved words, and **no nested folders**.
- The entry must **default-export** a class extending `Actor`; the class name itself is cosmetic.
- Handlers: `handleConnect(conn)` / `handleMessage(conn, msg)` / `handleClose(conn)`, plus optional `handleStart()` and `handleWake(key)`. For a room that must advance on its own (game loop, visible countdown), override `shouldTick()` and the platform calls `handleTick()` every `tickIntervalMs` (default 100) while it returns true. Never override `onStart`/`onAlarm`.
- Persist anything you can't lose in `this.storage` and rehydrate it in `handleStart()` — instance fields reset when the room hibernates.
- `this.broadcast(...)` for room-wide state; `conn.send(...)` for events about one client.
- Actors use direct connections; the SDK handles token minting and connection auth. `conn.identity` holds the verified authenticated `userId` or anonymous `anonymousId` and survives hibernation. Use authenticated `userId` for attribution and permissions; guest chat messages above use `authorUserId: null`.
- Keep client-chosen `conn.id` internal for reconnect bookkeeping. Use separate server-assigned participant ids for public presence; never broadcast reconnect ids or treat them as proof of authorship or permissions.
- `this.client` uses the **anonymous** role (RLS-gated). `this.client.asServiceRole` provides admin-level entity access, function calls, and integrations for validated, room-owned work. Neither impersonates a user: attribute records with an explicit field from authenticated `conn.identity.userId`.
- The actor is authoritative: clients send inputs, the actor validates and broadcasts. Never trust client-computed outcomes.
- Durable results (the finished drawing, a chat transcript): the **actor** stores the validated result in `this.storage` and persists the canonical entity record via `this.client.asServiceRole`, with idempotent writes keyed by the room's instance id. Retain pending writes and retry with a scheduled wake if needed. Broadcast and re-send the result to (re)connecting clients for display; the frontend writes only user-owned records. Keep canonical-result entity writes restricted to the server.
- Actors receive no app secrets or private data-source bindings. Put operations requiring them in a backend function and call it from the actor. `ACTOR_TOKEN_SECRET` is automatically provisioned; do not create or overwrite it during setup, since changing it rotates the actors' keys.
- Automations are not supported on an actor.
For the full authoring reference (naming, lifecycle, storage/hibernation, scheduled wakes, rooms and discovery), see the `base44-cli` skill's [`actors-create.md`](../base44-cli/references/actors-create.md) — but **ignore its "Deploying Actors" / CLI sections**, which assume a local project.
> **Connecting from the frontend:** `base44.actors.ChatRoom(roomId).connect()` returns a connection with `.subscribe(cb)`, `.send(data)`, and `.close()`. Connect inside a `useEffect` and clean up both. See the `base44-sdk` skill's [`actors.md`](../base44-sdk/references/actors.md).
## Entities
One `.jsonc` file per entity in `base44/entities/`. Just write the file — it auto-syncs; **don't run `base44 entities push` or `deploy`.**
- **File name:** `{kebab-case}.jsonc` — e.g. `team-member.jsonc` for an entity named `TeamMember`.
- **Entity `name`:** PascalCase, alphanumeric only (`/^[a-zA-Z0-9]+$/`).
- **Field names:** `snake_case`.
```jsonc
// base44/entities/task.jsonc
{
"name": "Task",
"type": "object",
"properties": {
"title": { "type": "string", "description": "Task title" },
"status": { "type": "string", "enum": ["todo", "doing", "done"], "default": "todo" },
"due_date": { "type": "string", "format": "date" },
"board_id": { "type": "string", "description": "Owning board" }
},
"required": ["title"]
}
```
Field types: `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary`. String formats include `date`, `date-time`, `email`, `uri`, `uuid`, `file`, `richtext`. For full schema detail and row-level security (RLS), see the `base44-cli` references [`entities-create.md`](../base44-cli/references/entities-create.md) and [`rls-examples.md`](../base44-cli/references/rls-examples.md) — but **ignore their `entities push` / deploy sections**; the sandbox syncs the file for you.
## Agents
One `.jsonc` file per agent in `base44/agents/`. Just write the file — it auto-syncs; **don't run `base44 agents push` or `deploy`.**
- **File name:** `{agent_name}.jsonc` — e.g. `support_agent.jsonc`.
- **Agent `name`:** `/^[a-z0-9_]+$/` (lowercase, underscores, 1–100 chars).
```jsonc
// base44/agents/support_agent.jsonc
{
"name": "support_agent",
"description": "Brief description of what this agent does",
"instructions": "Detailed instructions for the agent's behavior",
"tool_configs": [
{ "entity_name": "tasks", "allowed_operations": ["read", "create", "update", "delete"] },
{ "function_name": "send_email", "description": "Send an email notification" }
],
"whatsapp_greeting": "Hello! How can I help you today?"
}
```
Required: `name`, `description`, `instructions`. Optional: `tool_configs` (default `[]`), `whatsapp_greeting`. Tool configs are either an **entity tool** (`entity_name` + `allowed_operations`: any of `read`/`create`/`update`/`delete`) or a **backend-function tool** (`function_name` + `description`). For the full agent schema, see the **Agent Schema** section of the `base44-cli` skill's [`SKILL.md`](../base44-cli/SKILL.md) — but **ignore its `agents push` / `agents pull` / deploy commands**, which assume a local project; in the sandbox the file auto-syncs.
## Connectors (OAuth integrations)
Connectors (Google Calendar, Gmail, Slack, …) give your backend functions tokens to call third-party APIs. In remote-dev there are **no connector files to write** — you operate on the connector directly against the app by its id. Two surfaces, same backend and same behavior:
> **Declarative scopes — read before you set.** Connecting a connector **replaces** its scope set with exactly the scopes you pass (it does not merge). Any scope you omit is removed and the user is re-prompted to consent. **Always list the connector's current scopes first and pass the complete desired set** (the ones you want to keep **plus** any new ones).
> **OAuth needs a human.** Connecting returns an **authorization URL** the user must open in a browser to sign in and consent — you cannot complete it yourself. After they finish, re-list to confirm it's connected and to read the **granted** scopes (a provider may grant fewer than you requested).
### Over MCP (`base44-remote-dev` transport)
Two tools, both taking `appId`. Scopes: `list_connectors` needs `apps:read`; `initiate_connector_connection` needs `apps:write` (note: **not** `sandbox:write`).
1. **`list_connectors`** — `{ appId, integrationTypes? }`. With no `integrationTypes`, returns the full catalog; each entry has the connector's name, description, whether it's connected, and (if connected) its status and granted scopes. Pass `integrationTypes` for full detail on specific connectors.
2. **`initiate_connector_connection`** — `{ appId, integrationType, scopes, connectionConfig? }`. `scopes` is the **complete** desired set (see the declarative-scopes note). Returns either `already_authorized: true` (nothing to do) or a `redirect_url` for the user to open. After they sign in, call `list_connectors` again to verify.
```
On appId <APP_ID>: call list_connectors to read googlecalendar's current scopes,
then initiate_connector_connection for googlecalendar with the full scope set
(existing + the calendar.events scope I need). Give me the authorization URL.
```
### Over the CLI (projectless, `--app-id`)
These `base44 connectors` subcommands work **without a local project** — they resolve the app id from `--app-id`, then `BASE44_APP_ID`, then a local `.app.jsonc`. No `config.jsonc` is required.
```bash
# 1. See available integration types for the app
npx base44 connectors list-available --app-id <APP_ID>
# 2. Initialize the connector and start OAuth (sets it to EXACTLY these scopes).
# Non-interactive: prints the authorization URL. Interactive: also opens the
# browser and polls until authorized.
npx base44 connectors initiate --app-id <APP_ID> \
--integration-type googlecalendar \
--scopes https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/calendar.events
# 3. (optional) Fetch the resulting connector config
npx base44 connectors pull --app-id <APP_ID> --dir ./connectors
```
`--scopes` accepts a space- or comma-separated list. As with MCP, the user must open the printed authorization URL to finish consent; afterwards `list-available` / `pull` reflects the connected state and granted scopes.
> This is the **only** Base44 CLI use that belongs in remote-dev — it targets a remote app by id with no local project and no deploy step. It is not a contradiction of the "no CLI" rule above, which is about local-project/deploy commands.
### Using a connected connector in code
Connecting only authorizes the connector. To actually call the third-party API, fetch its OAuth access token **inside a backend function** with the service-role connectors module — `base44.asServiceRole.connectors.getConnection(integrationType)` — and use the returned `accessToken` (and optional `connectionConfig`) in your own `fetch`:
```typescript
import { createClientFromRequest } from "npm:@base44/sdk";
export default async function (req) {
const base44 = createClientFromRequest(req);
// App-scoped OAuth token — backend / service role only.
const { accessToken, connectionConfig } =
await base44.asServiceRole.connectors.getConnection("googlecalendar");
const events = await fetch(
"https://www.googleapis.com/calendar/v3/calendars/primary/events",
{ headers: { Authorization: `Bearer ${accessToken}` } },
).then((r) => r.json());
return Response.json({ events });
}
```
Notes: the connector is **app-scoped** (one connected account shared by all users); Base44 refreshes the token for you; you make the API calls. `getConnection()` replaces the deprecated `getAccessToken()`. For the full module reference (signatures, `connectionConfig`, the list of available services and their type identifiers), see the `base44-sdk` skill's [`connectors.md`](../base44-sdk/references/connectors.md).
## Reference order & the complete README
**Consult the references in this skill and its sibling skills (`base44-remote-dev`, `base44-sdk`) before searching the web.** They are the source of truth for the sandbox bridge, file/resource conventions, and SDK APIs — prefer them over general internet results, which are often stale or wrong for Base44.
For the complete, app-specific remote-dev reference (instructions + every endpoint, public, no auth needed to fetch), read the onboarding README for your app:
```
https://app.base44.com/api/sandbox/<APP_ID>/local-agent/readme.md
```
(The cloud/MCP equivalent is `…/api/sandbox/<APP_ID>/claude-web/readme.md`.) See the `base44-remote-dev` skill for the connection mechanics this README describes.
## Workflow in the sandbox
1. **Orient** — `list_directory` / `read_file` / `grep` (`sandbox ls` / `sandbox read` / `sandbox grep` in the CLI) to understand the app before changing anything.
2. **Author** — create or edit resource files (backend functions, actors, entities, agents) and frontend code following the conventions above; set up connectors via the connect flow.
3. **Verify** — optionally `run_command` (`sandbox run`) `npm run build` / `npx tsc --noEmit`, and use `get_app_preview_url` to eyeball changes (see `base44-remote-dev`).
4. **Let it ship** — do **nothing** to deploy. Writing the file is the deploy; the auto-commit (~5s) persists and ships it. Pause a moment after your last edit before disconnecting so the commit lands.
5. **Checkpoint** — when you finish a unit of work, and always before you stop, call `create_checkpoint` (`base44 sandbox checkpoint --name "..."` in the CLI). The auto-commit persists your files but is **not** a checkpoint: only checkpoints appear in the builder's version history, and a Restore or Revert there rolls the app back to the last checkpoint and discards everything written after it. The checkpoint flushes pending changes first, so it captures your latest code. See `base44-remote-dev` for details.