Admin extensions

Add your own screens, record cards and actions to the admin UI. Put a file in your app's admin/ folder; what it exports decides where it shows up. There's no manifest to write and nothing to register.

admin/orders.tsx
import { signal } from "sluurp/reactive";
import { Button } from "sluurp/kit/button.js";

export const title = "Orders";
export const icon = "card";               // one of the admin's icons
export const collections = ["customers"]; // where Block, Action and Bulk show; all when left out

// A screen of its own, under Extensions in the sidebar.
export default ({ api }) => {
  const open = signal(0);
  api.send("/collections/orders/records?perPage=1&filter=status='open'")
    .then((page) => open.set(page.totalItems));
  return <p>{() => `${open()} open orders`}</p>;
};

// A card at the end of a record.
export const Block = ({ api, record }) => <p>Customer since {record.created.slice(0, 4)}</p>;

// An item in the record's More actions menu, opening a dialog.
export const Action = ({ record, toast, close }) => (
  <Button onClick={() => { toast("Sent", `Invoice to ${record.email}`); close(); }}>Send invoice</Button>
);

// A button on the selection bar when rows are ticked.
export const Bulk = ({ ids, toast, close }) => {
  toast("Tagged", `${ids.length} customers`);
  close();
  return "";
};

What goes where

ExportShows up as
defaulta screen, listed under Extensions in the sidebar and kept on reload
Blocka card at the end of a record, titled with title
Actionan item in a record’s More actions menu, opening a dialog
Bulka button on the selection bar, opening a dialog
titlethe name everywhere (defaults to the file name: report-cards → “Report cards”)
iconthe icon in the sidebar and menus
collectionswhich collections get Block, Action and Bulk

A file can export any combination of these.

What each one receives

Every export gets:

  • api, the admin’s client, signed in as the superuser: api.send(path, { method, body });
  • toast(title, body);
  • open(collection, id?) to go to a collection or one of its records;
  • refresh() to reload the open collection’s records after changing them.

Block and Action also get collection and record. Bulk gets collection and the ticked ids. Action and Bulk get close(), which shuts their dialog; after a bulk action the table is read again.

Good to know

  • Files are .tsx, .ts, .jsx or .js, compiled like the rest of your app, and they import the UI kit the way your pages do.
  • They run in the admin with no sandbox: extensions are your own code, trusted like a hook. They do run in the browser, though, so keep secrets out of them. Call a server function for anything secret.
  • If an extension fails to load or throws, it shows the error in its own place and the rest of the admin keeps working.