Projections

The two optional files that project your entities into something else — search.json for search indexes and analytics.json for analysable datasets.

schema.json declares the tables your app owns. Two optional files project those tables into somewhere else: a search index, and an analytics dataset. Neither changes your data — both are declarations about views over it.

text
my-app/
├── schema.json        the tables you own
├── search.json        optional — search index declarations
└── analytics.json     optional — analytics dataset declarations

Both are picked up by the normal deploy, alongside the manifest and schema. Both are validated against a published JSON Schema — https://schemas.revenexx.com/search.schema.json and https://schemas.revenexx.com/analytics.schema.json. Point your editor at them with $schema and you get completion and validation while you type. A search.json that fails its schema also fails the deployment of the app version; see Validation.

analytics.json — analysable datasets

analytics.json declares one or more flat views over the entities in your schema.json. Deploy the app and each view is compiled and registered as a dataset for the tenant, appearing in that tenant's Analytics Studio — in the dataset picker, in cohorts, and in the chart builder.

You declare what is analysable and where each column comes from. You write no SQL, and you configure no isolation: every view is scoped to the tenant that queries it, and an app cannot opt out of that.

analytics.json
{
  "$schema": "https://schemas.revenexx.com/analytics.schema.json",
  "version": "1",
  "views": [
    {
      "name": "device_serials",
      "source": "device_serials",
      "key": "id",
      "title": "Device serials",
      "roles": {
        "time": "created_at",
        "measures": ["sold_price"],
        "dimensions": ["status"]
      },
      "columns": [
        { "name": "serial_id", "from": "column:id", "type": "uuid", "optional": false },
        { "name": "status", "from": "column:status", "type": "text", "optional": false },
        { "name": "sold_price", "from": "column:sold_price", "type": "numeric" },
        { "name": "created_at", "from": "column:created_at", "type": "timestamptz", "optional": false }
      ]
    }
  ]
}

The emitted dataset name is analytics.<vendor>__<app>__<view>, so the view above in the acme/serials app registers as analytics.acme__serials__device_serials.

The full field reference lives with Analytics Studio, and that is deliberately the only copy. Analytics Studio documents analytics.json end to end — every view field, the whole from locator vocabulary (column:, attribute:, relation:, reference:, computed:), the semantic roles, and what happens at deploy time. Read it there rather than here; a second copy of a field table is a second copy to get wrong.

That page is also the honest answer to the wider question. There is no public analytics API to call — analytics.json is the one part of Analytics Studio with a public, versioned contract, and shipping one is how your app's data becomes analysable at all.

search.json — search index declarations

search.json declares which of your entities are indexed for search, and what of each row goes into the index. Like analytics.json, it is a projection: the platform builds and maintains the index from your rows, and you write no indexing code and run no crawler.

The authoritative contract is the JSON Schema at https://schemas.revenexx.com/search.schema.json. Point your editor at it and let it guide you. Every key, the from locators, locale fields and scope are explained on the search.json reference, and how a storefront queries the result on Search collections.

Two things worth knowing before you reach for it:

  • It is not the same mechanism as lookup. lookup is a narrow, cross-app id-to-label resolver for pickers in another app's admin UI. search.json is a full-text index over your own entity.
  • It is not the same mechanism as a list filter. A text filter on a Cockpit list view, or an ilike in the typed client, is a substring match against a column. A search index is a ranked query over analysed text, and it is worth the extra file only when that difference matters to the operator.

Which one you want

You wantDeclare
An operator to find a row by typing part of a wordsearch.json
A merchant to chart, segment or trend your dataanalytics.json
Another app's form to resolve your id to a namelookup in schema.json
A partner integration to filter a list by columnA capability with declared parameters

Next steps

Was this page helpful?