· Nacho Coll · Guides  · 11 min read

Migrating from Web3.Storage to IPFS.NINJA — Step-by-Step

Migrate your pinned files from Web3.Storage (Storacha) to IPFS.NINJA. Automated migration script included.

Migrate your pinned files from Web3.Storage (Storacha) to IPFS.NINJA. Automated migration script included.

With Web3.Storage rebranding to Storacha and introducing significant changes to their platform, many developers are looking for alternatives. If you’re planning to migrate your IPFS pinned files to a more reliable and developer-friendly service, IPFS.NINJA offers a seamless solution with powerful features and competitive pricing.

IPFS Ninja Upload Interface

This comprehensive guide will walk you through migrating from Web3.Storage (Storacha) to IPFS.NINJA, including automated scripts to streamline the process.

Why Migrate from Web3.Storage?

Web3.Storage’s transition to Storacha has introduced several changes that affect how developers interact with the platform:

  • Service interruptions during the transition period
  • Changes in API endpoints and authentication methods
  • Pricing model adjustments that may impact your budget
  • Platform instability as they restructure their infrastructure

IPFS.NINJA provides a stable alternative with:

  • 99.9% uptime with enterprise-grade infrastructure
  • Transparent pricing starting with a generous free tier
  • Advanced features like custom gateways and image optimization
  • Developer-first approach with comprehensive documentation

Understanding the Migration Process

Before diving into the migration steps, it’s important to understand what IPFS pinning is and how it works. When you pin content on IPFS, you’re ensuring that your files remain accessible on the network by maintaining copies on specific nodes.

The migration process involves three main steps:

  1. Export your data from Web3.Storage
  2. Re-pin content on IPFS.NINJA
  3. Verify successful migration and update your applications

Step 1: Exporting Data from Web3.Storage

Getting Your CID List

First, you’ll need to export your pinned content identifiers (CIDs) from Web3.Storage. You can do this through their dashboard or API.

Using the Web3.Storage Dashboard

  1. Log into your Web3.Storage account
  2. Navigate to your files section
  3. Export your file list (usually available as CSV or JSON)
  4. Save the file containing your CIDs and metadata

Using the Web3.Storage API

If you prefer programmatic access:

// Web3.Storage API example
const response = await fetch('https://api.web3.storage/user/uploads', {
  headers: {
    'Authorization': `Bearer ${WEB3_STORAGE_TOKEN}`
  }
});

const uploads = await response.json();
const cids = uploads.results.map(upload => upload.cid);
console.log('Found', cids.length, 'CIDs to migrate');

Organizing Your Data

Create a structured list of your content with relevant metadata:

[
  {
    "cid": "QmYourContentHash1",
    "name": "my-important-file.json",
    "size": 1024,
    "created": "2023-01-15T10:30:00Z"
  },
  {
    "cid": "bafybeiyourcontenthashs2",
    "name": "website-assets.tar",
    "size": 5242880,
    "created": "2023-02-20T14:45:00Z"
  }
]

Step 2: Setting Up IPFS.NINJA

Creating Your Account

Start by creating a free IPFS.NINJA account. The free Dharma plan includes:

  • 500 files
  • 1 GB storage
  • 2 GB bandwidth per month
  • 1 API key
  • 1 custom gateway

Getting Your API Key

  1. Navigate to the API Keys section in your dashboard
  2. Create a new API key for migration
  3. Copy your key (format: bws_ followed by 32 hexadecimal characters)
  4. Store it securely for the migration process

Step 3: Migration Script

Here’s a comprehensive Node.js script to automate your migration from Web3.Storage to IPFS.NINJA:

import fetch from 'node-fetch';
import fs from 'fs/promises';

class IPFSMigrator {
  constructor(ipfsNinjaApiKey) {
    this.apiKey = ipfsNinjaApiKey;
    this.apiBase = 'https://api.ipfs.ninja';
    this.retryDelay = 1000;
    this.maxRetries = 3;
  }

  async pinExistingContent(cid, description = '') {
    const url = `${this.apiBase}/pin`;
    const payload = {
      cid,
      description
    };

    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-Api-Key': this.apiKey
        },
        body: JSON.stringify(payload)
      });

      if (!response.ok) {
        const error = await response.text();
        throw new Error(`HTTP ${response.status}: ${error}`);
      }

      const result = await response.json();
      return result;
    } catch (error) {
      console.error(`Failed to pin ${cid}:`, error.message);
      throw error;
    }
  }

  async migrateWithRetry(cid, description, attempt = 1) {
    try {
      const result = await this.pinExistingContent(cid, description);
      console.log(`✅ Successfully pinned: ${cid}`);
      return result;
    } catch (error) {
      if (attempt <= this.maxRetries) {
        console.log(`⏳ Retrying ${cid} (attempt ${attempt}/${this.maxRetries})`);
        await this.sleep(this.retryDelay * attempt);
        return this.migrateWithRetry(cid, description, attempt + 1);
      } else {
        console.error(`❌ Failed to pin after ${this.maxRetries} attempts: ${cid}`);
        return { error: error.message, cid };
      }
    }
  }

  async batchMigrate(contentList, batchSize = 10) {
    const results = [];
    const failed = [];

    console.log(`Starting migration of ${contentList.length} files...`);

    for (let i = 0; i < contentList.length; i += batchSize) {
      const batch = contentList.slice(i, i + batchSize);
      console.log(`Processing batch ${Math.floor(i/batchSize) + 1}/${Math.ceil(contentList.length/batchSize)}`);

      const batchPromises = batch.map(async (item) => {
        const description = `Migrated from Web3.Storage: ${item.name || 'Unnamed file'}`;
        return this.migrateWithRetry(item.cid, description);
      });

      const batchResults = await Promise.allSettled(batchPromises);
      
      batchResults.forEach((result, index) => {
        if (result.status === 'fulfilled' && !result.value.error) {
          results.push(result.value);
        } else {
          failed.push({
            cid: batch[index].cid,
            error: result.value?.error || result.reason?.message
          });
        }
      });

      // Rate limiting - wait between batches
      if (i + batchSize < contentList.length) {
        await this.sleep(2000);
      }
    }

    return { successful: results, failed };
  }

  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  async generateMigrationReport(results) {
    const report = {
      timestamp: new Date().toISOString(),
      total: results.successful.length + results.failed.length,
      successful: results.successful.length,
      failed: results.failed.length,
      successRate: ((results.successful.length / (results.successful.length + results.failed.length)) * 100).toFixed(2),
      failedItems: results.failed
    };

    await fs.writeFile('migration-report.json', JSON.stringify(report, null, 2));
    console.log('\n📊 Migration Report:');
    console.log(`Total files: ${report.total}`);
    console.log(`Successful: ${report.successful}`);
    console.log(`Failed: ${report.failed}`);
    console.log(`Success rate: ${report.successRate}%`);
    
    if (report.failed > 0) {
      console.log('\n❌ Failed migrations saved to migration-report.json');
    }

    return report;
  }
}

// Usage example
async function migrate() {
  // Your IPFS.NINJA API key
  const API_KEY = 'bws_your_32_character_hex_api_key_here';
  
  // Load your exported CID list
  const contentList = JSON.parse(await fs.readFile('web3-storage-export.json', 'utf8'));
  
  const migrator = new IPFSMigrator(API_KEY);
  
  try {
    const results = await migrator.batchMigrate(contentList);
    const report = await migrator.generateMigrationReport(results);
    
    console.log('\n🎉 Migration completed!');
    
  } catch (error) {
    console.error('Migration failed:', error);
  }
}

// Run the migration
migrate().catch(console.error);

Running the Migration Script

  1. Install dependencies:
npm install node-fetch
  1. Prepare your data: Save your exported CIDs as web3-storage-export.json

  2. Update the script: Replace bws_your_32_character_hex_api_key_here with your actual API key

  3. Run the migration:

node migrate.js

Step 4: Verification and Testing

After running the migration script, verify that your content is properly pinned on IPFS.NINJA:

Check via Dashboard

  1. Log into your IPFS.NINJA dashboard
  2. Navigate to the Files section
  3. Verify that your migrated files appear in the list
  4. Check that the CIDs match your original data

Programmatic Verification

// Verify a specific CID is pinned
async function verifyCID(cid, apiKey) {
  const response = await fetch(`https://api.ipfs.ninja/files/${cid}`, {
    headers: {
      'X-Api-Key': apiKey
    }
  });
  
  if (response.ok) {
    const fileInfo = await response.json();
    console.log(`✅ ${cid} is successfully pinned`);
    console.log(`Gateway URL: https://ipfs.ninja/ipfs/${cid}`);
    return true;
  } else {
    console.log(`❌ ${cid} not found`);
    return false;
  }
}

Test Gateway Access

Verify that your content is accessible through IPFS.NINJA’s gateway:

// Test gateway access
async function testGatewayAccess(cid) {
  try {
    const response = await fetch(`https://ipfs.ninja/ipfs/${cid}`, {
      method: 'HEAD'
    });
    
    if (response.ok) {
      console.log(`✅ ${cid} is accessible via gateway`);
      return true;
    } else {
      console.log(`❌ ${cid} not accessible (Status: ${response.status})`);
      return false;
    }
  } catch (error) {
    console.log(`❌ Error accessing ${cid}:`, error.message);
    return false;
  }
}

Step 5: Updating Your Applications

Once your migration is complete, you’ll need to update your applications to use IPFS.NINJA’s endpoints:

Update API Endpoints

Replace Web3.Storage API calls with IPFS.NINJA equivalents:

// Before (Web3.Storage)
const response = await fetch('https://api.web3.storage/upload', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${web3StorageToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ content })
});

// After (IPFS.NINJA)
const response = await fetch('https://api.ipfs.ninja/upload/new', {
  method: 'POST',
  headers: {
    'X-Api-Key': `${ipfsNinjaApiKey}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ 
    content,
    description: 'Uploaded via IPFS.NINJA'
  })
});

For detailed upload instructions, check our IPFS upload API tutorial.

Update Gateway URLs

Replace Web3.Storage gateway URLs with IPFS.NINJA:

// Before
const fileUrl = `https://w3s.link/ipfs/${cid}`;

// After
const fileUrl = `https://ipfs.ninja/ipfs/${cid}`;

Custom Gateway Configuration

IPFS.NINJA offers custom gateways for enhanced performance and branding:

  1. Navigate to Gateways in your dashboard
  2. Create a custom gateway with your preferred subdomain
  3. Configure access controls and restrictions
  4. Use your custom gateway: https://yourname.gw.ipfs.ninja/ipfs/${cid}

Advanced Migration Features

Folder Organization

IPFS.NINJA supports folder organization for better content management:

// Create folders during migration
async function createFolder(name, description, apiKey) {
  const response = await fetch('https://api.ipfs.ninja/folders', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Api-Key': apiKey
    },
    body: JSON.stringify({
      name,
      description
    })
  });
  
  return response.json();
}

// Organize content into folders
async function organizeContent(contentList, apiKey) {
  const webAppsFolder = await createFolder('Web Apps', 'Migrated web applications', apiKey);
  const assetsFolder = await createFolder('Assets', 'Static assets and media', apiKey);
  
  // Pin content to specific folders
  for (const item of contentList) {
    const folderId = item.type === 'webapp' ? webAppsFolder.id : assetsFolder.id;
    
    await fetch('https://api.ipfs.ninja/pin', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Api-Key': apiKey
      },
      body: JSON.stringify({
        cid: item.cid,
        description: item.name,
        folderId
      })
    });
  }
}

Metadata Preservation

Maintain your existing metadata during migration:

async function pinWithMetadata(cid, originalMetadata, apiKey) {
  const enhancedMetadata = {
    ...originalMetadata,
    migratedFrom: 'web3.storage',
    migrationDate: new Date().toISOString(),
    originalUploadDate: originalMetadata.created
  };
  
  return fetch('https://api.ipfs.ninja/pin', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Api-Key': apiKey
    },
    body: JSON.stringify({
      cid,
      description: originalMetadata.name || 'Migrated content',
      metadata: enhancedMetadata
    })
  });
}

Troubleshooting Common Migration Issues

Content Not Found

If some CIDs fail to pin:

  1. Verify CID format: Ensure CIDs are valid and not corrupted
  2. Check content availability: Some content might no longer be available on the IPFS network
  3. Network timeout: Increase retry delays for large files
  4. Rate limiting: Reduce batch size if you encounter rate limits

Large File Migrations

For files larger than 100MB, use IPFS.NINJA’s large file upload capabilities:

// Check file size before migration
async function checkContentSize(cid) {
  try {
    const response = await fetch(`https://ipfs.ninja/ipfs/${cid}`, {
      method: 'HEAD'
    });
    
    const contentLength = response.headers.get('content-length');
    return parseInt(contentLength) || 0;
  } catch (error) {
    return 0;
  }
}

// Handle large files differently
async function smartMigration(item, apiKey) {
  const size = await checkContentSize(item.cid);
  
  if (size > 100 * 1024 * 1024) { // 100MB threshold
    console.log(`Large file detected (${size} bytes): ${item.cid}`);
    // Use specialized large file handling
    return await pinLargeContent(item.cid, item.description, apiKey);
  } else {
    return await pinExistingContent(item.cid, item.description, apiKey);
  }
}

Cost Comparison and Plan Selection

When migrating from Web3.Storage, consider IPFS.NINJA’s pricing structure:

Free Tier (Dharma Plan)

  • 500 files
  • 1 GB storage
  • 2 GB bandwidth/month
  • Perfect for: Small projects and testing
  • Bodhi Plan ($5/month): 50K files, 10 GB storage
  • Nirvana Plan ($29/month): 500K files, 100 GB storage

For a detailed comparison with other services, check our best IPFS pinning services comparison or our specific IPFS.NINJA vs Pinata analysis.

Post-Migration Best Practices

Monitor Your Usage

  1. Set up analytics to track your file access patterns
  2. Monitor bandwidth usage to optimize costs
  3. Review pinned content regularly to remove unused files

Optimize Performance

  1. Use custom gateways for faster content delivery
  2. Enable image optimization for web assets
  3. Configure proper caching headers on your applications

Backup Strategy

  1. Export your new CID list regularly
  2. Document your folder structure and organization
  3. Keep migration logs for future reference

Conclusion

Migrating from Web3.Storage to IPFS.NINJA provides you with a more stable, feature-rich platform for your IPFS pinning needs. With the automated migration script and verification tools provided in this guide, you can ensure a smooth transition with minimal downtime.

IPFS.NINJA’s developer-friendly approach, transparent pricing, and advanced features make it an excellent choice for both small projects and enterprise applications. The platform’s focus on reliability and performance ensures your content remains accessible while providing you with the tools to optimize your IPFS workflow.

For more information on getting started with IPFS, check out our comprehensive guide on how to upload files to IPFS.

Ready to start pinning? Create a free account — 500 files, 1 GB storage, 2 GB bandwidth/month. No credit card required.

Back to Blog

Related Posts

View All Posts »