npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

fs-box-sync

v1.5.1

Published

TypeScript SDK for Box API with seamless Box Drive integration - manage files, folders, webhooks, and sync between cloud and local filesystem

Readme

fs-box-sync

npm version License Types NPM Downloads Last Commit Coverage CI Status GitHub Stars

Toolkit for Box REST API with automatic token management, OAuth automation support, and Box Drive integration.

Features

  • 3-Layer Architecture - BoxAPI (raw API) → BoxDrive (sync bridge) → BoxFS (fs-like interface)
  • Token Provider Pattern - Injectable OAuth automation (e.g., Playwright)
  • Persistent Storage - Tokens saved across sessions
    • Windows: C:\Users\{user}\AppData\Local\fs-box-sync\tokens.json
    • Mac/Linux: ~/.config/fs-box-sync/tokens.json
  • Smart Sync - Intelligent Box Drive sync verification with multiple strategies
  • ID-based Operations - All operations use Box IDs (no local paths in public API)
  • Chunked Uploads - Automatic chunking for files >20MB

Architecture

┌─────────────────────────────────────┐
│   BoxFS (High-level API)            │  ← Recommended
│  - readDir(id, ensureSync)          │
│  - readFile(id, ensureSync)         │
│  - uploadWithYearMonthFolders()     │
└──────────────┬──────────────────────┘
               │
┌──────────────▼──────────────────────┐
│     BoxDrive (Sync Bridge)          │
│  - getLocalPath(id)                 │
│  - waitForSync(id, strategy)        │
│  - Smart sync verification          │
└──────────────┬──────────────────────┘
               │
┌──────────────▼──────────────────────┐
│      BoxAPI (Pure API)              │
│  - getFileInfo(id)                  │
│  - uploadFile(id, file)             │
└─────────────────────────────────────┘

Installation

npm install fs-box-sync

Usage Guide: Choose Your Integration Level

fs-box-sync supports three usage patterns based on your needs:

🟢 Tier 1: Quick Testing (Access Token Only)

Best for: Learning Box API, POC, quick experiments, one-off scripts

Get access token: Box Developer Console → My Apps → Your App → Configuration → Developer Token (Generate)

import { BoxAPI } from 'fs-box-sync';

// Just paste the developer token - works immediately!
const api = new BoxAPI({
  accessToken: 'your-developer-token-from-console',
});

// Use right away - perfect for testing
const files = await api.listFolderItems('folder-id');
await api.uploadFile('folder-id', './test.pdf');

Characteristics:

  • Zero setup - paste token and go
  • No OAuth flow required
  • Perfect for learning Box API
  • ⚠️ Expires in ~1 hour - must regenerate manually
  • ⚠️ No auto-refresh - you manage token lifecycle

🟡 Tier 2: Production (Refresh Token)

Best for: Scheduled tasks, automation scripts, long-running services (up to 60 days)

Setup: Perform OAuth flow once to get refresh token, then use it directly

import box from 'fs-box-sync';

box.configure({
  clientId: process.env.BOX_CLIENT_ID,
  clientSecret: process.env.BOX_CLIENT_SECRET,
  refreshToken: 'your-refresh-token', // From initial OAuth flow
});

// Auto-refreshes access tokens for ~60 days
await box.uploadFile('folder-id', './file.pdf');
await box.uploadWithYearMonthFolders('folder-id', './file.pdf', 'ja-JP');

Characteristics:

  • Auto-refreshes access tokens
  • Works for ~60 days
  • Tokens persist to disk automatically
  • Cross-platform storage (Windows/Mac/Linux)
  • ⚠️ Requires initial OAuth to get refresh token
  • ⚠️ Needs re-auth every ~60 days

Storage locations:

  • Windows: C:\Users\{user}\AppData\Local\fs-box-sync\tokens.json
  • Mac/Linux: ~/.config/fs-box-sync\tokens.json

🔵 Tier 3: Enterprise (Token Provider - Recommended for Automation)

Best for: Fully automated systems, CI/CD, unattended services, production deployments

Setup: Implement OAuth automation (e.g., Playwright) - works indefinitely

// boxClient.ts - Create a wrapper module
import credentials from '@constants/credentials';
import box from 'fs-box-sync';
import Playwright from './Playwright';

box.configure({
  clientId: credentials.BOX_CLIENT_ID,
  clientSecret: credentials.BOX_CLIENT_SECRET,
  tokenProvider: async (authUrl) => {
    // Fully automate OAuth - no manual intervention needed
    return await Playwright.getBoxCode(authUrl);
  },
});

export default box;

Then use anywhere in your app:

import box from './boxClient';

// Works indefinitely - auto re-authenticates when needed
await box.uploadFile('folder-id', './file.pdf');
await box.readDir('folder-id'); // Always reads from synced local filesystem

Characteristics:

  • Works indefinitely - never expires
  • Zero manual intervention after setup
  • Perfect for unattended automation
  • Handles token expiration automatically
  • ⚠️ Requires OAuth automation setup (Playwright, Puppeteer, etc.)

Comparison Table

| Feature | Tier 1: Testing | Tier 2: Production | Tier 3: Enterprise | | -------------------- | -------------------- | --------------------- | ------------------- | | Setup Complexity | Minimal (copy/paste) | Low | Medium | | Duration | ~1 hour | ~60 days | Indefinite | | Auto-Refresh | ❌ No | ✅ Yes | ✅ Yes | | Manual Work | Regenerate hourly | Re-auth every 60 days | None | | Best For | Testing, Learning | Automation Scripts | Production Services | | OAuth Required | ❌ No | ✅ Initial only | ✅ Fully automated |


Quick Decision Guide

Choose Tier 1 if you want to:

  • 🧪 Test Box API quickly
  • 📚 Learn how the API works
  • ⚡ Get started in 30 seconds
  • 🔬 Experiment with features

Choose Tier 2 if you have:

  • 🤖 Automated workflows
  • ⏰ Scheduled tasks (cron jobs)
  • 📊 Scripts that run periodically
  • ✅ OK with re-auth every ~60 days

Choose Tier 3 if you need:

  • 🏢 Production-grade automation
  • 🔄 Services that run 24/7
  • 🚫 Zero manual intervention
  • ⚙️ CI/CD integration

Best Practice: Wrapper Module Pattern (Recommended)

For any production application, we recommend creating a wrapper module that exports a pre-configured singleton instance. This is the cleanest and most maintainable approach.

Why Use a Wrapper Module?

Single source of truth - Configuration in one place ✅ No duplication - Import once, use everywhere ✅ Type safety - Full TypeScript support ✅ Easy testing - Simple to mock in tests ✅ DI friendly - Easy to swap implementations

How to Set Up

Step 1: Create a wrapper module (e.g., boxClient.ts or lib/box.ts)

// src/lib/boxClient.ts
import credentials from '@constants/credentials';
import box from 'fs-box-sync';
import Playwright from './Playwright';

// Configure once
box.configure({
  clientId: credentials.BOX_CLIENT_ID,
  clientSecret: credentials.BOX_CLIENT_SECRET,
  tokenProvider: async (authUrl) => {
    return await Playwright.getBoxCode(authUrl);
  },
});

// Export the pre-configured singleton
export default box;

Step 2: Use anywhere in your application

// In any file - just import and use!
import box from '@/lib/boxClient';

// Ready to use - no configuration needed
async function uploadReport() {
  await box.uploadFile('folder-id', './report.pdf');
}

async function listFiles() {
  const files = await box.readDir('folder-id');
  return files;
}

Pattern Benefits

This pattern works for all tiers:

Tier 1 (Testing):

// boxClient.ts
import { BoxAPI } from 'fs-box-sync';

const api = new BoxAPI({
  accessToken: process.env.BOX_ACCESS_TOKEN,
});

export default api;

Tier 2 (Production):

// boxClient.ts
import box from 'fs-box-sync';

box.configure({
  clientId: process.env.BOX_CLIENT_ID,
  clientSecret: process.env.BOX_CLIENT_SECRET,
  refreshToken: process.env.BOX_REFRESH_TOKEN,
});

export default box;

Tier 3 (Enterprise):

// boxClient.ts
import box from 'fs-box-sync';
import Playwright from './auth/Playwright';

box.configure({
  clientId: process.env.BOX_CLIENT_ID,
  clientSecret: process.env.BOX_CLIENT_SECRET,
  tokenProvider: async (authUrl) => {
    return await Playwright.getBoxCode(authUrl);
  },
});

export default box;

Why This Is the Intended Design

The package exports a singleton by default (export default Box.getInstance()), which means:

  1. Designed for global use - One instance across your app
  2. Token storage coordination - All calls share the same tokens
  3. Built-in singleton pattern - You don't manage instances

The wrapper module pattern simply organizes the singleton configuration - it's the recommended way to use this package in production.


Configuration

interface BoxConfig {
  // === Authentication ===
  accessToken?: string; // For quick testing (Tier 1)
  tokenProvider?: (callback: string) => Promise<string> | string; // For automation (Tier 3)
  refreshToken?: string; // For production (Tier 2)
  clientId?: string;
  clientSecret?: string;
  redirectUri?: string; // Default: 'https://oauth.pstmn.io/v1/callback'

  // === Box Drive ===
  boxDriveRoot?: string; // Auto-detected if not provided
  // Windows: C:/Users/{username}/Box
  // Mac: ~/Library/CloudStorage/Box-Box
  // Linux: ~/Box

  // === Box Domain ===
  domain?: string; // Default: 'app.box.com'

  // === Sync Settings ===
  syncTimeout?: number; // Default: 30000 (30 seconds)
  syncInterval?: number; // Default: 1000 (1 second)
}

Sync Strategies

BoxDrive supports 3 sync strategies:

poll - Simple existence check

// Just checks if file exists locally (fastest, least reliable)
await box.waitForSync('file-id', 'file', 'poll');

smart - Size & modification verification (default)

// Verifies file size matches cloud (recommended)
await box.waitForSync('file-id', 'file', 'smart');

force - Try to trigger sync

// Attempts to force Box Drive sync (limited capabilities)
await box.waitForSync('file-id', 'file', 'force');

API Reference

BoxFS API (High-level)

Filesystem Operations

  • readDir(folderId) - Read directory contents (always synced locally)
  • listFolderItems(folderId) - Read with IDs and types (from cloud API)
  • readFile(fileId) - Read file content (always synced locally)
  • writeFile(folderId, filename, content) - Write file
  • deleteFile(fileId) - Delete file
  • getLocalPath(id, type) - Get Box Drive path (fast, may not exist)
  • getLocalPathSynced(id, type, strategy?) - Get Box Drive path (guaranteed to exist)
  • openLocally(id, type) - Open in Box Drive
  • existsAndSynced(id, type) - Check if ID exists and is synced
  • existsByNameAndSynced(parentId, name, type) - Check if named item exists and is synced

Search & Find

  • findByName(folderId, name) - Find by partial name
  • search(folderId, query, type?) - Search in folder

Upload & Download

  • uploadFile(folderId, filePath) - Upload file
  • downloadFile(fileId, destPath) - Download file
  • uploadWithYearMonthFolders(folderId, filePath, locale?) - Upload with date structure (default locale: 'en-US')
  • moveFile(fileId, toFolderId) - Move file

Folder Operations

  • createFolderIfNotExists(parentId, name) - Create if needed
  • getFileInfo(fileId) - Get metadata
  • getFolderInfo(folderId) - Get metadata

Box Drive

  • isBoxDriveRunning() - Check if Box Drive is running
  • waitForSync(id, type, strategy?) - Wait for sync
  • getBoxDriveRoot() - Get Box Drive root path

Webhooks

  • getAllWebhooks() - List webhooks
  • createWebhook(folderId, address) - Create webhook
  • deleteWebhook(webhookId) - Delete webhook

Utilities

  • getOfficeOnlineUrl(fileId) - Get Office Online URL
  • getOfficeOnlineUrlByName(folderId, fileName) - Get by search

BoxAPI API (Low-level)

All pure Box REST API operations without Box Drive integration:

  • getFileInfo(fileId), getFolderInfo(folderId)
  • listFolderItems(folderId)
  • uploadFile(folderId, filePath) - With auto-chunking >20MB
  • downloadFile(fileId, destPath)
  • createFolder(parentId, name)
  • deleteFile(fileId), moveFile(fileId, toId)
  • searchInFolder(folderId, query, type?)
  • Webhooks, shared links, etc.

BoxDrive API (Sync Bridge)

  • getLocalPath(id, type) - Convert ID to local path
  • waitForSync(id, type, strategy) - Wait for sync
  • isSynced(id, type) - Check sync status
  • isBoxDriveRunning() - Health check
  • openLocally(localPath) - Open file/folder

Development

Build

npm run build

Test

npm test

Lint

npm run lint
npm run format

Validate Package Exports

npm run check:exports

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Issues

If you encounter any issues, please report them here.

License

MIT © oharu121