ar.io Logoar.io Documentation

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 SDKturbo-upload
KeysArweave, Solana, Ethereum, KYVE, PolygonArweave JWK or Solana key
Runs inNode and the browserNode only
Doesupload, payments, credits, folders, CLI, ArNSsign and upload
Installed on its own784 lockfile entries, 895 MB1 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-upload

Upload

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, so sign() then upload() charges for two items. Use sign() then uploadSigned(item), and record item.idB64Url, which is the string form.
  • target and anchor look alike and are not. Adjacent 32-byte fields: target is base64url that decodes to 32 bytes, anchor is 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

uploadpaymentgateway
PRODUCTIONhttps://upload.ardrive.iohttps://payment.ardrive.iohttps://turbo-gateway.com
TESTNEThttps://upload.services.ar-io.devhttps://payment.services.ar-io.devhttps://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

How is this guide?