Protect custom server actions with permission checks, duplicate protection and error handling. Test retries and simultaneous requests before using them with customers.
Sign-in and permissions
For actions that need a user, require authentication and derive identity from the verified request. Do not accept a browser-supplied user ID as proof of permission. Keep data access scoped to the intended user and check the required role before sensitive actions.
An endpoint's sign-in setting, its code and the table's access rules all matter. Test a signed-out caller and two different users, including attempts to read or change another user's record. See Table access.
Writes, retries and notifications
Several await calls are not automatically one database transaction. If the first write succeeds and the next call fails, do not assume the first write was rolled back. A timeout can also happen after work has completed.
Before a repeatable action, decide how your app recognizes the same operation. Use a stable operation identifier and a server-enforced uniqueness rule where appropriate, then test two simultaneous requests. A “check whether it exists, then create” sequence alone can race. Verify any locking or atomic-update guarantee before relying on it.
Track an outgoing email or notification separately from the record that caused it. Distinguish “queued” or “accepted” from “delivered.” Check what completed before retrying, especially when money or external messages are involved.
Public tools and slow providers
A public form is exposed to repeated and automated requests. Ask the builder to add input validation, a reasonable request limit, clear errors and duplicate protection before it triggers costly work. A browser button being disabled is not a server-side limit.
Keep private provider keys in server-side Env Vars. Handle timeouts and rate-limit responses; do not advertise unlimited requests or one timeout value for every endpoint, outside provider and task. For slow work, consider saving progress and processing it in a scheduled task, with a clear completion state and retry rules. See Scheduling automatic updates.
Test without contacting real customers
Preview is not a no-send environment. It shares the app's data with the published version, and outbound actions can be real. Use synthetic records and your own recipient accounts. If you need a dry-run mode, implement and test it explicitly in the app; do not assume the platform supplies one for every custom endpoint.
For a failed action, follow Troubleshooting a custom server endpoint.