Skip to content
CanopyX

The canopyx/2 data protocol

The JSON documents a host sends to the CanopyX runtime, each beside the grid it produces, from the view definition to children pages and hosted endpoints.

The C# types write these documents and the browser runtime reads them. A host that is not .NET can write the same JSON by hand. Every top-level document carries "protocol": "canopyx/2" and a type; readers ignore fields they do not know, so a newer writer's document still loads. The examples below show the main document shapes beside the grids they produce.

A view and the tree it produces

CanopyTreeView.ToJson() writes a view: columns, behaviour, the data source and, for an inline source, the nodes. A cell is a bare value or an object with a value for sorting, a text to show and a tone the theme turns into a colour. Every string is inserted as text, never as HTML.

view.jsonJSON
{
  "protocol": "canopyx/2",
  "type": "view",
  "id": "files",
  "label": "Project files",
  "rowHeight": 28,
  "pageSize": 100,
  "selection": "single",
  "columns": [
    {"id": "name", "title": "Name", "width": 260, "kind": "text"},
    {"id": "size", "title": "Size", "width": 100, "kind": "number"}
  ],
  "search": {"minLength": 2, "maxResults": 20},
  "source": {"kind": "inline"},
  "roots": [
    {
      "id": "projects",
      "label": "Projects",
      "hasChildren": true,
      "childCount": 2,
      "expanded": true,
      "children": [
        {
          "id": "plan",
          "label": "Project plan.docx",
          "cells": {"size": {"value": 48128, "text": "47 KiB"}}
        },
        {
          "id": "report",
          "label": "Quarterly report.pdf",
          "cells": {"size": {"value": 2048, "text": "2 KiB", "tone": "warning"}},
          "badges": [{"text": "new", "tone": "info"}]
        }
      ]
    }
  ]
}
Outputtype: viewThe tree it producesOpen type: view in its own page
The output is this exact document mounted in a page. Collapse Projects or sort by Size: the runtime does it from this JSON alone.

A grid and the table it produces

Flat grids have their own grid document, without parents or a tree column. CanopyDataGridView.ToJson() writes it, and a host serves later windows of the same query as gridPage documents.

grid.jsonJSON
{
  "protocol": "canopyx/2",
  "type": "grid",
  "id": "sites",
  "label": "Sites",
  "rowHeight": 34,
  "noun": "sites",
  "pageSize": 100,
  "selection": "single",
  "search": true,
  "columns": [
    {
      "id": "site",
      "title": "Site",
      "width": 160,
      "autoWidth": true,
      "kind": "text",
      "sortable": true
    },
    {
      "id": "devices",
      "title": "Devices",
      "width": 160,
      "autoWidth": true,
      "kind": "number",
      "sortable": true
    }
  ],
  "source": {"kind": "inline", "navigation": "offset"},
  "rows": [
    {"id": "warsaw", "cells": {"site": "Warsaw", "devices": 412}},
    {"id": "london", "cells": {"site": "London", "devices": 318}},
    {"id": "berlin", "cells": {"site": "Berlin", "devices": 270}}
  ]
}
Outputtype: gridThe table it producesOpen type: grid in its own page
A grid document beside the table it produces.

A page of children

When a lazy node opens, the data source answers with a page of its children. offset says where the page starts, total how many children there are, and next is an opaque continuation the runtime passes back for the next page; null ends the list.

Children.csC#
using CanopyX;

var page = new CanopyPage("projects") { Offset = 0, Total = 3 };
page.Items.Add(new CanopyNode("plans", "Plans") { HasChildren = true, ChildCount = 12 });
page.Items.Add(new CanopyNode("budget", "Budget 2026.xlsx"));
page.Items.Add(new CanopyNode("report", "Quarterly report.pdf"));
string json = page.ToJson();
Writespage.json
{
  "protocol": "canopyx/2",
  "type": "page",
  "parent": "projects",
  "offset": 0,
  "total": 3,
  "items": [
    {"id": "plans", "label": "Plans", "hasChildren": true, "childCount": 12},
    {"id": "budget", "label": "Budget 2026.xlsx"},
    {"id": "report", "label": "Quarterly report.pdf"}
  ],
  "next": null
}
The C# that builds a children page, and the document it writes. Plans declares 12 children it has not sent yet, so it gets an expander and loads them on demand.

Hosted endpoints

A remote source names URL templates. The runtime fills the placeholders with URL-encoded values and sends GET requests; your application answers children with a page, search with a search document of id paths, and counts with a counts document.

view.jsonJSON
"source": {
  "kind": "remote",
  "children": "api/files/children?ref={ref}&cursor={cursor}",
  "search": "api/files/search?q={query}&limit={limit}",
  "counts": "api/files/counts?ids={ids}"
}
The source block of the hosted view from Delivery modes.

A response with a failing status, invalid JSON, or the wrong protocol or type shows an error row with a retry action in place of the children. Authorization, page-size limits and query-time limits belong to the host's endpoints.

Documents

Rendereddocs/data-protocol.md, DocumentsScript-free HTML
TypeWritten byRead when
viewCanopyTreeView.ToJson(), or CanopyTreeView.WriteJson(stream) for the same bytes without the whole stringThe page loads; it defines columns, behavior, the data source, and inline nodes
gridCanopyDataGridView.ToJson()A flat-grid definition supplies columns and inline rows or a host source
gridPageCanopyGridPage.ToJson()A host supplies a revisioned page of ordered matching rows
gridQueryCanopyGridQuery.ToJson(view)A host receives a typed query and requested result window
gridRangeQueryThe browser; CanopyGridRangeQuery.ToJson(view)A calendar or timeline asks a host for the records that overlap a period (Date ranges)
gridRangePageCanopyGridRangePage.ToJson(view)A host answers a range request with the overlapping records in start order
gridCommitThe editing partA grid asks its writer to save changed rows (Editing and commit)
gridCommitResultThe host's writerThe writer answers per row: saved, error or conflict
gridRecordQueryThe browser; CanopyGridRecordQuery.ToJson()A record's drawer opens and its details load on demand (Record details)
gridRecordCanopyGridRecord.ToJson(view)A host answers a record request
gridRecordPackCanopyRecordSidecar.WriteA record's drawer opens and its details are in a record pack beside the page
gridTransactionCanopyGridTransaction.ToJson(view)A host sends rows to add, update or remove to an open grid (Live data)
pageCanopyPage.ToJson(), bundle writerA node opens, or the user asks for more children
searchCanopySearchResult.ToJson()A remote search endpoint answers
countsCanopyCounts.ToJson()A live host refreshes counts and rollups without reloading rows
manifestBundle writerA sidecar view loads; it points at the first top-level page and describes the packs and the search index
packBundle writerA node opens whose children page is stored in a pack file next to the pages of other nodes
searchShardBundle writerA sidecar search needs the words that start with a prefix
searchEntriesBundle writerA sidecar search needs the nodes its matches refer to
Every document type, read from the protocol reference during the build and drawn by the CanopyX static renderer.