Apps
Workspace apps stored inside a workspace and rendered at /w/[handle]/apps/[appHandle] for signed-in members
Apps belong to one workspace.
Each app stores:
namehandle- optional
description code
App URL
Every app is published at:
/w/[handle]/apps/[appHandle]The page renders the saved JSX inside a sandboxed iframe for signed-in workspace members.
Code contract
For now, code must be a single JSX module that exports a default React component.
When editing an existing app, fetch the current saved app first and treat that code as the source of truth.
- Preserve the current design and structure unless the user explicitly asks for a redesign.
- Make focused improvements instead of replacing the whole UI.
- Reuse existing
@andibase/uicomponents and their default styles wherever possible. - Avoid custom CSS and Tailwind unless they are strictly needed for layout or functionality.
import { useState } from "react";
import { Button } from "@andibase/ui/button";
export default function App() {
const [count, setCount] = useState(0);
return (
<main style={{ minHeight: "100vh", padding: 24 }}>
<h1>Counter</h1>
<Button onClick={() => setCount((value) => value + 1)}>Count: {count}</Button>
</main>
);
}The renderer loads React from esm.sh, transpiles the JSX in-browser, and mounts the default export into the iframe root.
UI guidance
Use @andibase/ui first.
- Import from
@andibase/uior component subpaths such as@andibase/ui/button,@andibase/ui/input, and@andibase/ui/card. - Prefer the shared
shadcn/uiprimitives from@andibase/uiover custom-built elements. - Keep styling minimal and close to the existing app so updates feel like refinements, not replacements.
File uploads in custom apps
Use FileUpload from @andibase/ui/file-upload when your app needs to:
- upload a local file into the workspace file API
- create a new text or markdown file directly from the app
- save to either a user-entered workspace path or a fixed path chosen by the app
Inside embedded apps, the component already uses window.andibase.fetch(...), so no extra fetch wrapper is required.
Simplest usage
import { FileUpload } from "@andibase/ui/file-upload";
export default function App() {
return (
<main style={{ padding: 24 }}>
<FileUpload />
</main>
);
}Upload only to a fixed path
import { FileUpload } from "@andibase/ui/file-upload";
export default function App() {
return (
<main style={{ padding: 24 }}>
<FileUpload
mode="upload"
path="imports/customers.csv"
heading="Customer import"
description="Upload the latest customer CSV."
/>
</main>
);
}Create a new markdown file
import { FileUpload } from "@andibase/ui/file-upload";
export default function App() {
return (
<main style={{ padding: 24 }}>
<FileUpload
mode="new"
defaultPath="notes/today.md"
defaultNewFileContent={"# Notes\n\n"}
heading="Daily notes"
/>
</main>
);
}Handling the saved file
import { useState } from "react";
import { FileUpload } from "@andibase/ui/file-upload";
export default function App() {
const [savedPath, setSavedPath] = useState<string | null>(null);
return (
<main style={{ padding: 24 }}>
<FileUpload
onSuccess={(result) => {
setSavedPath(result.path);
console.log(result.file.id, result.file.path);
}}
/>
{savedPath ? <p>Saved to {savedPath}</p> : null}
</main>
);
}See UI Components for the full FileUpload component reference.
API endpoints
Apps are documented in the generated API reference:
GET /api/v1/apps: list apps for the current workspaceGET /api/v1/apps/:id: get one app by id with full stored data, includingcodePOST /api/v1/apps: create an app in the current workspacePATCH /api/v1/apps/:handle: update an app in the current workspaceDELETE /api/v1/apps/:handle: delete an app from the current workspace
Use the full request and response schemas here:
Auth model
/w/[handle]/apps/[appHandle]renders the app inside the workspace shell for signed-in members.- Signed-in non-members receive a 404.
- The iframe uses the normal first-party session cookie.
window.andibase.fetch(path, init?)sendscredentials: "include"and the app workspace handle automatically.- Workspace management endpoints use the resolved workspace.
- Session requests should send
x-workspace-handlewhen needed. - Manual API keys still need
apps.readorapps.write.
Host actions from iframe apps
The embedded runtime also exposes host-action helpers from @andibase/hooks.
These helpers let iframe apps ask the host workspace UI to open native drawers on top of the iframe, instead of re-implementing those flows inside the app.
import { openDataRecordForm, openInlineAgentChat } from "@andibase/hooks";
openDataRecordForm({
definitionHandle: "customer",
defaultValues: { firstName: "Ada" },
});
openDataRecordForm({
mode: "edit",
definitionHandle: "customer",
recordId: "rec_123",
defaultValues: { firstName: "Ada" },
});
openInlineAgentChat({
agentHandle: "support-assistant",
});Refreshing after host drawers close
If your app needs to refresh after the user returns from a host-managed drawer or chat, use useAndibaseRefreshOnReturn() from @andibase/hooks.
import { useEffect, useState } from "react";
import { useAndibaseRefreshOnReturn } from "@andibase/hooks";
export default function App() {
const [items, setItems] = useState([]);
async function loadData() {
const response = await window.andibase.fetch("/data-definitions/customer/query");
const payload = await response.json();
setItems(payload.items ?? []);
}
useEffect(() => {
loadData();
}, []);
useAndibaseRefreshOnReturn(() => {
loadData();
});
return <main>{items.length} customers</main>;
}Avoid unconditional window.addEventListener("focus", ...) refresh handlers in iframe apps. The iframe's first user click usually gives it focus, so a raw focus listener can look like a full app reload even when the user only opened local UI inside the app.
See Embedded Host Actions for the full API.