Skip to content
Pinner.xyz

Migrate from Pinata

Why migrate?

  • Decentralized infrastructure: no single point of failure, no lock-in
  • Competitive pricing: pay for pinning, not bundled gateway infrastructure
  • Privacy-focused: your data, your control
  • Compatibility adapter: swap the SDK, keep most of your call patterns

Migration strategy

ApproachBest forEffort
Pinata AdapterQuick migration, minimal code changesLow
Native Pinner APILong-term projects, better featuresMedium

Feature compatibility

The adapter provides broad Pinata API coverage, but some features are not supported because Pinner's architecture differs from Pinata's:

Featurev2 AdapterLegacy AdapterNotes
Public file uploadfile, fileArray, base64, json
Pin by CID
Private uploadsThrows "not supported"
URL uploadN/AThrows "not supported" in v2
File listingPagination filters limited
Get file by ID
Delete / unpin
Update metadata
Pin queueReturns pinned items (no true queue)
Groups⚠️N/Alist() returns empty; create/add/remove throw
GatewaysN/APublic gateways only; private throws
Signed URLs⚠️v2 throws; legacy returns gateway URL fallback
Swap CIDThrows "not supported"
Analytics⚠️⚠️Returns empty data (not real analytics)

For the full adapter method reference, see Adapter Reference.

Adapter setup

Install Pinner

npm install @lumeweb/pinner

Pick your adapter

Pinata SDK 2.x: use pinataAdapter:

import { Pinner, pinataAdapter } from "@lumeweb/pinner";
 
const pinner = new Pinner({ jwt: process.env.PINNER_AUTH_TOKEN! });
const adapter = pinataAdapter(pinner);

Pinata SDK 1.x: use pinataLegacyAdapter:

import { Pinner, pinataLegacyAdapter } from "@lumeweb/pinner";
 
const pinner = new Pinner({ jwt: process.env.PINNER_AUTH_TOKEN! });
const adapter = pinataLegacyAdapter(pinner);

Step-by-step: replace initialization

  1. Replace the import. Swap pinata for @lumeweb/pinner and import the adapter.
  2. Replace the constructor. Instead of new PinataSDK({ pinataJwt }), use new Pinner({ jwt }) + adapter.
  3. Handle unsupported features. Remove or replace calls to private uploads, groups, swap CID, signed URLs, and URL uploads (see Feature compatibility).
  4. Add .execute() to v2 builder methods. The v2 adapter uses a builder pattern: chain .name() and .keyvalues(), then call .execute().
  5. Test uploads and listings. Verify your existing calls work with the adapter.

Error handling

Pinner errors use a different structure. Instead of catching generic errors:

import { PinnerError } from "@lumeweb/pinner";
 
try {
  const result = await adapter.upload.public.file(file).execute();
} catch (error) {
  if (error instanceof PinnerError) {
    console.error("Code:", error.code);
    console.error("Message:", error.message);
    console.error("Retryable:", error.retryable);
  }
}

PinnerError has the following properties:

PropertyTypeDescription
codestringMachine-readable error code (e.g. "UPLOAD_ERROR", "PIN_ERROR")
messagestringHuman-readable error message
retryablebooleanWhether the operation can be retried

See the Error Reference for all error codes and retry guidance.

Migration checklist

  • Install @lumeweb/pinner
  • Update imports and initialization
  • Remove or replace unsupported features (private uploads, groups, swap CID, signed URLs, URL uploads)
  • Add .execute() to builder methods (v2 adapter only)
  • Test file uploads
  • Test file listing and deletion
  • Update error handling
  • Remove Pinata SDK dependency
  • Deploy and monitor

Testing your migration

  1. Test in development: verify all upload operations work
  2. Check file listings: ensure metadata is preserved
  3. Verify deletions: test unpin operations
  4. Test unsupported feature handling: confirm your app gracefully handles notSupported errors
  5. Monitor performance: compare upload speeds
  6. Review costs: check your billing

Next steps