Convex: internal vs public — don't expose what doesn't need exposing
By default, any Convex function exported with export const is reachable from the frontend.
export const confirmPayment = mutation({ ... })
// → anyone can call api.payments.confirmPayment from the browserFor a payment confirmation function, that's a problem. You don't want a user calling this directly from their browser console.
The distinction is simple:
// Reachable from the frontend — for real user interactions
export const getMyOrders = query({ ... });
// Reachable only from the Convex backend — never from the client
export const confirmPayment = internalMutation({ ... });
export const sendNotification = internalAction({ ... });The rule: anything that touches financial data, critical statuses, or user access → internal.
Calling it from the backend:
await ctx.runMutation(internal.payments.confirmPayment, { orderId });
// internal. instead of api.api. → frontend can call it.
internal. → backend only.
When in doubt: would you be comfortable with a curious user calling it from their console? If not — it's internal.