softr-vibe-coding 1.12.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,11 @@ All notable changes to this skill are documented here. Versions follow [Semantic
4
4
 
5
5
  Entries from 1.3.1 onward are generated automatically from git commit subjects between version bumps (see `.github/workflows/publish.yml`). Entries before 1.3.1 were backfilled by hand from the existing commit history.
6
6
 
7
+ ## [2.1.0] - 2026-08-25
8
+ - Bump to 2.1.0 — official dev-guide sync (field-name rule for Airtable/Notion/Sheets, PHONE sanitization, useProxyFetch datasource routing, useCurrentUser properties) + workspace-wide Softr MCP reference with direct block deployment (softr-mcp.md replaces softr-database-mcp.md)
9
+ - Sync with the official Vibe Coding developer guide and the workspace-wide Softr MCP server (28 verified fixes). Field-name rule extended: Airtable AND Notion AND Google Sheets use field NAMES in q.select (Softr DB/Supabase use IDs; wrong form fails silently with empty data) — fixed in Hard Constraint 11, notion.md, helper-blocks.md publisher template (was modeling fldXXX IDs), fields.md. New PHONE write rule: + followed by digits only, Monday.com hard-rejects formatted values — official sanitizePhone added to writing.md (new Phone section), monday.md, anti-patterns.md. useProxyFetch is datasource-scoped: alias as its ARGUMENT (useProxyFetch(ds.store)), throws when omitted with >1 source; proxy payloads are text-only (no FormData/streams/uploads) — added to rest-api.md, multi-datasource.md, quick-reference.md, anti-patterns.md. fetchNextPage rule relaxed to match the guide's canonical Load More onClick: never in the render body, event handler or guarded useEffect both fine (SKILL.md x2, README, anti-patterns.md). useCurrentUser({ properties }) exposes custom user fields under user.properties — window.__softr_current_user narrowed to userGroups/role only; id only present with user sync (reading.md, quick-reference.md, fields.md). useLinkedRecords count default 100 max 1000. useUpload: isUploading + result.error handling + multi-file pattern. Linked-record field-type row corrected to string-array shape. TRIGGER_CUSTOM_WORKFLOW takes no destination per the documented type. references/softr-mcp.md REPLACES softr-database-mcp.md: the MCP is now workspace-wide — vibe coding block tools (get_vibe_coding_docs, create/edit/versions, data source wiring + the five official gotchas incl. action-permission reset on every code change), integrations browsing down to field level (Airtable/Sheets/Notion/Supabase/Softr DB only), databases tools (no deletes yet; 100-create/200-read/2-group-by limits), three-area bundled permission levels replacing the old granular scopes. SKILL.md workflow now offers direct MCP deployment alongside paste-into-Studio. Description gains negative scope (building-design-md, non-Softr dashboards); reading/writing/fields now linked directly from SKILL.md instead of only via the shared-patterns index; README tree gains the tools/ dir; retired "no ?." mention dropped from airtable-automations.md; common-patterns var-style sentence reworded to legacy-but-valid
10
+ - Bump to 2.0.0 — verified 2026-08 platform contract: modern TypeScript, mutateAsync write queues, label-string SELECT writes, flat create payloads, inline hook options, Actions reset on recompile
11
+
7
12
  ## [1.12.0] - 2026-07-22
8
13
  - Bump to 1.12.0 — multi-datasource blocks, Airtable rating writability, dark-brand canvas + page-background anti-patterns
9
14
  - Document the Airtable rating field as writable (plain integer 0-max; 0 clears it, so AVERAGE formulas skip it) — it was absent from the Supported Fields table, which made it the main unknown when building a star-rating form. Add two dark-brand styling anti-patterns: a block on a dark brand MUST paint its own backgroundColor because custom-code-header's body rule doesn't cross the shadow-DOM boundary and a default Softr page is white, so white text/logos/buttons render invisibly on white (the existing don't-double-paint row assumed a light app and inverts here); and setting html,body alone doesn't change the page background because Softr paints the same fill on FOUR stacked layers — html, body, #page-content, and a class-less wrapper div inside it — so paint html then clear the duplicates, excluding the .softr-topbar subtree
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Softr Vibe Coding Block — Claude Skill
2
2
 
3
- > Turn Claude into a Softr Vibe Coding expert — generate production-ready JSX blocks with polished UI, correct data fetching, and all 14 Softr data sources supported out of the box.
3
+ > Turn Claude into a Softr Vibe Coding expert — generate production-ready React blocks (TSX/JSX) with polished UI, correct data fetching, and all 14 Softr data sources supported out of the box.
4
4
 
5
5
  ![Claude Code Skill](https://img.shields.io/badge/Claude_Code-Skill-blue)
6
6
  ![Version](https://img.shields.io/npm/v/softr-vibe-coding?label=version&color=green)
@@ -12,7 +12,7 @@
12
12
 
13
13
  ## TL;DR
14
14
 
15
- This Claude skill teaches Claude Code how to generate complete, polished Softr Vibe Coding blocks as `.jsx` files. It includes:
15
+ This Claude skill teaches Claude Code how to generate complete, polished Softr Vibe Coding blocks as `.tsx` / `.jsx` files (the current platform compiles TypeScript with modern syntax — verified live August 2026). It includes:
16
16
 
17
17
  - **Complete Vibe Coding API reference** — `useRecords`, `q.select()`, mutations, uploads, metrics, charts, editable settings, `useProxyFetch` for REST APIs
18
18
  - **All 14 Softr data sources** — Airtable, Softr Database, Google Sheets, HubSpot, Notion, Coda, monday.com, SmartSuite, ClickUp, Xano, Supabase, BigQuery, SQL Database, and REST API — each with field mapping, rate limits, and gotchas
@@ -20,10 +20,10 @@ This Claude skill teaches Claude Code how to generate complete, polished Softr V
20
20
  - **Advanced integrations** — Shadow DOM CSS isolation for third-party libraries (Leaflet, Mapbox, TinyMCE, Quill, FullCalendar)
21
21
  - **Native shell styling** — re-skin Softr's native top bar, **footer**, nav, dropdowns, and **page background** via global Custom Code CSS (stable selectors vs. hashed classes, floating "island" header/footer, the dropdown grid fix, the multi-layer page-background stacking, restyle-vs-replace) — distinct from blocks
22
22
  - **UI/UX design guidelines** — 26 sections covering visual hierarchy, color, typography, spacing, motion design, accessibility, responsive patterns, and an AI slop anti-pattern checklist
23
- - **Self-validation** — Claude checks for Softr bundler compatibility (no optional chaining, correct imports, container wrappers, `getFieldValue()` wrapping, hooks ordering) before delivering code
23
+ - **Self-validation** — Claude checks for Softr platform compatibility (inline hook options, correct payload shapes, correct imports, container wrappers, `getFieldValue()` wrapping, hooks ordering) before delivering code
24
24
  - **Premium visual baseline** — Every block ships polished from v1: gradient backgrounds, card elevation, loading skeletons, empty states, error states
25
25
  - **Debug utilities** — Field Inspector, API Response Inspector, and User Inspector blocks for diagnosing data source and permissions issues
26
- - **Softr Database MCP integration** — when the [official Softr MCP server](https://docs.softr.io/mcp-server) is installed (`claude mcp add --transport http softr https://mcp.softr.io/mcp`), Claude reads Softr DB schema, field IDs, and dropdown option UUIDs directly — no more pasting `tablespace-with-tables` JSON. See `references/softr-database-mcp.md`.
26
+ - **Softr MCP integration** — when the [official Softr MCP server](https://docs.softr.io/mcp/overview) is installed (`claude mcp add --transport http softr https://mcp.softr.io/mcp`), Claude reads Softr DB schema, field IDs, and dropdown option UUIDs directly, browses connected Airtable / Google Sheets / Notion / Supabase integrations down to field level, and can even create and deploy Vibe Coding blocks straight into your app — no more pasting `tablespace-with-tables` JSON or copy-pasting code into Studio. See `references/softr-mcp.md`.
27
27
 
28
28
  ---
29
29
 
@@ -91,7 +91,7 @@ Build me a Softr Vibe Coding block that shows a team directory with cards
91
91
  This skill is **Step 2** of a two-skill brand-to-blocks pipeline:
92
92
 
93
93
  ```
94
- New client → building-design-md (brand → DESIGN.md) → softr-vibe-coding (DESIGN.md → JSX blocks) → shipped Softr app
94
+ New client → building-design-md (brand → DESIGN.md) → softr-vibe-coding (DESIGN.md → blocks) → shipped Softr app
95
95
  ```
96
96
 
97
97
  The upstream skill is [`building-design-md`](https://github.com/leo-softr/design-md-extractor-skill), which extracts a brand foundation (colors, typography, voice) from a website URL or brand guide into a portable `DESIGN.md` file. When that file exists in your project folder, this skill picks it up automatically and applies the brand tokens throughout every block it generates — no re-asking about colors or fonts.
@@ -152,8 +152,8 @@ Create a contact form that creates records in our Airtable Contacts table
152
152
 
153
153
  1. **Asks only what it needs** — data source type and field IDs. Everything else (folder, colors, filename) uses smart defaults.
154
154
  2. **Loads the relevant guides** — reads the specific data source guide (Airtable, REST API, etc.) and reference files (helper blocks, Shadow DOM) as needed.
155
- 3. **Generates a complete `.jsx` file** — production-ready, visually polished, with loading/error/empty states. Never delivers code inline (prevents JSX character corruption).
156
- 4. **Self-validates** — checks 13 items including Softr bundler compatibility, `getFieldValue()` wrapping, hooks ordering, and mutation patterns before delivering code.
155
+ 3. **Generates a complete block file** (`.tsx` preferred) — production-ready, visually polished, with loading/error/empty states. Never delivers code inline (prevents JSX character corruption).
156
+ 4. **Self-validates** — runs a platform-compatibility checklist (`getFieldValue()` wrapping, hooks ordering, payload shapes, inline hook options) before delivering code.
157
157
 
158
158
  ---
159
159
 
@@ -163,7 +163,7 @@ Create a contact form that creates records in our Airtable Contacts table
163
163
  softr-vibe-coding/
164
164
  ├── SKILL.md # Main skill (330 lines)
165
165
  │ # Workflow, code structure, visual baseline,
166
- │ # components, settings, 20 hard constraints
166
+ │ # components, settings, 21 hard constraints
167
167
 
168
168
  ├── ui-ux-guidelines.md # Design reference (746 lines)
169
169
  │ # 26 sections: hierarchy, color, typography,
@@ -179,10 +179,10 @@ softr-vibe-coding/
179
179
  │ │ # Scripting Extension, cross-table cascades,
180
180
  │ │ # batch update gotchas, field-ID discipline,
181
181
  │ │ # Airtable formulas
182
- │ ├── softr-database-mcp.md # Softr Database MCP server (sibling to
183
- │ │ # datasources/softr-database.md; AI-assisted
184
- │ │ # schema discovery, field-ID lookup, OAuth
185
- │ │ # install, scope limitations)
182
+ │ ├── softr-mcp.md # Official Softr MCP server vibe coding block
183
+ │ │ # tools (create/edit/version/deploy), integrations
184
+ │ │ # browsing (Airtable/Sheets/Notion/Supabase),
185
+ │ │ # Softr DB schema + record tools, auth, permissions
186
186
  │ ├── advanced-integrations.md # Shadow DOM CSS isolation (69 lines)
187
187
  │ │ # Leaflet, Mapbox, TinyMCE, Quill, FullCalendar
188
188
  │ ├── native-chrome-styling.md # Restyle Softr's native shell (header, footer,
@@ -202,6 +202,10 @@ softr-vibe-coding/
202
202
  │ # Imports, hook signatures, mutation shapes,
203
203
  │ # field mapping, component skeleton
204
204
 
205
+ ├── tools/ # Bundled CLI scripts (run, not read)
206
+ │ ├── get-airtable-base # Full Airtable base schema export (bash + jq)
207
+ │ └── get-softr-database.py # Full Softr DB schema export (Python stdlib)
208
+
205
209
  └── datasources/ # Data source guides (loaded on demand)
206
210
  ├── overview.md # Comparison matrix, selection guide
207
211
  ├── shared-patterns.md # Index → multi-datasource, reading, writing, fields
@@ -209,8 +213,8 @@ softr-vibe-coding/
209
213
  │ # the from: parameter, getting the datasource UUIDs
210
214
  ├── reading.md # useRecords, filtering, sorting, pagination,
211
215
  │ # metrics, charts, current user (198 lines)
212
- ├── writing.md # Mutations, uploads, linked record format,
213
- │ # cross-table REST API writes (146 lines)
216
+ ├── writing.md # Mutations, sequential write queues, uploads,
217
+ │ # linked record format, cross-table writes
214
218
  ├── fields.md # getFieldValue(), field type shapes, record
215
219
  │ # structure, debug utilities (160 lines)
216
220
  ├── rest-api.md # useProxyFetch + useQuery (full docs)
@@ -256,17 +260,21 @@ Only `SKILL.md` loads into Claude's context when the skill triggers (~330 lines)
256
260
 
257
261
  ---
258
262
 
259
- ## Key Softr Bundler Constraints
263
+ ## Key Softr Platform Constraints
260
264
 
261
- The skill enforces these automatically, but good to know:
265
+ The skill enforces these automatically, but good to know (verified live against the platform, August 2026):
262
266
 
263
- - No optional chaining (`?.`) or nullish coalescing (`??`) Softr's bundler fails on these
267
+ - Modern TypeScript compiles — optional chaining, nullish coalescing, arrows, `const`, generics are all fine (the old `var`-only / no-`?.` rules are retired)
268
+ - Data hook options must be **inline object literals** — `useRecords(opts)` with a variable or wrapper fails to compile
269
+ - Create payloads are **flat**; update payloads are `{ recordId, fields: {...} }` — asymmetric by design
270
+ - `mutateAsync` is fully supported — it's the tool for sequential multi-row saves
271
+ - SELECT fields write by option **label string**; linked records write as arrays of record-id strings
272
+ - Every code recompile resets the block's auto-registered Actions to default permissions — tighten permissions after the last redeploy
264
273
  - No `import React from 'react'` — use named imports (`import { useState } from "react"`)
265
- - No arrow functions in JSX callback props — use `function() {}`
266
274
  - Must use `export default function Block()`
267
- - Must wrap layout in `<div className="container py-6"><div className="content">`
275
+ - Must wrap layout in `<div className="container py-0"><div className="content">`
268
276
  - Only ONE `useRecords` call per **datasource** — but a block can connect to several sources; declare them with `datasource.define()` and pass `from:` on every hook
269
- - `fetchNextPage` only inside `useEffect`in render body causes infinite loops
277
+ - `fetchNextPage` never in the render body (infinite loop) call it from an event handler (Load More `onClick`) or a guarded `useEffect`
270
278
  - All hooks declared before any conditional `return` — React error #310
271
279
  - Every field value rendered in JSX must pass through `getFieldValue()`
272
280
  - Mutations use `recordId` (not `id`) and always call `refetch()` in `onSuccess`
package/SKILL.md CHANGED
@@ -1,12 +1,13 @@
1
1
  ---
2
2
  name: softr-vibe-coding
3
3
  description: >
4
- Generate custom Softr Vibe Coding blocks as complete JSX components. Use this skill whenever the user
4
+ Generate custom Softr Vibe Coding blocks as complete React components (TSX/JSX). Use this skill whenever the user
5
5
  mentions Softr, Vibe Coding, Softr blocks, or wants to build a custom UI component for a Softr app.
6
6
  Also trigger when the user asks to create cards, lists, forms, dashboards, charts, detail pages,
7
7
  or any interactive block intended for Softr — even if they don't say "Vibe Coding" explicitly.
8
8
  If the user mentions Softr in the context of building a custom UI component, creating a JSX block,
9
- or vibe coding, use this skill.
9
+ or vibe coding, use this skill. Do NOT use for extracting brand tokens or producing a DESIGN.md
10
+ (that is the building-design-md skill), or for charts/dashboards with no Softr app involved.
10
11
  when_to_use: >
11
12
  Triggers on "build me a Softr block", "create a card component", "make a dashboard",
12
13
  "vibe code this", "custom block for Softr", "JSX component for Softr app",
@@ -18,7 +19,7 @@ allowed-tools: Read Write Glob Grep Bash
18
19
 
19
20
  # Softr Vibe Coding Block Generator
20
21
 
21
- You generate complete, production-ready Softr Vibe Coding blocks as JSX files. A Vibe Coding block is a JavaScript file with a default-exported React component that runs exclusively in the browser inside a Softr app.
22
+ You generate complete, production-ready Softr Vibe Coding blocks as TypeScript React files. A Vibe Coding block is a single file with a default-exported React component, compiled by Softr's server and run in the browser inside a Softr app. The current platform compiles TypeScript with modern syntax — optional chaining (`?.`), nullish coalescing (`??`), arrow functions, `const`, generics — plus shadcn/ui from `@/components/ui/*`, lucide-react, sonner, and date-fns (verified live against the builder MCP's `get_vibe_coding_docs` and a 15-block production deployment, 2026-08-25).
22
23
 
23
24
  > **Scope note — blocks vs. native chrome.** A block is page *content*, rendered inside a shadow DOM. Softr's global **header / top bar / nav / dropdown menus** are native chrome (configured in Studio, rendered in the main document) — you **cannot** build or replace them as a block. To restyle them, add CSS to Settings → Custom Code → Code inside header. See [references/native-chrome-styling.md](references/native-chrome-styling.md).
24
25
 
@@ -55,14 +56,16 @@ You generate complete, production-ready Softr Vibe Coding blocks as JSX files. A
55
56
  3. **Apply defaults for the rest, don't ask.** Infer these from context instead of asking:
56
57
  - **Project folder**: Derive from the block description (e.g., "partner-portal", "client-dashboard"). If the user has already specified a folder in this session, reuse it.
57
58
  - **Brand colors**: Use whatever was chosen in Step 1 — DESIGN.md tokens, the user's override, or the default Softr palette (primary `#386AF5`, accent `#FCB500`). Never silently fall back to defaults.
58
- - **Filename**: Derive from the block purpose (e.g., `partner-invite.jsx`, `team-directory.jsx`). The user can rename later.
59
+ - **Filename**: Derive from the block purpose (e.g., `partner-invite.tsx`, `team-directory.tsx`). The user can rename later.
59
60
 
60
61
  4. **Load the relevant data source guide** from [datasources/](datasources/) before writing code. Read the specific guide for the user's data source type.
61
62
 
62
- 5. **Write the complete `.jsx` file** to the project sub-folder and tell the user the full path. Create the sub-folder if it doesn't exist yet. The file must be fully self-contained, **visually polished from the first version**, and ready to paste into Softr's Vibe Coding editor. Styling is not an afterthought -- it ships in v1. **Never deliver code inline in chat.** Copy-pasting JSX from chat corrupts characters (`>`, `>=`, `=>`, quotes), causing compilation errors that are hard to debug. Always write to a file.
63
+ 5. **Write the complete block file** (`.tsx` preferred; `.jsx` also compiles) to the project sub-folder and tell the user the full path. Create the sub-folder if it doesn't exist yet. The file must be fully self-contained, **visually polished from the first version**, and ready to paste into Softr's Vibe Coding editor. Styling is not an afterthought -- it ships in v1. **Never deliver code inline in chat.** Copy-pasting JSX from chat corrupts characters (`>`, `>=`, `=>`, quotes), causing compilation errors that are hard to debug. Always write to a file.
64
+
65
+ **Delivery path:** if the official Softr MCP server is connected with Applications & Forms full access, offer to deploy the block directly after writing the file — `create_vibe_coding_block` (or `update_vibe_coding_block_code` for edits) plus `connect_vibe_coding_block_data_source` to wire the data. The local `.tsx` file stays the source of truth. Remember: every code push resets the block's Action permissions to defaults (Hard Constraint 21), and a block whose data source isn't connected saves fine but errors at page load. Details in [references/softr-mcp.md](references/softr-mcp.md).
63
66
 
64
67
  6. **Self-validate before delivering.** Before presenting the code as complete, verify:
65
- - No optional chaining (`?.`) or nullish coalescing (`??`)
68
+ - Every data hook is called with an **inline options object literal** — `useRecords({ ... })` written through a variable or wrapper function fails to compile (verified live 2026-08-25). Share `q.select` mappings between hooks, never whole options objects
66
69
  - All imports use named imports (no `import React from 'react'`)
67
70
  - `export default function Block()` is present
68
71
  - Container + content wrappers present (`<div className="container py-0"><div className="content">`)
@@ -74,10 +77,10 @@ You generate complete, production-ready Softr Vibe Coding blocks as JSX files. A
74
77
  - All hooks declared before any conditional `return` -- prevents React error #310
75
78
  - Sub-components (FieldLabel, TextInput, ChipButton, SectionCard, etc.) defined at **module scope**, NOT inside `Block()` -- prevents inputs losing focus after one keystroke (each render creates a new component identity, React unmounts/remounts the `<input>`)
76
79
  - When a custom DESIGN.md is in use, brand `fontFamily` (and any non-inherited brand defaults) set as an **inline style on the block's outermost wrapper** `<div>`, not relied on from `custom-code-header.html` -- Vibe Coding blocks render inside a shadow DOM and `html, body` rules don't cross that boundary. Per-element overrides (e.g. Fraunces serif on h1) still set inline at the element.
77
- - `fetchNextPage` only inside `useEffect`, never in render body
80
+ - `fetchNextPage` never called in the render body — only from an event handler (Load More `onClick` with `disabled={isFetching}`, the official pattern) or a guarded `useEffect` (auto-load-all)
78
81
  - Mutations use `recordId` (not `id`) and call `refetch()` in `onSuccess`
79
- - `useRecordUpdate` calls use `.mutate(payload, { onSuccess, onError })` — **NOT** `.mutateAsync(...).then(...).catch(...)`. Softr's Action parser only recognizes the `.mutate(` token; `.mutateAsync(` is invisible to it and the Action never gets derived (`enabled` stays `false`, Actions tab shows "No actions used in this block yet")
80
- - `useRecordUpdate` payload is `{ recordId, fields: { ... } }`nested, not flat. Field references inside flat payloads are invisible to the parser, same silent-failure mode as the `.mutateAsync` issue
82
+ - `useRecordUpdate` payload is `{ recordId, fields: { ... } }` — nested. `useRecordCreate` payload is **flat** (no `fields` wrapper). The two shapes are asymmetric by design (verified live 2026-08-25)
83
+ - Sequential multi-row saves use `await hook.mutateAsync(...)` per row, in order, with stop-on-failure + retry state`mutateAsync` is fully supported on the current platform (verified 2026-08-25; the old ".mutate() only" Action-parser rule is gone see [datasources/writing.md](datasources/writing.md))
81
84
  - No hardcoded domains in links -- use relative paths (`/page?recordId=...`)
82
85
 
83
86
  ## What to Clarify
@@ -86,7 +89,7 @@ When the user describes their block, figure out which of these areas apply and a
86
89
 
87
90
  - **Data source type**: Is it Airtable, Softr Database, REST API, or another source? This determines the data fetching approach. **Load the relevant data source guide** from the [datasources/](datasources/) directory before writing code.
88
91
  - **Data source fields**: For Airtable/Softr Database, you need actual field IDs. For REST APIs, you access the raw API response directly. If the user doesn't know field IDs:
89
- - For **Softr Database**, the cleanest path is the **Softr Database MCP server** — ask whether they have it installed (`claude mcp list` shows it as `softr` or similar). If yes, query schema directly with the MCP tools instead of asking for paste-ins. If no, the next-best option is the bundled **`get-softr-database` CLI script** — tell the user to run `python3 ~/.claude/skills/softr-vibe-coding/tools/get-softr-database.py <database_id>` (it prompts for their Softr API key and exports the full schema to `~/Desktop/softr-database-<id>-<timestamp>.json` — Python stdlib only, nothing to install) and paste the resulting JSON into chat. As a final fallback, ask them to paste the `tablespace-with-tables` network response (DevTools -> Network -> filter that string while on Studio's Data tab) — same JSON content, different acquisition path. Optionally tell them they can install the MCP once with `claude mcp add --transport http softr https://mcp.softr.io/mcp` for future sessions. Full MCP details in [references/softr-database-mcp.md](references/softr-database-mcp.md); CLI script details in [datasources/softr-database.md](datasources/softr-database.md#bundled-cli-script-get-softr-database); fallback paste-in workflows in [datasources/fields.md](datasources/fields.md#field-inspector-block).
92
+ - For **Softr Database**, the cleanest path is the official **Softr MCP server** — ask whether they have it installed (`claude mcp list` shows it as `softr` or similar). If yes, query schema directly with the MCP tools instead of asking for paste-ins. The same server also browses connected **Airtable, Google Sheets, Notion, and Supabase** integrations down to field level (`list_data_sources` → ... → `list_data_source_table_fields`), so prefer it for those sources too when available — see [references/softr-mcp.md](references/softr-mcp.md). If no MCP, the next-best option for Softr DB is the bundled **`get-softr-database` CLI script** — tell the user to run `python3 ~/.claude/skills/softr-vibe-coding/tools/get-softr-database.py <database_id>` (it prompts for their Softr API key and exports the full schema to `~/Desktop/softr-database-<id>-<timestamp>.json` — Python stdlib only, nothing to install) and paste the resulting JSON into chat. As a final fallback, ask them to paste the `tablespace-with-tables` network response (DevTools -> Network -> filter that string while on Studio's Data tab) — same JSON content, different acquisition path. Optionally tell them they can install the MCP once with `claude mcp add --transport http softr https://mcp.softr.io/mcp` for future sessions. Full MCP details in [references/softr-mcp.md](references/softr-mcp.md); CLI script details in [datasources/softr-database.md](datasources/softr-database.md#bundled-cli-script-get-softr-database); fallback paste-in workflows in [datasources/fields.md](datasources/fields.md#field-inspector-block).
90
93
  - For **Airtable**, the most thorough path is the bundled **`get-airtable-base` shell script** — `bash ~/.claude/skills/softr-vibe-coding/tools/get-airtable-base` (requires `jq` — `brew install jq` on macOS). It prompts for Base ID + PAT, then exports the full schema (every table, every field with both `fld...` IDs and column names, relationships, webhooks, interfaces) to a timestamped Desktop folder. The user pastes `02-schema.json` or the combined `00-bundle.json` into chat. For lighter inspection (just a few fields, runtime-only), suggest the Field Inspector block — empty `q.select({})` works for Airtable. CLI script details in [datasources/airtable.md](datasources/airtable.md#bundled-cli-script-get-airtable-base).
91
94
  - For other non-Softr-DB sources where empty `q.select({})` works, suggest the Field Inspector block.
92
95
  - **Brand colors**: Already resolved in Step 1 (Detect the brand source). Don't re-ask. The brand source is one of:
@@ -143,7 +146,7 @@ Softr supports 14 data sources. **Before writing any data-fetching code, read th
143
146
 
144
147
  **A block can connect to MORE THAN ONE of these at a time.** Declare them with `datasource.define({ alias: "uuid" })` and pass `from: ds.alias` on every data hook — read two tables and write to a third from a single block. Required reading before building anything multi-table: [datasources/multi-datasource.md](datasources/multi-datasource.md). (This replaces the old one-table-per-block limit and the invisible-helper-block workaround.)
145
148
 
146
- For shared data fetching patterns (useRecords, mutations, uploads, metrics, charts), see [datasources/shared-patterns.md](datasources/shared-patterns.md).
149
+ Shared data patterns, linked directly (read the one the task needs): **reading** records, filtering, sorting, pagination, metrics, charts, current user — [datasources/reading.md](datasources/reading.md); **writing** — mutations, sequential write queues, uploads, field-type write shapes — [datasources/writing.md](datasources/writing.md); **field values** — `getFieldValue()`, field shapes, debug blocks — [datasources/fields.md](datasources/fields.md). ([datasources/shared-patterns.md](datasources/shared-patterns.md) is the thin index of the same set.)
147
150
 
148
151
  For data source comparison and selection guidance, see [datasources/overview.md](datasources/overview.md).
149
152
 
@@ -160,7 +163,7 @@ For advanced patterns beyond data fetching, load the relevant reference when the
160
163
  | Quick syntax check — import paths, hook signatures, mutation call shapes, field mapping | [references/quick-reference.md](references/quick-reference.md) |
161
164
  | Small reusable patterns — `localStorage` cross-page state, clipboard copy button | [references/common-patterns.md](references/common-patterns.md) |
162
165
  | Writing Airtable Automation Scripts / Scripting Extension scripts / Airtable formulas — companion to Softr blocks for cross-table cascades and computed values | [references/airtable-automations.md](references/airtable-automations.md) |
163
- | AI-assisted Softr DB schema discovery / field-ID lookup / record reads via the official Softr MCP server (sibling to the in-block `useRecords` workflow) | [references/softr-database-mcp.md](references/softr-database-mcp.md) |
166
+ | The official **Softr MCP server** — schema discovery and record reads for Softr DB, field-level browsing of connected Airtable / Google Sheets / Notion / Supabase integrations, and **creating, editing, versioning, and deploying Vibe Coding blocks directly** (`get_vibe_coding_docs`, `create_vibe_coding_block`, `connect_vibe_coding_block_data_source`, ...) | [references/softr-mcp.md](references/softr-mcp.md) |
164
167
  | Restyling Softr's **native shell — header / footer / nav / dropdowns / page background** (not a block; it's Softr chrome, done with global Custom Code CSS): stable selectors vs. hashed classes, floating "island" header+footer, the dropdown blank-space grid fix, the multi-layer page-background stacking, restyle-vs-replace | [references/native-chrome-styling.md](references/native-chrome-styling.md) |
165
168
  | Adding a **dynamic date filter or custom filter control to a native List/Grid block** (via a Custom Code Static block, not a Vibe block): drive the block's conditional filter with `{URL_PARAM:…}`, the empty-param "match nothing" wide-range sentinel, inject the control into the filter row and keep it alive across Softr's re-renders | [references/native-block-filters.md](references/native-block-filters.md) |
166
169
 
@@ -361,7 +364,7 @@ var askAi = useNavigationSetting({
361
364
  - `OPEN_CHAT` — opens Softr's AI chat. **No `destination` or `openIn` needed** — it's the cheapest "Ask AI" button to wire up. **GOTCHA:** Softr's AI pulls context from the block that triggered the chat, NOT from the page. If the block has no data source connected, `chat/prepare` returns HTTP 500 ("Failed to prepare AI assistant") even though the chat panel opens. Fix: in Softr Studio, connect the block to whatever data source the AI should read from — even if the block doesn't read or write any records itself, the connection is what gives the AI context. Verified by direct experiment, May 2026: a button-only helper block with no data source caused this exact failure; connecting it to the same table as the main block fixed it without any code change.
362
365
  - `OPEN_URL` — opens an external URL. Needs `destination` (the URL) + `openIn` (`"SELF"` | `"TAB"`).
363
366
  - `OPEN_PAGE` — navigates to a Softr page in-app. Needs `destination` (page path) + `openIn` (`"SELF"` | `"TAB"` | `"MODAL"`).
364
- - `TRIGGER_CUSTOM_WORKFLOW` — runs a Softr workflow. Needs the workflow id in `destination`.
367
+ - `TRIGGER_CUSTOM_WORKFLOW` — runs a Softr workflow. The documented setting shape is `{ action: "TRIGGER_CUSTOM_WORKFLOW" }` with no `destination` (same shape as `OPEN_CHAT`); the builder picks the workflow in the block's settings panel. Not verified live whether a `destination` is also accepted — don't rely on one.
365
368
 
366
369
  When the action navigates to a record-specific page, pass the runtime record id via the `recordId` prop on `<NavigationAction>` (not on the setting) so Softr can resolve dynamic URLs:
367
370
 
@@ -446,17 +449,13 @@ Non-negotiable rules enforced by the Softr platform:
446
449
  7. **No nested arrays in settings** — Use text with separator, split in code.
447
450
  8. **Default export required** — `export default function Block()`.
448
451
  9. **Container wrapping** — Always wrap in `<div className="container py-0"><div className="content">`. Vertical padding lives on the inner wrapper and depends on block placement (see "Block Placement & Page Spacing").
449
- 10. **No optional chaining or nullish coalescing** — Softr's bundler fails on `?.` and `??`. Use:
450
- - `(user && user.email) || ""` instead of `user?.email ?? ""`
451
- - `(data && data.pages) ? data.pages.flatMap(function(p) { return p.items; }) : []`
452
-
453
- ⚠️ **Possibly stale do not relax on this note alone.** July 2026: a block scaffolded by Studio's own
454
- AI assistant used `peopleData?.pages` and `status?.label` and rendered correctly in a published app,
455
- which contradicts this rule. That's a single observation and the failure mode is a block that won't
456
- compile, so the rule stands until someone re-tests it deliberately. Writing `&&` costs nothing and
457
- works under either behaviour. If you confirm `?.` compiles reliably, update this constraint, the
458
- self-validation checklist, and the Style Conventions note together.
459
- 11. **Airtable: use column names, not fld... IDs** — See [datasources/airtable.md](datasources/airtable.md).
452
+ 10. **Inline options literals for data hooks** — `useRecords` fails to compile when its options
453
+ object is passed through a variable or wrapper function. Build the options object inline at the
454
+ call site; share `q.select` mappings between hooks, not whole options objects. (Same
455
+ static-analysis family as the `q.select()` / `datasource.define()` literal rules. Verified live
456
+ 2026-08-25hit in a production block whose options came from a wrapper function; the fix was
457
+ changing the wrapper to take the hook's *result* instead.)
458
+ 11. **Airtable, Notion, Google Sheets: use field NAMES, not IDs** — `q.select()` values are field names for these three sources; Softr Database and Supabase use field IDs (Supabase = SQL column name). Getting this wrong fails silently: the block compiles and saves, then renders empty. See [datasources/airtable.md](datasources/airtable.md).
460
459
  12. **Record fields nested under `fields`** — Access via `record.fields.alias`, not `record.alias`.
461
460
  13. **ONE `useRecords` per datasource** — filter client-side rather than issuing several queries against
462
461
  the same table. A block CAN connect to multiple data sources and call `useRecords` once per source;
@@ -466,19 +465,21 @@ Non-negotiable rules enforced by the Softr platform:
466
465
  15. **Do NOT `import React from 'react'`** — Use named imports for hooks.
467
466
  16. **No CSS modules or styled-components** — Tailwind only.
468
467
  17. **setTimeout for scroll** -- Wrap programmatic scroll in `setTimeout(fn, 0)`.
469
- 18. **`fetchNextPage` inside `useEffect` only** -- Calling it during render causes infinite re-render loops. The component calls `fetchNextPage`, which updates data, which triggers re-render, which calls `fetchNextPage` again.
468
+ 18. **Never call `fetchNextPage` in the render body** -- render calls `fetchNextPage`, which updates data, which triggers re-render, which calls it again: infinite loop. Call it from an event handler (the official Load More pattern: `<button onClick={() => fetchNextPage()} disabled={isFetching}>`) or from a guarded `useEffect` when intentionally auto-loading all pages.
470
469
  19. **All hooks before any conditional `return`** -- Hooks must be called in the same order every render. A hook declared after a conditional `return` causes React error #310.
471
470
  20. **Relative paths in navigation** -- Use `/page-name?recordId=...`, never hardcoded domains like `app.client.com/page`.
471
+ 21. **Recompiles reset Action permissions** -- Every code recompile/redeploy resets the block's
472
+ auto-registered Actions to **default permissions**. Do the Actions-tab permission tightening pass
473
+ only after the LAST redeploy, and re-check it after any future one. Verified live 2026-08-25
474
+ across a 15-block deployment. See [datasources/writing.md](datasources/writing.md#how-actions-work-studios-actions-tab).
472
475
 
473
- ## Style Conventions (preferred, not enforced)
476
+ ## Style Conventions
474
477
 
475
- The conventions below improve consistency across the skill's examples but are NOT enforced by the Softr bundler. Softr's AI assistant in Studio outputs `const` and arrow functions, and both compile and run fine. Adopting these conventions makes hand-written blocks visually uniform with the rest of the skill, but blocks generated by Softr's AI work without conversion.
478
+ **The current platform compiles TypeScript with modern syntax** optional chaining (`?.`), nullish coalescing (`??`), arrow functions, `const`/`let`, generics verified live against `get_vibe_coding_docs` and a 15-block production deployment on 2026-08-25. Write new blocks in modern TS (`.tsx`); it matches what Softr's own Studio AI emits.
476
479
 
477
- - Prefer `var` over `const` / `let`
478
- - Prefer `function() {}` over arrow functions in JSX callbacks and component props
479
- - Field-value helper property priority: `label` -> `name` -> `title`
480
+ History, kept so old guidance elsewhere is recognizable as superseded: until mid-2026 this skill mandated `var` + `function(){}` and banned `?.` / `??` because the then-current bundler failed on them (verified April 2026; a Studio-scaffolded block already contradicted it by July 2026). That platform behavior is gone. Existing var-style blocks remain valid — the compiler accepts both styles — so don't churn a working block just to modernize its syntax, and don't "fix" modern syntax back to var-style when editing.
480
481
 
481
- If you're editing a block originally generated by Softr's AI assistant, you can convert to skill style for consistency or leave the AI's syntax as-is. Both produce a working block. Only `?.` and `??` (Hard Constraint #10) are actual bundler blockers verified by direct experiment, April 2026. *(But see the caveat on constraint #10: a Studio-scaffolded block using `?.` rendered fine in July 2026. Keep avoiding it until that's deliberately re-tested.)*
482
+ Still house conventions: the field-value helper is named `getFieldValue()`, with property priority `label` -> `name` -> `title`.
482
483
 
483
484
  ## Anti-Patterns Checklist
484
485
 
@@ -84,7 +84,7 @@ Two throwaway diagnostic blocks you can drop into a page to diagnose data proble
84
84
 
85
85
  Use when records load but fields come back empty, or when you're not sure which Field IDs exist on a table.
86
86
 
87
- **Important caveat for Softr Database:** `q.select({})` returns record IDs with empty `fields: {}` -- it does NOT dump all fields. Verified by direct experiment, April 2026. Use one of the alternatives below for Softr DB. The empty-select pattern still works for Airtable and other sources where field IDs come back automatically.
87
+ **Important caveat for Softr Database:** `q.select({})` returns record IDs with empty `fields: {}` -- it does NOT dump all fields. Verified by direct experiment, April 2026. Use one of the alternatives below for Softr DB. The empty-select pattern still works for Airtable and other sources where the field keys come back automatically (for Airtable / Notion / Google Sheets those keys are field NAMES — the same names `q.select()` uses; see the name-vs-ID rule in [../references/softr-mcp.md](../references/softr-mcp.md#browsing-integrations-external-data-sources)).
88
88
 
89
89
  For Airtable and other non-Softr-DB sources:
90
90
 
@@ -111,7 +111,7 @@ export default function Block() {
111
111
 
112
112
  **For Softr Database, find field IDs via:**
113
113
 
114
- 1. **Softr Database MCP server (recommended for AI-assisted workflows)** -- if you're collaborating with an AI assistant (Claude Code, Claude Desktop, Cursor, ChatGPT, Mistral) to write Vibe Coding blocks, the official Softr MCP server is the cleanest path. The AI calls schema/list-fields tools directly against your workspace and reads back every field's `id`, `name`, `type`, and dropdown option UUIDs -- no copy-paste, no transcription errors. Full setup, scopes, and scope limitations (Softr DB only -- does NOT cover Airtable / external sources) in [../references/softr-database-mcp.md](../references/softr-database-mcp.md).
114
+ 1. **Softr MCP server (recommended for AI-assisted workflows)** -- if you're collaborating with an AI assistant (Claude Code, Claude Desktop, Cursor, ChatGPT, Mistral) to write Vibe Coding blocks, the official Softr MCP server is the cleanest path. The AI calls schema/list-fields tools directly against your workspace and reads back every field's `id`, `name`, `type`, and dropdown option UUIDs -- no copy-paste, no transcription errors. The same server also browses connected Airtable / Google Sheets / Notion / Supabase integrations down to field level. Full setup and permissions in [../references/softr-mcp.md](../references/softr-mcp.md).
115
115
 
116
116
  2. **`get-softr-database` CLI script (bundled, no MCP needed)** -- a Python CLI bundled with this skill at `~/.claude/skills/softr-vibe-coding/tools/get-softr-database.py`. Exports the full schema (every table, every field, all dropdown option UUIDs) to `~/Desktop/softr-database-<id>-<timestamp>.json`. Run with `python3 ~/.claude/skills/softr-vibe-coding/tools/get-softr-database.py <database_id>` (prompts for API key) or set `SOFTR_API_KEY=xxx` env var to skip the prompt. Stdlib only, no `pip install`. Best when you want a portable JSON dump for sharing in chat, archiving, or diffing across schema versions. Full usage in [softr-database.md](softr-database.md#bundled-cli-script-get-softr-database).
117
117
 
@@ -119,7 +119,7 @@ export default function Block() {
119
119
  - Each field's `id`, `name`, `type`, and `options`
120
120
  - For dropdown / SELECT fields: the full `choices` array with every option's `id` (UUID), `label`, and `color`
121
121
 
122
- Use this when scaffolding a block that needs many field IDs at once, or to look up dropdown option UUIDs needed for write payloads. **When working with an AI assistant without the MCP installed**, paste this JSON response into the chat -- second-best way to share accurate field IDs and dropdown UUIDs in one shot.
122
+ Use this when scaffolding a block that needs many field IDs at once, or to see a SELECT field's full choice list. (Write payloads take the option **label**, not the UUID — verified 2026-08-25, see [writing.md](writing.md#dropdown--single-select-softr-database) — but the exported labels are exactly what you need for a correct write vocabulary.) **When working with an AI assistant without the MCP installed**, paste this JSON response into the chat -- second-best way to share accurate field IDs and choice lists in one shot.
123
123
 
124
124
  4. **Inline in Studio (one field at a time)** -- in the Data tab, click a field's name to open its edit drawer. The field ID appears next to the "Field name" label (e.g. `ID: 37fts`). Fastest for spot-checking a single field.
125
125
 
@@ -182,7 +182,7 @@ export default function Block() {
182
182
 
183
183
  ### User Inspector Block
184
184
 
185
- Use when debugging permissions, user groups, or the `useCurrentUser()` vs `window.__softr_current_user` distinction. Renders both side-by-side as JSON. This catches a common surprise: `userGroups` only lives on the `window.__softr_current_user` object, not on `useCurrentUser()`.
185
+ Use when debugging permissions, user groups, or the `useCurrentUser()` vs `window.__softr_current_user` distinction. Renders both side-by-side as JSON. This catches a common surprise: `userGroups` only lives on the `window.__softr_current_user` object, not on `useCurrentUser()`. (Custom user-record fields, by contrast, ARE available through the hook — `useCurrentUser({ properties: { alias: "FIELD_ID" } })` exposes them under `user.properties`; see [reading.md](reading.md#current-user).)
186
186
 
187
187
  ```jsx
188
188
  import { useCurrentUser } from "@/lib/user";
@@ -23,7 +23,7 @@ Use the Field Inspector block to determine exact field IDs for your monday.com b
23
23
  | Dropdown | Yes | |
24
24
  | Checkbox | Yes | |
25
25
  | Email | Yes | |
26
- | Phone | Yes | |
26
+ | Phone | Yes | Writes rejected unless value is `+` followed by digits only — no spaces, dashes, or parentheses. See Gotchas |
27
27
  | Link | Yes | |
28
28
  | People | Yes | |
29
29
  | File | Yes | |
@@ -42,6 +42,13 @@ monday.com enforces API rate limits based on your monday.com plan tier. Softr re
42
42
  - **API token authentication only.** Uses a Personal API Token from monday.com's admin settings, not OAuth.
43
43
  - **Connected Boards** (linked records between boards) are supported but may have limitations depending on the board configuration.
44
44
  - **Mirror columns are read-only.** These reflect data from connected boards and cannot be written to from Softr.
45
+ - **Phone writes are strict.** monday.com is the datasource the official Softr guide names as rejecting formatted phone values. Send unformatted international format only (`+` then digits, e.g. `+12125550100`) and sanitize before `mutate()`:
46
+
47
+ ```tsx
48
+ const sanitizePhone = (raw: string) => raw.replace(/[^\d+]/g, "").replace(/(?!^)\+/g, "");
49
+ ```
50
+
51
+ See [writing.md](writing.md#phone).
45
52
  - **Status columns** use monday.com's internal label/index system. Map these appropriately in your block logic for display purposes.
46
53
  - **Subitems** may require additional configuration to surface correctly in Softr blocks.
47
54
 
@@ -45,6 +45,8 @@ Applies to: `useRecords`, `useRecord`, `useLinkedRecords`, `useFieldOptions`, `u
45
45
 
46
46
  Does **not** apply to `useUpload` and `useCurrentRecordId` — those are app-level and take no `from`.
47
47
 
48
+ `useProxyFetch` (REST API sources) is **also datasource-scoped**, but takes the alias as its **function argument** rather than a `from:` option — `useProxyFetch(ds.store)`. With a single datasource `useProxyFetch()` works bare; once the block has more than one, omitting the alias throws, exactly like omitting `from:` on a record hook. See [rest-api.md](rest-api.md#multiple-datasources).
49
+
48
50
  ## The values must be inline string literals
49
51
 
50
52
  Softr statically analyses `datasource.define()`, exactly like `q.select()`. Hoisting the ids
@@ -80,8 +82,8 @@ block, plus one useRecords per source. Output code only.
80
82
  ```
81
83
 
82
84
  Code generation is bound to the block's real connections, so the ids come out correct — the
83
- same reason Softr's assistant reliably inlines select-option UUIDs when scaffolding a form but
84
- invents them when asked to recite one.
85
+ same reason Softr's assistant reliably inlines real select options when scaffolding a form but
86
+ invents values when asked to recite one.
85
87
 
86
88
  **Then verify by running it.** The scaffold renders a list per source; if real rows appear
87
89
  under each heading, every alias maps to the table you think it does. A wrong uuid fails safe
@@ -10,8 +10,8 @@ Document and database platform used as a data source for Softr. Requires a Basic
10
10
  4. If a database does not appear in the list: open Notion, click the ellipsis menu (...) on the database page, go to Connections, find Softr, and click Allow.
11
11
  5. Multiple Notion workspaces can be connected to a single Softr app.
12
12
 
13
- ## Vibe Coding Field IDs
14
- Use the Field Inspector block to determine exact field IDs for Notion database properties. Property names in Notion are typically used as field IDs.
13
+ ## Vibe Coding Field References
14
+ `q.select()` values for Notion are the database's **property NAMES**, not internal IDs same rule as Airtable and Google Sheets (official developer guide). Getting this wrong fails silently: the block compiles and saves, then renders with empty data. Use the Field Inspector block to confirm the exact property names when unsure.
15
15
 
16
16
  ## Supported Fields
17
17
 
@@ -32,7 +32,7 @@
32
32
  - Data is connected to **dynamic blocks** (List, Grid, Table, Kanban, Chart, Form, etc.) in the Softr Studio.
33
33
  - **Multiple data sources** can coexist in a single Softr app, even on the same page.
34
34
  - Softr IP addresses to whitelist for secured databases: `3.120.79.212`, `3.123.159.186`, `52.58.246.121`
35
- - **AI-assisted schema discovery via MCP:** Softr Database is the only source with a first-party MCP server (`https://mcp.softr.io/mcp`). When installed, the AI reads schema and field IDs directly from your workspace -- no manual paste-in. External sources still need the manual workflows in [fields.md](fields.md). See [softr-database.md](softr-database.md#ai-assisted-workflows-softr-database-mcp-server).
35
+ - **AI-assisted schema discovery via MCP:** the official Softr MCP server (`https://mcp.softr.io/mcp`) reads schema and field references directly from the workspace -- Softr Databases in full, plus browsable field-level access to connected **Airtable, Google Sheets, Notion, and Supabase** integrations. Other external sources still need the manual workflows in [fields.md](fields.md). See [../references/softr-mcp.md](../references/softr-mcp.md).
36
36
 
37
37
  ## User Sync Availability
38
38
 
@@ -58,6 +58,12 @@ var items = (data && data.pages) ? data.pages.flatMap(function(p) { return p.ite
58
58
 
59
59
  **CRITICAL:** Only ONE `useRecords` call **per datasource**. Fetch that table's data in one call and filter client-side. Multiple `useMetric` calls ARE allowed.
60
60
 
61
+ **CRITICAL:** The options object must be an **inline literal** at the call site. Passing it
62
+ through a variable or a wrapper function (`useRecords(buildOpts())`) **fails to compile** —
63
+ verified live 2026-08-25, hit in a production block; the fix was changing the wrapper to take
64
+ the hook's *result* instead. Share `q.select` mappings between hooks, never whole options
65
+ objects.
66
+
61
67
  A block can connect to **several data sources** and call `useRecords` once per source — declare them with `datasource.define()` and pass `from:` on every hook. See [multi-datasource.md](multi-datasource.md). (This replaces the old one-table-per-block limit; blocks no longer need an invisible helper block just to read a second table.)
62
68
 
63
69
  ### Loading All Records (Auto-Pagination)
@@ -99,7 +105,7 @@ var result = useLinkedRecords({
99
105
  sortOrder: "ASC", // "ASC" | "DESC"
100
106
  search: "", // optional search string
101
107
  enabled: true, // defer loading until needed
102
- count: 50,
108
+ count: 50, // optional page size — default 100, max 1000
103
109
  });
104
110
 
105
111
  var options = (result.data && result.data.pages) ? result.data.pages.flatMap(function(p) { return p.items; }) : [];
@@ -128,8 +134,9 @@ var statusOptions = useFieldOptions({
128
134
 
129
135
  // statusOptions → { options: [...], isLoading: bool }
130
136
  // statusOptions.options → [{ id: "sel...", label: "Active", color: "greenLight1" }, ...]
131
- // id — the option's UUID, used in mutate payloads
132
- // label display string
137
+ // id — stable option id (use as a React key; NOT needed in mutate payloads — SELECT
138
+ // fields write by LABEL string on the current platform, verified 2026-08-25)
139
+ // label — display string AND the value to write in mutate payloads
133
140
  // color — Airtable swatch color name (optional; handy for tinting chips)
134
141
  ```
135
142
 
@@ -145,8 +152,8 @@ same `select` object can be shared by both. Reuse one `select` for many fields a
145
152
 
146
153
  **When to use this vs. hardcoding:**
147
154
 
148
- - **Use `useFieldOptions`** when option IDs / labels could change post-deploy — selects with rapidly-evolving lists, user-editable choices, or any case where re-pasting blocks for an option rename is annoying. Cross-table case: to render a select field from table B inside a block bound to table A (e.g. an intake form bound to Jobs that needs the Wigs `Color` options), put the `useRecords` + `useFieldOptions` in a hidden helper block bound to table B and publish the options to a `window` global (see [helper-blocks.md](../references/helper-blocks.md)).
149
- - **Hardcode** when the option set is stable and frequently referenced (e.g. a status enum that drives a state machine), so the IDs live in source and rename-safety is enforced by greppable constants. A robust middle ground: prefer the live options, fall back to a hardcoded list per field so the UI still renders if the helper hasn't published yet.
155
+ - **Use `useFieldOptions`** when option labels could change post-deploy — selects with rapidly-evolving lists, user-editable choices, or any case where re-pasting blocks for an option rename is annoying. Since SELECT fields write by label (verified 2026-08-25), live options also keep write payloads rename-proof: render and write `option.label`. Hardcoded labels remain fine as a display-only loading fallback while the live options fetch. Cross-table case: to render a select field from table B inside a block bound to table A (e.g. an intake form bound to Jobs that needs the Wigs `Color` options), put the `useRecords` + `useFieldOptions` in a hidden helper block bound to table B and publish the options to a `window` global (see [helper-blocks.md](../references/helper-blocks.md)).
156
+ - **Hardcode** when the option set is stable and frequently referenced (e.g. a status enum that drives a state machine), so the label vocabulary lives in source and rename-safety is enforced by greppable constants. A robust middle ground: prefer the live options, fall back to a hardcoded list per field so the UI still renders if the helper hasn't published yet.
150
157
 
151
158
  `useFieldOptions` is the read-side equivalent of using `useLinkedRecords` for foreign records — it abstracts away the field's option store. Items are shaped `{ id, label, color }` (note: `label`, not `title` like `useLinkedRecords`).
152
159
 
@@ -198,9 +205,22 @@ import { useCurrentUser } from "@/lib/user";
198
205
  var user = useCurrentUser();
199
206
  // Returns null if not logged in
200
207
  // Fields: { id, fullName, firstName, lastName, email, avatar } (all string or null)
208
+ // Note: `id` is only present when user sync is enabled.
209
+ ```
210
+
211
+ **Custom user-record fields** are first-class: pass a `properties` map (aliased like a `select` query) and read them under `user.properties`:
212
+
213
+ ```jsx
214
+ var user = useCurrentUser({
215
+ properties: {
216
+ stripeId: "FIELD_ID1",
217
+ plan: "FIELD_ID2",
218
+ },
219
+ });
220
+ // user.properties.stripeId, user.properties.plan
201
221
  ```
202
222
 
203
- **For user groups, role, or custom fields** -- use `window.__softr_current_user` (NOT `useCurrentUser()`):
223
+ **For user groups / role ONLY** -- these are not exposed by `useCurrentUser()` (not even via `properties`); use `window.__softr_current_user`:
204
224
 
205
225
  ```jsx
206
226
  var softrUser = window.__softr_current_user || {};
@@ -100,6 +100,24 @@ export default function Block() {
100
100
  - Softr injects the authentication headers configured in the data source automatically
101
101
  - API keys are **never exposed** in client-side code
102
102
  - The response is the raw API JSON -- access fields directly (e.g., `item.name`, not `record.fields.name`)
103
+ - **The proxy only supports text payloads** -- streams, `FormData`, and file uploads won't work. Serialize request bodies as JSON/text.
104
+
105
+ ### Multiple datasources
106
+
107
+ When the block has more than one datasource, `useProxyFetch` needs to know which source to route through. Unlike the record hooks (which take a `from:` option), it takes the alias as its **function argument**:
108
+
109
+ ```jsx
110
+ import { datasource, useProxyFetch } from "@/lib/datasource";
111
+
112
+ var ds = datasource.define({
113
+ store: "ds_id_1", // the REST API datasource
114
+ orders: "ds_id_2", // e.g. a Softr DB table alongside it
115
+ });
116
+
117
+ var proxyFetch = useProxyFetch(ds.store); // alias as ARGUMENT, not from:
118
+ ```
119
+
120
+ With a single datasource `useProxyFetch()` works with no argument; once there's more than one, omitting the alias **throws**. Define the aliases with `datasource.define`, the same pattern the record hooks use — see [multi-datasource.md](multi-datasource.md).
103
121
 
104
122
  ### Dynamic Query Parameters
105
123
 
@@ -124,7 +142,7 @@ var result = useQuery({
124
142
 
125
143
  ### POST Requests
126
144
 
127
- `proxyFetch` accepts the same options as standard `fetch`:
145
+ `proxyFetch` accepts the same options as standard `fetch`, but bodies must be text (JSON strings etc.) — `FormData`, streams, and file uploads are not supported by the proxy:
128
146
 
129
147
  ```jsx
130
148
  var result = useQuery({
@@ -172,6 +190,7 @@ fetch("https://workflows-api.softr.io/v1/workflows/WORKFLOW_ID/executions/EXECUT
172
190
 
173
191
  - Supports GET and POST methods only in the data source connector (no PUT, PATCH, DELETE -- use Softr Action buttons with custom API call actions for those)
174
192
  - Response data must be parseable JSON
193
+ - **Proxy payloads are text-only** -- streams, `FormData`, and file uploads through `proxyFetch` fail. For file-to-record uploads use `useUpload` (see [writing.md](writing.md#file-uploads)), which targets the connected record datasource; uploading files to an arbitrary external API needs a different mechanism (e.g. a Softr workflow / webhook receiver)
175
194
  - No 2-way user sync
176
195
  - Requires Business or Enterprise plan
177
196
 
@@ -6,5 +6,5 @@ These patterns apply to all data sources that use `useRecords` + `q.select()` (e
6
6
  |---|---|
7
7
  | **Multiple data sources in one block** — `datasource.define()`, the `from:` parameter, getting the datasource UUIDs | [multi-datasource.md](multi-datasource.md) |
8
8
  | Query builder, useRecords, useRecord, useLinkedRecords, filtering, sorting, pagination, current user, metrics, chart data | [reading.md](reading.md) |
9
- | Record mutations (create/update/delete), file uploads, linked record format, cross-table REST API writes | [writing.md](writing.md) |
9
+ | Record mutations (create/update/delete), sequential multi-row write queues, file uploads, linked record format, cross-table writes | [writing.md](writing.md) |
10
10
  | `getFieldValue()` helper, field type shapes, record structure, debug utilities (Field Inspector, User Inspector) | [fields.md](fields.md) |