# How to Integrate Bolt.new with Dropbox

- Tool: Bolt.new
- Difficulty: Intermediate
- Time required: 40 minutes
- Last updated: April 2026

## TL;DR

Dropbox integrates with Bolt.new via Dropbox's HTTP API v2 — an RPC-style REST API that works over HTTPS in Bolt's WebContainer. Create a Dropbox app in the Developer Portal to get OAuth credentials, implement the PKCE authorization flow in Next.js API routes (requires deployment for the redirect URI), then use the Dropbox SDK or direct fetch calls to upload, download, list, and share files. RPC-style endpoints differ from standard REST — read the API format carefully.

## Dropbox in Bolt.new: File Sync and Storage via HTTP API v2

Dropbox is one of the most widely used cloud storage platforms, with over 700 million registered users. For Bolt.new apps that need to interact with users' existing files — reading documents, saving exports, synchronizing data — Dropbox provides a clean HTTP-based API that works in Bolt's WebContainer. Unlike database drivers that require TCP connections, all Dropbox API calls use HTTPS, making them fully compatible during development.

Dropbox API v2 uses an unusual RPC-style architecture that catches many developers by surprise. Instead of standard REST (GET /files/list, POST /files/upload), Dropbox uses JSON-body POST requests to action-named endpoints: POST /files/list_folder, POST /files/upload. Even GET-semantics operations (listing files) use POST requests with JSON bodies. The endpoint paths are descriptive action names rather than resource paths. Once you understand this pattern, the API is very consistent — but it is worth knowing before you start prompting Bolt.

The Dropbox JavaScript SDK (dropbox package) abstracts the RPC-style calls into normal function invocations. For most Bolt integrations, using the SDK is simpler than raw fetch calls. The SDK handles the Content-Type headers, API versioning, and error format parsing. Install it as a pure JavaScript package — no native binaries, works in WebContainers. The main integration steps are: register a Dropbox app, implement PKCE OAuth, store tokens, and call SDK methods from your API routes.

## Before you start

- A Dropbox account (personal free or Dropbox Business) for testing
- A Dropbox app registered at dropbox.com/developers/apps (requires a Dropbox account)
- A Bolt.new Next.js project for the OAuth callback routes and server-side API handling
- A deployed Netlify or Bolt Cloud URL for the OAuth redirect URI — Dropbox OAuth callbacks cannot use Bolt's WebContainer preview URLs
- Familiarity with OAuth 2.0 PKCE flow — especially that PKCE requires generating a code_verifier before the authorization request

## Step-by-step guide

### 1. Create a Dropbox App and Get OAuth Credentials

Register a Dropbox application at dropbox.com/developers/apps. Click 'Create app', then make these choices: API — Dropbox API (not Scoped App, which is for Dropbox Business). App folder access vs Full Dropbox — choose 'App folder' for apps that only need one dedicated folder (simpler permission, Dropbox creates /Apps/YourAppName in the user's Dropbox), or 'Full Dropbox' for apps that need to browse the user's entire Dropbox. Give the app a unique name.

On the app settings page, you will find the App key (this is your OAuth client ID — Dropbox calls it 'App key', not 'Client ID'). Under OAuth 2.0, set the Redirect URI to your deployed callback URL: https://your-app.netlify.app/api/auth/dropbox/callback. Note: add localhost:3000 as a second redirect URI for local development testing.

For permissions, go to the Permissions tab and enable: files.content.read (download files), files.content.write (upload and modify files), files.metadata.read (list folders), sharing.write (create shared links). Save changes. Dropbox permissions must be set before generating tokens — changing permissions requires users to re-authorize.

Dropbox now uses short-lived access tokens (4 hours) with refresh tokens by default. The older long-lived tokens are being deprecated. During development, you can generate a test access token on the app settings page under OAuth 2 → Generated access token — this gives you a long-lived token for rapid development iteration without implementing the full OAuth flow first.

```
# .env
DROPBOX_APP_KEY=your_dropbox_app_key
DROPBOX_APP_SECRET=your_dropbox_app_secret
DROPBOX_REDIRECT_URI=https://your-app.netlify.app/api/auth/dropbox/callback

# For development testing only (generated on Dropbox app settings page):
# DROPBOX_TEST_TOKEN=sl.xxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Remove before production deployment!
```

**Expected result:** Dropbox app is registered with App key and App secret. Permissions include files.content.read, files.content.write, files.metadata.read, and sharing.write. Redirect URI points to your Netlify callback URL.

### 2. Install Dropbox SDK and Configure Client

Install the official Dropbox JavaScript SDK using the package name 'dropbox'. This SDK wraps Dropbox API v2's RPC-style endpoints into typed methods, handling the request format (JSON body POSTs), authentication headers, and response parsing. The package is pure JavaScript with no native binaries — it installs and works in Bolt's WebContainer.

The Dropbox SDK is initialized with your App key for OAuth flows, or with an access token for API calls. Create two utility files: one for OAuth URL generation and token exchange (uses App key + App secret), and one that creates an authenticated Dropbox client instance from a stored access token.

Dropbox API v2's RPC format note for Bolt prompting: when prompting Bolt to call Dropbox methods, reference the SDK method names (filesListFolder, filesUpload, sharingCreateSharedLinkWithSettings) rather than the raw API endpoints. Bolt generates cleaner SDK-based code when you reference method names. The SDK method names match the Dropbox API documentation endpoints with camelCase: /files/list_folder → filesListFolder, /files/upload → filesUpload, /sharing/create_shared_link_with_settings → sharingCreateSharedLinkWithSettings.

```
// lib/dropbox-client.ts
import { Dropbox } from 'dropbox';

export function getDropboxClient(accessToken: string): Dropbox {
  return new Dropbox({ accessToken });
}

// lib/dropbox-auth.ts
function base64urlEncode(buffer: ArrayBuffer): string {
  return Buffer.from(buffer)
    .toString('base64')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=/g, '');
}

export async function generateCodeChallenge(verifier: string): Promise<string> {
  const encoded = new TextEncoder().encode(verifier);
  const hash = await crypto.subtle.digest('SHA-256', encoded);
  return base64urlEncode(hash);
}

export function generateCodeVerifier(): string {
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);
  return base64urlEncode(array.buffer);
}

export async function buildAuthUrl(codeVerifier: string): Promise<string> {
  const challenge = await generateCodeChallenge(codeVerifier);
  const params = new URLSearchParams({
    client_id: process.env.DROPBOX_APP_KEY!,
    response_type: 'code',
    redirect_uri: process.env.DROPBOX_REDIRECT_URI!,
    code_challenge: challenge,
    code_challenge_method: 'S256',
    token_access_type: 'offline', // Enables refresh tokens
  });
  return `https://www.dropbox.com/oauth2/authorize?${params}`;
}
```

**Expected result:** The 'dropbox' package is installed. lib/dropbox-client.ts exports a configured Dropbox instance factory. lib/dropbox-auth.ts exports PKCE code verifier generation and auth URL building utilities.

### 3. Implement OAuth PKCE Authorization Routes

Dropbox recommends PKCE (Proof Key for Code Exchange) for OAuth 2.0. PKCE enhances security for public clients by generating a cryptographic verifier before the authorization request and proving possession of that verifier during token exchange — preventing authorization code interception attacks.

The PKCE flow requires two API routes. The initiation route generates a code_verifier (a random 32-byte string, base64url encoded), hashes it with SHA-256 to create the code_challenge, stores the verifier in an httpOnly cookie (needed for the callback), and redirects the user to Dropbox's authorization page with the code_challenge. The callback route receives the authorization code from Dropbox, reads the code_verifier from the cookie, exchanges both for access and refresh tokens, and stores the tokens securely.

This OAuth flow only completes on a deployed app where the redirect URI matches exactly. During development in Bolt, you have two options: (1) deploy to Netlify first and test OAuth there, or (2) use a development access token from the Dropbox app settings page (App Settings → Generated access token) to test file operations independently without implementing OAuth. The generated development token works for testing filesListFolder, filesUpload, etc., without going through the OAuth flow — useful for building and testing the file operation UI before the OAuth plumbing is complete.

```
// app/api/auth/dropbox/route.ts
import { NextResponse } from 'next/server';
import { buildAuthUrl, generateCodeVerifier } from '@/lib/dropbox-auth';

export async function GET() {
  const codeVerifier = generateCodeVerifier();
  const authUrl = await buildAuthUrl(codeVerifier);
  const response = NextResponse.redirect(authUrl);
  response.cookies.set('dropbox_cv', codeVerifier, {
    httpOnly: true,
    secure: true,
    maxAge: 600,
    sameSite: 'lax',
  });
  return response;
}

// app/api/auth/dropbox/callback/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const code = searchParams.get('code');
  const codeVerifier = request.cookies.get('dropbox_cv')?.value;

  if (!code || !codeVerifier) {
    return NextResponse.redirect('/login?error=missing_params');
  }

  const tokenRes = await fetch('https://api.dropboxapi.com/oauth2/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      code,
      grant_type: 'authorization_code',
      client_id: process.env.DROPBOX_APP_KEY!,
      client_secret: process.env.DROPBOX_APP_SECRET!,
      redirect_uri: process.env.DROPBOX_REDIRECT_URI!,
      code_verifier: codeVerifier,
    }),
  });

  const tokens = await tokenRes.json();
  if (tokens.error) {
    return NextResponse.redirect(`/login?error=${tokens.error}`);
  }

  const response = NextResponse.redirect('/dashboard');
  response.cookies.set('dropbox_at', tokens.access_token, { httpOnly: true, secure: true, maxAge: 14400 });
  if (tokens.refresh_token) {
    response.cookies.set('dropbox_rt', tokens.refresh_token, { httpOnly: true, secure: true, maxAge: 86400 * 30 });
  }
  response.cookies.delete('dropbox_cv');
  return response;
}
```

**Expected result:** OAuth routes are in place. On deployed Netlify, visiting /api/auth/dropbox initiates the PKCE flow, and /api/auth/dropbox/callback stores tokens and redirects to /dashboard.

### 4. Implement File Operations with the Dropbox SDK

With authentication in place, implement the file operations your app needs using the Dropbox SDK. The SDK's method signatures follow Dropbox API v2's RPC naming — remember that listing and reading files uses POST requests under the hood, but the SDK presents them as straightforward method calls.

Key SDK methods for common operations:
- List folder: dropbox.filesListFolder({ path: '' }) for root (empty string, not '/'), returns result.entries array of FileMetadata and FolderMetadata objects
- List with pagination: check result.has_more, then call dropbox.filesListFolderContinue({ cursor: result.cursor }) until has_more is false
- Download file: dropbox.filesDownload({ path: '/path/to/file.pdf' }) — in Node.js, the response contains a fileBlob property with the binary content
- Upload file: dropbox.filesUpload({ path: '/target/path/file.pdf', contents: Buffer.from(fileContent), mode: { '.tag': 'add' }, autorename: true })
- Create folder: dropbox.filesCreateFolderV2({ path: '/NewFolder', autorename: false })
- Create sharing link: dropbox.sharingCreateSharedLinkWithSettings({ path: '/file.pdf', settings: { requested_visibility: { '.tag': 'public' } } })

Error handling: Dropbox SDK methods throw errors with an error.error property containing the Dropbox error object, which has a '.tag' field identifying the error type. Common tags: 'path' for path errors, 'too_many_requests' for rate limiting, 'expired_access_token' for auth errors.

```
// app/api/dropbox/files/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { getDropboxClient } from '@/lib/dropbox-client';

export async function GET(request: NextRequest) {
  const accessToken = request.cookies.get('dropbox_at')?.value;
  if (!accessToken) return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });

  const { searchParams } = new URL(request.url);
  const path = searchParams.get('path') || '';

  const dbx = getDropboxClient(accessToken);

  try {
    const result = await dbx.filesListFolder({ path });
    const files = result.result.entries.map((entry) => ({
      name: entry.name,
      path_lower: entry.path_lower,
      isFolder: entry['.tag'] === 'folder',
      size: entry['.tag'] === 'file' ? entry.size : undefined,
      modified: entry['.tag'] === 'file' ? entry.server_modified : undefined,
    }));
    return NextResponse.json({ files, cursor: result.result.cursor, hasMore: result.result.has_more });
  } catch (error: unknown) {
    const e = error as { status?: number; error?: { error_summary?: string } };
    if (e.status === 401) return NextResponse.json({ error: 'Token expired' }, { status: 401 });
    return NextResponse.json({ error: e.error?.error_summary || 'Dropbox error' }, { status: 500 });
  }
}
```

**Expected result:** Dropbox file operation routes work correctly. GET /api/dropbox/files returns file and folder entries. Upload and share routes function after deployment with a valid access token.

## Best practices

- Always include token_access_type: 'offline' in the Dropbox authorization URL to get a refresh token — without it, users must re-authorize every 4 hours
- Store Dropbox OAuth tokens in httpOnly cookies, never in localStorage or React state — access tokens grant full Dropbox access as configured in your app's permission scopes
- Use empty string '' for Dropbox root path, not '/' — this is one of the most common Dropbox API v2 mistakes that causes path/malformed_path errors
- Handle the 'shared_link_already_exists' error in share link creation by retrieving the existing link — creating a second share link always fails since Dropbox permits one per visibility level
- Test OAuth flow on your deployed Netlify URL before implementing it fully — the redirect URI must be pre-registered and the WebContainer preview URL cannot be used
- Request minimal Dropbox permissions: files.content.read only if you need read access, files.content.write only if you need to upload or modify files — narrower permissions improve user trust
- Implement pagination for Dropbox folder listings using has_more and cursor — users' Dropbox folders may contain hundreds or thousands of files that won't all return in a single API call

## Use cases

### Import Files from User's Dropbox

Allow users to browse their Dropbox and select files to import into your app. After OAuth authentication, list the user's Dropbox folders, let them navigate and select a file, then download the file content to process in your app. Useful for importing CSV data, images, or documents that users already have in Dropbox.

Prompt example:

```
Build a Dropbox file importer. Create two API routes: GET /api/dropbox/list?path={path} (lists folder contents at the given path, defaults to root '') using the Dropbox SDK filesListFolder method, returning entries with .tag ('file' or 'folder'), name, path_lower, size, and server_modified. POST /api/dropbox/download with body {path: string} using filesDownload, returning the file content as base64 for text files under 5MB. Create a DropboxFilePicker React component that calls the list route on mount, shows a navigable file tree with folder navigation and breadcrumbs, and calls onFileImport(path, name, content) prop callback when a file is selected. Read the Dropbox access token from a server-side session.
```

### Export and Save Documents to Dropbox

Enable users to save documents, reports, or exports from your app directly to their Dropbox. After the user clicks a save button, your app uploads the file to a dedicated folder in their Dropbox and optionally returns a shareable link. This is useful for invoice generators, report builders, or any tool that creates files users want to store.

Prompt example:

```
Add 'Save to Dropbox' functionality to my document generator. Create a POST /api/dropbox/upload route that accepts {filename: string, content: string, folder: '/AppExports'} in the request body. Use Dropbox SDK filesUpload with mode 'add' and autorename: true to upload the file to the specified folder. If the folder doesn't exist, use filesCreateFolderV2 to create it first, then upload. After upload, use sharingCreateSharedLinkWithSettings to create a view-only shared link. Return {shareUrl: string, dropboxPath: string}. Add a SaveToDropbox button component that calls this route and shows the share URL with a copy-to-clipboard button.
```

### Continuous Sync — Watch a Dropbox Folder

Poll a specific Dropbox folder for new files using the Dropbox list_folder/get_latest_cursor and list_folder/longpoll endpoints. When new files appear, process them in your app. Useful for automation workflows where users drop files into a watched Dropbox folder to trigger processing. Note: longpoll requires a deployed environment, not the Bolt WebContainer.

Prompt example:

```
Build a Dropbox folder watcher that checks for new files every 30 seconds (polling, not longpoll — works in preview). Create a GET /api/dropbox/new-files?cursor={cursor} route that uses filesListFolderContinue if cursor is provided, or filesListFolder with cursor: true for the initial call. Return {entries: DropboxEntry[], cursor: string, hasMore: boolean}. Store the cursor in Supabase per user after each check. Create a useDropboxSync hook that polls /api/dropbox/new-files every 30 seconds and calls onNewFile(entry) for each new file found since the last poll. Show a 'Watching /Imports folder' status badge that turns green when active.
```

## Troubleshooting

### OAuth callback returns 'invalid_grant' or 'The provided authorization grant is invalid'

Cause: The authorization code has expired (codes are valid for ~1 minute), or the code_verifier does not match the code_challenge used in the authorization URL (PKCE mismatch), or the redirect_uri in the token exchange doesn't exactly match the one used in the authorization URL.

Solution: Verify the code_verifier is being stored in the cookie before the redirect and retrieved correctly in the callback. Ensure the DROPBOX_REDIRECT_URI environment variable is identical in both the auth URL and the token exchange request. Authorization codes expire quickly — any delay between authorization and callback causes this error.

### filesListFolder throws an error: 'path/not_found/' or 'path/malformed_path'

Cause: The path parameter uses '/' for root instead of '' (empty string), or contains characters that Dropbox doesn't allow in paths, or the path starts without a leading slash for non-root folders.

Solution: Use empty string '' for the Dropbox root folder. All non-root paths must start with '/': '/Documents', '/Apps/MyApp/exports'. Never pass '/' as a path — Dropbox API v2 does not accept it.

```
// Correct Dropbox path format:
const rootPath = '';  // Root folder: empty string, NOT '/'
const subfolder = '/Documents';  // Non-root: leading slash required
const appFolder = '/Apps/MyApp';  // App folder path

// Wrong:
// const rootPath = '/'  ← throws path/malformed_path error
```

### Dropbox access token expires after 4 hours and users are logged out

Cause: Short-lived access tokens expire after 4 hours. Without a refresh token (requires token_access_type: 'offline' in the auth URL and offline_access permission on the app), there is no way to get a new token without re-authorization.

Solution: Verify token_access_type: 'offline' is included in the buildAuthUrl function. Check that the Dropbox app settings include the correct permission scope. Implement a token refresh endpoint that uses the refresh_token to get a new access_token when the current one expires.

```
// Token refresh endpoint:
export async function POST(request: NextRequest) {
  const refreshToken = request.cookies.get('dropbox_rt')?.value;
  if (!refreshToken) return NextResponse.json({ error: 'No refresh token' }, { status: 401 });

  const res = await fetch('https://api.dropboxapi.com/oauth2/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'refresh_token',
      refresh_token: refreshToken,
      client_id: process.env.DROPBOX_APP_KEY!,
      client_secret: process.env.DROPBOX_APP_SECRET!,
    }),
  });
  const tokens = await res.json();
  const response = NextResponse.json({ ok: true });
  response.cookies.set('dropbox_at', tokens.access_token, { httpOnly: true, secure: true, maxAge: 14400 });
  return response;
}
```

### sharingCreateSharedLinkWithSettings fails with 'shared_link_already_exists'

Cause: A shared link already exists for this file. Dropbox only allows one shared link per file per visibility setting.

Solution: Catch the 'shared_link_already_exists' error and call sharingListSharedLinks with path to retrieve the existing link instead of trying to create a new one.

```
try {
  const result = await dbx.sharingCreateSharedLinkWithSettings({ path });
  return result.result.url;
} catch (err: unknown) {
  const e = err as { error?: { error_summary?: string } };
  if (e.error?.error_summary?.includes('shared_link_already_exists')) {
    // Retrieve existing shared link
    const existing = await dbx.sharingListSharedLinks({ path, direct_only: true });
    return existing.result.links[0]?.url;
  }
  throw err;
}
```

## Frequently asked questions

### Does Dropbox integration work in Bolt's WebContainer preview?

File operations (list, upload, download) work in the Bolt preview because they use outbound HTTPS requests through the Dropbox SDK. The OAuth authorization flow requires a stable redirect URI that must be registered with Dropbox — the Bolt WebContainer URL is not stable and cannot be registered. Deploy to Netlify to test the OAuth flow. For faster development, use a generated test access token from the Dropbox app settings page to test file operations without OAuth.

### What is the difference between Dropbox App folder and Full Dropbox access?

App folder access creates a dedicated /Apps/YourAppName folder in the user's Dropbox — your app can only read and write files within that folder. Full Dropbox access allows reading and writing anywhere in the user's Dropbox. App folder access is preferred for most apps: it requires less user trust (the OAuth consent screen shows 'one folder' not 'your entire Dropbox'), and limits accidental data access. Use Full Dropbox only if your app genuinely needs to access files outside a dedicated folder.

### Why does Dropbox API v2 use POST requests for listing files?

Dropbox API v2 uses an RPC (Remote Procedure Call) style rather than standard REST. All API calls are POST requests to action-named endpoints with JSON bodies — even read operations like listing folders. This differs from REST conventions where GET retrieves data. The Dropbox JavaScript SDK abstracts this into method calls like filesListFolder(), so you rarely need to deal with the raw HTTP format unless you are using fetch() directly.

### How long does the Dropbox refresh token last?

Dropbox offline refresh tokens don't have a fixed expiration — they remain valid until the user revokes your app's access in their Dropbox account settings, your app explicitly calls the revoke endpoint, or the token hasn't been used in 90 days. Practically, store the refresh token securely (httpOnly cookie or database) and implement automatic token refresh when the 4-hour access token expires.

### Can I connect to a Dropbox Business (team) account?

Yes. Dropbox Business accounts use the same API and OAuth flow as personal accounts. For team-specific features (listing team folders, accessing team members' files with admin consent), use the Dropbox Business API endpoints under /team/ — these require additional Dropbox Business API permission scopes. Standard file operations (reading/writing to the authenticated user's Dropbox) work identically for both personal and business accounts.

---

Source: https://www.rapidevelopers.com/bolt-ai-integrations/dropbox
© RapidDev — https://www.rapidevelopers.com/bolt-ai-integrations/dropbox
