Box uses enterprise JWT authentication with RSA private key signing — a cryptographic operation Bubble cannot perform natively. The correct pattern for production is a small external signing proxy (Supabase Edge Function or Cloudflare Worker) that holds your JWT config and returns short-lived Bearer tokens to Bubble. For prototyping, a Developer Token (60-minute expiry) lets you build and test without the proxy. Root folder ID in Box is always the string '0', and Admin Console authorization is a separate mandatory step that trips up nearly every first-time integrator.
| Fact | Value |
|---|---|
| Tool | Box |
| Category | Storage & Files |
| Method | Bubble API Connector |
| Difficulty | Advanced |
| Time required | 3–4 hours |
| Last updated | July 2026 |
Bubble + Box: JWT Proxy, Admin Console Authorization, and the '0' Root Folder
Box is built for enterprises where security and compliance are non-negotiable. That focus shows up in the integration experience: where Dropbox hands you a simple Bearer token from the developer dashboard, Box asks you to sign JWTs with an RSA private key, navigate an Admin Console authorization flow, and manage token lifecycle carefully. None of this is insurmountable, but it requires understanding each piece before wiring it into Bubble.
The core challenge: Box's JWT server-to-server authentication flow involves RSA private key signing, which is a cryptographic algorithm. Bubble's visual workflow engine does not have a native 'sign JWT with RSA key' action. You cannot safely do this in a Bubble client-side action because the private key would be visible. The solution used by every experienced Bubble developer integrating Box is a small external signing proxy — a Supabase Edge Function or Cloudflare Worker that takes the Box JWT config JSON (your app credentials), performs the RSA signing, calls Box's token endpoint, and returns a short-lived Bearer token. Bubble's Backend Workflow calls this proxy, caches the token in a Config data type, and the API Connector uses the cached token with the Private checkbox.
The mandatory step that catches nearly every first-time integrator: creating the Box Custom App in the Developer Console is NOT sufficient for API calls to succeed. A Box organization Admin must separately go to Box Admin Console → Apps → Custom Apps Manager and explicitly authorize the app. Without this step, every API call returns 'App not authorized' regardless of how correctly configured the credentials are.
The third gotcha: Box's root folder ID is the string `"0"` (the digit zero, as a string). It is not an empty string (Dropbox uses that), not a path like `"/"`, and not the word `"root"`. Every folder listing and file operation that targets the root uses `"0"` as the parent folder ID.
For prototyping, you can generate a 60-minute Developer Token from the Box Developer Console and paste it directly into the API Connector as a Private Bearer header. This skips the JWT proxy entirely and lets you build and test all the API calls. Before going to production, replace the Developer Token with the proxy-based flow.
Bubble Backend Workflows are required for the scheduled token refresh (every 50 minutes before the token expires). Backend Workflows are a paid Bubble plan feature — this integration requires a paid plan for production use.
Integration method
Bubble API Connector targeting api.box.com/2.0 for metadata and upload.box.com/api/2.0 for file uploads; JWT token generated by an external signing proxy and cached in a Bubble Config data type, then used as a Private Bearer header.
Prerequisites
- A Box account with Administrator access (required to authorize Custom Apps in the Admin Console — a regular user account cannot complete the Admin Console authorization step)
- A Box Custom App created at developer.box.com with JWT server authentication
- A deployed Supabase Edge Function or Cloudflare Worker for JWT signing (or a Box Developer Token for prototyping only)
- A Bubble app on a paid plan (Starter or above) — Backend Workflows required for token refresh scheduling and all server-side file operations
- The Bubble API Connector plugin installed (Plugins tab → Add plugins → search 'API Connector' by Bubble)
Step-by-step guide
Create a Box Custom App and download the JWT config
Go to developer.box.com and click 'My Apps' → 'Create New App'. Choose 'Custom App' as the app type and 'Server Authentication (with JWT)' as the authentication method. Give your app a name like 'Bubble Integration'. After creating the app, go to the app's Configuration page. You will see an 'Add and Manage Public Keys' section — click 'Generate a Public/Private Keypair'. Box will prompt you to install two-factor authentication on your Box developer account if you haven't already. After confirming, Box downloads a JSON config file automatically. Save this file — it contains your App ID, client secret, RSA private key, and passphrase. This is the only time you can download it; if you lose it, you must generate a new keypair. Also in the Configuration page, set the App Access Level to 'App + Enterprise Access' (not just App Access) if you need to manage files across the organization, or 'App Access Only' if the JWT service account should only see files it creates. Next, go to the 'Application Scopes' section and enable: - Read all files and folders stored in Box - Write all files and folders stored in Box - Manage enterprise properties Save the app configuration. Important note: you now have credentials, but the app is NOT authorized to make API calls yet. The next mandatory step is Box Admin Console authorization.
Pro tip: Download and safely store the JWT config JSON file immediately after generating the keypair — Box does not let you re-download it. If you lose it, go to app Configuration → Add and Manage Public Keys → revoke the existing key and generate a new keypair. Store the JSON file in a password manager or secrets vault, not in a git repository.
Expected result: Box Custom App created with JWT authentication, app scopes configured, and the JWT config JSON file downloaded and stored securely.
Authorize the app in Box Admin Console (mandatory, often missed step)
This is the step that breaks every first-time Box integrator. Even with a perfectly configured Custom App and valid JWT credentials, every API call will return 'App not authorized' until a Box Administrator completes this step. Log into your Box account as an Administrator. Go to the Admin Console (click your name → Admin Console, or navigate to app.box.com/master). In the left sidebar, click 'Apps'. Select the 'Custom Apps Manager' tab. You should see your newly created Box app in the list of pending apps. Click on it, review the permissions, and click 'Authorize'. If your app does not appear in the list, confirm that the app was created under your organization's Box account (not a personal account) and that you gave the box_admin email the same domain as your organization. After authorization, Box creates a 'Service Account' user for your app. Files created by the JWT app are owned by this service account. If you need the service account to access existing files in a specific folder, you must add the service account as a Collaborator on that folder in Box (or create files in folders the service account owns). Note the Service Account email shown in the Admin Console — you will use it to share Box folders with the app's service account.
Pro tip: The Admin Console authorization step cannot be done by a regular Box user — it requires a Box Administrator account (the account that pays for Box or was designated as admin when the Box organization was set up). If you are building for a client who uses Box, you need their IT administrator to complete this step. Without it, API calls return 'unauthorized_access' or 'app_not_authorized' regardless of credentials.
Expected result: The Custom App appears as 'Authorized' in Box Admin Console → Apps → Custom Apps Manager. The app's Service Account is visible in the User Management section.
Deploy a JWT signing proxy and set up Bubble Config data type
Box's JWT signing requires RSA cryptography with the private key from your downloaded config file. You will deploy a Supabase Edge Function (or Cloudflare Worker) that holds this config and returns short-lived access tokens to Bubble. In your Supabase project, go to Edge Functions → New Function. Name it `box-token`. The function reads your Box JWT config from an environment variable, constructs the JWT assertion, and exchanges it for an access token: ```javascript // supabase/functions/box-token/index.ts import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'; import { create } from 'https://deno.land/x/djwt@v2.8/mod.ts'; import { importPKCS8 } from 'https://deno.land/x/jose@v4.15.4/index.ts'; const boxConfig = JSON.parse(Deno.env.get('BOX_JWT_CONFIG') || '{}'); serve(async (req) => { const privateKey = await importPKCS8(boxConfig.boxAppSettings.appAuth.privateKey, 'RS256'); const now = Math.floor(Date.now() / 1000); const assertion = await create( { alg: 'RS256', typ: 'JWT', kid: boxConfig.boxAppSettings.appAuth.publicKeyID }, { iss: boxConfig.boxAppSettings.clientID, sub: boxConfig.enterpriseID, box_sub_type: 'enterprise', aud: 'https://api.box.com/oauth2/token', jti: crypto.randomUUID(), exp: now + 60 }, privateKey ); const tokenRes = await fetch('https://api.box.com/oauth2/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', assertion, client_id: boxConfig.boxAppSettings.clientID, client_secret: boxConfig.boxAppSettings.clientSecret }) }); const data = await tokenRes.json(); return new Response(JSON.stringify({ access_token: data.access_token, expires_in: data.expires_in }), { headers: { 'Content-Type': 'application/json' } }); }); ``` Set the environment variable `BOX_JWT_CONFIG` to the contents of your downloaded JSON config file. In Bubble, create a data type called `BoxConfig` with two fields: `access_token` (text) and `expires_at` (date). This will cache the token so you don't call the proxy on every API request. Create one BoxConfig record manually with placeholder values — it will be updated by a Backend Workflow.
1// supabase/functions/box-token/index.ts2import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';3import { create } from 'https://deno.land/x/djwt@v2.8/mod.ts';4import { importPKCS8 } from 'https://deno.land/x/jose@v4.15.4/index.ts';56const boxConfig = JSON.parse(Deno.env.get('BOX_JWT_CONFIG') || '{}');78serve(async (req) => {9 const privateKey = await importPKCS8(boxConfig.boxAppSettings.appAuth.privateKey, 'RS256');10 const now = Math.floor(Date.now() / 1000);1112 const assertion = await create(13 { alg: 'RS256', typ: 'JWT', kid: boxConfig.boxAppSettings.appAuth.publicKeyID },14 {15 iss: boxConfig.boxAppSettings.clientID,16 sub: boxConfig.enterpriseID,17 box_sub_type: 'enterprise',18 aud: 'https://api.box.com/oauth2/token',19 jti: crypto.randomUUID(),20 exp: now + 6021 },22 privateKey23 );2425 const tokenRes = await fetch('https://api.box.com/oauth2/token', {26 method: 'POST',27 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },28 body: new URLSearchParams({29 grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',30 assertion,31 client_id: boxConfig.boxAppSettings.clientID,32 client_secret: boxConfig.boxAppSettings.clientSecret33 })34 });3536 const data = await tokenRes.json();37 return new Response(JSON.stringify({ access_token: data.access_token, expires_in: data.expires_in }), {38 headers: { 'Content-Type': 'application/json' }39 });40});Pro tip: For prototyping, skip the proxy entirely: go to your Box app's Developer Console → Configuration page and click 'Generate Developer Token'. Paste this token directly into the Bubble API Connector Authorization header as Private. It expires every 60 minutes and you must regenerate it manually, but it lets you build and test all the API calls without deploying the proxy first. Switch to the proxy before going to production.
Expected result: JWT signing proxy deployed at a stable URL. Calling the proxy endpoint returns a JSON object with an access_token field (valid ~60 minutes). Bubble BoxConfig data type created with access_token and expires_at fields.
Add Box API Connector groups and build token refresh Backend Workflow
In Bubble, go to Plugins tab → Add plugins → install the API Connector by Bubble if not already installed. Create two API Connector groups: **Group 1: Box Metadata** - Name: 'Box Metadata' - Shared header: `Authorization` → value: `Bearer <paste token for testing>` → **Private** checkbox checked - Add a call named 'List Folder Items': - Method: GET - URL: `https://api.box.com/2.0/folders/0/items` - Query params: `fields` = `id,name,type,size,modified_at`, `limit` = `100` - Use as: Data - Initialize call: run with real token — Bubble detects the `entries` array The root folder ID is `"0"` (the digit zero) — use this in the URL path for root access. **Group 2: Box Upload** - Name: 'Box Upload' - Same Authorization header (Private) - Base area: `https://upload.box.com/api/2.0` - Add a call named 'Upload File': - Method: POST - URL: `https://upload.box.com/api/2.0/files/content` - Body type: Form-data (multipart) - Use as: Action **Token refresh Backend Workflow (paid plan required):** Go to Backend Workflows → New API Workflow named `Refresh Box Token`: 1. Action: Plugins → API Connector → call the proxy endpoint to get a new access_token 2. Action: Data → Make changes to BoxConfig (the cached record) → set access_token = Step 1 result's access_token, set expires_at = Current date/time + 3,000 seconds 3. Schedule this workflow to run every 50 minutes via Settings → API → Schedule recurring event Update the Bubble API Connector to read the access_token from the BoxConfig Thing dynamically — set the Authorization header value to 'Bearer ' + [BoxConfig's access_token from database search].
1{2 "method": "GET",3 "url": "https://api.box.com/2.0/folders/0/items",4 "headers": {5 "Authorization": "Bearer <private>"6 },7 "params": {8 "fields": "id,name,type,size,modified_at",9 "limit": "100"10 }11}Pro tip: Box's /folders/{id}/items endpoint supports pagination via the `offset` query parameter. If a folder has more than 100 items, the response includes `total_count` and you can page through with `offset=100`, `offset=200`, etc. For most Bubble use cases, 100 items per page is sufficient for the initial implementation.
Expected result: Two API Connector groups ('Box Metadata' and 'Box Upload') are configured with Private Authorization headers. The 'List Folder Items' call is initialized and returns Box file entries. A token refresh Backend Workflow is scheduled to run every 50 minutes.
Build file browser, upload, and shared link workflows in Bubble
Now connect the Box API Connector calls to your Bubble UI. **Add more API calls to the Box Metadata group:** - 'Get Shared Link': POST `https://api.box.com/2.0/files/{fileId}` with body `{"shared_link": {"access": "open"}}` — creates a publicly accessible shared link. Set Use as: Action. - 'Search Files': GET `https://api.box.com/2.0/search` with query param `query` = `<dynamic>` — full-text search across all Box content. Set Use as: Data. **File browser repeating group:** 1. Add a Repeating Group to your page 2. Type of content: Box Metadata - List Folder Items (the API data type) 3. Data source: Get data from external API → Box Metadata - List Folder Items 4. Inside RG cells: Text = current cell's name, Text = current cell's type 5. Conditional: if type = 'folder', show a folder icon; if type = 'file', show a file icon **Share button workflow:** 1. Button labeled 'Share' inside the repeating group 2. On click: Action → Plugins → Box Metadata → Get Shared Link → set fileId = current cell's id 3. Next action: Set App State 'box_shared_url' = result of Step 1's shared_link url 4. Next action: Copy to clipboard → box_shared_url **Folder navigation:** - Add a Custom State 'current_folder_id' (text, default '0') to the page - When a folder row is clicked: set current_folder_id = current cell's id - Update the RG data source to use current_folder_id instead of hardcoded '0' - Add a Back button: on click, set current_folder_id to the parent folder ID (store navigation history in a List state) **Privacy rules:** If you create any data types storing Box file IDs, names, or URLs, go to Data tab → Privacy and add rules ensuring only the record creator can read the data.
1{2 "method": "POST",3 "url": "https://api.box.com/2.0/files/<dynamic-fileId>",4 "headers": {5 "Authorization": "Bearer <private>",6 "Content-Type": "application/json"7 },8 "body": {9 "shared_link": {10 "access": "open"11 }12 }13}Pro tip: Box's shared link is embedded in the file object response — the API call to create a shared link is actually a PATCH/POST on the file itself, not a separate sharing endpoint like Dropbox. This is a non-obvious API design. The returned shared_link.url field contains the direct https://app.box.com/s/{hash} link.
Expected result: Bubble page shows a live folder browser of Box contents, a Share button that creates Box shared links, folder navigation between sub-directories, and privacy rules on any Box-related data types.
Common use cases
Enterprise document management portal
Build a Bubble internal tool for teams using Box as their document repository. Employees see a folder browser backed by Box, can upload new documents, and generate shared links — all from a Bubble app that respects Box's collaboration and permission model.
Create a Bubble page with a repeating group showing Box folder contents via GET /folders/0/items. Each row shows the item name and type (file/folder). A 'View' button generates a shared link via POST /files/{itemId}/shared_links and opens it in a new tab.
Copy this prompt to try it in Bubble
Automated document delivery workflow
When a Bubble workflow generates a PDF (via a third-party PDF plugin), automatically upload it to a specific Box folder and send the shared link to the relevant contact — integrating Bubble's workflow engine with Box's document storage and sharing.
After a Bubble workflow finishes generating a contract PDF, trigger a Backend Workflow that uploads the PDF bytes to Box folder ID '123456' via POST /files/content, then calls POST /files/{newFileId}/shared_links to get a shareable URL, and emails the link to the client via SendGrid.
Copy this prompt to try it in Bubble
Compliance file archiving
For regulated industries (healthcare, legal, finance), automatically archive completed case or project files from Bubble's database into Box folders with specific retention settings, leveraging Box's FedRAMP and HIPAA compliance certifications for the storage layer.
When a Case record in Bubble is marked 'Closed', run a Backend Workflow that creates a Box folder named after the case ID under the '/archive/{year}/' path, moves all associated documents into it, and updates the Case record's box_folder_id field.
Copy this prompt to try it in Bubble
Troubleshooting
Every Box API call returns 'unauthorized_access' or 'App not authorized' even with correct credentials
Cause: The Box Custom App has not been authorized in Box Admin Console → Apps → Custom Apps Manager. Creating the app in the Developer Console is not sufficient — a Box Administrator must explicitly authorize it separately.
Solution: Log into Box as an Administrator and navigate to Admin Console → Apps → Custom Apps Manager. Find your app and click 'Authorize'. If your app does not appear in the list, confirm the app was created under the same Box organization account (not a personal Box account). After authorization, retry the API call — it should succeed within seconds.
Folder listing returns 404 when trying to access the root folder
Cause: The root folder ID in Box is the string '0' (digit zero), not an empty string, not '/', and not 'root'. Using any other value returns a 404.
Solution: In the Bubble API Connector, open the 'List Folder Items' call. Verify the URL is /folders/0/items (literal digit zero). For subfolder navigation, use the folder `id` field from the listing response — never construct folder paths as strings.
JWT signing proxy returns an error when calling the Box token endpoint
Cause: Common issues: the Box JWT config JSON is malformed when stored as an environment variable, the keypair has been revoked in the Developer Console, or the enterprise ID in the config does not match the authorized organization.
Solution: Test the proxy directly by calling its URL and logging the response. Verify the BOX_JWT_CONFIG environment variable contains valid JSON (the exact contents of the downloaded config file). Check the Box Developer Console → Configuration → Add and Manage Public Keys — confirm the key ID matches what's in your config JSON and the key is not revoked. The enterprise ID in the config must match the Box organization that authorized the app.
API calls succeed for 60 minutes then start returning 401 Unauthorized
Cause: Box access tokens expire in approximately 60 minutes. If the token refresh Backend Workflow is not running, or not running on a paid Bubble plan, the cached token expires and all calls fail.
Solution: Verify you are on a paid Bubble plan (Starter or above) — scheduled Backend Workflows are not available on the free plan. In Bubble, go to Logs tab → Backend Workflow logs to confirm the 'Refresh Box Token' workflow is running every 50 minutes. If it's not scheduled, go to Settings → API → Scheduled workflow and add it. Check that the workflow successfully updates the BoxConfig data type's access_token field.
Initialize call in Bubble shows 'There was an issue setting up your call' for the Box API
Cause: The Initialize call needs a real successful response. If using a Developer Token, it may have expired (60-minute limit). If using the JWT proxy, the proxy may not be returning a valid access_token.
Solution: For Developer Token: regenerate a fresh token from Box Developer Console → Configuration → Generate Developer Token and update the Authorization header in the API Connector. For JWT proxy: call the proxy URL directly in a browser to verify it returns `{access_token: '...', expires_in: 3600}`. Then retry Initialize call with the fresh token.
Best practices
- Always use Developer Token during the initial build and testing phase — it saves hours of proxy setup time. Switch to JWT before your app goes into production with real users.
- Cache the Box access token in a BoxConfig Bubble data type (single record) rather than fetching a new token on every API call. Each proxy call adds latency and costs WUs. Refresh every 50 minutes proactively, before the 60-minute expiry.
- Add the Box service account as a Collaborator on any Box folder the integration needs to access. Files created by the JWT service account are owned by that account — they are not visible to human Box users unless explicitly shared.
- Use Box's $fields parameter to limit response payload size: GET /folders/0/items?fields=id,name,type,size,modified_at returns only the fields you need, reducing Bubble WU cost for processing the response.
- Set Bubble privacy rules on any data type storing Box file IDs, folder IDs, or shared link URLs. Without privacy rules, all authenticated users in your Bubble app can query these records.
- Never store the Box JWT config JSON file in git or in Bubble's data. Keep it exclusively in your proxy function's environment variables (Supabase Secrets or Cloudflare Worker environment variables with encryption enabled).
- Handle 429 rate limit errors in box-heavy workflows by adding Pause workflow steps between bulk operations. Box allows ~10 API calls/second per user — Bubble repeating groups that trigger multiple API calls simultaneously can exceed this limit.
- For regulated industries using Box for compliance reasons, document which Bubble data types mirror Box file metadata and ensure your Bubble privacy rules are as restrictive as Box's own permission model. RapidDev's team has built Bubble apps with enterprise Box integrations — book a free scoping call at rapidevelopers.com/contact if you need help with compliance-grade architecture.
Alternatives
Dropbox is significantly simpler to integrate: a long-lived Bearer token from the Developer Console works without RSA signing or Admin Console authorization. Use Dropbox for consumer and small-team use cases where simplicity matters. Use Box when your users are in enterprise organizations requiring FedRAMP, HIPAA, or SOC 2 compliance, or where IT administrator control over file permissions is mandatory.
OneDrive via Microsoft Graph is comparable in complexity to Box — both require Azure AD / enterprise app registration and admin consent. If your users are already on Microsoft 365, OneDrive is the natural choice. Box is better for organizations that use Box as their primary content platform, especially in regulated US industries where Box's FedRAMP authorization matters.
S3 is raw object storage without the collaboration, permissions, and compliance features of Box. S3 is cheaper and gives more control over storage structure and CDN delivery, but requires your own access control logic. Use S3 when you need app-controlled file storage at scale; use Box when enterprise collaboration features, version history, and compliance certifications are requirements.
Frequently asked questions
Can I use Box on Bubble's free plan?
Only partially. Client-side API Connector calls (triggered from page workflows) work on the free plan, but they expose the Box access token in browser network requests. The secure pattern requires Backend Workflows for token refresh and server-side operations, which are only available on paid Bubble plans (Starter or above). For a production Box integration with proper token management, a paid plan is required.
Why does Box require an Admin Console step that Dropbox doesn't?
Box is designed for enterprise organizations where IT administrators control which apps can access company files. The Admin Console authorization step is Box's mechanism for an IT admin to review and approve third-party app access before it can operate in the organization. Dropbox takes a more consumer-oriented approach where individual account owners can authorize apps without organizational oversight.
Can I use Box with individual user accounts (not a service account)?
Yes, but it requires OAuth 2.0 per-user authentication instead of JWT. Each user clicks an 'Connect Box' button that redirects to Box's OAuth authorization page. After approval, Box redirects back with an authorization code, which a Backend Workflow exchanges for a per-user access token and refresh token. Store these tokens linked to the Bubble user record. This is the delegated auth pattern — more complex but gives each user access to their own Box files.
What is a Box Service Account and why does it matter?
When you create a JWT Custom App and authorize it in the Admin Console, Box creates a special 'Service Account' user for your app (visible in User Management as a machine user). Files created by your app via the JWT flow are owned by this service account, not by any human Box user. To give the service account access to existing folders, a Box Admin must add it as a Collaborator on those folders in Box. Files the service account can see are limited to folders where it has been explicitly added as a collaborator, plus any files it created itself.
What happens if my access token expires mid-session?
API calls return a 401 Unauthorized error. Your token refresh Backend Workflow prevents this by refreshing the token proactively every 50 minutes (before the 60-minute expiry). If a 401 does occur, build a retry condition in your Bubble workflow: if API call returns 401, trigger the token refresh workflow immediately and retry the original call. Store the token expiry time in the BoxConfig record so you can check 'is token still valid?' before any API call.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation