- Brand
- Softr
- Category
- Operations
- Primary Subcategory
- AI App & Website Builders
Integration details
Description
Softr is an AI app builder that turns your data into apps, portals, and automations. This connector gives ChatGPT direct access to your Softr workspace, so you can go from an idea to a working, published app in a single conversation. Databases: create tables and the fields to match your project, structure and import records, and browse data sources you have already connected. Applications: generate an app on top of your data, add and arrange pages and blocks, configure sign-up and login, set user groups and page permissions, and manage app users. Extend any page with a custom code block that ChatGPT writes for you, with every version saved so you can roll back. Workflows: build multi-step automations with triggers, actions, and branching logic, test each step against real data, and publish when it works. ChatGPT can preview the result and, when you ask it to, publish live. Everything it makes appears in Softr Studio exactly like work done by hand, so you and your team keep editing in the visual builder afterward - nothing is locked into the chat. Day to day, ChatGPT can also work with your data directly: search, filter, and aggregate records, and create or update them in bulk. Every action runs with your own permissions, and you choose the access level per workspace when you connect: read-only, edit, or full access.
- Integration type
- Plugin
- Verification status
- Not applicable
- Platform
- ChatGPT
- Primary Subcategory
- AI App & Website Builders
- Secondary Subcategories
- None listed
- Brand
- Softr
- Access
- Account required
- First tracked
- 2026-09-17
- Tool count
- 110
- Market
- US
The Primary Subcategory used for this profile’s headline score.
Other Subcategories where the Integration is listed.
Your score is coming
ChatGPT now suggests Plugins on its own when they match a user's request.Your Plugin Discovery Score measures how often yours appears, and it will show here as soon as it’s ready.
What discovery looks like

Get alerts for Softr
Get updates when Softr’s Discoverability Score or category rank changes.
Competing in ChatGPT AI App & Website Builders
View Category110 tools agents can invoke
Add an end-user to an application (a user of the published app, not a builder/collaborator). Both name and email are required. Returns the created user: id, email, name, applicationId and timestamps. Fails if a user with this email already exists in the app. Use application_list first to find valid application IDs.
application_add_user
Adds a Branch step after an existing node. The step splits the workflow into branches that run in parallel, and each branch starts with its own FILTER node, which holds the branch condition. Name the branches in `branchTitles`, from left to right, and the command creates one branch for each name. The result lists the new FILTER node ids in the same order in `newFilterNodeIds`, so the first step of each branch uses one of them as its `initiateId`. To add a branch to an existing Branch step, use `workflow_create_branch`. To rename an existing branch, use `workflow_rename_node` on its FILTER node, because a note is not a branch name. Rules: - BRANCH is not composite - use parent's compositeNodeId This tool saves the change to the workflow immediately. Its result contains real IDs (see resultMetadata) — use them in subsequent calls. There is no result-placeholder mechanism: never invent IDs. Result shape (JSON Schema): {"properties":{"newNodeId":{"type":"string"},"newFilterNodeIds":{"type":"array","items":{"type":"string"}}},"required":["newNodeId","newFilterNodeIds"]}
workflow_add_branch_node
Aggregate table data with grouping and metrics (SUM, AVG, COUNT, MIN, MAX, DISTINCT). Returns pre-aggregated rows, so the caller does not read every record and total them itself. metrics (required): [{"field": "fieldId", "aggregation": "SUM"|"AVG"|"MIN"|"MAX"|"COUNT"|"DISTINCT"}] For COUNT aggregation, the field value is ignored (counts all rows). groupByFields (optional, max 2): [{"field": "fieldId", "options": {"timeInterval": "DAY"|"WEEK"|"MONTH"|"QUARTER"|"YEAR"|"HOUR_OF_DAY"|"DAY_OF_WEEK"|"MONTH_OF_YEAR"|"QUARTER_OF_YEAR", "displayFormat": "YYYY-MM-DD"}}] options is only needed for date/datetime fields. If omitted, returns a single row with the metric totals. filter (optional): Same format as database_search_records filter. Call database_get_field_reference for the full filter operator reference. sortingOptions (optional): [{"sortingField": "fieldId", "sortType": "ASC"|"DESC"}] — only for grouped queries. Field keys in groupByFields, metrics, and sortingOptions must be field IDs, taken from database_get_table or database_list_fields if you do not already have them.
database_aggregate_records
Configure the email sender for an application: the name and address the app's emails (invites, magic links, notifications) are sent from. Which fields you pass selects the mode: - email + name: register the sender on the workspace (or rename an existing one) and assign it to this app. Needs workspace-wide write access and a plan with custom senders. - email only: assign an EXISTING workspace sender to this app (use workspace_list_email_senders to see them; fails if none matches). App-level write access is enough for this. - neither: reset the app to the default Softr sender (its subdomain @softr.app address). Whichever mode you use REPLACES the app's current sender, and the previous one is not kept. IMPORTANT: registering a new sender triggers a verification email to the address, which cannot be undone once sent, and the response's `confirmed` field is the source of truth. While confirmed=false the app KEEPS sending from the default Softr sender — tell the user the sender is pending confirmation and that someone with access to that mailbox must click the confirmation link; do not report the sender as active. A sender on a custom domain also needs DKIM and Return-Path DNS records so receiving servers trust its mail: the response lists them in dnsRecords (dkim and returnPath), with domainVerified false until they are verified. Give them to the user, then check them with workspace_verify_email_sender. Use application_list to find application IDs.
application_configure_email_sender
Configure end-user sign-up for an application. The policy you pass REPLACES the current one, and narrowing it withdraws sign-up from people who could register a moment ago: DISABLED stops everyone, DOMAIN_RESTRICTED stops every address outside allowedDomains. Confirm either move with the user before calling it. Set policy to one of: - DISABLED: no one can sign up - OPEN_TO_PUBLIC: anyone can sign up - DOMAIN_RESTRICTED: only people whose email is in allowedDomains can sign up (requires allowedDomains) allowedDomains is a list of email domains like ["example.com"]; it is used only with DOMAIN_RESTRICTED and ignored for the other policies. Returns the applicationId, the resulting policy and allowedDomains. Only applies to apps that support login/sign-up; studio-api rejects apps without login settings. Use application_list to find application IDs.
application_configure_sign_up
Set when end users of an application are logged out. sessionExpiration is how long a login lasts: 24_HOURS, 7_DAYS, 30_DAYS or 90_DAYS (the default). inactivityTimeout logs a user out after a period without activity: 5_MINUTES, 15_MINUTES, 30_MINUTES, 1_HOUR, 3_HOURS or NEVER (the default). Pass the values exactly as written here. Omit either to keep its current value, but pass at least one. A value you pass replaces the current one. Returns both resulting values. Shorter values make users log in more often, so confirm them with the user first. Values other than the defaults need a paid plan with advanced security; on other plans the call fails with a payment-required error, so relay it rather than reporting the setting as changed. Only applies to apps that support login; studio-api rejects apps without login settings. Use application_list to find application IDs.
application_configure_user_sessions
Wire a specific table or sheet, from within a workspace data source, to a vibe-coding block, making it queryable in code via hooks like useRecords()/useRecord()/mutation hooks. A block can have several data sources at once, each identified by its own dataSourceId; calling this again with the same dataSourceId on the same block replaces that connection, so treat it as a real change to the block, not a no-op re-wiring. Every data source type Softr supports can be connected. Resolve the IDs in this order: integration_list (integrationId + type), then integration_list_databases (databaseId), then — for SUPABASE, POSTGRESQL, SQL_SERVER, SNOWFLAKE, GOOGLE_BIGQUERY, SMARTSUITE and CLICKUP only — integration_list_schemas (schema), then integration_list_tables (tableId/tableName). Proxy-only integrations (Gmail, Slack, OpenAI, ...) are the only kind that cannot be connected. This only persists the data binding — it does NOT write or change any code. A brand-new block never has a datasource wired yet, and code referencing hooks like useRecords() compiles fine even before this is called, but fails at runtime until it is. Follow up with vibe_coding_block_update_code (or search-replace) to actually reference it: define a human-readable name for it via `datasource.define({ <name>: "<dataSourceId>" })` (the name is just for code readability, chosen freely), then pass `from: ds.<name>` on hook calls — vibe_coding_block_get_code's `dataSources` entry for this dataSourceId will then include the resolved fields and fieldReferenceKey to use in that code. Once the code referencing it is in place, give the user the Studio link to this page — the server instructions carry the exact URL pattern — and offer to show them the app with application_preview or make it live with application_publish.
vibe_coding_block_connect_data_source
Create a new blank application (app) and return its details: generated id, name, workspaceId, applicationType, home page id and timestamps. The app starts blank with a default home page and is NOT published — use application_publish when the user wants it live, and application_preview to look at it first. Optionally pass workspaceId to choose which workspace the app is created in; omit it to use the user's default workspace. Use workspace_list to resolve a workspace name to an ID if needed. Optionally pass name to set the app's name; omit it to let Softr auto-generate one.
application_create
Create a link to upload a file to a record's ATTACHMENT field. The file is added to the field automatically, and the files already in the field stay. If you can run shell commands or make HTTP requests, upload the file yourself with the returned curlExample, for example: curl -F "file=@/path/to/file" <uploadUrl> Only if you cannot, give uploadUrl to the user and ask them to open it in a browser and drop the file there. After the upload you do not need to do anything. To check the result, call database_get_record. Anyone with the link can upload to this field until expiresAt. After that the link stops working. One link takes at most 10 files of at most 25 MB each. For more files, create a new link. For a file that is on the internet, use database_attach_file_from_url. To upload a file to a new record, create the record first.
database_create_attachment_upload_link
Adds one more branch to an existing Branch step, at the right end. The new branch starts with a FILTER node, and the result returns its id in `newFilterNodeId`, so the first step of the branch uses it as its `initiateId`. To create a new Branch step with several named branches at once, use `workflow_add_branch_node` with `branchTitles`. This tool saves the change to the workflow immediately. Its result contains real IDs (see resultMetadata) — use them in subsequent calls. There is no result-placeholder mechanism: never invent IDs. Result shape (JSON Schema): {"properties":{"newFilterNodeId":{"type":"string"}},"required":["newFilterNodeId"]}
workflow_create_branch
Create a new database in the specified workspace. Requires write permission to the workspace. Returns the created database with its generated ID. On the vibe coding application endpoints the new database is empty. Create its tables with database_create_table. On every other endpoint the new database already contains one empty starter table named "Table 1", holding Name, Description and Status fields - the same table the Softr builder creates. Reshape that table with database_update_table, database_create_field and database_update_field instead of calling database_create_table, which would leave the starter table behind as a second, empty one. A database with that starter table is not empty, so database_delete refuses it without force=true. Pass timezone and locale only when you know them, for example because the user named their city, country or language. Do not guess them, and do not pass UTC as a stand-in for "unknown": without a timezone each viewer sees their own browser timezone, while "UTC" would fix every viewer to UTC. Without a locale dates use English formats. They work as in database_update.
database_create
Add a new field to a table. Field types: SINGLE_LINE_TEXT, LONG_TEXT, CHECKBOX, NUMBER, PERCENT, CURRENCY, RATING, DATETIME, SELECT, EMAIL, PHONE, URL, USER, ATTACHMENT, LINKED_RECORD, DURATION. Computed (read-only) field types: FORMULA, LOOKUP, ROLLUP. Their values are derived automatically. COUNT is not a supported field type; a request with type COUNT is rejected. System (read-only) field types that take no options: CREATED_AT, UPDATED_AT, CREATED_BY, UPDATED_BY, AUTONUMBER, RECORD_ID. Type-specific 'options': - SELECT: 'choices' as an array of string choices. 'allowMultipleEntries': true makes it a multi-select whose value is an array of choices; omit it for a single choice. - LINKED_RECORD: 'linkedTableId' (the table to link to); the inverse link field on that table is created automatically. Optionally set 'allowMultipleEntries' (boolean) to allow linking multiple records. - FORMULA: 'formula' (string expression; referenced fields and return type are resolved automatically). - LOOKUP: 'linkedRecordFieldId' (a LINKED_RECORD field in this table) and 'lookupFieldId' (a field id in the linked table). - ROLLUP: 'linkedRecordFieldId', 'rollupFieldId' (field id in the linked table) and 'function' (SUM, MIN, MAX, COUNT, AVERAGE, ARRAYCOMPACT, ARRAYJOIN, ARRAYUNIQUE, ARRAYFLATTEN, CONCATENATE, COUNTALL, COUNTA, OR, AND). To count linked records, use function COUNTALL and set 'rollupFieldId' to the linked table's primary field. Call database_get_field_reference first for the authoritative reference of field types, their options, and value examples.
database_create_field
Create a new, empty page in an application and return its id, path, name and type. Use this when the user wants a brand-new page (a pricing page, a dashboard, an about page), as opposed to editing one that already exists — call application_page_list first to see what the app already has. Both `name` and `path` are required, and BOTH must be unique within the application: a duplicate name or a duplicate path is rejected with an error naming the conflict. `name` is the label shown in Studio UI's page list; `path` is the URL segment visitors see. `path` is normalized to a leading slash, so "pricing" and "/pricing" are equivalent and a trailing slash is dropped. The path "/" is REJECTED: that is the application's home page, which exists from the moment the app is created and is never added afterwards. If the user wants different content on the home page, edit the existing home page (find it with application_page_list) instead of trying to create one. The paths "/assets", "/studio" and "/health" (and anything nested under them) are reserved by Softr and rejected too. The page is created EMPTY — no blocks — and inherits the app's default view permissions (logged-in users if the app has login enabled, otherwise everyone). Adding content is a separate step: use vibe_coding_block_create to put a block on the page afterwards. Page nesting/ordering is not controlled here; a new page lands at the top level. Close by giving the user the Studio link to this page — the server instructions carry the exact URL pattern — noting that a Studio tab they already have open won't show the new page until it is reloaded. Then offer to show them the app with application_preview or make it live with application_publish.
application_page_create
Create a new record with specified field values. Fields is an object mapping field IDs to values. Take the IDs from database_get_table or database_list_fields if you do not already have them. For DATETIME fields, use ISO format: yyyy-MM-dd'T'HH:mm:ss.SSS'Z' For SELECT fields, use the option value as a string. For CHECKBOX, use true/false. createdAt is optional: an ISO 8601 UTC time, not in the future, for example 2026-01-31T09:00:00Z. It sets the record's creation time, for example for demo data from past weeks. Without it, the creation time is now. Returns the created record with its generated ID. Field keys in the response are field IDs — resolve them with the same field list.
database_create_record
Batch create multiple records. Maximum 100 records per call. Each record should have a 'fields' object mapping field IDs to values. Take the IDs from database_get_table or database_list_fields if you do not already have them. A record may also have a 'createdAt' next to 'fields': an ISO 8601 UTC time, not in the future, for example 2026-01-31T09:00:00Z. It sets the record's creation time, for example to spread demo data over past weeks. Without it, the creation time is now. Returns the list of created records with their generated IDs. Field keys in the response are field IDs — resolve them with the same field list.
database_create_records
Create a new table in a database. Field types available: SINGLE_LINE_TEXT, LONG_TEXT, CHECKBOX, NUMBER, PERCENT, CURRENCY, RATING, DATETIME, SELECT, EMAIL, PHONE, URL, USER, ATTACHMENT, LINKED_RECORD, DURATION. Computed (read-only) field types: FORMULA, LOOKUP, ROLLUP (values derived automatically). COUNT is not a supported field type. To count linked records, use a ROLLUP field with function COUNTALL. System (read-only) field types that take no options: CREATED_AT, UPDATED_AT, CREATED_BY, UPDATED_BY, AUTONUMBER, RECORD_ID. For SELECT type, include 'options' with 'choices' as an array of available choices. Add 'allowMultipleEntries': true in the same 'options' for a multi-select ("select all that apply"): its value is then an array of choices. Without it a SELECT holds exactly one choice. For LINKED_RECORD type, include 'options' with 'linkedTableId' referencing an existing table. USER holds a Softr workspace collaborator, not an end user of the app. Do not use it unless the user explicitly asks for it. For "who owns this record" use a LINKED_RECORD to the table that holds the people or users, with LOOKUP fields for the details a page shows, or alternatively an EMAIL field. ATTACHMENT holds one or more files; database_get_field_reference shows an example value. For computed fields, see the database_create_field tool for the required 'options' per type. Call database_get_field_reference first for the authoritative reference of field types, their options, and value examples.
database_create_table
Connect an existing table as the one that authenticates an application's end users, and map which of its fields hold the email, name and the rest. Use this when application_get_user_connection comes back empty (`source` null, `fields` []) — that is the app having no users table, which is what blocks condition-based user groups. It reports empty rather than failing, so do not wait for an error. Returns the connected table and its fields in exactly the shape application_get_user_connection returns, so you can name what you connected and build a condition from the result without calling application_get_user_connection afterwards. Connects a table that already exists; it does not create one. Browse to it first and take the IDs from those calls rather than guessing any of them: integration_list → integration_list_databases → integration_list_tables → integration_list_table_fields for the fields and their fieldReferenceKey. For SUPABASE, POSTGRESQL, SQL_SERVER, SNOWFLAKE, GOOGLE_BIGQUERY, SMARTSUITE and CLICKUP there is a level in between: call integration_list_schemas and pass `schema`. Every other type has no schema level and REJECTS `schema`. Pass databaseName and tableName as well as the IDs, or the app's users settings show the connected table blank. Only emailFieldId is required — an end user is identified by their email address. nameFieldId and avatarFieldId are worth mapping whenever the table has them, since the app shows both. Every mapping references a field by the table's fieldReferenceKey from integration_list_table_fields: the field's id when it is "id", the field's name when it is "name" - "id" for SOFTR_TABLES, SMARTSUITE, CLICKUP, MONDAY, CODA, HUBSPOT, SALESFORCE and ZOHO_CRM, "name" for every other type. The user sync reads records by that value, so a mapping in the other key, one that names no field, or a field whose type cannot hold the value is rejected with the reason; re-read the fields rather than retrying. IMPORTANT: if the application ALREADY authenticates against a table, this fails with a 409 and `replaceExisting` is NOT the fix. Re-pointing an app at a different table does not move its existing users across — they stay in the old table and can no longer sign in. The 409 names the table that is connected: quote it to the user, say what they would lose, and set replaceExisting only after they say to. The 409 message is enough on its own — application_get_user_connection reports the same table under `source.table`, but calling it costs a slow live read to learn what you were just told. Never set replaceExisting on your own judgement, and never merely because the call was refused. Connecting REQUESTS a user sync — the thing that turns the table's rows into application users — but one only runs if two-way user sync is on for the app. Apps have it on from creation; it is off only where someone paused it. So do not assume the users arrived. Check `userSyncStarted`: false means the table is connected and NO rows were imported. When it is false `userSyncStatus` carries a sentence saying why and what fixes it — relay that rather than reporting success; the fix is usually the app's users settings, not another call to this tool. When a sync IS running `userSyncStatus` is null, because there is nothing to report beyond the flag. Connecting the table does not switch end-user login on: application_set_login does that, and application_configure_sign_up controls who may sign up. Say so rather than reporting the app as ready for users. Reads the live datasource once before it writes, so it is slow and fails when the integration is unreachable or the table is gone. Every check runs before the write, so a call that fails changed nothing and the app still has whatever users table it had — relay the reason as it is, since a bad field mapping, an unsupported source type and an unreachable datasource need different responses.
application_create_user_connection
Create a custom end-user access-control user group on an application (the groups that gate what app users can see and do). Returns the created group: id, name, type, userEmails, condition and order. A group is one of two kinds and never both: - manual: pass userEmails, an explicit list of app-user email addresses - condition-based: pass condition, a rule evaluated against each app user's own record Passing both fails with a 400; passing neither creates an empty manual group you can fill in later. The condition's shape, which the schema advertises only as an object: {"logicalOperator": "AND", "expressions": [{"subject": {"field": "USER:<field reference>", "type": "ARRAY"}, "operator": "IS", "value": ["<choice reference>"]}]} logicalOperator is AND or OR and defaults to AND. Build it by calling application_get_user_connection first: subject.field must be "USER:<field reference>" naming a real field from that call by its fieldReferenceKey - the field's id when that is "id", its name when that is "name" - and never a guess: a reference in the other key is refused naming the value to send, and one that matches no field at all is refused too, so neither reaches a group. The predefined "USER:EMAIL" and "USER:EMAIL_DOMAIN" work without a lookup. subject.type is TEXT, ARRAY, NUMERIC, DATE or BOOLEAN, chosen as application_get_user_connection describes; for an ARRAY field the value entries are that field's choices by the same key - choice ids when fieldReferenceKey is "id", labels when it is "name". value is ALWAYS a list of strings, whatever subject.type is: a single-select Role compared as TEXT still sends ["<choice reference>"], never a bare "<choice reference>". A scalar fails JSON binding before any validation runs and comes back as a 400 that names no property, so there is nothing to relay. IMPORTANT: IS_NOT and NOT_EQUAL on an ARRAY field are rejected - they match no users at all. Use HAS_NONE_OF to exclude option values instead. How many values the operator takes is part of its contract, and a mismatch is rejected: IS_EMPTY and IS_NOT_EMPTY read the field alone and take [], BETWEEN takes exactly two (lower, upper), and every other operator takes at least one. A DATE subject compares against a structured token, never prose - "TODAY" is not a date. IS, IS_NOT, LT, GT, LTE and GTE take "PREDEFINED:TODAY" / "PREDEFINED:YESTERDAY" / "PREDEFINED:TOMORROW", "FIXED_DATE:DAYS_AGO:<n>", "FIXED_DATE:DAYS_FROM_NOW:<n>" or an exact "YYYY-MM-DD"; IS_WITHIN and IS_NOT_WITHIN take "RELATIVE_DATE:THIS:<WEEK|MONTH|QUARTER|YEAR>" or "RELATIVE_DATE:LAST:<n>" / "RELATIVE_DATE:NEXT:<n>". The two sets do not overlap, and each token's suffix is part of its grammar - THIS takes a calendar period, LAST/NEXT take a whole number of days. Names are unique within an application, case-insensitively, so call application_list_user_groups first and pick a free one rather than sending a name that is taken - the call is rejected either way, but a rejected call is a wasted turn. A blank name is also rejected, as is a create once the workspace is at its custom-user-group allowance. Relay whatever this fails with as it is, rather than reporting the group as created or retrying the same call: a name collision and an exhausted plan allowance need different responses from you. When the user names specific people for a manual group but gives no email addresses, ask for them instead of calling this - a group created without them has no members. Use application_list to find application IDs.
application_create_user_group
Create a new vibe-coding (AI-generated custom code) block on an existing page and return its blockId. Use this when the user wants a brand-new custom block, as opposed to editing one that already exists (find those with application_page_get first). Pass `code` (and a brief task summary in `userPrompt`) to compile your real source directly on creation — this is the normal path when you already know what to build, and avoids a separate vibe_coding_block_update_code round-trip. Omit both to create an empty placeholder block instead (seeded with non-functional placeholder source, not blank/uncompiled) for the user to build out later via vibe_coding_block_update_code. `code` and `userPrompt` must be given together — passing one without the other is rejected. Call vibe_coding_block_get_docs before writing `code`, same as you would before any vibe_coding_block_update_code call — do this every time, even if you called it earlier in this conversation. Critical: a brand-new block NEVER has a datasource wired yet — vibe_coding_block_get_code's `dataSources` field, which you'd normally check before calling a data-fetching/mutation hook, doesn't apply here since there's nothing to check yet. Any `code` you pass that calls useRecords, useRecord, useMetric, useChartData, useRecordCreate/Update/Delete, or useProxyFetch will compile fine but throw at runtime. If the block needs a datasource: if you already know which table/base/API to use (the user told you, or it's the obvious single choice), create the block first (this call, with no code or only static placeholder code), then call vibe_coding_block_connect_data_source, and only then write the data-dependent code via vibe_coding_block_update_code — do not pass data-dependent `code` in this same call, since the datasource doesn't exist yet at creation time. If it's not clear which table to use, ask the user instead of guessing. Either way, plain static content (e.g. a memes block with hardcoded images) needs no datasource and can be written directly. Like vibe_coding_block_update_code, `code` is compiled server-side before saving: if compilation fails, NOTHING is created — unlike the two-call path, there's no empty block left behind to retry against, so the whole call must be repeated with fixed code. If the compiled `code` calls a mutation hook, the block's Actions (Studio UI's "Actions" tab) are derived from it automatically — one per (datasource x hook): `useRecordCreate` -> ADD_RECORD, `useRecordUpdate` -> UPDATE_RECORD, `useRecordDelete` -> DELETE_RECORD. They come back in this tool's `actions` field, each at its default visibility (UPDATE_RECORD and DELETE_RECORD are restricted to logged-in users; the rest inherit the block's own visibility). Restrict them further with vibe_coding_block_set_action_visibility if the user asked for that. The block is inserted on the page right after any header/navigation blocks. Optionally pass a title to override the default "Vibe coding block" title shown in Studio UI. Close by giving the user the Studio link to this page — the server instructions carry the exact URL pattern — noting that a Studio tab they already have open won't show the new block until it is reloaded. Then offer to show them the app with application_preview or make it live with application_publish.
vibe_coding_block_create
Create a saved view on a table, optionally configured in the same call: hide fields, sort, group, and filter. Every field is referenced by its FIELD ID, never its name — call database_get_table or database_list_fields first to resolve names (e.g. "Name", "Status") to ids, then pass the ids here. A config part you omit keeps the default: all fields visible, no sorting, no grouping, no filter. Formats: - hiddenFields: ["fieldId", ...] - sorting: [{ "fieldId": "fieldId", "direction": "ASC"|"DESC" }, ...] - grouping: [{ "fieldId": "fieldId", "direction": "ASC"|"DESC" }, ...] (max 3 levels, distinct fields) (sorting and grouping also accept "sortingField"/"sortType" as synonyms for "fieldId"/"direction") - filter: same format as database_search_records — { "condition": { "operator": "AND"|"OR", "conditions": [...] } } with Binary/Unary/Ternary conditions whose leftSide is a field id. Call database_get_field_reference for the operators each field type supports — the filter is SAVED on the view, so a wrong operator would misfilter every later read. An unknown field id anywhere in the config fails the whole call with a 400 — nothing is created. The name is required and not unique: creating the same name twice makes two views. Returns the new view's id and name; use database_list_views to see all views.
database_create_view
Creates a new workflow in a workspace, starting from the chosen trigger, and saves it at once. Use it for any automation, whatever starts it. Add the action steps afterwards with workflow_add_node and fill in their inputs with workflow_update_node_inputs. It only creates the workflow definition: no step runs until one is tested (workflow_test_node, workflow_test) or the workflow is published with workflow_publish and its trigger fires. Returns `workflowId`, `triggerId` and a `url` that opens the workflow in Softr Studio. This tool opens nothing by itself, so give the user the `url`. A webhook trigger also returns the `webhookUrl` it listens at, and some triggers return a `hint` about how to get test data. The trigger type sets the workflow's shape. Record-watch, schedule and webhook triggers get a plain workflow. `SOFTR_APPS_TRIGGER_WORKFLOW` (a workflow an app runs on demand from a "Run custom workflow" button) also gets the built-in Show Wait Screen and End User Interactions steps and returns `initiateId`. Add its first real step after `initiateId` with workflow_add_node, never before it or in place of those built-in steps.
workflow_create
Delete a data restriction. Everything it was keeping back becomes visible and editable again for the user groups it bound, on every page and in any generated code that reads the table — so confirm with the user before calling this unless they asked for it in as many words. Removes the whole restriction, every rule in it. To drop ONE rule and keep the rest, call application_set_data_restriction with that restriction's id and the rules you want to keep. Idempotent: an id the application does not hold comes back deleted: false rather than failing, so a repeat after a timeout is safe. Restriction IDs come from application_list_data_restrictions.
application_delete_data_restriction
Delete a database permanently, together with every table, field and record it contains. Deletion fails if the database still has tables; set force=true to delete it anyway. This is also the only way to remove a database's last table, which database_delete_table rejects. WARNING: This action cannot be undone. Confirm with the user before calling it.
database_delete
Delete a field from a table, together with its value in every record of that table. A table's primary field cannot be deleted. Softr removes the field from any view's filters and sorts and marks formulas that referenced it invalid. It does NOT clean up two things, so tell the user about them rather than assuming they are handled: deleting a LINKED_RECORD field leaves the inverse link field on the other table in place, which has to be deleted separately if the relationship is going away; and lookups and rollups reading through the deleted field are left as they are. WARNING: This action cannot be undone. Confirm with the user before calling it.
database_delete_field
Removes a specified node from the workflow along with all its associated paths. Rules: - Never delete triggers - Search ALL levels when finding nodes: workflow.actions AND inside each LOOP_ACTION_GROUP.inputs.actions - Use paths to find execution order: workflow.paths for top-level, loop.inputs.paths for inside loops - LOOP nodes delete their internal actions but NOT downstream nodes - if there are nodes after a LOOP, you still need commands for those - When deleting multiple nodes in a single response, NEVER include the same node ID twice in the array - Delete all nodes after X means only delete nodes downstream of X, bottom-up and right-to-left. NEVER include the X node itself in deleted nodes. - **CRITICAL** If you can't clearly identify the exact node to be deleted, NEVER guess and NEVER try to find the closest match. ALWAYS ask user to clarify. This tool saves the change to the workflow immediately. Its result contains real IDs (see resultMetadata) — use them in subsequent calls. There is no result-placeholder mechanism: never invent IDs. Result shape (JSON Schema): {"properties":{"deletedNodeId":{"type":"string"}},"required":["deletedNodeId"]}
workflow_delete_node
Delete a single record from a table. Fails if no record with that ID exists in the table. WARNING: This action cannot be undone. Confirm with the user before calling it.
database_delete_record
Delete several records from one table in a single call. At most 100 record IDs per call. IDs that no longer exist are skipped rather than failing the batch, and the returned message reports how many records were actually deleted. WARNING: This action cannot be undone. Confirm with the user before calling it.
database_delete_records
Delete a table permanently, together with its fields, views and every record it holds. Deletion fails if the table still has records; set force=true to delete it anyway. Any LINKED_RECORD field in another table that points at this one loses its links. A database cannot be left without tables, so deleting its only table is rejected and force does not override that. Before deleting, check the database's tablesCount from database_get (or count database_list_tables): if this is the last table, do not call this tool. Tell the user the database cannot be left empty and ask whether they want the whole database gone — if they do, call database_delete with force=true instead, which removes the database and this table with it. WARNING: This action cannot be undone. Confirm with the user before calling it.
database_delete_table
Delete a custom end-user user group. Also removes its sign-in/sign-out redirections and its entries in the application's data restrictions. Returns a confirmation with the applicationId and userGroupId. Fails with a 404 if the group is not in this application, and a 409 if it is a predefined group (Logged in users, Visitors, Everyone) - only custom groups can be deleted. IMPORTANT: anything gated on this group loses that grant, and the two ways that happens differ. - Data restrictions: the group is dropped from every restriction that named it, and a restriction this group was the ONLY grantee of is removed outright. Access is default-deny, so data the application reads can become inaccessible to its users - which is how this bites an app built from code rather than from pages. - Page permissions and block or action-button visibility, where the application has them, KEEP the now-unresolvable group ID; the builder shows those as "Deleted group". Anything visible only to this group may end up reachable by nobody. So after deleting, tell the user what was gated on the group so they can re-check it. WARNING: This action cannot be undone. Confirm with the user before calling it. To change who is in a group rather than remove the group, use application_update_user_group. If the delete is refused, relay the reason as it is - a predefined group and a group from another application are different problems.
application_delete_user_group
Permanently remove a vibe-coding block from a page. Use this when the user wants a block gone — "delete the chart block", "remove the block you just added", "scrap it and start over" — as opposed to hiding it from some or all visitors, which is vibe_coding_block_set_visibility and leaves the block in place. Vibe-coding blocks ONLY. Find the block with application_page_get and check its type is "vibe-coding" before calling; any other block type (a header, a form, a list, a navigation bar) is rejected with an error, and there is no MCP tool that deletes one — those are deleted by the user in Studio UI. Say that rather than looking for a way around it. This is not undoable through MCP. vibe_coding_block_restore_version rolls a block that still exists back to an earlier version; it cannot bring back a deleted block, and neither can vibe_coding_block_duplicate_from_version, which needs the original block to copy from. The block's version history stops being reachable with it. If the user's goal is really "go back to how it was", that is vibe_coding_block_restore_version on the existing block — do not delete and rebuild. So confirm with the user before calling this unless they were already explicit that the block should be deleted, and if the block might still be wanted, offer vibe_coding_block_duplicate_from_version (to keep a copy) or vibe_coding_block_set_visibility (to hide it) first. Any data sources connected to the block go with it — there is no separate vibe_coding_block_disconnect_data_source call to make first. The workspace integration itself stays connected, and no other block is touched. Deleting the block does NOT publish the change: the block disappears from Studio immediately, but the live app keeps serving it until the next publish. Close by giving the user the Studio link to this page — the server instructions carry the exact URL pattern — noting that a Studio tab they already have open still shows the block until it is reloaded. Then offer to show them the page without it with application_preview, or to make the removal live with application_publish.
vibe_coding_block_delete
Remove a data source connection from a vibe-coding block (undoes vibe_coding_block_connect_data_source for the given dataSourceId). Only removes the data binding — it does NOT change or remove any code. Any hooks in the block's code still referencing this dataSourceId (e.g. useRecords({ from: ds.<name> })) will compile fine but fail at runtime afterward, so also update or remove that code (vibe_coding_block_update_code or search-replace) unless you're about to reconnect a different data source with the same dataSourceId right away. Once any code still referencing this dataSourceId has been updated or removed, give the user the Studio link to this page — the server instructions carry the exact URL pattern — and offer to show them the app with application_preview or make it live with application_publish.
vibe_coding_block_disconnect_data_source
Creates an exact copy of a specified node and inserts it immediately after the original node in the workflow. This tool saves the change to the workflow immediately. Its result contains real IDs (see resultMetadata) — use them in subsequent calls. There is no result-placeholder mechanism: never invent IDs. Result shape (JSON Schema): {"properties":{"newNodeId":{"type":"string"}},"required":["newNodeId"]}
workflow_duplicate_node
Create a NEW vibe-coding block on the SAME page from one of an existing block's versions, leaving the original block completely untouched. Use this to fork a version — "keep both", "make a variant of the old version", "copy this block as it was before" — as opposed to vibe_coding_block_restore_version, which rolls the original block back in place and creates no new block. The copy is the whole block as that VERSION stored it — code, editable-setting values, datasource wiring and action visibility — so it reflects that version rather than how the original is configured today, and normally works immediately without a follow-up vibe_coding_block_connect_data_source. It is appended to the end of the page; move it in Studio UI if the user wants it elsewhere. Its version history starts fresh with a single entry copied from the source version. Returns the new `blockId` — use that, not the original, for any further edits to the copy. For an OLDER version, nothing needs checking or re-applying: the user asked for that version and the copy is that version — action visibility included — and its code, settings and wiring all come from the same capture, so they are consistent with each other. It may well differ from how the original is configured today; that is the point. For the NEWEST version (`versionNumber` == `totalCount`) it is different, because the user means "copy this block as it is now" — but even the newest version was captured at the last CODE write, so anything changed since without one is missing from the copy: action visibility, setting values and datasource wiring. Compare the original's current state against the copy — one vibe_coding_block_get_settings call on the ORIGINAL gives all three (`settings`, `actions`, `dataSources`) without its source code — then apply whatever differs to the NEW blockId with vibe_coding_block_update_settings / vibe_coding_block_set_action_visibility / vibe_coding_block_connect_data_source, and tell the user what you carried over. Close by giving the user the Studio link to this page — the copy sits on the same page as the original, so it is the same link, and the server instructions carry the exact URL pattern — noting that a Studio tab they already have open won't show the new block until it is reloaded. Then offer to show them the app with application_preview or make it live with application_publish.
vibe_coding_block_duplicate_from_version
Get an overview of an application's access-control configuration: the user groups plus the counts of user-group redirections, sign-up-based redirections, and data restrictions.
application_get_access_overview
Get details of a specific application by ID: name, description, workspace, type, custom domain/subdomain, home page id, timestamps, and the configured email sender (`emailSender`: signatureId, name, email, confirmed, and isDefault — isDefault=true means the app sends from the default Softr address; an unconfirmed custom sender also still sends from the default one; domainVerified and dnsRecords show whether the sender domain's DKIM and Return-Path records are in place, as last checked — workspace_verify_email_sender re-checks them), and when end users are logged out (`userSessions`: sessionExpiration and inactivityTimeout, null for apps without login; change them with application_configure_user_sessions). Use application_list first to find valid application IDs.
application_get
Get a single block on a page by its block ID. Returns the block's structural metadata and visibility / user-group gating. Heavy custom code (markup/style/script) is omitted.
application_page_get_block
Get details of a specific database by ID including name, description, workspace, table count, timestamps and localization: timezone, dateLocale and numberLocale. Use this to get full information about a database before working with its tables, and to see its current localization before you change it with database_update. timezone is the IANA timezone that every viewer sees dates and times in. dateLocale formats dates (for example 31.12.2026 for de-DE) and numberLocale sets the thousands and decimal separators (1.000,5 for de-DE) for every viewer. A null value means each viewer's own browser timezone or locale is used; a null dateLocale can also mean a custom date format that was set in the Softr builder.
database_get
Fetches the live allowed values for a node input whose options are not in the specification: `DYNAMIC_SELECT` / `DYNAMIC_MULTI_SELECT` inputs (a Slack channel, a Trello board and its list, a Gmail label, a Brevo contact list, …) and `DYNAMIC_KEY_VALUE_LIST` inputs (an integration-defined field set, e.g. Brevo contact attributes), both fetched from the connected integration; and `AI_MODEL_SELECT` inputs (e.g. `model` on `SOFTR_AI_SUMMARIZE_TEXT` / `SOFTR_AI_SEND_PROMPT`), fetched from the workspace's live AI model list. workflow_get_node_specifications only declares that such an input is dynamic (or, for `AI_MODEL_SELECT`, just that its value is a model name) — it never lists the values, and they differ per workspace and per connected account, so you MUST call this tool and then pass a returned `value` verbatim. Never invent one and never send a human-readable label or a guessed marketing name (e.g. `claude-sonnet`, `gpt-4o-mini`). Omit `inputName` for an inventory of the node's dynamic inputs, each with the sibling inputs it depends on and whether it is ready to fetch: the integration-backed inputs form a dependency chain (`integrationId` first, then e.g. `idBoard`, then `idList`) and must be filled and saved in that order, while `AI_MODEL_SELECT` inputs have no dependencies and are always ready. Returns `status: "BLOCKED"` with `unresolvedParameters` when a dependency is still unset — set it via workflow_update_node_inputs (or pass it in `pendingInputs`) and call again. For `AI_MODEL_SELECT`, a node whose saved value is a now-deprecated model shows that value in `currentValue` even though it won't appear among `options` — that is expected, not a bug; leave it as-is unless the user asks to change it.
workflow_get_dynamic_input_options
Get Softr Tables' field-type and filter-operator reference: all field types with descriptions, available/required options and value examples, filter operators (binary, unary, ternary, composite), and the list of read-only field types. This is the reference for the whole product, not the schema of one table: it takes no table ID and returns the same content every time. To read the fields of a specific table, use database_get_table. Call this before database_create_table, database_create_field, or database_update_field, and when building filters for database_search_records or database_aggregate_records. Do not guess field types, options, or operators.
database_get_field_reference
Reads a node's saved sample output and its ready-to-paste `variables` without re-running it. For a step inside a loop it reads the row of one iteration, and `nextSteps` says so. Returns `status: 'NOT_TESTED'` if the node has no saved sample yet — call workflow_test_node first. A step inside a loop with no row of its own falls back to its top-level sample, and `nextSteps` warns that the sample can be stale. Returns `status: 'BLOCKED'` with `nextSteps` only when no sample exists at any scope — ask the user to test that loop in the workflow builder. Omit `nodeId` to get a tested/untested overview of every node in the workflow, useful for planning which nodes still need workflow_test_node before you can wire their outputs into other nodes' inputs.
workflow_get_node_output
Get a single page with its blocks. Returns page metadata plus a light list of blocks (id, name, title, type, category, enabled, visibility / user-group gating). Heavy custom code (markup/style/script) is intentionally omitted. The blocks are listed in the page's own order, top to bottom, so a block's position is its place in this list. The list is flat but a page is not: a block nested inside a column or tab container is listed alongside the rest, and it is the container that decides where it actually appears.
application_page_get
Get the permission gating for a single page: for each permission type (VIEW/EDIT/ADD/DELETE) the user groups allowed to perform it. Use application_list_user_groups to resolve custom user-group IDs.
application_page_get_permissions
Get a single record by ID with all field values, createdAt, and updatedAt. Returns null if the record is not found. Field keys in the response are field IDs, not human-readable labels. Map them with the field list from database_get_table or database_list_fields — one schema call per table is enough; skip it if you already have it.
database_get_record
Get one table in a single call: id, name, description, defaultViewId, primaryFieldId, and every field definition (id, name, type, options, required, readonly) — the same field list database_list_fields returns. This is the one schema call you need for a table whose id you already know, whether you are going to read, query, aggregate or write its records. Do not follow it with database_list_fields, and do not call it again for the same table unless you changed its fields yourself (database_create_field, database_update_field) in between.
database_get_table
Get the table that authenticates an application's end users, and its fields. This is the table a condition-based user group is evaluated against, so call this BEFORE building any condition for application_create_user_group or application_update_user_group — it is the only way to learn the table's real fields, and a condition naming a field this table does not have is refused rather than silently matching nobody. Returns `source` — where the users table lives — `fieldReferenceKey`, and, per field, its id, name, type, rawType, multiValued and choices. Use `source` whenever you have to say WHICH table an app authenticates against; the field list cannot answer that. `source` mirrors the browse tools, so its parts feed straight back into application_create_user_connection: `dataSource` {id, name, type} as integration_list returns, `database` {id, name} as integration_list_databases returns, `schema`, and `table` {id, name} as integration_list_tables returns. Re-pointing an app at another table in the same data source needs only `source.dataSource.id` — do not go searching integration_list for it. Use the `name` of each when telling a person what is connected; ids mean nothing to them. `database` and `schema` are null when the source has no such level (HubSpot, Salesforce, Zoho and REST API have nothing above the table; only SUPABASE, POSTGRESQL, SQL_SERVER, SNOWFLAKE, GOOGLE_BIGQUERY, SMARTSUITE and CLICKUP have a middle one). A null `name` inside a level means "not recorded", never "no table" — `source.resourceIdentifier` always identifies it. For Airtable, Google Sheets and SQL sources the table's id IS its name — that is what addresses a table there, and integration_list_tables reports it the same way, so the two always agree. `fieldReferenceKey` is "id" or "name", as integration_list_table_fields reports it, and a condition MUST use it: "USER:<reference>" holds the field's id when it is "id" and the field's name when it is "name". It is "id" for SOFTR_TABLES, SMARTSUITE, CLICKUP, MONDAY, CODA, HUBSPOT, SALESFORCE and ZOHO_CRM, and "name" for every other type - read it rather than inferring it from the list, which is longer than the handful of sources that made this worth fixing. A choice follows the same key: its id when "id", its label when "name". A field reference in the other key is refused with the value to send. A CHOICE reference is only checked when subject.type is ARRAY, so a single-select compared as TEXT takes whatever it is given: a choice id there is stored, matches nobody and reports nothing. Get the choice right yourself; do not wait to be corrected. `fieldReferenceKey` is null when the app has no users table at all - the empty result above, which application_create_user_connection fixes rather than a different key - and against a studio-api that predates the field, where you can still read the same key off integration_list_table_fields for the table. When the user describes membership by a value ("whose Role is a manager"), match their wording against that field's `choices` case-insensitively and tolerantly of singular/plural, and carry the matching choice into the condition value by that key. When exactly one choice matches, use it and proceed; ask only when several plausibly match or none do. Picking the condition's subject.type: read `multiValued`, NOT `type`. `type` is a normalised value with no select member and cannot tell you a field holds several values. Map it as: multiValued true → ARRAY; otherwise number → NUMERIC, date or datetime → DATE, bool → BOOLEAN, anything else → TEXT. Returns an empty result (`source` null, `fields` []) when the application has no users table at all, so condition-based groups are impossible. Never build a condition against an empty result. That state is fixable: application_create_user_connection connects a table, after which conditions work — until then, create a manual group with userEmails instead. Fails if the datasource cannot be read (an expired connection, a deleted table); in that case say so rather than guessing a field. This fetches live schema from the external datasource rather than a stored copy. Call it once per application and reuse the result across the conditions you build.
application_get_user_connection
Get the current source code AND current persisted editable-setting values of an existing vibe-coding (AI-generated custom code) block. Use application_page_get first to find blocks with type "vibe-coding", then call this before making any change. If you only need the setting values — e.g. right before calling vibe_coding_block_update_settings, with no code change involved — call the lighter-weight vibe_coding_block_get_settings instead, which skips the (potentially large) source code entirely. To confirm a push landed rather than to read the code, pass `includeCode: false`: the response then describes the stored source without carrying it. When `sourceSha256` is present, compare it with the one the write tool returned, or with a SHA-256 of the text you sent; `sourceBytes` gives the same answer more cheaply for a length mismatch. Do not fetch the whole source just to verify it — a block routinely runs past 100 KB, and every byte of it goes through your context. Important: the returned `settings` list is the block's ACTUAL rendered content — it can differ from whatever `initialValue` is written in `sourceCode`, because once a setting has rendered once, its persisted value (not the code's initialValue) is what's shown. Always read `settings` here — never assume `sourceCode`'s initialValue reflects current content — before calling vibe_coding_block_update_settings. Critical: the returned `dataSources` list is the ONLY reliable way to know whether this block actually has a datasource wired to it (via Studio UI's "Source" tab, or the vibe_coding_block_connect_data_source tool) — this is completely independent of what the code itself calls, and the server-side compile step CANNOT check it (the compiler never sees the block's datasource wiring, only the code text). If `dataSources` is empty, the block has NO datasource connected: any code you write that calls a data-fetching or mutation hook (useRecords, useRecord, useMetric, useChartData, useRecordCreate/Update/Delete, useProxyFetch) will compile and save successfully but then THROW AT RUNTIME in the browser (e.g. "Cannot call useRecords/useProxyFetch because this block does not have a datasource configured") — a failure this tool's compile step cannot catch or report back to you. Only use those hooks when `dataSources` is non-empty, and match the hook family to the entry's `type`: - `REST_API` → use `useProxyFetch`. The entry's `baseUrl` (when present) is the exact, already-configured host — inline that literal value as-is; never hardcode a different host or guess one. - Any other type (e.g. `SOFTR_TABLES`, `AIRTABLE`, `GOOGLE_SHEET`, ...) → a table-shaped integration: use useRecords/useRecord/useMetric/useChartData/mutation hooks. The entry's `fields` array (when present) already lists every current field's exact `id`, `name`, `type`, and (for select/linked-record/datetime/etc) type-specific `options` (e.g. select choices, linked table id) — read this directly to write `q.select()`/filter code; never guess a field reference. MANDATORY: use the entry's own `fieldReferenceKey` (either `"id"` or `"name"`) to pick which field property goes in `q.select()`/filter code for THIS datasource — never assume `id` universally: it's accurate for `SOFTR_TABLES` and most other integrations, but for `AIRTABLE`, `GOOGLE_SHEET`, and `NOTION` specifically, `fieldReferenceKey` is `"name"`, and the field's `name` property (not `id`) MUST be used instead, full stop, no exception. Using the wrong one compiles and saves fine but silently fails to create/update records or leaves the block's Actions tab unable to show which fields are used (after a write, check the returned `actions` entry's `fields` to confirm they resolved), since the compiler cannot validate a field reference against the datasource's real schema — confirmed with a real Airtable-backed block. `fields`/`fieldReferenceKey` reflect the schema at the moment of this call — if you already fetched it earlier in this conversation and significant time or other schema changes may have passed, call this tool again rather than relying on a stale copy. If `fields` is absent (a lookup failure, or `type` is `SOFTR_TABLES` and the entry's `resourceIdentifier` carries `tablespaceId`/`tableId`), fall back to the database_list_fields/database_get_table tools with those ids (field IDs there are always the correct reference key); for other table-shaped integration types with neither `fields` nor a field-listing tool available, ask the user for exact field names/ids rather than guessing. A block can have more than one entry here (vibe-coding blocks support multiple wired datasources) — when there's more than one, pass `from: ds.<name>` on every hook call, where `<name>` is whatever readable key the code defines via `datasource.define({ <name>: "<id>" })`, mapped to each entry's `id`. An entry may also carry `sortOptions`/`condition` — the default sort order and record-visibility filter configured for that specific datasource (Studio UI's "Source" tab "Default sort"/"Record filters"). These apply automatically at the data layer; do not duplicate them in code unless the user asks for a DIFFERENT sort/filter on top. Change them with vibe_coding_block_set_data_source_sort/vibe_coding_block_set_data_source_record_filters, not by editing code. If `dataSources` is non-empty, that IS the correct datasource — use it exactly as given, even if its `name`/`resourceIdentifier` (e.g. table or workspace name) looks unrelated to the app, page, or the user's wording of the request. Copied/renamed/test workspaces routinely have datasource names that don't semantically match the app they're wired into — a mismatched- looking name is NOT a signal that the wiring is wrong or that you should look for a "better-sounding" table elsewhere. NEVER second-guess or replace an already-wired datasource with one you found by searching database_list/database_list_tables/database_search_records for a name that seems like a closer match — the human wired this connection deliberately; trust it. If `dataSources` is empty: this block is scoped to have NO datasource, full stop. Do NOT call database_list/database_list_tables/database_search_records (or any other table-discovery tool) to go find a "similar" or name-matching table to use instead — that is a different table the user never connected to this specific block, and silently substituting it is wrong even if the name looks related (e.g. matching the app's name). Instead: if the user's request (or earlier conversation) already makes clear which table/base/API this block should use, call vibe_coding_block_connect_data_source yourself to wire it, then retry — do not ask the user to do this manually in Studio UI when you already know the answer. Only ask the user which table/API to connect if that's genuinely ambiguous or unknown. The same applies even if the user's request is phrased in table/database terms (e.g. "add records to this database") — "this" always means whatever is (or isn't) wired to the block in `dataSources`, never a workspace-wide search for a table with a matching name. The returned `actions` list is the block's Actions (Studio UI's "Actions" tab): one entry per record-mutating capability the compiled code exposes. Actions are DERIVED from the code, never written by hand — the compiler creates exactly one per (datasource x mutation hook): `useRecordCreate` -> ADD_RECORD, `useRecordUpdate` -> UPDATE_RECORD, `useRecordDelete` -> DELETE_RECORD. Read hooks (useRecords/useRecord/useMetric/useChartData/useLinkedRecords/ useFieldOptions) create none. They are not cosmetic: a mutation hook with no matching action gets `enabled: false` at runtime and throws when called, so this list is how you confirm a mutation you wrote is actually live. Each entry carries `actionType`, `dataSourceId`, `fields` (the `q.select()` references that hook was given, with their resolved display names — the tab's "FIELDS USED" list), `permission` (who can see and use the button) and `isDefaultVisibility`. Visibility cannot be expressed in code at all; change it with vibe_coding_block_set_action_visibility. Note every action with `isDefaultVisibility: false` BEFORE you change code — a compile resets them all (see vibe_coding_block_update_code). `isDefaultVisibility` only means the permission matches the default computed for this block — and ADD_RECORD's default is the block's own visibility, so a deliberate setting often reads as default. It is not a record of intent, so never report "no custom visibility" from it: describe visibility with the actual `permission`, and detect changes by comparing `permission` before and after. Three tools update a block afterwards — pick by scope of the change: vibe_coding_block_update_settings (value-only change to an existing setting), vibe_coding_block_update_code_search_replace (small, localized code edits), or vibe_coding_block_update_code (new behavior/layout/schema, or full rewrites). Do NOT hand-write a code update to undo an earlier one: every code write records a version, so vibe_coding_block_list_versions shows the block's history (each entry with its own source code) and vibe_coding_block_restore_version rolls this block back to one of them, while vibe_coding_block_duplicate_from_version forks a version into a separate new block on the same page. Reach for those whenever the user says undo, revert, go back, or "keep both". Before writing or editing vibe-coding block code, call vibe_coding_block_get_docs to learn the current block framework: available hooks (data fetching, mutations, settings, user context), supported libraries, and constraints the code must follow. Do this every time, even if you called it earlier in this conversation — the documentation can change and must not be assumed from memory.
vibe_coding_block_get_code
Get ONLY the current persisted editable-setting values of an existing vibe-coding (AI-generated custom code) block — a lighter-weight alternative to vibe_coding_block_get_code when you don't need the source code, e.g. right before calling vibe_coding_block_update_settings. Skips the source code entirely, which matters because vibe-coding block source can be very large — don't pay for it when all you need is a setting's current value/schema. Important: the returned `settings` list is the block's ACTUAL rendered content — it can differ from whatever `initialValue` is written in the block's source code, because once a setting has rendered once, its persisted value (not the code's initialValue) is what's shown. Always read `settings` here — never assume a setting's initialValue in code reflects current content — before calling vibe_coding_block_update_settings. For an array setting, note its `schema` too: array items must only use keys present there, and required keys must be present and non-empty. Also returns the block's `actions` and wired `dataSources`, in the same shapes vibe_coding_block_get_code uses — so this is the read for anything about a block EXCEPT its code. `actions` gives the ADD_RECORD/UPDATE_RECORD/DELETE_RECORD capabilities with their `dataSourceId`, mapped `fields`, `permission` and `isDefaultVisibility`; `dataSources` gives the wiring with each entry's `type`, `fieldReferenceKey`, `fields`, `sortOptions` and `condition`. That makes it the cheap read before vibe_coding_block_set_action_visibility, vibe_coding_block_set_data_source_sort, vibe_coding_block_set_data_source_record_filters and vibe_coding_block_disconnect_data_source, and for checking whether a restore or duplicate kept the visibility and wiring you expected. Only reach for vibe_coding_block_get_code when you actually need the source. If you also need the source code or wired datasources (e.g. you're about to write/edit code, not just update a setting value), call vibe_coding_block_get_code instead — it returns settings alongside source code and dataSources in one call.
vibe_coding_block_get_settings
Fetch the CURRENT vibe-coding block framework documentation, live from docs.softr.io: the required React component contract, every available hook (data fetching — useRecords, useRecord, useMetric, useChartData; mutations — useRecordCreate, useRecordUpdate, useRecordDelete, with their exact import paths and call signatures; settings; user context; external API calls via useProxyFetch), supported libraries (Tailwind, shadcn/ui, Lucide icons, date-fns, TanStack Query, Recharts, etc.), and constraints the generated code must follow. Call this FIRST, before your first call in a conversation to vibe_coding_block_update_code or vibe_coding_block_update_code_search_replace (and ideally before vibe_coding_block_get_code too, so you already know the framework before reading the block). This documentation can change over time, so never rely on your training knowledge or an earlier turn's fetch of it — re-fetch here rather than guessing at a hook's import path or signature; guessing and trial-and-erroring against the compiler wastes many round-trips that a single call here avoids. Re-call it if you hit compile errors that suggest your understanding of the API is out of date.
vibe_coding_block_get_docs
Returns `{ workflow, nodeSamples }` — `workflow` is the full graph as raw JSON (triggers, actions, paths, and every node id); `nodeSamples` shows which nodes already have a tested sample output (`status`, `hasOutput`) and which do not. Re-read this whenever you need real node ids for a subsequent command (e.g. after workflow_add_node, before workflow_update_node_inputs) or are unsure of the current graph shape. Untested data-producing nodes (missing from nodeSamples, or hasOutput: false) need workflow_test_node (or workflow_test) before their outputs can be referenced via `{outputs.<nodeId>:::…}`. `webhookUrls` maps each Webhook trigger id to the URL it listens at.
workflow_get
Returns `url` — the link that opens this workflow in the studio workflow builder. It activates the workflow's own workspace on load, so the user lands in the right place with no workspace switching. Call this whenever you report a workflow as built, published, or needing something finished by hand, and give the user the link — never just a workflow id.
workflow_get_url
Softr ChatGPT Plugin FAQ
How the directory, categories and Discoverability Score work.
Read the methodologyHow do I improve Softr's ChatGPT Plugin discoverability?
The levers are the listing surface agents actually read: names, descriptions, keywords, tool metadata, and registry health. Which lever matters depends on where discovery breaks, which is what continuous measurement shows.
What are Softr alternatives on ChatGPT?
As of 2026-11-01, Softr competes with Adalo, Akwadra, Alpy, Apiway — AI Workspace, AppDeploy, appdesigns, Base44, Bilt and 34 more in ChatGPT AI App & Website Builders, ranked by public Discoverability Score.
Where is this profile measured?
This profile uses the geography attached to the latest public registry snapshot: US. Locale tags are intentionally omitted.