Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ AUTH_REDIRECT_PROXY_URL=""
# AUTH_URL=""

# --- Base44 app-factory bridge, server-only ---
# Without these the app runs fine; only the builder is unavailable, and it says
# Without these the app runs fine; only the app editor is unavailable, and it says
# so (the bridge answers 501 bridge_misconfigured).
#
# One Base44 account sits behind the whole integration: it owns every app your
Expand All @@ -54,7 +54,7 @@ BASE44_ACCESS_TOKEN=""
# The `b44k_` workspace API key. Not for building — for the two things only a
# workspace key does: signing a viewer into an embedded app (`app_users:provision`
# + `app_users:embed_token`, app access set to all apps) and registering the
# webhook endpoint. Sent as `api_key`. Optional: without it the builder still
# webhook endpoint. Sent as `api_key`. Optional: without it the app editor still
# works, and embedded apps load signed out.
BASE44_SVC_KEY=""
# Not secrets — the workspace id (sent as X-Active-Workspace-Id) and the platform
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Reference implementation of a **white-label platform built on Base44**. Base44 is the backend and
app-factory; this repo is the shell a company wraps around it. **Sunny** is the worked example — a
work-management product (boards / groups / items / widgets) whose users can build their own apps via
Base44's builder, embedded here.
Base44's app editor, embedded here.

Read `README.md` first: it is the walkthrough, organised around the four boundaries this repo exists
to demonstrate.
Expand Down Expand Up @@ -99,7 +99,7 @@ needs neither — it mints its own keypair.

- `docs/base44-identity.md` — the one-account model, the access token, and keeping builders apart
- `docs/base44-platform-api.md` — the platform REST endpoints
- `docs/base44-built-apps.md` — builder instructions, skills, and the callback API
- `docs/base44-built-apps.md` — custom instructions, skills, and the callback API
- `docs/sunny-platform-skill.md` — the skill text a built app reads, as a worked example
- `docs/base44-webhooks.md` — inbound events: registration, signature, delivery semantics
- `docs/deploy.md` — Netlify + Neon
4 changes: 2 additions & 2 deletions PREVIEW-DEPLOYS.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ curl -s -D- -o /dev/null -b /tmp/j -X POST --data "csrfToken=$T" "$P/api/auth/si
lives in the Base44 workspace and hardcodes one host (`docs/sunny-platform-skill.md`,
`<SUNNY_HOST>`), so an app in a preview reads and writes **production** rows — scoped to
its viewer, but production data all the same. Making it follow the deployment means a
per-environment skill, or teaching the builder instructions to inject the shell's own origin.
- **Base44 credentials on previews or not.** Without them a preview says the builder is not
per-environment skill, or teaching the custom instructions to inject the shell's own origin.
- **Base44 credentials on previews or not.** Without them a preview says the app editor is not
configured and everything else works. With them, previews share the production account and
workspace, so apps built in a preview land in production's workspace — and their `AppOwnership`
rows land in the preview's database branch, so production will not list them. Nothing per-user
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Build your own platform on Base44

**Base44** is an AI app builder: describe an app, get a real, deployed app. This repo shows how to
put that builder *inside your own product*, so your users build apps under your brand, your login
put its app editor *inside your own product*, so your users build apps under your brand, your login
and your governance — and those apps can read and write your product's data.

This README is a walkthrough. It has four steps, each one a boundary you have to get right, with
Expand Down Expand Up @@ -325,8 +325,8 @@ npm run db:migrate
npm run dev
```

Without the `BASE44_*` variables everything works except the builder: the bridge answers
`501 bridge_misconfigured` and the UI says the builder is not configured. That's on purpose — you
Without the `BASE44_*` variables everything works except the app editor: the bridge answers
`501 bridge_misconfigured` and the UI says the app editor is not configured. That's on purpose — you
can explore the product before you have a workspace.

Checks, each one asserting a boundary above:
Expand All @@ -346,7 +346,7 @@ npm run webhook:smoke # step 5: the inbound signature, negative controls

| Symptom | Likely cause |
| --- | --- |
| `501 bridge_misconfigured` | A `BASE44_*` env var is missing, or Base44 answered `401` to the integration account's token (revoked, expired). A deployment problem, not a user one — the UI says the builder is unavailable |
| `501 bridge_misconfigured` | A `BASE44_*` env var is missing, or Base44 answered `401` to the integration account's token (revoked, expired). A deployment problem, not a user one — the UI says the app editor is unavailable |
| `404 not_found` on an app-scoped action | The caller has no `AppOwnership` row for that app. Built before ownership was tracked? Insert the row |
| `403` on deploy | The integration account isn't an Editor in the workspace |
| Builds ignore your design system or skills | The token was created in the account's personal workspace. Create it in the enterprise workspace |
Expand All @@ -361,7 +361,7 @@ npm run webhook:smoke # step 5: the inbound signature, negative controls
- **[docs/base44-built-apps.md](docs/base44-built-apps.md)** — instructions, skills, and the
callback API
- **[docs/sunny-platform-skill.md](docs/sunny-platform-skill.md)** — the skill text a built app
reads, as a worked example of documenting your data model for a builder
reads, as a worked example of documenting your data model for the AI chat
- [docs/deploy.md](docs/deploy.md) — deploying to Netlify + Neon
- [CLAUDE.md](CLAUDE.md) — the conventions this repo holds itself to, and where each boundary
is enforced in the code
Expand Down
8 changes: 4 additions & 4 deletions docs/base44-built-apps.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ data lives) and **an API** (the data itself).

## Two instruction channels, and why they're separate

Every builder turn pays for the always-on instructions, so they have to stay small. Base44 gives you
Every build turn pays for the always-on instructions, so they have to stay small. Base44 gives you
a second channel that's loaded on demand.

| | `custom_instructions` | Skill |
| --- | --- | --- |
| Where | The app's `custom_instructions` field, set at create | A workspace skill document the builder loads by name |
| Where | The app's `custom_instructions` field, set at create | A workspace skill document the AI chat loads by name |
| Cost | Every turn | Only when loaded |
| Length | Keep it a page | As long as it needs to be |
| Content | Runtime constraints, visual direction, **a router** | The endpoint, actions, schemas, gotchas, an example |
Expand All @@ -37,7 +37,7 @@ a second channel that's loaded on demand.
`src/lib/builderInstructions.ts` covers three things:

1. **Context** — what your product is, and that this app was generated from inside it to extend it.
2. **Runtime constraints** — the ones the builder can't discover. In this repo: embedded in a
2. **Runtime constraints** — the ones the AI chat can't discover. In this repo: embedded in a
sandboxed iframe (`allow-scripts allow-same-origin allow-forms allow-popups`), user-resizable and
short by default, so design for a small viewport that scales up; no top-level navigation, no
downloads, no full-screen; `public_without_login`, so no sign-in UI to build — but the data is
Expand Down Expand Up @@ -153,7 +153,7 @@ Once apps are built against it, they are deployed code you don't control. So:
- **Freeze the shapes.** This repo asserts the contract action by action (`npm run sunny:smoke`)
precisely because built apps depend on it.

### An example of the client the builder writes
### An example of the client the AI chat writes

```js
const API = "https://your-host/api/sunny";
Expand Down
6 changes: 3 additions & 3 deletions docs/base44-identity.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,15 @@ It lands in env as `BASE44_ACCESS_TOKEN`, next to `BASE44_ORG_ID` (the workspace
and `BASE44_PLATFORM_HOST`. See `.env.example`.

There is no per-user token to mint, store, refresh or revoke, and no "Connect" step in the UI. The
builder is either configured on a deployment or it is not; `POST /api/base44/platform
app editor is either configured on a deployment or it is not; `POST /api/base44/platform
{action:"status"}` answers which, from config alone.

### What the workspace key is still for

`BASE44_SVC_KEY`, the `b44k_` workspace API key, does **not** build apps. It is for the two things
only a workspace key can do: signing a viewer into an embedded app — provisioning an app user and
minting an embed sign-in token (`src/lib/embedSession.ts`) — and registering the outbound webhook
endpoint. It is optional: without it the builder still works, and embedded apps load signed out.
endpoint. It is optional: without it the app editor still works, and embedded apps load signed out.

---

Expand Down Expand Up @@ -115,7 +115,7 @@ inserting its row for the right user.

```
set up create the token in the enterprise workspace (Editor+) → env → deploy
`status` answers 200 configured; the builder UI appears
`status` answers 200 configured; the app editor appears

build browser → /api/base44/platform {action} → ownership check → Bearer <token> → Base44
every app lands in the one account, in your workspace
Expand Down
6 changes: 3 additions & 3 deletions docs/base44-platform-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ stick.

### `GET /api/apps/{appId}/chat/full-conversation?limit=100&skip=0`

The full builder transcript, typically polled every few seconds while a build runs. Every tool
The full AI chat transcript, typically polled every few seconds while a build runs. Every tool
call's arguments and results come back with it — file contents included — so the body grows all
build long. Watch this one: a late-build read is much heavier than an early one, so if you see
timeouts here, raise the limit rather than treating it as an upstream fault.
Expand All @@ -146,7 +146,7 @@ build finished — poll the app and the conversation after.
}
```

When a builder turn pauses on a `requires_user_input` tool call: `rejected` records the call as
When a build turn pauses on a `requires_user_input` tool call: `rejected` records the call as
stopped and the tool never runs; `approved` runs it with `extra_user_input` injected as its
`user_input` argument.

Expand Down Expand Up @@ -191,7 +191,7 @@ either side of the call. Re-check that if anything ever sends more than `name`.

| Missing env var (thrown before any call) | Deployment isn't configured | `501 bridge_misconfigured` — **not** 400 and **not** 502. Echoing env var names into a response body is also worth avoiding |

Client-side, `bridge_misconfigured` is one UI state ("the builder is not available here") — see
Client-side, `bridge_misconfigured` is one UI state ("the app editor is not available here") — see
`isBuilderUnavailable()` in `src/lib/base44Platform.ts` — and `not_found` on an app-scoped action
means the caller does not own the app they named.

Expand Down
2 changes: 1 addition & 1 deletion docs/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ one is.
| `NEXTAUTH_URL` | `https://<your-host>` — the production domain, exactly. **Scope it to production**: it overrides the origin read off the request, so a preview holding it would sign users into production |
| `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` | the production OAuth client |
| `AUTH_REDIRECT_PROXY_URL` | `https://<your-host>/api/auth` — only needed if you turn previews on; set it on production *and* the preview contexts |
| `BASE44_ACCESS_TOKEN` | the integration account's personal access token, created in the enterprise workspace — without it the builder says it is not configured |
| `BASE44_ACCESS_TOKEN` | the integration account's personal access token, created in the enterprise workspace — without it the app editor says it is not configured |
| `BASE44_SVC_KEY` | the `b44k_` workspace key, for signing viewers into embedded apps and registering webhooks; optional |
| `BASE44_ORG_ID`, `BASE44_PLATFORM_HOST` | from your workspace |
| `NEXT_PUBLIC_BASE44_APP_HOST` | the host Base44 serves built apps from. The one public var — these URLs are built in the browser to be iframed. Unset, the UI shows no app previews |
Expand Down
20 changes: 10 additions & 10 deletions docs/p8-marketplace.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ The **Add widget** picker reads both halves. **My apps** reads only the built ha
merge stays in one place so the two surfaces cannot disagree about a URL or a
screenshot — only about which apps they have reason to show.

That split is what lets *Publish* and *Edit in builder* sit unconditionally on a My apps
That split is what lets *Publish* and *Edit app* sit unconditionally on a My apps
card: everything on that page is the user's own code, so neither control has a case to
hide for.

Expand Down Expand Up @@ -111,12 +111,12 @@ Three ways in, and the third is the one that matters:
* **My apps → Publish** on any app you built. Labelled, not a bare storefront glyph:
an icon does not say "offer this to other people", and nobody hovers a control whose
meaning they have not already guessed.
* **From the chat.** Opening the builder from the market passes `origin: "market"`, and
* **From the chat.** Opening the app editor from the market passes `origin: "market"`, and
the finished app's ready card offers **Add to the market** as its primary action —
deploy, then the listing dialog.

That last one follows a pattern this codebase already had: `origin: "home-widget"`
makes the ready card offer *Add to my widgets*. **Where the builder was opened from is
makes the ready card offer *Add to my widgets*. **Where the app editor was opened from is
already a statement of intent**, so it picks the destination instead of asking. Someone
who clicked "Build an app" inside the market is building it *for* the market.

Expand All @@ -137,15 +137,15 @@ two correct answers, which is the same as having none.

The line that actually exists in the data is **authorship**:

* **My apps** — code you wrote. Open it, rename it, edit it in the builder, publish it.
* **My apps** — code you wrote. Open it, rename it, edit it in the app editor, publish it.
Every affordance on the page assumes you own the thing.
* **Market** — everything that crosses accounts. Browse and preview what other people
built, install it, and manage your own listings. Installed apps live here, under
*Installed*, because installing and uninstalling are one decision and belong in one
place: this is the page that granted somebody else's code access to your data, and it
is the page that takes it back.

The route matches the name: `/apps`, which `?app=` deep links from the builder and the
The route matches the name: `/apps`, which `?app=` deep links from the app editor and the
widgets point at. `/MyTools` still redirects there — those are URLs people keep.

The nav says **My apps**, not *Apps*. Beside *Market* the possessive is doing the work —
Expand All @@ -160,14 +160,14 @@ picker that opens on thirteen of your own apps buries the two you just installed

## The market refreshes itself

The builder is a side panel over whatever page you were on, so you can publish an app
The app editor is a side panel over whatever page you were on, so you can publish an app
from the chat while looking straight at the market grid behind it — and the grid has no
reason to refetch. The only fix was a manual reload, which reads as "it didn't work".

`src/lib/marketEvents.ts` is a `market-changed` window event, the same shape as the
`widgets-updated` and `app-rebuilt` events this codebase already uses to coordinate
across surfaces. It is announced from **`PublishDialog`** rather than from each caller:
the builder, the My apps page and the market page all publish through that one component,
the app editor, the My apps page and the market page all publish through that one component,
so a new caller gets the refresh for free instead of having to remember. Install and
uninstall announce it too.

Expand Down Expand Up @@ -201,7 +201,7 @@ Three glyphs were doing work no glyph can do:

Uninstall now lives on **Apps**, not in the market. A storefront is for getting things;
managing what you already have belongs next to the thing itself. That also makes the
two card kinds symmetrical: an app you built offers *Publish* and *Edit in builder*, an
two card kinds symmetrical: an app you built offers *Publish* and *Edit app*, an
app you installed offers *Uninstall*.

## Where you meet it
Expand All @@ -211,7 +211,7 @@ app you installed offers *Uninstall*.
| **Market** in the top nav | the catalogue: Browse / Installed / Published by me |
| **Apps** in the top nav | everything you can open, filtered by source |
| Home | an App market card with a live count, and *Build your own* |
| Add widget | links to the market and the builder |
| Add widget | links to the market and the app editor |
| Apps | a labelled *Publish* on each app you built |
| Market header | *Build an app* — the market is where you learn nobody built the thing you need |

Expand All @@ -227,7 +227,7 @@ app you installed offers *Uninstall*.

## What is left

**Publish from chat.** The builder should offer to publish when an app is ready — a
**Publish from chat.** The AI chat should offer to publish when an app is ready — a
tool widget with the assistant proposing the title and the one-liner. Wants P6's
`InvokeLLM`.

Expand Down
4 changes: 2 additions & 2 deletions examples/white-label-minimal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Every Base44 call is one function in [server/base44.ts](server/base44.ts).
| --- | --- |
| [The build turn](https://docs.base44.com/developers/white-label/the-build-turn) | `createApp`, `sendMessage`, `submitToolCallInput` |
| Watch it build, live | `openLiveSession`, and the socket in [components/useLiveChat.ts](components/useLiveChat.ts) |
| Answer the builder's questions | [components/ToolQuestion.tsx](components/ToolQuestion.tsx): a choice, values to enter, or an approval |
| Answer the AI chat's questions | [components/ToolQuestion.tsx](components/ToolQuestion.tsx): a choice, values to enter, or an approval |
| [Embed the app](https://docs.base44.com/developers/white-label/embed-the-app) | `getPreviewUrl` and [components/AppPreview.tsx](components/AppPreview.tsx) |
| [Custom instructions](https://docs.base44.com/developers/white-label/custom-instructions) | `customInstructions`, sent with every new app |

Expand Down Expand Up @@ -56,7 +56,7 @@ Open [Tiny Sunny](http://127.0.0.1:3001).

- **Sign-in and app ownership.** Everyone sees every app. In production, sign your users in, record
who created each app, and check it on every call: Base44 sees only the one account.
- **Publishing.** The builder is told it isn't available.
- **Publishing.** The AI chat is told it isn't available.
- **Closing the live session.** It expires after an hour.

## Deploy to Netlify
Expand Down
2 changes: 1 addition & 1 deletion src/components/AppBuilderSidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ export default function AppBuilderSidebar({
)}
{builderReady === false && embedded && (
<div className="flex-1 flex flex-col items-center justify-center gap-3 px-6 text-center">
<p className="text-sm text-muted-foreground">The app builder is not configured on this deployment.</p>
<p className="text-sm text-muted-foreground">The app editor is not configured on this deployment.</p>
<button
disabled={rechecking}
onClick={async () => {
Expand Down