← Plugin catalog
Productivity

Noloco

Noloco v1.0.0

Build apps and manage data

Full description ↓

View on ChatGPT ↗

Save this plugin to Following and return to review observed changes. Follow up to 10 plugins free.

Description

From the marketplace listing

Noloco lets teams build business apps on top of their data. This plugin connects ChatGPT and Codex to the Noloco apps you choose, so you can explore their data, read and update records, and, if you're a builder on an app, build and change the app itself. No copy-pasting or manual exports. What it can do: - Discover your data: list apps, tables and full field details, including formulas, lookups, rollups and relationships. - Read records: list, filter, sort and page through records, or fetch one record with its related records. - Write records: create, update and delete records, each with its own tool. - Build apps: create tables and fields, add pages with the right view for the data (table, board, calendar, timeline, map and more), set up forms, record pages and action buttons, create workflows, manage roles and permissions, and publish your changes. - Built-in guides: for common jobs, such as building an app from your data, adding a field or setting up filters, the plugin follows step-by-step guides based on Noloco's best practices. Who it's for: Noloco customers who are data admins on an app. Building also needs the builder role on that app. Limits and safety: - You sign in with your Noloco account and choose which apps to share, and whether each gets read-only or read and write access. - Only apps where you're a data admin are available. Within those, the plugin reads and writes all data, ignoring the app's row-level permissions. - On a published app, changes to pages, workflows and most settings stay as drafts until you publish them. Data, tables, fields, roles and permissions change at once. - Deleted records can't be restored, so the plugin is set up to confirm with you before deleting records or publishing.

Language: English · Automatically detected from descriptions.

Competitor profile

From the developer’s listing and package; capabilities are not independently tested. Package observed Nov 2, 2026.

Declared capabilities

  • Read
  • Write

Declared limitations

  • Billing is out of scope: the plugin has no billing tools and must not try to change plans or payments.
  • Bulk deletes can't be undone, so the plugin must not delete without explicit confirmation.
  • The plugin only reaches the apps the user shared when connecting.

Review scenarios; not an exhaustive list.

Cost & access

Noloco doesn't sell products or take payments through this plugin.

Terms may have changed since this observation.

Starter prompt

What tables are in my Noloco app, and what does each one hold?

View package source →

Keywords

Files & skills

File archives

Plugin package10 files · 74.8 KBBrowse files →
Skill instructions
action-buttons11.5 KB

View saved version →

---
name: action-buttons
description: Adding, editing or deleting record-view action buttons (delete, copy, navigate, run workflow, show message, add comment, create/update record) — per-action-type value examples.
---

# Action buttons on record views

A record view has two button rows, selected with the `location` parameter:
- `"record"` (default): the record buttons, shown on the record page (and on collection rows when `display.collection` is true). Any action type.
- `"navigation"`: the collection's top row, above the list. Only NAVIGATE and LOGIN_PUBLIC_USER can be authored there — no `display`, and record-dependent actions (DELETE, UPDATE, CREATE, ADD_COMMENT, ON_DEMAND) are rejected.

Manage both with:

- `upsert_action_button`: set `location`, then omit `actionButtonId` to CREATE a button or pass an id to UPDATE it. On update, omitted fields keep their value, and `actions` is the complete final list (see "Updating the actions of an existing button" below) — read the button, edit its `actions`, and send the whole list back.
- `delete_action_button`: removes a button by id and can't be undone, so only remove a button the user asked to delete; pass the same `location`.
- Get current buttons and their ids from `get_page` with `include: ["actionButtons"]` — it returns `actionButtons` (the record row) and `navigationButtons` (the top row).
- `display` controls where a record button shows: `{ "collection": true }` also shows it on collection rows (new buttons default to the record page only).
- Buttons INSIDE page sections (TITLE, DETAILS, NOTICE, ACTION_BUTTONS, COLLECTION) are SEPARATE storage — they belong to the sections editor (delegate to `sections`). A record view may already have an ACTION_BUTTONS section, which the sections editor can edit but cannot create; check `get_page` with `include: ["sections"]` before deciding where a button should go.

Action buttons run one or more actions when clicked. Values are written in the authoring format below — field, workflow and page references are validated and expanded on save, and every button/action gets a stable server id.

## Where action buttons live

1. **Navigation buttons** — a collection's top row (above the list). Only navigation-style actions work here: NAVIGATE and LOGIN_PUBLIC_USER are authorable.
2. **Record buttons** — the record object's button row: shown next to each record on the collection page and at the top of the record view (`display` picks where).
3. **Section buttons** — TITLE, DETAILS, NOTICE, ACTION_BUTTONS and COLLECTION sections each carry their own buttons.

These are separate storage locations — a page can have buttons in several of them at once, so read the current state before choosing where to add one.

## Button envelope

```json
{
  "buttonText": "Send invoice",
  "execution": "ONE_CLICK",
  "appearance": "primary",
  "icon": "Send",
  "notification": { "enabled": true, "text": "Invoice sent" },
  "actions": [{ "type": "ON_DEMAND", "workflow": "Send Invoice" }]
}
```

- `buttonText` is PLAIN TEXT only — never a segment array or a field reference.
- The other text values (`title`, `description`, `tooltip`, `notification.text`, message/body texts) accept a plain string or a segment array mixing static text and field references: `[{ "text": "Owner: " }, { "data": { "path": "owner.name" } }]`.
- `execution`: `MODAL` opens a confirmation dialog (`title`/`description`, and the form for CREATE/UPDATE); `ONE_CLICK` runs immediately. Defaults to MODAL when any action confirms or collects input (CREATE, UPDATE, DELETE, ADD_COMMENT, SHOW_MESSAGE), else ONE_CLICK.
- `visibilityRules` gate who sees the button, in the same shape as section visibility rules: `roleRule` (`WITH_ROLES` / `WITHOUT_ROLES`) with `roles` (role names or reference ids — stored and read back as reference ids), `type` (`INTERNAL` / `CLIENT`), `auth` (`LOGGED_IN` / `NOT_LOGGED_IN`), `deviceRule` (`DESKTOP` / `MOBILE`), `publicAppVisibility` (`PRIVATE` / `VISITOR` / `ALL`), `spaces` (space ids) and `customRules` — OR-of-AND condition groups whose `field` is a field on the button's table (by name, or by path through relationships such as `owner.email`) or a logged-in-user reference such as `{ "source": "LOGGED_IN_USER", "value": "role.name" }`. On an update the whole object is replaced, so send every rule to keep; `null` clears them. Unknown keys are kept and reported as a warning.
- Builder-only action types (VIEW, IFRAME, SCAN_BARCODE) cannot be authored; existing ones pass through edits untouched.

## Per-type examples

**DELETE** — deletes the record in scope (confirm via MODAL):
```json
{ "type": "DELETE" }
```

**COPY** — copies a field's value to the clipboard:
```json
{ "type": "COPY", "fieldPath": "email" }
```

**NAVIGATE** — link to a page, the page's new-record form, a URL, an email or a phone number:
```json
{ "type": "NAVIGATE", "navigate": { "pageIdentifier": "Projects" } }
{ "type": "NAVIGATE", "navigate": { "pageIdentifier": "Projects", "pageTarget": "newForm" } }
{ "type": "NAVIGATE", "navigate": { "url": "https://docs.example.com", "newTab": true } }
{ "type": "NAVIGATE", "navigate": { "email": [{ "data": { "path": "email" } }], "subject": "Hello" } }
```
Links to a specific record page are not supported — link to the view page instead.

**ON_DEMAND** — runs an ON_DEMAND workflow on the record; pass the workflow's name (it must belong to the record's table):
```json
{ "type": "ON_DEMAND", "workflow": "Send Invoice" }
```

**SHOW_MESSAGE** — displays a message in the dialog (MODAL execution only):
```json
{ "type": "SHOW_MESSAGE", "showMessage": { "message": "Contact support to change this", "buttonText": "Got it" } }
```

**ADD_COMMENT** — posts a comment (or internal note); `body` presets the text and is required for ONE_CLICK. `field` (a relationship) posts the comment on that linked record instead of the current one; the body still references the current record either way:
```json
{ "type": "ADD_COMMENT", "addComment": { "note": true, "body": "Escalated" } }
{ "type": "ADD_COMMENT", "field": "employee", "addComment": { "body": "Reviewed" } }
```

**LOGIN_PUBLIC_USER** — opens the login dialog for public (not-signed-in) users. Zero config; must be the button's FIRST action, runs as MODAL, and the app needs public access enabled:
```json
{ "type": "LOGIN_PUBLIC_USER" }
```

**CREATE / UPDATE** — creates a related record or updates the record in scope. `field` names a relationship on the record's table (the action then targets that related table; omit it to act on the record's own table). `formFields` reference the TARGET table's fields:
```json
{ "type": "CREATE", "field": "tasksCollection", "formFields": ["taskName", { "name": "dueDate", "required": true }], "redirect": false }
{ "type": "UPDATE", "formFields": [{ "name": "status", "hidden": true, "value": "Approved" }] }
```
A form field takes the same config as a field of the view's new-record form: `label`, `hidden`, `value`, `defaultValue`, `required`, `requiredConditions`, `conditions`, `placeholder`, `helpText`, `readOnly`, `width`, `optionsConfig` and `orderBy`.
A `value` or `defaultValue` can copy another field of the same form instead of a static value — the source field must also be in `formFields` (it can be hidden), must not take its own value from the form, and a relationship is read one hop deep:
```json
{ "type": "UPDATE", "formFields": [{ "name": "email", "hidden": true }, { "name": "contactEmail", "hidden": true, "value": { "source": "FORM_VALUE", "value": "email" } }] }
```
With ONE_CLICK execution there is no form, so every `formFields` entry needs `"hidden": true` and a preset `"value"`, and adding a key that only matters on a visible form (`required`, `requiredConditions`, `conditions`, `defaultValue`, `placeholder`, `helpText`, `width`, `readOnly`, `optionsConfig`, `orderBy`) is rejected. Such keys already stored on a field (set in the builder) are kept when you send them back.

**Updating the actions of an existing button** — `actions` is the button's COMPLETE final list, matched to the stored actions by `id`:
- Start from the button as read (every action carries its `id`) and edit that; never send only the action you are changing.
- An action whose `id` matches a stored action of the same `type` is merged onto it (changing the `type` replaces the action instead): keys you omit keep their stored value (`formFields`, `redirect`, `workflow`, …) and `null` clears a key. Send nested objects (`navigate`, `copy`, `showMessage`, `addComment`) whole.
- `formFields` merge the same way, matched by field `name`: keys you omit on a field are kept and `null` clears one. Form sections and custom components sent back by name are kept exactly as stored.
- **An action or form field missing from the array you send is REMOVED.**
- An action without an `id` is added as a new action. An `id` that is not on the button is rejected.
- Builder-made actions (VIEW, IFRAME, SCAN_BARCODE) are sent back as read to keep them; they cannot be created or changed here.

## Button keys
- `buttonText` — Button label — plain text only. Required when creating.
- `execution` — MODAL shows a confirmation dialog (with the form for CREATE/UPDATE); ONE_CLICK runs immediately. Defaults by action type.
- `appearance`
- `icon` — Tabler icon name (e.g. "Copy", "Send").
- `tooltip`
- `title` — MODAL execution: dialog heading.
- `description` — MODAL execution: dialog body text.
- `notification` — Success toast shown after the action runs.
- `display` — Where a record button shows: on collection rows and/or the record page.
- `visibilityRules` — Who sees the button: `roleRule` + `roles` (names or reference ids), `type` (INTERNAL/CLIENT), `deviceRule`, and `customRules` (OR of AND groups whose `field` is a field name, a path through relationships like "owner.email", or a logged-in-user reference { "source": "LOGGED_IN_USER", "value": "role.name" }). Read back as stored, roles as reference ids. On an update the whole object is replaced, so send every rule to keep; null clears them. Unknown keys are kept and reported as a warning.
- `actions` — The steps the button runs. Required when creating. On an update it is the COMPLETE final list, matched to the stored actions by `id` — an action missing from it is removed (omit `actions` to keep them all).

## Action keys by type
- `DELETE`
  - `field`
- `COPY`
  - `fieldPath` — Shorthand: name (or dot-path) of the field whose value is copied to the clipboard.
  - `copy` — Full form: the copied text as a segment array.
- `NAVIGATE`
  - `navigate` — Required when adding the action; omit on a matched action to keep it.
- `ON_DEMAND`
  - `field`
  - `workflow` — The ON_DEMAND workflow to run: its name or referenceId. Must belong to the action's table. Required when adding the action; omit on a matched action to keep it.
- `SHOW_MESSAGE`
  - `showMessage` — Required when adding the action; omit on a matched action to keep it.
- `ADD_COMMENT`
  - `field` — Relationship field naming the linked record the comment is posted on. Omit to comment on the current record. (The body text still references the current record either way.)
  - `addComment`
- `CREATE`
  - `field` — Relationship field naming the table the new record is created in (linked back to the current record). Omit to create in the current table.
  - `formFields` — Form fields shown in the modal (or preset when ONE_CLICK, where every entry needs hidden + value).
  - `redirect` — Navigate to the new record after creating it.
- `UPDATE`
  - `field`
  - `formFields` — Form fields shown in the modal (or preset when ONE_CLICK, where every entry needs hidden + value).
- `LOGIN_PUBLIC_USER`
add-field5.3 KB

View saved version →

---
name: add-field
description: 'Adding a field to a table and making it usable: on every view''s list and form, on the main record page, and in the permission rules that would hide it. Load whenever the user asks for a new field or column.'
---

# Adding a field and showing it

The job: add a field to a table and leave it usable: in the list, on the new-record form, and on the record page where it is read and edited. A new field appears on no page by itself. Each view keeps its own field lists for its list, its form and its record page, so every place the field belongs is changed here. Load `build-views` too when the field should drive a layout (a board grouped by it, a calendar on it).

## 1. Read before you write

- `list_tables`: a table with a `source` is synced from an external source (Airtable, Google Sheets, a database) and refuses new fields. Tell the user to add the field in that source; once it syncs in, steps 3 to 7 show it.
- `get_table_schema`: the table's fields. When the field already exists, show that one instead of creating a second.
- `list_pages` with `nameFilters: ["<tableName>"]`: the table's views are the pages whose `dataType` is the table. From here on pass a view's `id` as `pageIdentifier`: a table often has several views, and its name picks only one.
- `list_version_history`: whether the app has been published before, which decides the last step.

## 2. Create the field

`upsert_field` with its type and format. Its name is derived from `display` in camelCase: use the `name` the result returns, because the view tools accept only that name. Some kinds change the steps below:
- Calculated fields (a formula, a lookup, a rollup or an AI-generated value) hold a value nobody types in: they go on the list and the record page, never on the form.
- A link to another table also adds a field to that table, listing the records that point back. Show it on that table's record page, as a list of related records, only when the user asks.
- An image field can be the card picture on a layout that shows cards (step 4). A single-option field can group a board: that is a view setting, see `build-views`.

## 3. Decide where it goes

- The views the user named. When they named none: the list and the form of every view of the table, and the record page of the table's main view only, the one that comes first in the navigation. A record page is changed by a sub-agent that takes minutes and spends the team's AI credits per page, so leave the other record pages alone unless the user asks, and say which ones you left.
- Skip views without a field list: CHARTS, PIVOT_TABLE and SINGLE_RECORD.

## 4. The list and the form

Work through the views one after another, never two calls on the same view at once: each call rewrites that view's whole field list, so parallel calls overwrite each other.
- `configure_view_fields` with `{ fieldName, enabled: true }` adds the field at the end of the list. Pass `fieldOrder`, naming every field still shown, only to put it somewhere else. On a layout that shows cards, an image field becomes the card picture with `heroImage: true`, placed first.
- `configure_form_fields` the same way for the new-record form. Mark it `required` only when the user said so. Skip calculated fields.

## 5. The record page

The record page is where users read a record and, after pressing Edit, change it. There is no separate edit form, so a field missing from the record page cannot be changed on existing records except inline in a list column.
- One `sections` call with the main view's `id` as `page` and a task like "Add the <display> field to the main Details section, keeping every field already there". Never run two `sections` calls at the same time.
- `get_page` cannot show which fields a section holds: rely on the summary `sections` returns.

## 6. Permissions

When a table has permissions on, each rule lists the fields its roles may read, create and update. A new field is in none of them, so app users with those roles do not see it, while admins do, the builder preview included.
- `list_permissions` for the table. With permissions off (`permissionsEnabled: false`), every app user sees the field and there is nothing to do. With permissions on but no rules, the table shows app users nothing at all, so there is nothing to grant: say so in the hand-over.
- When a rule lists the table's other fields but not the new one, add the field to it with `upsert_permission`: the rule's `ruleId` and `fields: { "<name>": { ... } }` with the access the rule gives its other fields (`{ read: true }` where they are read-only, `{ read: true, create: true, update: true }` where its roles create and update records), changing nothing else in the rule. Leave a rule out when the request says its roles should not see the field.
- Say in the hand-over which rules now include the field and with what access, so the user can narrow it.

## 7. Publish and hand over

- The field itself is live at once. List, form and record-page changes are saved to the draft: when the app has been published before, they reach app users only after `publish_project`, which also releases every other pending change in the project, so ask before running it. A never-published app already serves them.
- Hand over in product words: the field, the pages it now shows on, the record pages you left alone, any permission rules you changed, and whether publishing is still needed.
build-views4.09 KB

View saved version →

---
name: build-views
description: Creating or restyling collection view pages — why TABLE is almost never the answer, how to choose the right layout for the data (board/calendar/timeline/map/…), the companion settings each layout needs, and how to shape grouping, click actions, coloring, filters and naming.
---

# Building collection views people actually want to use

Almost never leave a view on TABLE. A created view starts as TABLE only as scaffolding — treat that as unstyled, not as a choice. Read the table's fields first: nearly every table offers a hook for something better (a status field → BOARD, dates → CALENDAR or TIMELINE, locations → MAP, a done-flag → CHECKLIST, images → CARDS). Reach for TABLE/TABLE_FULL only when the data is genuinely dense reference material and users' main job is scanning and sorting many columns. Pick the layout, then configure its companion settings in the SAME upsert_view call.

## Choosing the layout (`layout.layout`)

- BOARD — records that move through stages: a SINGLE_OPTION status/pipeline field exists and users drag records between columns (deals, tasks, applicants). Group by that field (`grouping.groups`).
- CALENDAR — records that happen on a date (events, bookings, deadlines). Needs `setup.dateStartField` (+ `dateEndField` when there's a range).
- TIMELINE / GANTT — records that span date ranges and overlap (projects, campaigns). Needs `setup.dateStartField` (+ `dateEndField` for the span); GANTT when dependencies or milestones matter (`ganttDependencies`, `milestones`).
- MAP — records with location data (`setup.map.latitudeField`/`longitudeField`).
- CHECKLIST — actionable to-dos with a BOOLEAN done field (`setup.checklistField`).
- CARDS / COLUMNS — visual browsing, few key fields, images matter (galleries, people directories, listings). Pair with a cover image via configure_view_fields.
- ROWS — tabular reading with more room per record than TABLE (long text, several secondary fields).
- TABLE / TABLE_FULL — dense reference data users scan and sort; TABLE_FULL for a spreadsheet feel.
- CHARTS — the question is aggregate ("how many/much per …"), not per-record (`charts.charts`).
- PIVOT_TABLE — two-dimensional aggregate comparisons (`pivotTable.rowGrouping`/`columnGrouping`/`values`).
- SPLIT — a list beside its record detail, for fast triage through records one by one.
- SINGLE_RECORD — the view should surface exactly one record, not a list.
Premium (plan-gated, the tool tells you): GANTT, TIMELINE, PIVOT_TABLE.

The field-driven layouts need a field that ALREADY exists — none of it is created for you, and a view built without it renders empty. No date field → not CALENDAR/TIMELINE/GANTT; no latitude/longitude pair → not MAP; no BOOLEAN → not CHECKLIST; no SINGLE_OPTION → not BOARD. Pick the best layout the table's real fields can drive, and add the field first if the user actually wants that layout.

## Shaping the view (same call or immediate follow-up)

- Grouping: group BOARD by the pipeline field; on TABLE/ROWS group by a meaningful category when the table has one (`grouping`).
- Click action: side panel (`clickAction.eventLink: openPanel`) keeps users in flow on list-heavy views; full page (openPage) when the record page is rich.
- Record coloring by the status field makes state scannable (`recordColoring`).
- App-user filters and search: enable `filterFields.search` on big tables; add quick filters for the fields users slice by (status, owner, date).
- Naming: the page title is the table's plural display name or a natural variation; `buttons.newButtonText` names the record ("Add deal"), `setup.recordTitle` is the record's name-like field.
- Sort by the field users expect (due date ascending, created descending) via `sort`.

## The record page

Every view carries a nested record page. After the view is shaped, delegate its layout to sections with the business context (what this team does, which fields matter day-to-day) — the sections editor has its own record-page design guidance. Set the fields the collection SHOWS via configure_view_fields — few, high-signal columns beat echoing the whole schema.
filters5.97 KB

View saved version →

---
name: filters
description: 'Scoping what a collection view loads: hard-coded custom filters (the condition-group format, per-field-type operators, dynamic values) and the relative filter that follows a relationship from the signed-in user ("only my records"). Load before setting or changing either, and to pick between them.'
---

# List filters (the relative filter + custom filters)

A list has two always-on filters, both invisible to app users (unlike filter fields):
- The **relative filter** answers WHOSE records: the signed-in user's, or — for a list section on a record view — the page's record's. It is a path from that parent record to a multi-relationship field holding the list's records, where every hop before the last is a single relationship. The last field is either a reverse multi relationship — the `…Collection` fields ("tasksCollection", "team.projectsCollection") — or a forward multi relationship ("company.projects"). The list becomes the parent's related collection, so a record created or imported from it is linked to the parent automatically.
- **Custom filters** answer WHICH records: conditions on the list's own fields (field · operator · value), ANDed within a group and ORed across groups. A value may be static, a relative date, or a field of the parent record — but the parent is only something to compare against, and nothing gets linked.

When the ask scopes the list to the parent ("my projects", "this employee's requests"), use the relative filter if a path exists, rather than a custom condition whose value references the signed-in user or the page's record. Custom filters cover the rest: field values ("approved", "due this week"), OR ("mine or unassigned"), a comparison against a field of the parent ("same department as me"), or a list whose table has no single-relationship path from the parent. Use both when the ask does both — the conditions narrow the related records. Neither is a security boundary: the records stay reachable through other pages and the API, so restrict the table's permissions when the goal is access control.

Both live in upsert_view's `customFilters` group. On a top-level collection view the only context record is the signed-in user (there is no surrounding record); record-view list sections, which the sections agent configures in this same format, can also anchor at the page's record. Access control is upsert_permission's job, not a filter's.

## Relative filter — `customFilters.relativeFilter`

`{ "relativeFilter": { "path": "…" } }`: a relationship path from the SIGNED-IN USER's record to a multi-relationship field holding the view's records — the field itself ("projectsCollection") or single-relationship hops first ("company.projectsCollection"). `null` removes it. A read echoes it as `viewSettings.customFilters.relativeFilter`.

## Custom filters — `customFilters.filters`

`{ "customFilters": { "filters": [ … ] } }` REPLACES the whole set on every call, so read the current rows first (`filters` or `viewSettings.customFilters` from get_page) and send back every row the view should keep — rows you return unchanged are kept as they are.

Hard-coded filters narrow what a list shows for everyone; app users never see or remove them (unlike filter fields).

Format — condition groups, outer array ORed, inner arrays ANDed. `[]` clears every filter.
```json
[[
  { "fieldName": "status", "operator": "IN", "value": { "source": "STATIC", "value": ["OPEN", "IN_PROGRESS"] } },
  { "fieldName": "owner", "operator": "EQUAL", "value": { "source": "LOGGED_IN_USER" } }
]]
```

- `fieldName`: an internal field name from the LIST's table schema, plus `id`, `uuid`, `createdAt` and `updatedAt`, which schema reads omit but stay filterable.
- `operator` by field type — TEXT: EQUAL, NOT_EQUAL, CONTAINS, DOES_NOT_CONTAIN, EMPTY, NOT_EMPTY · INTEGER/DECIMAL/DURATION: EQUAL, NOT_EQUAL, GREATER, GREATER_OR_EQUAL, LESS, LESS_OR_EQUAL, EMPTY, NOT_EMPTY, IN, NOT_IN · DATE: EQUAL, NOT_EQUAL, AFTER, BEFORE, AFTER_OR_EQUAL, BEFORE_OR_EQUAL, EMPTY, NOT_EMPTY · BOOLEAN: TRUE, FALSE · SINGLE_OPTION/single relationship: EQUAL, NOT_EQUAL, EMPTY, NOT_EMPTY, IN, NOT_IN · MULTIPLE_OPTION/multi relationship: EMPTY, NOT_EMPTY, IN, NOT_IN · ARRAY: CONTAINS, DOES_NOT_CONTAIN, EMPTY, NOT_EMPTY.
- `value`: omit it for EMPTY/NOT_EMPTY/TRUE/FALSE, otherwise pass one of
  - `{ "source": "STATIC", "value": … }` — a literal; an array for IN/NOT_IN; internal option names for option fields; ISO 8601 for dates.
  - `{ "source": "LOGGED_IN_USER" }` — the signed-in user. Omit `path` when the filtered field is a user relationship ("only my records"); pass a user field path ("email", "manager.email") to compare a scalar field against it.
  - `{ "source": "CURRENT_RECORD", "path": "…" }` — a field of the record the surrounding record view shows (list sections inside a record view only). Reference a related record by the relationship field's own name, e.g. "userAccount", never "userAccount.id".
  - `{ "source": "RELATIVE_DATE", "token": "…" }` — DATE fields only. Tokens: today.now, today.start, today.end, week.start, week.end, month.start, month.end, year.start, year.end, or custom.<hour|day|week|month|quarter|year>.<ago|from>.<whole number>.

Filters read back as they are stored: `field` is the internal filter key (a relationship's is `<name>Id`), `result` holds the value or values, and a reference reads as `{ "id", "path" }` — `auth-wrapper` is the signed-in user, `<viewId>:VIEW` the surrounding record, `values` a relative date. Send rows back exactly as you read them to keep them, so you can change one row and leave the rest alone. Every row you send is validated either way, and a value only the app can read — a literal mixed with a reference — is kept as it is.

Rules specific to top-level collection views:
- CURRENT_RECORD is unavailable (there is no surrounding record); it only works in record-view list sections.
- A view cannot combine OR groups with the search box — disable `filterFields.search` first, or express the filters as one AND group.
records5.51 KB

View saved version →

---
name: records
description: 'Reading, searching, creating, updating and deleting the records of a connected Noloco app: discovery first (list_projects, list_tables, get_table_schema), the read and write scopes, the data-admin access every call carries, filters, cursors, worked examples and what to do when a call fails. Load before the first data call on an app.'
---

# Working with records in a Noloco app

A Noloco app (a **project**, the `projectName` argument) holds **tables** of **records**; a table's **fields** have a type (text, number, date, single or multi select, relationship, formula, lookup, rollup, file). Every tool except `list_projects` and `load_skill` takes a `projectName`, and every tool answers with text: on success a message and, when there is data, a JSON block (also in `structuredContent`); a failed call is an error result whose text says what went wrong and, when there is one, what to do instead. Act on that suggestion rather than retrying the same call.

## Discover before you act

Project, table and field names are specific to each app and cannot be guessed. Inventing a `tableName` or a field name is the most common cause of a failed call. Work from discovery outward:

1. `list_projects`: the apps this connection may use and the exact `projectName` of each. When it returns one app, use it without asking.
2. `list_tables`: the tables of that app and their exact `tableName`. Pass `nameFilters` (case-insensitive substrings, several at once) to find related tables in one call.
3. `get_table_schema`: a table's fields, their types and select option values. Pass `fieldNames` (substrings) on a wide table to keep the response small. Read it before filtering, sorting or writing.
4. `list_records` and `get_record` to read; `create_record`, `update_record` and `delete_record` to write.

## Reading

- `list_records` takes `tableName` plus optional `filter`, `sort` (`{ field, direction: ASC | DESC }`), `first` (1 to 100, default 25), `after` and `before` (cursors), `include` (relationship fields to expand) and `fieldNames` (the columns to return). It returns `records`, `totalCount` and `pageInfo`.
- `get_record` takes `tableName` and `recordId`, plus optional `include` and `fieldNames`.
- Keep requests narrow. Filter with `filter` (value operators `equals, not, contains, gt, gte, lt, lte, in, notIn`; relationship operators `some, none, every`), return only the columns you need with `fieldNames`, and expand only the relationships you need with `include`.
- Page with cursors: request `first` (at most 100); while `pageInfo.hasNextPage` is true, call again with `after: pageInfo.endCursor`. `totalCount` tells the table size up front.

## Writing

- `create_record` takes `tableName` and `data` (a field to value map; `id`, `uuid` and timestamps are generated). It returns the new `recordId` and runs the app's automations.
- `update_record` takes `tableName`, `recordId` and `data` with only the fields that change. Repeating it leaves the values as they are but runs the app's update automations again, so read the record back with `get_record` before retrying a call whose result you did not see.
- `delete_record` takes `tableName` and `recordId`. It is permanent.
- Confirm a destructive or ambiguous write with the user first, and prefer the narrowest change.

## Scopes and access

Access is granted per app at sign-in as `mcp:read` or `mcp:read` plus `mcp:write`; `list_projects` reports the scopes held on each app. A write that fails for insufficient scope means the app is connected read-only: tell the user they can re-run the Noloco consent flow to grant write access, and do not retry.

Every tool acts with the connecting user's data admin role and bypasses the app's row-level and field-level permissions, reads included. Results are not what an app user sees, so never describe them that way. What is still enforced: the granted scope, a live data-admin check on every call, Noloco's data validation (required fields, field types, relationship rules) and the plan's API rate limits.

Some apps expose their own connector URL (`…/mcp/apps/<app>`). There, the tools act as the signed-in app user within their role: there is no `list_projects` and no `projectName` argument, and records or fields the user cannot see in the app are not returned.

## Worked examples

**Answer a question about data.** "How many deals did we close this quarter?": `list_tables` with `nameFilters: ["deal"]`; `get_table_schema` with `fieldNames: ["stage", "close", "amount"]` to confirm the stage options and the close date field; `list_records` with a `filter` on the stage and the close date, `fieldNames: ["amount"]` and `first: 100`, reading `totalCount` and paging with `after` when there is more.

**Make a targeted change.** "Mark deal 482 as Won": `get_table_schema` to confirm the stage field and that "Won" is one of its options; confirm the change with the user; `update_record` with `recordId: 482` and `data: { stage: "Won" }`.

**Walk a whole table.** `list_records` with `first: 100`, then `after: pageInfo.endCursor` while `pageInfo.hasNextPage` is true.

## When a call fails

- Table or record not found: re-run `list_tables` or `list_records` for the exact names and ids.
- Insufficient scope: the app is read-only for this connection; ask the user to re-consent with write access.
- Cannot view and manage all data: ask an admin of the project for a role that can view and manage all data, then retry.

For questions about Noloco as a product (how to build or configure an app, what a feature does), use the Noloco guides at https://guides.noloco.io/ rather than guessing.
scaffold-app8.64 KB

View saved version →

---
name: scaffold-app
description: 'Turning a project that has data but no useful pages into a usable app: which tables deserve a page, creating the tables and fields first when the data only exists as a description, one view per table in navigation order with its list fields, form and record page, the navigation itself, and what to check before handing over. Load before building an app from a connected source, from existing tables or from scratch.'
---

# Scaffolding a Noloco app from its data

The job: take a project that has data but no useful pages and leave it an app people can open and work in. One well-chosen view per table that matters, a record page behind each, a new-record form that asks for the right fields, and a navigation that starts with the table the team opens most. Load `build-views` with this skill: it chooses each view's layout and settings.

## 1. Survey before you plan

- `list_projects`: the `projectName`, and `canBuild` must be true.
- `list_tables`: every table with its `fieldsCount` and `primaryField`. A table synced from an external source (Airtable, Google Sheets, a database) carries a `source` naming that source's type; a Noloco table has none. `dataSources` lists those sources.
- `get_table_schema` on each table you will build for. Field types and option values decide the layout (a SINGLE_OPTION status field makes a BOARD, dates make a CALENDAR or TIMELINE), and relationships decide which related lists belong on a record page.
- `list_pages`: what the app already has. Never create a second view for a table that has one; configure the existing view with `upsert_view` and its `pageIdentifier` instead.
- `list_records` with `first: 1` on the main tables: a BOARD or CALENDAR of an empty table renders empty, so you need to know which tables have data.
- `list_version_history`: whether the app has ever been published, which decides the last step.

## 2. Decide what to build

Before the first write, decide in product words what each table gets, one line per table ("Deals: board by stage. Tasks: calendar by due date. Companies: cards."), plus the navigation order and anything you will skip. Writes take effect at once, so settle these choices up front: tables and fields have no draft, and on an app that has never been published app users see a page as soon as it is saved.

Which tables get a page:
- Every table the team works in day to day. A pure lookup table (statuses, categories, countries) gets none: its values are reached through the records that use them.
- From a connected source with many tables, pick at most 7 by these criteria and tell the user which tables you left out:
1. Core business entities users will view and update regularly
2. Tables with rich data (more fields typically means more important)
3. Prefer operational tables over pure lookup/reference tables (e.g. prefer "Projects" over "Project Statuses")
- Any table the user named comes first, in the plan and in the navigation.

## 3. The schema, only when the data is not there yet

When the user describes the app instead of pointing at tables, create Noloco tables first. Never add fields to a table with a `source`: its schema belongs to that source, so the field is added there and syncs in.
- `upsert_table` per table, parents before children (Companies before Deals) so relationship fields have a target.
- `upsert_field` per field. INTEGER or DECIMAL for numbers with the matching format in `typeOptions` (currency, percentage), DATE with a date or date-time format, SINGLE_OPTION with its `options` (a status field is what makes a BOARD and a STAGES section possible), OBJECT with the matching `format` (fullName for a person's name, phoneNumber for a phone number, address for an address), TEXT single-line by default and multi-line only for notes. A link to another table is `type: "<its tableName>"` with `relationship: MANY_TO_ONE` on the child table (Deals link to a Company); the reverse field appears on the parent by itself.
- `upsert_table` again with `primaryField` set to the name-like field once the fields exist, so a record is shown by its name wherever it is referenced.
- Sample records only on Noloco tables, and only when the user wants to see the app with data: a handful per table through `create_record`, named so they are obviously samples. A link field takes the related record's id, or a list of ids for a many link; an option field takes the option's stored name, its display in upper snake case ("In progress" is `IN_PROGRESS`); an OBJECT field takes an object with the sub-field keys `get_table_schema` reports. A connected source's rows live in that source; never write samples into it.

## 4. One view per table, in navigation order

`upsert_view` with `data.tableName` creates the view, its record page and its new-record form in one go, and takes the layout and every other setting in that same call. Create views one after another, never two in parallel, in the order the navigation should have: a new view lands at the end, and views created in parallel land in whatever order they finish. In the creating call set the layout and its companion settings (see `build-views`), `sort.orderBy` to the field users expect, `buttons.newButtonText` naming the record ("Add deal") and `filterFields.search` on tables users look things up in. On a TIMELINE, GANTT, CALENDAR, MAP, BOARD view also set `setup.recordTitle` to the name-like field; the other layouts reject it.

The view is named after its table and gets a random icon: follow up with `update_page_settings` for a fitting `icon` (a common Tabler icon name: Briefcase, Users, Calendar, Truck; an unknown name renders blank) and, when the table's name is not what users call the page, a `name`.

## 5. The list and the form

- `configure_view_fields`: a new view shows the table's first fields, which is rarely the best set. Keep the few high-signal fields (status, owner, date, amount), chosen from the schema you read in step 1; on a CARDS, BOARD or COLUMNS view put the image field first with `heroImage`. Where `setup.recordTitle` is set it is already the card's heading, so leave that field out of the list.
- `configure_form_fields`: the form starts with the table's first ten editable fields. Keep the ones a person fills in when creating the record, mark the essential ones `required` and drop the rest.
- One call per view and per tool, with every change in it. Changes merge onto the fields already there, so a field you drop stays until you pass `{ fieldName, enabled: false }` for it, and the complete `fieldOrder` lists every field still shown.

## 6. Record pages

A new view's record page starts with a sensible default: a STAGES section on the field a new BOARD is grouped by, or else on a single-option field named like a status, stage, phase or step (none when the table has neither), HIGHLIGHTS for its first numbers and dates, DETAILS for the other fields and lists of related records. Leave it on secondary tables. On the one or two tables the team opens most, hand the page to `sections` with the business context (what the team does, which fields matter day to day, which related records belong on the page) in one call per page. Never run two `sections` calls at the same time: each can take a few minutes and spends the team's AI credits.

## 7. Navigation

App users land on the first page of the navigation, so put the table they work in most first (`update_page_settings` with `position`). Group reference tables into a folder (`create_navigation_item` with `type: FOLDER`, then `parentPage` on each page) or hide a page with `hidden: true` when its records are only ever reached from other records. A placeholder page the app already had is deleted with `delete_page` only when the user asked for it: nothing asks for confirmation.

## 8. Check, then hand over

- `list_pages` once to confirm the pages, their order and their routes. A page whose layout depends on a field (BOARD, CALENDAR, TIMELINE, GANTT, MAP, CHECKLIST) deserves one `get_page` with `include: ["viewSettings"]` to confirm the field is set.
- `publish_project` only when the app has been published before (the version history from step 1): then the new pages wait in the draft until it runs. A never-published app already serves them. Say which case applies.
- A table with permissions off shows every record to every app user, who can also edit it; a table with permissions on shows a role nothing until it has a rule. `list_permissions` says which case each table is in. Leave access for a follow-up unless the user asked for it; `upsert_permission` sets a rule per table and role when they do, and turns that table's permissions on.
- Hand over in product words: each page and what it shows, what you skipped and why, and the natural next steps (roles and permissions for app users, workflows, a dashboard).

Release notes

First release. Read, search and edit the records of your Noloco apps, and build apps: tables, fields, pages and views, forms, record pages, action buttons, workflows, roles and permissions, and publishing. Includes six step-by-step guides for common jobs.

Declared in the saved package. Remote tools may change independently.

Package details

Publisher declarations from the archived package. These are separate from our research and the live service's terms.

Package author
Noloco
Keywords
See keywords
Commerce declaration
Does not support commerceThis does not establish whether access is free or paid.
Review scenarios
5 positive · 3 negativeScenario counts, not test results.

Declared capabilities

  • Read
  • Write

Package observed Nov 2, 2026.

Technical details
First seen
Oct 30, 2026 · 22:33 UTC
Last seen
Nov 3, 2026 · 18:00 UTC
Collection status
Collected

plugin_asdk_app_6ae1e0d67d688191b1dbffdf37677595

Download plugin data (JSON)