turbo-upload
For AI and LLM users: Access the complete turbo-upload documentation in plain text format at llm.txt for easy consumption by AI agents and language models. See AI Agents & LLMs for the full agent toolkit.
turbo-upload signs ANS-104 data items with an Arweave JWK or a Solana key and uploads them to Turbo. That is all it does, and it has no dependencies.
Which One To Use
Start with the Turbo SDK. It is the full client and the right choice for most things. Reach for turbo-upload when the size of a new dependency decides whether your change gets merged.
| Turbo SDK | turbo-upload | |
|---|---|---|
| Keys | Arweave, Solana, Ethereum, KYVE, Polygon | Arweave JWK or Solana key |
| Runs in | Node and the browser | Node only |
| Does | upload, payments, credits, folders, CLI, ArNS | sign and upload |
| Installed on its own | 784 lockfile entries, 895 MB | 1 entry, 460 KB |
Measured on 2026-09-11 against @ardrive/turbo-sdk@1.43.0 and @ardrive/turbo-upload@0.3.0, installing each into an empty project. Re-run it rather than trusting the table.
The two share no state, so having both installed is fine.
Quick Start
Install
npm install @ardrive/turbo-uploadUpload
import { TurboUpload } from "@ardrive/turbo-upload";
// .testnet() is free and nothing it writes is permanent.
// .production() is mainnet: permanent, public, paid from this wallet.
const client = TurboUpload.testnet({ jwk: JSON.parse(process.env.ARWEAVE_JWK) });
const { id, winc } = await client.upload({
data: Buffer.from("hello permanence"),
tags: [{ name: "Content-Type", value: "text/plain" }],
});Read it back from any gateway at <gateway>/<id>, so retrieval never depends on the service you uploaded through. Wayfinder handles gateway choice and verification for you.
Solana Keys
import { TurboUpload } from "@ardrive/turbo-upload";
const client = TurboUpload.production({
jwk: process.env.SOLANA_SECRET_KEY,
token: "solana",
});Takes any form a Solana user holds: a base58 secret key as Phantom exports it, the JSON array solana-keygen writes, raw 64 bytes, or a bare 32-byte seed. client.address is the base58 Solana address.
This is ANS-104 signature type 4, the same type the Turbo SDK uses for token: "solana", so both produce identical ids for identical content.
A 64-byte key carries its own public key, and that half is checked rather than trusted. A key whose halves disagree is refused when the client is constructed, because signing with one produces items nothing can verify and you find out after paying.
What Gets Written Wrong
The rest of the API behaves as you would expect. These do not.
- Signing twice charges twice. RSA-PSS uses a fresh random salt each time, so the same payload signed twice has a different id.
upload()signs internally, sosign()thenupload()charges for two items. Usesign()thenuploadSigned(item), and recorditem.idB64Url, which is the string form. targetandanchorlook alike and are not. Adjacent 32-byte fields:targetis base64url that decodes to 32 bytes,anchoris 32 raw bytes. A 43-character base64url anchor throws.- Production is the default. A bare
new TurboUpload({ jwk })writes to mainnet, permanently, paid. - An unrecognised option throws rather than being ignored, and the error names the key you meant.
Do not spread PRODUCTION or TESTNET into the constructor. Those records also carry name and
gatewayUrl, which are not constructor options. Use TurboUpload.production() and
TurboUpload.testnet().
Endpoints
| upload | payment | gateway | |
|---|---|---|---|
PRODUCTION | https://upload.ardrive.io | https://payment.ardrive.io | https://turbo-gateway.com |
TESTNET | https://upload.services.ar-io.dev | https://payment.services.ar-io.dev | https://ar-io.dev |
Exported as constants. Import PRODUCTION or TESTNET rather than typing a hostname: a near-miss on a testnet host can resolve to something that answers 200 with HTML, which fails much less obviously than a bad hostname should.
Requirements
Node 18.17 or later, tested on 18.17, 18, 20, 22 and 24. Server-side only, because a raw Arweave key does not belong in a browser. Published with npm provenance.
Every error extends TurboError. Catch TurboPaymentError separately: it means the wallet cannot pay, and retrying never helps.
Next Steps
Full API reference
Every method, error and option, plus runnable examples
Turbo SDK
The full client: payments, credits, folders, multi-chain signing and a CLI
Uploading with Turbo
Tagging, manifests, receipts and credits, whichever library you use
How is this guide?