Integration details
Description
Anchor gives ChatGPT a shared drive where it can create files and organize them in folders. ChatGPT can read and write files in HTML, Markdown (MD), JSON, CSV, and other text formats, design typed tables, and query them with SQL. It can move files around, make copies, and keep everything organized. Every file, table, and folder it creates is shareable with your team, while Anchor handles storage, permissions, and versioning. Log in once, with no API keys and no setup, and ChatGPT only ever touches what you can access.
- Integration type
- Plugin
- Verification status
- Not applicable
- Platform
- ChatGPT
- Primary Subcategory
- Cloud File Storage
- Secondary Subcategories
- None listed
- Brand
- Anchor
- Access
- Account required
- First tracked
- 2026-08-25
- Tool count
- 16
- 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 Cloud File Storage
View Category16 tools agents can invoke
Add a new column to an existing Anchor table. This is the column-level counterpart to create_table — same column schema, but for a single column added after the table already exists. Example input: { "table_id": "a195e7a1-b0a0-7f00-a1b2-c3d4e5f60001", "column": { "identifier": "project_id", "display_name": "Project", "type": "INTEGER", "isUnique": false, "isRequired": false, "foreignKeyTableIdentifier": "a195e7a1-b0a0-7f00-a1b2-c3d4e5f60002", "foreignKeyOnDelete": "CASCADE" } } Example with required ENUM column and defaultValue: { "table_id": "a195e7a1-b0a0-7f00-a1b2-c3d4e5f60001", "column": { "identifier": "priority", "display_name": "Priority", "type": "ENUM", "isUnique": false, "isRequired": true, "possibleValues": ["P0", "P1", "P2"], "defaultValue": "P2" } } Under the hood this runs an ALTER TABLE … ADD COLUMN on the underlying PostgreSQL table: ALTER TABLE anchor_tables."<table_uuid>" ADD COLUMN <identifier> <PG_TYPE> [NOT NULL] [UNIQUE] [DEFAULT <value>::<PG_TYPE>] [CHECK (...)] [REFERENCES <fk_table>(record_id) ON DELETE CASCADE|SET NULL]; Type mapping: STRING → TEXT, NUMBER → DOUBLE PRECISION, INTEGER → BIGINT, BOOLEAN → BOOLEAN, ENUM → TEXT (with CHECK constraint), DATE → DATE, DATETIME → TIMESTAMPTZ, EMAIL / CREATED_BY_EMAIL → TEXT. RULES: - The column uses exactly one of three forms: standard, ENUM (requires possibleValues), or foreign key (requires foreignKeyTableIdentifier + foreignKeyOnDelete). - Cannot add a column whose identifier already exists in the table. - If the column is required (isRequired: true), defaultValue MUST be set — existing rows are backfilled with it via DEFAULT. The column will be rejected without it. - If the column is not required, defaultValue is optional — existing rows get NULL when omitted. - Unique columns: when isUnique is true, isRequired must be false and defaultValue must not be set — existing rows are filled with NULL (which satisfies uniqueness). - CREATED_BY_EMAIL columns: defaultValue must NOT be set (the enforcement trigger overrides it on every INSERT/UPDATE with the caller's email). New rows inserted after the column is added are always populated by the trigger. Existing rows cannot be backfilled — the trigger only fires on INSERT/UPDATE — so isRequired may only be true if the table currently has zero rows. On a non-empty table, isRequired must be false; existing rows will be NULL while new rows are auto-filled by the trigger.
add_table_column
Copy a file. Only files can be copied (not folders). The copy gets a new ID, the caller becomes the owner, and no existing access roles are carried over. For tables, the full PostgreSQL table (schema + data) is duplicated. For apps, the full version history (every version's source, dependencies, and built bundle) is duplicated and the copy gets a fresh slug; copying an app requires contributor access to the source, since the copy's owner can unpack its source code. Requires read access to the source file and write access to the destination folder. Only in-org copies are allowed. An OTHER file may be copied into, out of, or between temporary folders (the scratch folders `mkdir` creates with `temporary: true`); apps and tables can never enter a temporary folder.
cp
Create a new folder. Pass folder_id to create a subfolder inside an existing folder, or org_id to create a folder at the org root. Requires permission at the parent location. Set temporary: true to create a temporary folder instead — private scratch space for app source code, visible only to you, always at the org root (org_id required, folder_id forbidden). Used to stage files before build_app. A temporary folder persists until a successful build_app run against it deletes it; there is currently no other way to remove one, so an abandoned temporary folder sits there permanently — only create one when you are about to populate and build it.
mkdir
Create a new table in Anchor (File type: TABLE). Tables are structured data stores with typed columns. Under the hood, this creates a PostgreSQL table whose name is the returned UUID. The table is displayed to users without exposing database concepts, but all column configurations (types, constraints, foreign keys) map directly to PostgreSQL features — the Postgres type is noted next to each config option. Tables are designed to work together through normalization: - Master data entities (Customers, Products, Employees, Vendors, Locations, Categories, etc.) are the core reusable "nouns" of an organization. - Transactional tables (Orders, Invoices, Appointments, Transactions) reference master tables via foreign keys (foreignKeyTableIdentifier) rather than embedding fields like "customer_name" or "product_name" directly. - Existing tables in the organization can be inspected with ls and describe_table to identify foreign key targets and avoid duplicating master data. SCHEMA RULES: - Every table must include a "record_id" column (INTEGER, isUnique: true, isRequired: true). This column is auto-generated (GENERATED ALWAYS AS IDENTITY) — do NOT include record_id values in subsequent INSERT queries. - Each column has an identifier (snake_case, immutable, used in SQL) and a display_name (human-friendly, shown in UI). - Each column uses exactly one of three forms: standard, ENUM (requires possibleValues), or foreign key (requires foreignKeyTableIdentifier + foreignKeyOnDelete). - columns is an array of column definitions (preferred). A legacy map keyed by identifier is also accepted. - The full column schema is set at creation time. After creating, use describe_table to inspect the schema. Example input: { "folder_id": "0195e7a1-b0a0-7f00-a1b2-c3d4e5f60001", "name": "Tasks", "description": "Tracks work items and their status", "columns": [ { "identifier": "record_id", "display_name": "Record ID", "type": "INTEGER", "isUnique": true, "isRequired": true }, { "identifier": "name", "display_name": "Name", "type": "STRING", "isUnique": false, "isRequired": true, "defaultValue": "" }, { "identifier": "due_date", "display_name": "Due Date", "type": "DATE", "isUnique": false, "isRequired": false }, { "identifier": "priority", "display_name": "Priority", "type": "ENUM", "isUnique": false, "isRequired": true, "possibleValues": ["P0", "P1", "P2"], "defaultValue": "P2" } ] }
create_table
Permanently delete a table, app, or other-file (HTML, Markdown, CSV, etc.). The deletion is immediate and irreversible — there is no trash or undo. For a TABLE, the underlying data is dropped along with the file; for an OTHER file, the stored content is removed; for an APP, every version of its source and build output is removed. Only an owner of the file (or of a parent folder) can delete a file. This is a destructive action — confirm with the user before deleting files you did not create.
rm_file
Permanently delete a column from an existing Anchor table. The deletion is immediate and irreversible — all data in the column across every row is destroyed and cannot be recovered. This is a destructive action — confirm with the user before deleting a column. Under the hood this runs an ALTER TABLE … DROP COLUMN on the underlying PostgreSQL table: ALTER TABLE anchor_tables."<table_uuid>" DROP COLUMN <column_identifier>; The "record_id" column cannot be deleted — it is the primary key.
delete_table_column
Get a table's full schema, metadata, and row count. Use this to understand a table's structure before querying or modifying it with query_table, add_table_column, or delete_table_column. Does not return row data — use query_table for that. For tables with many rows, use query_table to peek at a sample or write aggregated SQL queries rather than fetching all rows. Example input: { "table_id": "a195e7a1-b0a0-7f00-a1b2-c3d4e5f60001" } Returns: - Row count: total number of rows in the table. For large tables, use query_table with LIMIT to peek at sample data, or write aggregated SQL queries (COUNT, SUM, AVG, GROUP BY, etc.) instead of selecting all rows. - Column definitions: each column has an identifier (snake_case, immutable — use this in SQL queries) and a display_name (human-friendly — use this when communicating with the user). Also includes types, constraints, allowed values, and foreign key references. - Timestamps: creation and last update times.
describe_table
Get links to view or download a file. The view link opens the file as a live, rendered page in the browser that a human can read and share. Files of type OTHER return both a view link and a download link; TABLE files return a view link only (they have no downloadable file); APP files return a view link to the app's management page only (an app has no single downloadable file).
file_link
Get a deep link where the user can manually upload files to a folder in the Anchor UI. Supports only files of type OTHER; tables are created by their dedicated tool.
file_upload_link
Map of your orgs, folders, and files. A good starting point when the layout isn't already known. If you're not sure where to begin, omit `target` (or pass {by:"overview"}) — it lists every org you belong to plus "Shared with Me", each expanded breadth-first under number_of_items (default 20). To scope in, set `target` to exactly one tagged variant: - {by:"overview"} → every org + "Shared with Me" (the default). - {by:"org", org_id} → that org's root folders, expanded breadth-first. - {by:"folder", folder_id} → that folder, then its children, expanded breadth-first. - {by:"file", file_id} → one file's details (leaf — no recursion). For an APP file, also lists its source file names — read-only here; call unpack_app to get an editable copy in a temporary folder, then read_file/write_file on the copies there. - {by:"shared_with_me"} → folders and files shared with you from outside your orgs. - {by:"slug", slug} → resolve an app from its serving URL (e.g. "a1b2" or "a1b2-v3"); behaves like {by:"file"} on the resolved app. A bare slug needs consumer access; a -vN suffix needs contributor access (it previews an unpublished version). A listing that shows an app as a row does not repeat its source-file list — call ls again with {by:"file", file_id} (or {by:"slug"}) on that app to see it. Reading the output (indented text, not JSON): - Lines starting with "- " are items. Lines without "- " are properties of the nearest item above them. - Indentation is 2 spaces per level; a child sits one level deeper than its parent. - Folders end with "/". Files do not. Orgs are prefixed with "[Org] ". "Shared with Me" has no id. - Every item shows its uuid after " -- " (except "Shared with Me"). - "# items: N" under a folder/org is its total visible child count. If fewer rows appear beneath it, the rest were trimmed by number_of_items — call ls with target {by:"folder",folder_id} to drill in. - A trailing "... N more orgs not shown" line means the overview hit number_of_items before listing every org. "Shared with Me" is always shown; raise number_of_items or scope in with {by:"org",org_id}. Example: - [Org] Neural Bridge -- 019385a0-0000-0000-0000-000000000001 # items: 5 - Home/ -- 019385a0-0000-0000-0000-000000000002 # items: 2 - file1 -- 019385a0-0000-0000-0000-000000000010 - file2 -- 019385a0-0000-0000-0000-000000000011 Tunables (all optional): - number_of_items (default 20): global budget across orgs + folders + files. - show_org_details / show_folder_details / show_file_details / show_shared_with_me_details: add property blocks (timestamps, type, sizes, versions, …) per item type. - hide_orgs / hide_folders / hide_files / hide_shared_with_me: drop a whole category from the output and from the budget. - show_temporary (default false): include temporary folders when browsing an org/overview. A known temporary folder id always resolves via {by:"folder"} regardless of this flag — it only gates unprompted discovery. IDs appear in Anchor URLs: - https://anchor.cc/org/$org_id - https://anchor.cc/folder/$folder_id - https://anchor.cc/file/$file_id — also /table/$file_id - https://anchor.cc/shared-with-me — target {by:"shared_with_me"}
ls
List the organizations the user belongs to. Returns each org's id, name, and creation timestamp.
list_orgs
Move or rename a file or folder. Source can be a file or folder. Destination can be an org (root level) or a folder — but files cannot be moved directly under an org, only into folders. Requires editor (or owner) access on the source and on a destination folder; moving a folder to an org root only requires membership in that org. An OTHER file may be moved into, out of, or between temporary folders (the scratch folders `mkdir` creates with `temporary: true`); apps and tables can never enter a temporary folder, and a temporary folder itself can never be a move source or destination as a folder. Valid argument combinations (set exactly one source; unused fields must be omitted or null): • Move a file into a folder: { file_id, destination_folder_id, new_name? } • Rename a file in place: { file_id, new_name } • Move a folder into a folder: { folder_id, destination_folder_id, new_name? } • Move a folder to an org root: { folder_id, destination_org_id, new_name? } • Rename a folder in place: { folder_id, new_name } Constraints: set exactly one of file_id / folder_id. Provide at least one of destination_folder_id, destination_org_id, or new_name. destination_org_id is only valid for folder sources (files cannot live at the org root). destination_folder_id takes precedence over destination_org_id when both are set.
mv
Run a SQL query against Anchor tables. This executes real SQL against the underlying PostgreSQL database — every Anchor table is a Postgres table whose name is its UUID. ALLOWED STATEMENTS: SELECT, INSERT, UPDATE, DELETE (data operations only). BLOCKED STATEMENTS: CREATE, ALTER, DROP, TRUNCATE, triggers, indexes, and all other schema/DDL operations — these are rejected. Use create_table, add_table_column, or delete_table_column for schema changes. ONE STATEMENT PER CALL: - Exactly one SQL statement per call: no ";"-stacked statements, no BEGIN/COMMIT wrappers (each call already runs in its own transaction). - Bulk-insert with a single multi-row VALUES list. - For upserts use INSERT ... ON CONFLICT (MERGE is not allowed). - Inline literal values; bind parameters ($1) are not supported. ACCESS CONTROL: - Viewer access (read-only): SELECT queries only. - Editor access (read + write): SELECT, INSERT, UPDATE, and DELETE. If a user only has viewer access, write queries will be rejected with an access error. REFERENCING TABLES AND COLUMNS: - Tables are referenced by UUID as a double-quoted identifier: SELECT * FROM "a195e7a1-b0a0-7f00-a1b2-c3d4e5f60001" - Columns are referenced by their snake_case identifier (no quoting needed). - Always call describe_table first to discover column identifiers, types, and constraints before writing queries. - Every query must reference at least one Anchor table (a bare SELECT 1 is rejected). - System catalogs (pg_catalog, information_schema) are not queryable; use describe_table for schema discovery. CROSS-TABLE JOINS: You can JOIN multiple tables in a single query as long as all tables belong to the same organization. Cross-org queries are not allowed. RECORD_ID: The record_id column is auto-generated (GENERATED ALWAYS AS IDENTITY). Never include record_id in INSERT statements — it is assigned automatically. You can use record_id in WHERE clauses, JOINs, and SELECT lists. Add RETURNING record_id to an INSERT to get the generated ids back. CREATED_BY_EMAIL COLUMNS: Columns of type CREATED_BY_EMAIL are enforced server-side by a trigger that overrides the column with the calling user's email on every INSERT and UPDATE. You usually don't need to include these columns in writes — INSERT without them and the trigger will populate them automatically. If you do include a CREATED_BY_EMAIL column in an INSERT or UPDATE, you MUST set it to your own email (the email of the user making the request); any other value will be silently overwritten with your email by the trigger, so inserting or updating a peer's email will not work. DESTRUCTIVE OPERATIONS (UPDATE, DELETE): Before running UPDATE or DELETE queries, ALWAYS confirm with the user first. Describe what rows will be affected (e.g. "This will delete 3 rows where status = 'archived'") and wait for explicit approval. Data modifications cannot be undone. EXAMPLES: Select all rows: SELECT * FROM "a195e7a1-b0a0-7f00-a1b2-c3d4e5f60001" Select with filter: SELECT name, email FROM "a195e7a1-b0a0-7f00-a1b2-c3d4e5f60001" WHERE status = 'active' ORDER BY name Insert a single row (omit record_id): INSERT INTO "a195e7a1-b0a0-7f00-a1b2-c3d4e5f60001" (name, email, status) VALUES ('Alice', 'alice@example.com', 'active') RETURNING record_id Insert multiple rows: INSERT INTO "a195e7a1-b0a0-7f00-a1b2-c3d4e5f60001" (name, email, status) VALUES ('Alice', 'alice@example.com', 'active'), ('Bob', 'bob@example.com', 'pending') Join two tables (same org): SELECT o.order_date, c.name AS customer_name, o.total FROM "a195e7a1-b0a0-7f00-a1b2-c3d4e5f60001" o JOIN "a195e7a1-b0a0-7f00-a1b2-c3d4e5f60002" c ON o.customer_id = c.record_id WHERE o.total > 100 Aggregate query: SELECT status, COUNT(*) AS count FROM "a195e7a1-b0a0-7f00-a1b2-c3d4e5f60001" GROUP BY status Update rows (confirm with user first): UPDATE "a195e7a1-b0a0-7f00-a1b2-c3d4e5f60001" SET status = 'archived' WHERE last_login < '2024-01-01' Delete rows (confirm with user first): DELETE FROM "a195e7a1-b0a0-7f00-a1b2-c3d4e5f60001" WHERE status = 'archived'
query_table
Reads the parsed text content of an OTHER file (e.g. extracted text from PDFs, spreadsheets, documents, or text files written via write_file). Applies only to OTHER files where textable is true. Text-like formats (Markdown, HTML, JSON, CSV/TSV, XML/YAML, plain text, and source code) are returned as raw bytes verbatim — byte-for-byte identical to what write_file would round-trip, so oldString-based edits will match exactly. Binary document formats (PDF, DOC/DOCX, PPT/PPTX, XLS/XLSX, application/rtf) are returned as extracted plain text only — the original binary structure is lost and write_file cannot edit these formats. For TABLE files use describe_table or query_table. For APP files, use unpack_app to get its source as files, then read_file those. Works on files inside a temporary folder (e.g. to read back a file just written via write_file, before calling build_app).
read_file
Create a temporary folder and copy every source file from a chosen app version into it. Build artifacts (bundle.html, fetch.ts) are skipped — those are regenerated by build_app from source. The new folder's UUID is returned as temporary_folder_id, which you pass back to build_app. Also pulls in the OTHER files declared as dependencies in functions.json (file_dependencies entries with type "OTHER") so the unpacked folder is fully self-contained — you can edit, add, or remove files freely without touching anything outside the temporary folder. Requires contributor/owner access to the app — the source code is not exposed to viewers. OTHER-file dependencies are only copied if you hold your own direct CONSUMER access to them — apps never hold grants, so unpacking cannot be used to read bytes you were never granted. A skipped dependency is reported in the response but does not fail the unpack. Version selection: omit `version` to unpack the published version (the safer default — that is what end users see). Pass `version` explicitly to unpack a specific version. Recommended workflow: call ls on the app first to inspect published_version and latest_version, then pass version explicitly when iterating on unpublished work. After unpack, edit the temporary folder via write_file (surgical or rewrite), cp/mv to bring in or take out OTHER files, and rm_file to prune. Then call build_app(temporary_folder_id, app_id) to publish a new version of the same app, or build_app(temporary_folder_id, name, destination_folder_id) to fork a new app from the unpacked source. The temporary folder persists until a successful build_app run against it deletes it — there is currently no other way to remove one, so an abandoned temporary folder sits there permanently. The folder is private/hidden — it has NO anchor link and is invisible to the user; do not direct the user to it.
unpack_app
Each file becomes a live, shareable page (its `view_link`), so prefer writing to Anchor whenever the user will want to view, keep, or share what you produce. Create or edit one or more textable OTHER files in a single call. Pass `writes`: an array where each entry independently creates a new file (folder_id + name) or edits an existing one (file_id). Malformed entries are rejected by schema validation before anything runs (the whole call fails — fix the entry and retry). Once the batch starts, entries are applied sequentially in array order; each gets its own per-item result so a single runtime failure (e.g. NO_ACCESS, EDIT_CONFLICT) does not halt the rest of the batch — the response array preserves the same length and order as the input. Writable mime types — text only. write_file accepts plain-text formats whose mime type is in the platform textable allowlist: Markdown (.md), plain text (.txt, .log, .ndjson), JSON (.json), HTML (.html), CSV/TSV (.csv, .tsv), XML (.xml), YAML (.yaml, .yml), TOML (.toml), SQL (.sql), and source code (.tsx, .ts, .js, .jsx, .css, .py, .go, .rs, .rb, .java, .c, .cpp, .cs, .php, .swift, .kt, .scala, .sh, .lua, .dockerfile, etc.). Binary document formats — PDF, DOC/DOCX, PPT/PPTX, XLS/XLSX, application/rtf — CANNOT be created or rewritten via write_file (they are read-only through read_file). Images, archives, and other binary content are likewise unsupported. The mime type is auto-detected from content + filename extension on every create and every full rewrite; if the detected type is not on the allowlist the entry is rejected with NOT_TEXTABLE. Per-entry shape — exactly one of the following three forms (enforced by the input schema; each form accepts only its listed fields): • Create new: { folder_id, name, text_content: { newString } }. • Edit existing — surgical: { file_id, text_content: { newString, oldString, replaceAll? } }. oldString must match exactly once unless replaceAll is true; include surrounding context to keep oldString unambiguous. • Edit existing — full rewrite: { file_id, text_content: { newString, rewrite: true } }. Replaces the entire file with newString. Temporary folders as a write target. A temporary folder (created by mkdir with `temporary: true`, or by unpack_app) is a normal write target — create and edit files inside it exactly as in any other folder, same create / surgical-edit / full-rewrite mechanics. Two rules apply only inside a temporary folder: sibling names are compared case-insensitively (`Index.tsx` collides with `index.tsx`, rejected with TEMPORARY_FOLDER_NAME_CONFLICT), and a temporary folder's total content is capped at 100 MB (rejected with TEMPORARY_FOLDER_FULL). This is where an app's source is authored before calling build_app: `index.tsx`, `favicon.json`, `functions.json`, optionally `index.css`, plus any helper modules. Authoring `functions.json` and `favicon.json`. Both are plain JSON text written the same way as any other file — write_file does not parse or validate their contents against a schema; that only happens later, at build_app. A small escaping mistake here produces a file that looks fine and only fails later at build_app, so get these two rules right: • Table UUID → double-quoted in SQL → escaped in JSON. A table is referenced by its file UUID as the literal Postgres table name, and hyphens make it an invalid unquoted identifier, so it must be double-quoted in the SQL: FROM "a198a3ac-...". Since that whole SQL string is itself a JSON string value, those double quotes need JSON escaping: \"a198a3ac-...\". • Nothing else needs extra escaping. Column names (product_id, record_id) are plain lowercase identifiers — no quoting in SQL, so nothing to escape in JSON. SQL string literals use single quotes ('like this') — JSON escaping only applies to the surrounding double quotes of the JSON string itself, never to the single quotes inside it. There is no `other-file-connections.json` in V2 — OTHER-file dependencies go inside `functions.json`'s `file_dependencies` list with `"type": "OTHER"` (just `type`, `file_id`, `description` — no `access` field for those). Worked example — functions.json: { "file_dependencies": [ { "type": "TABLE", "file_id": "a198a3ac-1111-7abc-9def-000000000001", "access": "READ", "description": "Reads the product catalog to render the product picker." }, { "type": "TABLE", "file_id": "a198a3ac-2222-7abc-9def-000000000002", "access": "WRITE", "description": "Inserts one sale row per checkout; never edits existing sales." } ], "sql_functions": [ { "name": "getProducts", "description": "Retrieves all products", "sql": "SELECT * FROM \"a198a3ac-1111-7abc-9def-000000000001\" ORDER BY record_id", "inputSchema": [], "outputSchema": { "record_id": { "type": "integer", "description": "Auto-generated row ID" }, "name": { "type": "string", "description": "Product name" } } }, { "name": "insertSale", "description": "Insert a sale for the signed-in cashier", "sql": "INSERT INTO \"a198a3ac-2222-7abc-9def-000000000002\" (product_id, cashier_email) VALUES ($1, $2) RETURNING record_id", "inputSchema": [ { "name": "product_id", "type": "INTEGER", "description": "Product record id", "isNullable": false }, { "name": "cashier_email", "type": "EMAIL", "description": "Email of the signed-in cashier", "isNullable": false, "requireAuth": true } ], "outputSchema": { "record_id": { "type": "integer", "description": "New sale row id" } } } ] } Worked example — favicon.json (simpler, no SQL to escape): { "lucide_icon_name": "rocket", "foreground_color": "#FFFFFF", "background_color": "#1a1a1a" } Calling backend functions from `index.tsx`. build_app turns each `sql_functions` entry into one exported helper in `./fetch` — same name, POSITIONAL parameters in `inputSchema` order, always resolving to an array of rows. You never write fetch.ts yourself. import { getProducts, insertSale } from './fetch'; await getProducts(); // inputSchema [] → zero arguments await insertSale(42); // one argument per inputSchema entry, in order // cashier_email is requireAuth → optional, sorts last, omit it and // the server injects the caller's email await insertSale({ product_id: 42 }); // ❌ NEVER an object. Builds fine (build_app does not typecheck), // then every call fails with INVALID_REQUEST. Rows come back keyed by the outputSchema column names ([] for a mutation with no RETURNING). A failed call throws `Error` whose message is the server code — CONSENT_REQUIRED (normal until each user consents), NOT_AUTHORIZED, INVALID_REQUEST, BUDGET_EXCEEDED — so catch it and show the user. Also exported: `getUserInfo(email?)` → the signed-in user or null, `anchorOtherFileFetch(fileId)` → a URL for a declared OTHER dependency, used directly as an src/href, and `listenToDataUpdates(onChange)` → live change notifications for the app's tables (the callback receives the changed table's file id whenever any user edits it; refetch through the `./fetch` functions in response — the event carries no data). It returns an unsubscribe function, so in React: `useEffect(() => listenToDataUpdates(() => { loadTodos(); }), [])`. Use it whenever the app displays table data that other users can change. Editing `functions.json` or `favicon.json`: prefer `rewrite: true` over a surgical edit. A surgical `oldString`/`newString` edit only checks that `oldString` matched once — it does not check that the result is still valid JSON. Adding a `sql_functions` entry via a surgical edit means getting comma placement and bracket nesting right by hand across a diff; a full `rewrite: true` of the whole file is much harder to corrupt, since you are looking at the complete, valid JSON about to be submitted rather than a fragment. Use a surgical edit here only for a single, unambiguous one-line value swap. No JSON validation happens at write time. write_file only checks that the mime type is textable (`.json` always is) — it does not parse or validate `functions.json` or `favicon.json` against their schemas. A malformed or semantically wrong file writes successfully and only fails later, at build_app's verification pipeline. If unsure after an edit, read_file the result back before calling build_app, to catch a JSON syntax slip early. Picking a format for documents and presentations. Default to HTML (.html) for documents, write-ups, and slides — full layout control that Markdown's constrained renderer can't match. Use .md only when explicitly requested or for README/changelog-style source. Markdown contract (`.md` files). Markdown files are rendered with GitHub-Flavored Markdown (GFM): headings (`#`–`######`), lists, task lists (`- [ ]`), tables, fenced code blocks, blockquotes (`>`), autolinks, strikethrough (`~~`). A limited HTML subset is supported (the same allowlist GitHub uses: `<h1>`–`<h6>`, `<p>`, `<blockquote>`, `<details>`/`<summary>`, `<pre>`, `<code>`, `<kbd>`, `<sub>`/`<sup>`, `<table>` family, `<a>`, `<img>`, `<hr>`, `<br>`, `<em>`, `<strong>`, `<del>`, `<ins>`). The `style`, `class`, and `id` attributes are stripped, as are `<script>`, `<iframe>`, `<object>`, `<embed>`, and bare `<div>`/`<span>` wrappers — do NOT rely on inline CSS or layout HTML; it will be silently removed and the document will collapse to plain text. Write semantic Markdown (`## Heading`, `> blockquote`, `**bold**`, lists, tables) — the renderer applies consistent typography, spacing, and color tokens automatically. Designing a custom font/color/border per document is an anti-pattern: the doc will look broken in the Anchor preview and unportable everywhere else. HTML contract (`.html` files). HTML files are rendered inside a full-screen iframe with no surrounding chrome and no parent-supplied padding — the document IS the viewport. Author every .html as a complete, self-contained page: • Include `<!doctype html>`, `<html>`, `<head>`, and `<body>`, and a viewport meta tag: `<meta name="viewport" content="width=device-width, initial-scale=1">`. • Put padding/margin on `body` (or a single content wrapper) so text does not touch the iframe edges. Constrain a readable measure (e.g. `max-width: 72ch; margin: 0 auto;` for prose; full-bleed sections for slides) so long lines don't sprawl on wide screens. • Be mobile-first and responsive. Mobile viewports (≤ 480px wide) are common — use fluid units (`rem`, `%`, `vw`/`vh`, `clamp()`), media queries, and flex/grid that wraps. Never assume a desktop width; never produce horizontal scroll on a phone. • For slide-like presentations, build each slide as a full-viewport section (`min-height: 100vh`) and let the user scroll between them; keep titles, bullets, and imagery readable on a narrow viewport. • Inline `<style>` and `<script>` are allowed and encouraged — the iframe sandboxes the document, so there is no parent CSS to inherit or conflict with. Choose colors, fonts, and spacing deliberately; nothing is applied for you. • Always use relative hyperlinks for inter-document links — never hardcode absolute URLs (e.g. `https://anchor.cc/...`) for content that lives in the same folder. Use relative paths (`./other-file.html`) for sibling files. Absolute links break when the document is moved, copied, or shared, while relative paths stay portable. In-page navigation is not supported (the document is rendered inside an iframe), so do not rely on `href="#..."` fragment anchors for tables of contents or cross-references. • Links to a different page or external site MUST add `target="_blank" rel="noopener"` — the document lives in an iframe, so a plain link loads the destination inside that frame instead of as a full page. Constraints: ≤ 100 writes per call. Rewrites that produce non-textable content are rejected. Each resulting file must stay under the platform file-size ceiling, else the entry is rejected with FILE_TOO_LARGE. Inspect the returned `results` array: each entry is either a success object (file_id, name, mime_type, size_bytes, line_count, created_or_updated) or an error object ({ error, detail? }). The `summary` field reports per-status counts so the agent can see at a glance whether the batch fully succeeded.
write_file
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 Anchor alternatives on ChatGPT?
As of 2026-09-11, Anchor competes with Box, Dropbox, FileAssist, FilesAnywhere, firestorage.ai, IDrive e2, ShareWatch, WeTransfer in ChatGPT Cloud File Storage, 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.