useCapxulTransfer
Mutation hook that moves money within one Account — between the main balance and its sub-account envelopes.
Moves money between two balances of the same Account: main balance into an
envelope ("Add money"), envelope back to main ("Move money out"), or envelope
to envelope. A Transfer never leaves the Account and never reaches another
party — it is not a Payment, and nothing settles externally. To send money
to someone else, use useCapxulPay.
See Money & accounts for the model.
Import
import { useCapxulTransfer } from "@capxul/sdk-react";Usage
"use client";
import { useCapxulAccountBalance, useCapxulTransfer } from "@capxul/sdk-react";
import type { SubAccountId } from "@capxul/sdk";
function AddMoneyButton({ subAccountId }: { subAccountId: SubAccountId }) {
const balance = useCapxulAccountBalance();
const transfer = useCapxulTransfer();
const account = balance.data;
if (account === undefined) return null;
return (
<button
type="button"
disabled={transfer.isPending}
onClick={() =>
transfer.mutate({
accountId: account.id,
from: "main",
to: subAccountId,
// Reuse the Account's currency and precision for the Money amount.
amount: { ...account.available, value: "25" },
})
}
>
{transfer.isPending ? "Moving…" : "Add $25 to this envelope"}
</button>
);
}Parameters
The hook itself takes no parameters. The mutation input is a TransferInput
plus an accountId:
accountId
AccountId
The Account the transfer happens inside. It is carried only so the hook can invalidate the right cache keys — the backend resolves the Account from the signed-in session, not from this field.
from, to, amount
Prop
Type
from and to are each a TransferEndpoint: either the literal "main" or
a SubAccountId. The main balance is the remainder left after every
envelope is funded — it is not a stored row. The two endpoints must differ,
and amount (a Money) must be positive.
Return type
import { type UseCapxulTransferReturn } from "@capxul/sdk-react";
// UseMutationResult<TransferResult, CapxulError, { accountId: AccountId } & TransferInput>On success, data is a TransferResult:
Prop
Type
available is the recomputed unassigned money (balance − Σ envelope
balances). The from / to slots carry the updated sub-account rows; an
endpoint that is "main" has no stored row, so its slot is null.
The hook returns TanStack Query's UseMutationResult. The most-used fields:
| Field | Description |
|---|---|
mutate | Fire the mutation (fire-and-forget; pair with onSuccess/onError callbacks). |
mutateAsync | Fire the mutation and get a Promise of the result. Rejects with a CapxulError on failure. |
data | The mutation result (typed per hook, shown above). undefined until the first success. |
error | A CapxulError when the last attempt failed, otherwise null. |
isPending | true while the mutation is in flight. Use it to disable submit buttons. |
reset | Clear the mutation state (data, error) back to idle. |
Mutations do not retry by default. The full field list is in the
TanStack Query useMutation reference;
see also the TanStack Query integration guide.
Cache behavior
On success this mutation invalidates the Account's sub-account list
(["capxul", "subAccounts", accountId]) and the personal balance
(["capxul", "accountBalance"]). Because a Transfer is not a Payment, it does
not touch the payments, activity, or insights caches.
Errors
Failures surface as a CapxulError on
error / thrown from mutateAsync — for example when from and to are the
same endpoint, when the amount is not positive, when the source balance is
insufficient (the error reports what was available versus requested), or when
the mutation fires while <CapxulProvider> is still bootstrapping.
Client method
This hook wraps
client.subAccounts.transfer({ from, to, amount }) on the
core SDK.