Files
appsmith-statistics-app/.cursor/skills/add-query-to-table/SKILL.md
Adam Pitel b5e74cebb7 Add Operations - WO Shortages page with MTS/MTO shortage tracking
Automates the production manager's manual workflow of checking xTuple WO
Schedule + Kit Material Shortage for FA department work orders. Two tabs:
WO Shortages (detail per WO + shortage line with customer name, YYYY-MM-DD
dates) and Critical Parts (aggregated parts blocking near-term shipments
with QOH from MPE warehouse). Nav button added to all pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:47:58 -04:00

6.8 KiB

name, description
name description
add-query-to-table Adds a new DB query to a page and wires it to a Table widget; documents how parameter/search syntax works (widget bindings, optional date range). Use when the user asks to add a query, connect a query to a table, create a new query, or how parameters/filters work in queries.

Add Query to Table & Parameter Search Syntax

Use this skill when adding a new query to a page, wiring a query to a table, or when you need the correct syntax for parameterized filters (e.g. date range from widgets).

1. Query structure (single source of truth)

Each query lives under the page in a folder named after the query:

  • Path: pages/<PageName>/queries/<query_name>/
  • Files:
    • <query_name>.txt -- Raw SQL. This is the source of truth for the query body. Keep it readable (real newlines, no escaping).
    • metadata.json -- Appsmith action config. The body inside unpublishedAction.actionConfiguration must match the SQL in the .txt file, stored as a single-line string with \n for newlines and \" for double quotes.

Naming: Use lowercase with underscores (e.g. units_ordered_by_series, pending_pos_slx_pending). The query name is the identifier used in bindings (e.g. {{query_name.data}}).

2. Metadata.json required fields

  • gitSyncId (CRITICAL): Must be the first field in the root object. Format: "<24-char-hex>_<uuid-v4>". Without this, Appsmith will auto-remove the query on the next pull/sync. Generate with:
    python3 -c "import uuid; print(uuid.uuid4().hex[:24] + '_' + str(uuid.uuid4()))"
    
    Use the same 24-char hex prefix as the page's gitSyncId but a unique UUID suffix.
  • id: "<PageName>_<query_name>" (e.g. "Pending POs - SLx Pending_pending_pos_slx_pending").
  • name: "<query_name>" (must match the folder and the name used in bindings).
  • unpublishedAction.actionConfiguration.body: The exact SQL from <query_name>.txt, escaped for JSON (newlines -> \n, " -> \").
  • unpublishedAction.datasource: Use the same as other queries on the page (e.g. xTuple_Sandbox).
  • unpublishedAction.pageId: "<PageName>".
  • unpublishedAction.dynamicBindingPathList: [{"key":"body"}] when the body contains {{...}} widget references.
  • Keep pluginId: "postgres-plugin", pluginType: "DB", and existing flags like encodeParamsToggle, paginationType, pluginSpecifiedTemplates, timeoutInMillisecond consistent with other DB queries in the app.

When adding a new query, copy an existing query's metadata.json from the same page and change gitSyncId, id, name, and body (and ensure body stays in sync with the new .txt file).

3. Wiring the query to a Table widget

  • In the Table widget JSON (e.g. widgets/.../Table1.json), set:
    • tableData: "{{<query_name>.data}}"
  • Table columns read from the query result by key. The key is the SELECT alias from the query (e.g. "Product", "Qty Ordered"). In primaryColumns, the column id can be a safe identifier (e.g. Qty_Ordered) while originalId / alias / label match the display name; computedValue must use the same key as in the query result, e.g. currentRow["Qty Ordered"].

So: query SELECT aliases = keys in the table's row object. Keep column keys and any currentRow["..."] references in the table in sync with those aliases.

4. Parameter search syntax (widgets in SQL)

4.1 Referencing a widget value

  • Syntax: {{WidgetName.property}}
  • Examples:
    • DatePicker date: {{SeriesDateFrom.selectedDate}}, {{PowerDateTo.selectedDate}}
    • Other widgets: use the widget's value property (e.g. selectedOptionValue, text) as per Appsmith docs.

Values are injected as strings. For PostgreSQL dates you typically cast in SQL, e.g. '{{DatePicker1.selectedDate}}'::date.

4.2 Optional date range (no filter when either date is empty)

Use this pattern so that:

  • If either date widget is empty -> the date condition is not applied (all dates allowed).
  • If both are set -> filter by date_column BETWEEN from ::date AND to ::date.

SQL pattern (replace widget names and column as needed):

AND (
	NULLIF('{{DateFromWidget.selectedDate}}','') IS NULL
	OR NULLIF('{{DateToWidget.selectedDate}}','') IS NULL
	OR date_column BETWEEN
		NULLIF('{{DateFromWidget.selectedDate}}','')::date
		AND NULLIF('{{DateToWidget.selectedDate}}','')::date
)
  • Logic: NULLIF('{{...}}','') turns an empty string into SQL NULL. If either widget is empty, one of the first two conditions is true, so the whole AND (...) is true and the BETWEEN is not applied. When both are non-empty, the third branch applies the range.
  • Widget names: Use the actual widget names (e.g. SeriesDateFrom / SeriesDateTo for one tab, PowerDateFrom / PowerDateTo for another). Ensure those widgets exist on the same page and (if in a tab) the same tab so the query runs with the right context.

4.3 Required parameters (always filter)

If the filter must always be applied (no "show all" when empty):

  • Use the binding directly and ensure the widget always has a value, or use a default in the widget.
  • Example: AND cohead_orderdate BETWEEN '{{DateFrom.selectedDate}}'::date AND '{{DateTo.selectedDate}}'::date -- then empty dates may produce invalid SQL or empty results, so prefer the optional pattern above unless the UI guarantees non-empty values.

5. Checklist when adding a new query

  1. Create pages/<PageName>/queries/<query_name>/.
  2. Add <query_name>.txt with the full SQL (source of truth).
  3. Add metadata.json with correct gitSyncId, id, name, body (body = SQL from .txt, JSON-escaped), and same datasource/pageId as other page queries.
  4. If the SQL uses {{...}}, set dynamicBindingPathList to [{"key":"body"}].
  5. In the Table widget that should show the data, set tableData to {{<query_name>.data}}.
  6. Ensure table column keys / currentRow["..."] match the query's SELECT aliases.

6. Renaming or replacing a query

  • To rename (e.g. units_shipped_by_series -> units_ordered_by_series):
    • Create the new folder and files under the new name (with a new gitSyncId).
    • Update every reference to the old query (e.g. tableData: {{old_name.data}} -> {{new_name.data}}).
    • Remove the old query folder (both .txt and metadata.json).
  • When replacing the SQL but keeping the same name, update the .txt first, then update the body in metadata.json to match (same content, JSON-escaped).

Reference

  • gitSyncId: <24-char-hex>_<uuid-v4>. Without this, Appsmith auto-removes the query on sync.
  • Optional date range: NULLIF + IS NULL + OR + BETWEEN as above.
  • Table data binding: {{<query_name>.data}}.
  • Column keys: Must match query SELECT aliases (e.g. "Product", "Qty Ordered").