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
- 105
- Geography
- US
The Primary Subcategory used for this profile’s headline score.
Other Subcategories where the Integration is listed.
ChatGPT Plugin Discovery Score
ChatGPT Plugin discovery is coming soon
ChatGPT can surface a Plugin when it matches a user's request.Your Plugin Discovery Score measures how often yours appears.
No spam. Unsubscribe any time.
What discovery looks like

Competing in ChatGPT AI App & Website Builders
View Category105 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 list_applications first to find valid application IDs.
add_application_user
Adds a branch node with filter paths to the workflow. Rules: - For 3+ branches, use CreateBranchCommand after this - BRANCH is not composite - use parent's compositeNodeId This tool executes immediately and persists the workflow. 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"]}
add_branch_node
Adds a new action node of the specified type directly after a given existing node. Rules: - compositeNodeId is workflow ID unless inside LOOP_ACTION_GROUP - Only use node types from Specifications - Never pick a third-party vendor action type unless the user named that vendor in the prompt or the workflow already uses it. `datasource_integrations` membership is not a vendor cue. This tool executes immediately and persists the workflow. 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"]}
add_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 search_records filter. Call get_schema 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 get_table or list_fields if you do not already have them.
aggregate_data
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 list_workspace_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. Custom-domain senders additionally need DKIM/Return-Path DNS verification, which is done in Softr Studio, not through this tool. Use list_applications to find application IDs.
configure_application_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 list_applications to find application IDs.
configure_application_sign_up
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: list_data_sources (integrationId + type), then list_data_source_databases (databaseId), then — for SUPABASE, POSTGRESQL, SQL_SERVER, SNOWFLAKE, GOOGLE_BIGQUERY, SMARTSUITE and CLICKUP only — list_data_source_schemas (schema), then list_data_source_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 update_vibe_coding_block_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 — get_vibe_coding_block_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 preview_app or make it live with publish_app.
connect_vibe_coding_block_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 publish_app when the user wants it live, and preview_app 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 list_workspaces 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.
create_application
Creates an additional branch path on an existing branch node. Rules: - Use on any existing BRANCH node to add more paths This tool executes immediately and persists the workflow. 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"]}
create_branch
Create a new database in the specified workspace. Requires write permission to the workspace. Returns the created database with its generated ID.
create_database
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 get_schema first for the authoritative reference of field types, their options, and value examples.
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 list_pages 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 list_pages) 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 create_vibe_coding_block 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 preview_app or make it live with publish_app.
create_page
Create a new record with specified field values. Fields is an object mapping field IDs to values. Take the IDs from get_table or 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. Returns the created record with its generated ID. Field keys in the response are field IDs — resolve them with the same field list.
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 get_table or list_fields if you do not already have them. 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.
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; get_schema shows an example value. For computed fields, see the create_field tool for the required 'options' per type. Call get_schema first for the authoritative reference of field types, their options, and value examples.
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 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 get_user_connection returns, so you can name what you connected and build a condition from the result without calling 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: list_data_sources → list_data_source_databases → list_data_source_tables → list_data_source_table_fields for the field IDs. For SUPABASE, POSTGRESQL, SQL_SERVER, SNOWFLAKE, GOOGLE_BIGQUERY, SMARTSUITE and CLICKUP there is a level in between: call list_data_source_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 takes a field ID, never a field label, and a field whose type cannot hold that value is rejected with the reason; re-read the fields rather than retrying the same ID. 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 — 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: set_application_login does that, and configure_application_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.
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:<fieldId>", "type": "ARRAY"}, "operator": "IS", "value": ["<optionId>"]}]} logicalOperator is AND or OR and defaults to AND. Build it by calling get_user_connection first: subject.field must be "USER:<fieldId>" using a real field ID from that call - never a field label, and never a guess, because a condition on a non-existent field matches nobody and reports no error. The predefined "USER:EMAIL" and "USER:EMAIL_DOMAIN" work without a lookup. subject.type is TEXT, ARRAY, NUMERIC, DATE or BOOLEAN, chosen as get_user_connection describes; for an ARRAY field the value entries are that field's choice IDs, never the visible labels. value is ALWAYS a list of strings, whatever subject.type is: a single-select Role compared as TEXT still sends ["<optionId>"], never a bare "<optionId>". 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. Names are unique within an application, case-insensitively, so call 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 list_applications to find application IDs.
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 get_page first). Pass `code` (and `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 update_vibe_coding_block_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 update_vibe_coding_block_code. `code` and `userPrompt` must be given together — passing one without the other is rejected. Call get_vibe_coding_docs before writing `code`, same as you would before any update_vibe_coding_block_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 — get_vibe_coding_block_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 connect_vibe_coding_block_data_source, and only then write the data-dependent code via update_vibe_coding_block_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 update_vibe_coding_block_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 set_vibe_coding_block_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 preview_app or make it live with publish_app.
create_vibe_coding_block
Creates a new workflow with the chosen trigger and opens its editor — the command for any automation, whatever starts it. Retrieved by the workflow editor's own co-builder: from inside an already-open workflow, use it to stand up an additional workflow, then chain build commands (e.g. AddNodeCommand, UpdateNodeInputsCommand) in the same response to add its action steps. The application and database co-builders cannot emit this command directly — for those surfaces, emit DelegateToCreateWorkflowAssistant instead; its generation pipeline runs this command as its first step, in a separate, hidden conversation neither co-builder sees. The trigger type drives the workflow's shape. Record-watch, schedule, and webhook automations (a record or row created, updated, or deleted; a form submitted; a new user; a recurring or one-time schedule; an inbound webhook) get a plain workflow. The `SOFTR_APPS_TRIGGER_WORKFLOW` trigger — a workflow the app runs on demand behind a "Run custom workflow" action button — additionally gets its mandatory Show-Wait-Screen + End-User-Interactions scaffold built in and the command returns `initiateId`. Rules: - Never accept or take an application id — whether the workflow pins to the current app is derived from context. - Never ask the user which workspace to create the workflow in or where they land — both are derived from context. - For `SOFTR_APPS_TRIGGER_WORKFLOW`, insert the workflow's first real step after the returned `initiateId` via `AddNodeCommand` — never before it or in place of the built-in Show-Wait-Screen/End-User-Interactions scaffold.
create_workflow
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 delete_table rejects. WARNING: This action cannot be undone. Confirm with the user before calling it.
delete_database
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.
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 executes immediately and persists the workflow. 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"]}
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.
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.
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 get_database (or count 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 delete_database 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.
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 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.
delete_user_group
Remove a data source connection from a vibe-coding block (undoes connect_vibe_coding_block_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 (update_vibe_coding_block_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 preview_app or make it live with publish_app.
disconnect_vibe_coding_block_data_source
Creates an exact copy of a specified node and inserts it immediately after the original node in the workflow. This tool executes immediately and persists the workflow. 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"]}
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 restore_vibe_coding_block_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 connect_vibe_coding_block_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 get_vibe_coding_block_settings call on the ORIGINAL gives all three (`settings`, `actions`, `dataSources`) without its source code — then apply whatever differs to the NEW blockId with update_vibe_coding_block_settings / set_vibe_coding_block_action_visibility / connect_vibe_coding_block_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 preview_app or make it live with publish_app.
duplicate_vibe_coding_block_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.
get_access_control
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`: 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). Use list_applications first to find valid application IDs.
get_application
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.
get_block
Get details of a specific database by ID including name, description, workspace, table count and timestamps. Use this to get full information about a database before working with its tables.
get_database
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. 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 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.
get_dynamic_input_options
Reads a node's saved sample output and its ready-to-paste `variables` without re-running it. Returns `status: 'NOT_TESTED'` if the node has no saved sample yet — call test_node first. Omit `nodeId` to get a tested/untested overview of every node in the workflow, useful for planning which nodes still need test_node before you can wire their outputs into other nodes' inputs.
get_node_output
Returns the full specification (all inputs, required flags, and configuration hints) for one or more node types. Call this before update_node_inputs so you know exactly which inputs a node type expects. Inputs whose `renderType` is `DYNAMIC_SELECT`, `DYNAMIC_MULTI_SELECT`, or `DYNAMIC_KEY_VALUE_LIST` carry no value list here — their allowed values live in the connected integration; `AI_MODEL_SELECT` inputs carry no value list either — their allowed values are the workspace's live AI model list. Fetch either kind with get_dynamic_input_options. When present, `inputVisibilityRules` lists mutually exclusive inputs: when the named `input` equals `whenValue`, every input in `hiddenInputs` is hidden and cleared by the builder and is NOT required — never try to fill both sides of such a pair.
get_node_specifications
Get a single page with its blocks. Returns page metadata plus a light list of blocks (id, name, title, type, category, enabled, order, visibility / user-group gating). Heavy custom code (markup/style/script) is intentionally omitted.
get_page
Get the permission gating for a single page: for each permission type (VIEW/EDIT/ADD/DELETE) the user groups allowed to perform it. Use list_user_groups to resolve custom user-group IDs.
get_page_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 get_table or list_fields — one schema call per table is enough; skip it if you already have it.
get_record
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 get_table. Call this before create_table, create_field, or update_field, and when building filters for search_records or aggregate_data. Do not guess field types, options, or operators.
get_schema
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 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 list_fields, and do not call it again for the same table unless you changed its fields yourself (create_field, update_field) in between.
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 create_user_group or update_user_group — it is the only way to learn the real field IDs, and a condition naming a field that does not exist on this table matches no users while reporting no error. Returns `source` — where the users table lives — 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 create_user_connection: `dataSource` {id, name, type} as list_data_sources returns, `database` {id, name} as list_data_source_databases returns, `schema`, and `table` {id, name} as list_data_source_tables returns. Re-pointing an app at another table in the same data source needs only `source.dataSource.id` — do not go searching list_data_sources 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 list_data_source_tables reports it the same way, so the two always agree. Use a field's id in "USER:<id>" — never its name. 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's **id** into the condition value, never its label. 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: 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 ID. 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.
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 get_page 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 update_vibe_coding_block_settings, with no code change involved — call the lighter-weight get_vibe_coding_block_settings instead, which skips the (potentially large) source code entirely. 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 update_vibe_coding_block_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 connect_vibe_coding_block_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 list_fields/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 set_vibe_coding_block_data_source_sort/set_vibe_coding_block_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 list_databases/list_tables/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 list_databases/list_tables/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 connect_vibe_coding_block_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 set_vibe_coding_block_action_visibility. Note every action with `isDefaultVisibility: false` BEFORE you change code — a compile resets them all (see update_vibe_coding_block_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: update_vibe_coding_block_settings (value-only change to an existing setting), update_vibe_coding_block_code_search_replace (small, localized code edits), or update_vibe_coding_block_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 list_vibe_coding_block_versions shows the block's history (each entry with its own source code) and restore_vibe_coding_block_version rolls this block back to one of them, while duplicate_vibe_coding_block_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 get_vibe_coding_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.
get_vibe_coding_block_code
Get ONLY the current persisted editable-setting values of an existing vibe-coding (AI-generated custom code) block — a lighter-weight alternative to get_vibe_coding_block_code when you don't need the source code, e.g. right before calling update_vibe_coding_block_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 update_vibe_coding_block_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 get_vibe_coding_block_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 set_vibe_coding_block_action_visibility, set_vibe_coding_block_data_source_sort, set_vibe_coding_block_data_source_record_filters and disconnect_vibe_coding_block_data_source, and for checking whether a restore or duplicate kept the visibility and wiring you expected. Only reach for get_vibe_coding_block_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 get_vibe_coding_block_code instead — it returns settings alongside source code and dataSources in one call.
get_vibe_coding_block_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 update_vibe_coding_block_code or update_vibe_coding_block_code_search_replace (and ideally before get_vibe_coding_block_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.
get_vibe_coding_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 add_node, before update_node_inputs) or are unsure of the current graph shape. Untested data-producing nodes (missing from nodeSamples, or hasOutput: false) need test_node before their outputs can be referenced via `{outputs.<nodeId>:::…}`.
get_workflow
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.
get_workflow_url
Lists the workspace's connected integrations (id, name, type). Use to resolve `integrationId` inputs on integration-backed trigger/action nodes (e.g. the SOFTR_TABLES integration) before calling update_node_inputs.
get_workspace_integrations
List or search an application's end users (people who log in to the published app, not builders/collaborators). Omit query to list users — first 10 by default. Provide query to filter by email or name: a case-insensitive substring matched against both fields. limit (1-100) and page (0-based) paginate; the response carries total and hasMore. Each user has id, email, name, status (NOT_INVITED, INVITED, PENDING_VERIFICATION, PENDING_SETUP, ACTIVATED or DEACTIVATED), active (false only when DEACTIVATED), created, lastSeen (null when they never signed in), and userGroups: the app's custom user groups the user belongs to, as {id, name}. Predefined groups like "All users" never appear there — use list_user_groups for full group details.
list_application_users
List all applications (apps) the authenticated builder can access across their workspaces. Returns id, name, description, workspaceId, applicationType, domain/subdomain and timestamps. Use this first to discover application IDs before calling other tools. Optionally filter by workspaceId to list apps in a specific workspace only. Use list_workspaces to resolve a workspace name to an ID if needed.
list_applications
How do I improve a ChatGPT Plugin's 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-09-17, Softr competes with Adalo, AI Roleplay Chat Simulator, AppDeploy, Base44, Buildfire, Charming, Craftian, Floot, FluxBuilder, FreakUI, GoodBarber, Hatchable, Hercules, Hostinger, Lovable, Macaly Cloud, MiniUp, ProductOS, Replit, Sticklight, Val Town, Zite 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.