Noon Docs · Getting Started · Concepts · Guides · Reference

Every read in Noon, from either the MCP client or a page render, goes through one tool: query. There is no per-collection read tool and no GraphQL-style resource endpoint. You send one JSON document naming a collection, the fields you want, and an optional filter, sort, and cursor.

The document

{
  "collection": "string",
  "select": ["field", "..."],
  "where": { "field": "value" },
  "orderBy": [{ "field": "field", "dir": "asc" }],
  "limit": 20,
  "cursor": "opaque string from a previous response"
}

collection and select are required. Everything else is optional. select is an array of field names, or validated JSON sub-paths for the fields that allow them (see below).

A worked example against the built-in pages collection, reading published pages back in creation order:

{
  "collection": "pages",
  "select": ["id", "slug", "title", "updatedAt"],
  "where": { "isPublished": true },
  "orderBy": [{ "field": "createdAt", "dir": "desc" }],
  "limit": 10
}

The response is { items, next_cursor? }. Each item carries exactly the fields you asked for, decoded to their normal JSON types (a timestamp field comes back as a number of epoch milliseconds). There are no counts, no metadata, and no extra columns riding along.

Built-in collections

These are the collections registered by Noon itself. None of them need a plugin installed.

CollectionWhat it holds
pagesPages: slug, title, layout, lifecycle columns
postsPosts: slug, title, lifecycle columns, plus frontmatter as a select-only sub-path
templatesRegion templates
content_typesYour content type definitions, including their delivery policy
content_entitiesEntities of your user-defined content types
content_entity_termsRaw taxonomy assignment rows (entity, term, taxonomy triples)
site_settingsThe site settings singleton
assetsThe media library
due_workDelivery/schedule state: scheduled publishes and webhook deliveries
audit_logThe audit trail. Readable by a root-scoped token only

Every plugin you install can register its own collections. Examples from Noon's own plugins: geo_rules needs the geo plugin, seo_jsonld needs the SEO plugin, vote_polls and vote_options need the vote plugin, products and orders need the shop plugin, theme_document needs the theme plugin, word_count_stats needs the word-count plugin.

Some collections are queryable by name but deliberately left off describe_schema's advertised list, because their rows carry secrets or personal data. The two you are most likely to want are connections, which returns connection metadata and never a secret, and form_submissions, which returns what visitors sent through a form. If you already know a collection's name, querying it still works and is still scope-checked; describe_schema just will not suggest it to you. For every other collection, describe_schema is the accurate, current list: it reflects exactly what is registered right now, including anything a plugin added.

The where grammar

where is an object mapping a field name to either a bare value (equality) or an operator object:

{
  "where": {
    "isPublished": true,
    "title": { "op": "contains", "value": "recipe" }
  }
}
FormMeaning
{"field": value}Shorthand for {"field": {"op": "eq", "value": value}}
{"field": {"op": "eq", "value": v}}Equal to v
{"field": {"op": "ne", "value": v}}Not equal to v
{"field": {"op": "in", "value": [v1, v2, ...]}}Equal to any value in the array
{"field": {"op": "gt", "value": v}}Greater than v
{"field": {"op": "gte", "value": v}}Greater than or equal to v
{"field": {"op": "lt", "value": v}}Less than v
{"field": {"op": "lte", "value": v}}Less than or equal to v
{"field": {"op": "contains", "value": v}}Substring match

in requires an array value. contains requires a string value; using it on a number or boolean field is refused. Only fields a collection actually declares as filterable accept where at all: an unregistered field name is refused rather than silently ignored, and describe_schema tells you which fields on a given collection are filterable.

Sorting, limits, and pagination

orderBy is an array of {field, dir} terms, evaluated in order. Omit it and a collection uses its own default order. Only fields the collection has marked sortable (generally, indexed columns) can be named.

limit bounds how many items come back. Ask for fewer and you get fewer; ask for more than the cap and the request is refused, not silently clamped.

Pagination is cursor-based, not offset-based. When a result is truncated, the response carries next_cursor. Pass that string back as cursor on your next call, with the same collection, where, and orderBy, to continue where you left off:

{
  "collection": "pages",
  "select": ["id", "slug", "title"],
  "where": { "isPublished": true },
  "orderBy": [{ "field": "createdAt", "dir": "desc" }],
  "cursor": "eyJjIjoicGFnZXMiLCJvIjpbXSwiayI6WzE3MjM..."
}

A cursor is bound to the exact collection and the exact canonical order it was issued for. Reusing one against a different collection, or against the same collection with a different orderBy, is refused rather than silently reinterpreted.

Nested JSON sub-paths

Some collections expose a JSON column for select beyond its declared top-level fields, at any validated sub-path beneath it. For example, on pages you can select layout.blocks.0.type even though only a couple of exact paths under layout are registered as filterable. A sub-path is dot-separated, each segment is either an object key or a numeric array index, and the whole path is capped at 6 segments deep. where/orderBy, unlike select, only accept the exact paths a collection has explicitly registered as filterable, never an arbitrary sub-path.

Limits

LimitValue
Max fields in select32
Max conditions in where16
Max elements in an in array64
Max limit100
Default limit when omitted20
Max terms in orderBy4
Max depth of a select sub-path6 segments
Max length of a field name / path string256 characters
Max length of a scalar value string (and of each in array element)1024 characters
Max length of a cursor string4096 characters
Max size of the whole query document you send65,536 bytes

Discovering what is actually queryable

This page describes the query engine's grammar, which is stable. The list of collections, and each collection's fields and operators, is not: it grows as you define content types and install plugins. Call describe_schema to get the live, current answer rather than relying on a list written down here.