Plugins

The plugins Dataembed ships, and building your own by asking — panels as a UI spec or a sandboxed frame.

Every feature ships as a plugin: field and view types, the sections of each page, actions, agent instructions, the data-source connection form. Settings → Plugins lists them: owners and admins turn plugins on and off and change their settings; everyone sees what each adds and its state: Ready, Off, or Needs configuration with what's missing.

What ships

Core plugins, always on:

PluginAdds
Field typesText, number, currency, date, select, reference and the other field types
Table and chart viewsThe table and chart views
Record pageFields, Related records and Activity on a record's page
HomeObjects, Get started and Recent activity on home
SidebarHome, Objects, SQL console and Settings in the sidebar
PostgreSQLThe form that connects a data source
Sales sampleThe sample database

The other bundled plugins are on by default; owners and admins can turn each off:

PluginAdds
Board viewsThe board view: cards in columns by a select field
CopilotThe copilot in the side panel
AI fieldsFill with AI: write a prompt once and the agent fills a field for every record, on the web if needed
Web searchFind records: the agent searches the web and adds what it finds to an object (Max results, 1–100, default 25)
Copilot instructionsWorkspace instructions (up to 4,000 characters) every agent reads: "Deals are in USD."

Copilot, AI fields and Web search need AI on the deployment; without it they show Needs configuration.

AI fields

Fill with AI is in the menu of a column that can be changed (the prompt is saved on the field, and only empty cells are filled unless you choose otherwise) and on selected records. It starts a job: the agent researches each record and writes the value, keeping its sources and reasoning in the record's activity, so each value can be checked and undone. The object's toolbar shows running jobs with their progress and Cancel. A job has its own budget: 5¢ a record, at least $2 and at most $50.

Build a plugin by asking

Ask the copilot, or your own agent, for a section: "on company pages, show their open deals and the total value". It builds a workspace plugin (plugins_create) from the manifest schema (plugins_manifest_schema) and tells you where it shows. Anyone can build one; it lists under Made in this workspace.

  • It's yours first: only you see its sections until an owner or admin chooses Save for everyone (the dialog lists any frame's calls first). Reset puts back everyone's version, or removes it if only you have it; Delete (owners and admins) removes it for everyone and can't be undone.
  • It changes by asking (plugins_update), and each change can be undone.
  • Its page lists problems, each where it is in the manifest ("Ask the copilot to fix them"). A manifest that can't work is refused with the same list.

Panels and slots

A workspace plugin is up to 10 panels, each a section in one slot: home.main, shell.sidebar, record.aside or record.tabs. A record page's panel can name the objects it shows on ("when": { "objects": ["companies"] }). default.order places it; without one it's only offered in Add section. Then it's placed like any section.

SlotSurfaceNameWhat it isShows
shell.sidebarshellSidebarSections of the workspace sidebar, top to bottom.Every shown section
shell.asideshellSide panelThe panel docked on the right of every page, opened from the page header (⌘J): the first shown section; hide it to have none.Its first shown section
home.mainhomeHomeSections of the workspace home page, top to bottom.Every shown section
record.asiderecordRecord sidebarSections in the record page's left column, under its title.Every shown section
record.tabsrecordRecord tabsThe record page's tabs, left to right; the first shown opens by default.Every shown section

Specs

A panel's body is a spec (prefer it) or a frame. A spec is JSON: data names what to read, root lays it out from the catalog's nodes:

NodeWhat it showsProps
stackIts children in a column, or a row that wrapschildren, direction?, gap?
gridIts children in equal columnschildren, columns
headingA titlelevel?, text
textA paragraphtext, tone?
badgeA short labeltext, variant?
statA number with its labelcurrency?, format?, label, value
recordListRecords by their titles, each opening its pagedata, empty?
recordTableRecords as rows, a field per columncolumns, data, empty?
linkA link to a page of the app, or a literal https onelabel, to
buttonA button that makes a writeaction, label, variant?
chartRecords grouped and measured, as a bar, line, area or pie chartdata, empty?, kind?
aggregateStatA measure of the records a filter matches (a count, a sum) as a number with its label, formatted as its fielddata, label, measure?
  • Bindings (data, up to 20): a read the agent tools offer, by its procedure (records.list, records.aggregate, objects.get) and its input without workspace. A node shows one with "data": "<key>", or a value of it with { "$data": "<key>", "path": "total.count" }.
  • On a record page, { "$context": "recordId" } and { "$context": "object" } stand for the record shown and its object's slug, anywhere in an input.
  • Buttons make one write that can be undone (records.update, views.update); one that may overwrite data asks the viewer first. Nothing irreversible, nothing admin, nothing that reaches outside the workspace.
  • Links go to a record, an object, or an https page in a new tab.
  • Limits: 64 KB, 200 nodes, 8 levels deep, 50 children per node, 12 columns per table. The whole manifest: 512 KB.

Everything runs as the viewer: a panel never shows what its viewer can't read.

{
  "id": "ws-company-deals",
  "name": "Company deals",
  "description": "A company's open deals and their value, on its page.",
  "icon": "handshake",
  "panels": [
    {
      "id": "deals",
      "title": "Deals",
      "icon": "handshake",
      "slot": "record.aside",
      "when": { "objects": ["companies"] },
      "default": { "order": 20 },
      "body": {
        "kind": "spec",
        "spec": {
          "data": {
            "deals": {
              "procedure": "records.list",
              "input": {
                "object": "deals",
                "filter": {
                  "conditions": [
                    {
                      "attributeId": "company",
                      "operator": "eq",
                      "value": { "$context": "recordId" }
                    }
                  ]
                },
                "limit": 5
              }
            },
            "value": {
              "procedure": "records.aggregate",
              "input": {
                "object": "deals",
                "filter": {
                  "conditions": [
                    {
                      "attributeId": "company",
                      "operator": "eq",
                      "value": { "$context": "recordId" }
                    }
                  ]
                },
                "measures": [{ "fn": "sum", "attributeId": "amount" }]
              }
            }
          },
          "root": {
            "type": "stack",
            "children": [
              { "type": "aggregateStat", "data": "value", "label": "Total value" },
              { "type": "recordList", "data": "deals", "empty": "No deals yet" }
            ]
          }
        }
      }
    }
  ]
}

Frames

When the catalog can't draw it, a panel can be a frame: up to 256 KB of HTML, scripts and styles inline, in a sandboxed iframe (allow-scripts only, no fetches, images only as data: URLs), styled with the app's theme. It declares up to 20 calls, under the same rules as a spec's bindings and buttons, and reaches the app only through window.dataembed:

  • call(procedure, input): runs a declared call as the viewer;
  • context, and on("context" | "invalidate", handler): the record shown, and when what it read may have changed;
  • navigate(...), openRecord(object, recordId), toast(message, tone).

A frame writes and navigates only right after the viewer clicks or types in it, and a write that may overwrite data asks them first. A frame that loads another page is stopped. Its HTML is still code you trust: nothing stops a frame from sending what its calls read away in a link it loads. Prefer a spec, and read a frame's calls before saving it for everyone.

On this page