idempotent
Mutations only. Marks the mutation as safe to retry on network errors. By default, mutations are never retried — even with a retry policy configured. Queries (GET) are always retryable.
Basic Usage
// Safe to retry on failure — repeated calls produce the same result
#[rpc_mutation(idempotent)]
async fn upsert_user(input: UpsertInput) -> User { ... }
// NOT safe to retry (default) — each call creates a new resource
#[rpc_mutation]
async fn create_order(input: OrderInput) -> Order { ... }Good Candidates
Mark mutations as idempotent when repeated calls produce the same result: upserts, overwrites, deletes.
// Good candidates for idempotent:
#[rpc_mutation(idempotent)]
async fn set_preference(input: PrefInput) -> Pref { ... } // upsert
#[rpc_mutation(idempotent)]
async fn update_status(input: StatusInput) -> Status { ... } // overwrite
#[rpc_mutation(idempotent)]
async fn delete_item(id: u64) -> bool { ... } // delete is idempotent
// BAD — do NOT mark as idempotent:
// #[rpc_mutation(idempotent)]
// async fn transfer_money(input: TransferInput) -> Receipt { ... }Retry Behavior
When marked idempotent, the mutation follows the same retry policy as queries. Without it, the mutation is never retried regardless of the retry configuration.
// Without idempotent: mutation is NEVER retried, even with retry policy
// With idempotent: mutation follows the same retry policy as queries
const rpc = createRpcClient({
baseUrl: '/api',
retry: { attempts: 3, delay: 1000 },
});
// upsert_user (idempotent) — will retry up to 3 times on failure
await rpc.mutate('upsert_user', input);
// create_order (not idempotent) — will NOT retry, even though retry is configured
await rpc.mutate('create_order', input);Combining Attributes
// Combine with other attributes
#[rpc_mutation(idempotent, timeout = "10s")]
async fn upsert_record(input: RecordInput) -> Record { ... }
#[rpc_mutation(idempotent, init = "setup_db")]
async fn sync_profile(pool: &PgPool, input: ProfileInput) -> Profile { ... }
// ❌ Compile error — idempotent is mutations only
// #[rpc_query(idempotent)]
// async fn get_data() -> Data { ... }Try it
Idempotent Upsert
This mutation stores a value on the server (upsert). Click the same button twice — the result is identical, proving it's safe to retry. The call counter increments, but the stored value stays the same.