writing-skills
writing-skills
Use when creating new skills, editing existing skills, or verifying skills work before deployment
appsmithAppsmith is an open-source low-code platform for building internal tools, admin panels, and dashboards. Use when a user asks to create CRUD interfaces, connect to databases or APIs with drag-and-drop widgets, write JSObjects for business logic, or self-host Appsmith with Docker or Kubernetes.
Independent GitHub snapshot · Automated classification · Original documentation governs
Appsmith is an open-source low-code platform for internal tools: widgets are dragged onto a canvas, queries talk to databases and APIs, and JavaScript ({{ }} bindings and JSObjects) ties them together. It runs on Appsmith Cloud or self-hosted as a single container that bundles the server, MongoDB, Redis and PostgreSQL. Two images exist: appsmith-ce (Community, Apache-2.0) and appsmith-ee (Commercial edition with a free plan; Business and Enterprise features unlock with a license key). This skill reflects Appsmith v2.4 (September 2026).
-- OrdersQuery (PostgreSQL). Bindings read widget values; with prepared statements on, each one is a parameter.
-- Never put a binding inside a SQL comment: it is still counted as a parameter.
SELECT * FROM orders
WHERE status = {{ StatusSelect.selectedOptionValue }}
AND created_at BETWEEN {{ FromDate.selectedDate }} AND {{ ToDate.selectedDate }}
AND ({{ SearchInput.text }} = '' OR customer_email ILIKE '%' || {{ SearchInput.text }} || '%')
ORDER BY created_at DESC
LIMIT {{ OrdersTable.pageSize }} OFFSET {{ OrdersTable.pageOffset }}
-- UpdateOrderQuery: values passed at call time arrive as this.params
UPDATE orders SET status = {{ this.params.status }} WHERE id = {{ this.params.orderId }}
UpdatePlanAPI (REST API datasource; auth headers live in the datasource)
PUT https://api.northwind.io/users/{{ UsersTable.selectedRow.id }}
Body: { "plan": {{ PlanSelect.selectedOptionValue }}, "note": {{ NoteInput.text }} }
Widget properties used most: Table selectedRow, pageNo, pageSize, pageOffset, searchText; Select selectedOptionValue; Input text; DatePicker selectedDate. For server-side pagination turn on the table's Server side pagination and run the query from onPageChange.
Each query has a Run behavior in its settings: Manual (only .run() or a widget event), On page load, or Automatic (re-runs whenever a bound value changes). Do not combine Automatic with an event handler that also calls .run() — the query executes twice. Other settings there: Use Prepared Statements (on by default), Request confirmation before running, and Query timeout (default 10 000 ms, maximum 60 000 ms).
// JSObject "Refunds" — reusable business logic
export default {
// Transform query data for charts (Lodash `_` and `moment` are built in)
getRevenueByMonth() {
return OrdersQuery.data.reduce((acc, order) => {
const month = moment(order.created_at).format("YYYY-MM");
acc[month] = (acc[month] || 0) + order.amount;
return acc;
}, {});
},
// Multi-step action, run from the Confirm button inside ConfirmRefundModal
async processRefund() {
const order = OrdersTable.selectedRow; // {} when nothing is selected
if (!order.id) {
showAlert("Select an order first", "warning");
return;
}
try {
await StripeRefundAPI.run({ chargeId: order.stripe_charge_id }); // {{ this.params.chargeId }}
await UpdateOrderQuery.run({ orderId: order.id, status: "refunded" });
await SlackNotifyAPI.run({ message: `Refund processed for order #${order.id} (${order.amount})` });
showAlert("Refund processed successfully", "success");
await OrdersQuery.run(); // refresh the table
} catch (error) {
showAlert(`Refund failed: ${error.message}`, "error");
} finally {
closeModal(ConfirmRefundModal.name);
}
},
// Form validation
validateForm() {
const errors = {};
if (!EmailInput.text?.includes("@")) errors.email = "Invalid email";
if (Number(AmountInput.text) <= 0) errors.amount = "Amount must be positive";
if (!ReasonSelect.selectedOptionValue) errors.reason = "Select a reason";
return errors;
},
};
Framework functions: Query.run(params) returns a promise with the response; showAlert(message, type) shows a 5-second toast (info, success, warning, error); showModal(Modal.name) and closeModal(Modal.name) open and close a Modal widget; storeValue(key, value, persist) writes to appsmith.store. showModal only opens the modal — it does not wait for an answer, so put the confirmed action on a button inside the modal (or use the query's confirmation setting).
# ~/appsmith/docker-compose.yml — pin a release tag from github.com/appsmithorg/appsmith/releases
services:
appsmith:
image: index.docker.io/appsmith/appsmith-ee:v2.4.3 # or appsmith/appsmith-ce:v2.4.3
container_name: appsmith
ports:
- "80:80"
- "443:443"
volumes:
- ./stacks:/appsmith-stacks
restart: unless-stopped
docker compose up -d # first start takes up to 5 minutes; then open http://localhost
docker compose exec -it appsmith appsmithctl backup # encrypted archive in ./stacks/data/backup/
# settings: edit ./stacks/configuration/docker.env, then
docker compose restart appsmith
# Kubernetes with Helm (Commercial edition chart; the Community chart is at https://helm.appsmith.com)
helm repo add appsmith-ee https://helm-ee.appsmith.com && helm repo update
helm install appsmith-ee appsmith-ee/appsmith -n appsmith-ee --create-namespace -f values.yaml
kubectl get pods -n appsmith-ee
values.yaml must set image.tag and the ingress className and host; the Kubernetes guide also sets mongodb.enabled: false and enables mongodbCommunity and mongodbOperator, replacing the legacy Bitnami MongoDB subchart.
Branch protection, a per-instance default branch and automatic pull-and-deploy after a merge (a deploy URL called by CI with a bearer token) are Enterprise features in Git settings; without them someone pulls the branch in Appsmith by hand.
User: "Set up Appsmith on our Ubuntu server with Docker and make sure we can back it up."
mkdir -p ~/appsmith && cd ~/appsmith
# write the docker-compose.yml shown above with image tag v2.4.3
docker compose up -d
docker compose ps # the appsmith container should be listed as running
until curl -fsS http://localhost/api/v1/health >/dev/null; do sleep 10; done # server is up (up to 5 minutes)
docker compose exec -it appsmith appsmithctl backup
ls stacks/data/backup/ # appsmith-backup-*.tar.gz.enc
After a few minutes the server's address (http://localhost on the same machine) shows the setup page where the first visitor creates the administrator account, so open it right away. The backup prompts for a password and cannot be restored without it. To set a custom domain or disable telemetry, edit stacks/configuration/docker.env (APPSMITH_CUSTOM_DOMAIN, APPSMITH_DISABLE_TELEMETRY=true) and restart.
User: "Build an orders page: filter by status, search by email, and let support refund the selected order after confirming."
StatusSelect, FromDate, ToDate and SearchInput, then a Table OrdersTable with data {{ OrdersQuery.data }} and server-side pagination on.OrdersQuery and UpdateOrderQuery from the Data Queries section. Set OrdersQuery to Automatic so it re-runs when a filter or the table page changes — do not also call it from onPageChange. Create the REST queries StripeRefundAPI and SlackNotifyAPI (reading {{ this.params.chargeId }} and {{ this.params.message }}) and the Refunds JSObject from above.{{ showModal(ConfirmRefundModal.name) }} and Disabled {{ !OrdersTable.selectedRow.id || OrdersTable.selectedRow.status === 'refunded' }}.ConfirmRefundModal and set its Confirm button's onClick to {{ Refunds.processRefund() }}.Selecting order 4812 and confirming runs the three queries in order, shows the toast "Refund processed successfully", reloads the table with the row's status now refunded and closes the modal. If Stripe rejects the refund, the toast reads "Refund failed: …", the order is not updated and the modal closes.
latest and a container recreate becomes an unplanned upgrade.appsmithctl backup first.'%' || {{ SearchInput.text }} || '%'.{{ }} handlers; use async/await with try/catch so a failed query stops the chain.selectedRow is never null — it is {} when nothing is selected, so test a field (selectedRow.id), not the object.APPSMITH_SERVER_TIMEOUT (in seconds). Move longer work out of the request.cs.appsmith.com; allow it in outbound firewall rules.