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:
| Plugin | Adds |
|---|---|
| Field types | Text, number, currency, date, select, reference and the other field types |
| Table and chart views | The table and chart views |
| Record page | Fields, Related records and Activity on a record's page |
| Home | Objects, Get started and Recent activity on home |
| Sidebar | Home, Objects, SQL console and Settings in the sidebar |
| PostgreSQL | The form that connects a data source |
| Sales sample | The sample database |
The other bundled plugins are on by default; owners and admins can turn each off:
| Plugin | Adds |
|---|---|
| Board views | The board view: cards in columns by a select field |
| Copilot | The copilot in the side panel |
| AI fields | Fill with AI: write a prompt once and the agent fills a field for every record, on the web if needed |
| Web search | Find records: the agent searches the web and adds what it finds to an object (Max results, 1–100, default 25) |
| Copilot instructions | Workspace 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.
| Slot | Surface | Name | What it is | Shows |
|---|---|---|---|---|
shell.sidebar | shell | Sidebar | Sections of the workspace sidebar, top to bottom. | Every shown section |
shell.aside | shell | Side panel | The 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.main | home | Home | Sections of the workspace home page, top to bottom. | Every shown section |
record.aside | record | Record sidebar | Sections in the record page's left column, under its title. | Every shown section |
record.tabs | record | Record tabs | The 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:
| Node | What it shows | Props |
|---|---|---|
stack | Its children in a column, or a row that wraps | children, direction?, gap? |
grid | Its children in equal columns | children, columns |
heading | A title | level?, text |
text | A paragraph | text, tone? |
badge | A short label | text, variant? |
stat | A number with its label | currency?, format?, label, value |
recordList | Records by their titles, each opening its page | data, empty? |
recordTable | Records as rows, a field per column | columns, data, empty? |
link | A link to a page of the app, or a literal https one | label, to |
button | A button that makes a write | action, label, variant? |
chart | Records grouped and measured, as a bar, line, area or pie chart | data, empty?, kind? |
aggregateStat | A measure of the records a filter matches (a count, a sum) as a number with its label, formatted as its field | data, 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 withoutworkspace. 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
httpspage 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, andon("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.