andibase

Apps

Workspace apps stored inside a workspace and rendered at /w/[handle]/apps/[appHandle] for signed-in members

Open Markdown

Apps belong to one workspace.

Each app stores:

  • name
  • handle
  • 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/ui components 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/ui or component subpaths such as @andibase/ui/button, @andibase/ui/input, and @andibase/ui/card.
  • Prefer the shared shadcn/ui primitives from @andibase/ui over 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 workspace
  • GET /api/v1/apps/:id: get one app by id with full stored data, including code
  • POST /api/v1/apps: create an app in the current workspace
  • PATCH /api/v1/apps/:handle: update an app in the current workspace
  • DELETE /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?) sends credentials: "include" and the app workspace handle automatically.
  • Workspace management endpoints use the resolved workspace.
  • Session requests should send x-workspace-handle when needed.
  • Manual API keys still need apps.read or apps.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.

On this page