Need to see if your shared folder is taking up space on your dropbox 👨‍💻? Find out how to check here.

Forum Discussion

prjtprgn's avatar
prjtprgn
New member | Level 1
9 months ago
Solved

Unable to Retrieve Dropbox "path_display" for Folders in Google Sheets Script

I'm developing a Google Sheets database that feeds an interactive map on My Maps. The script uses the Dropbox API to update folder names (in column A) and folder paths (in column F) based on metadata retrieved from Dropbox shared links. Although every folder is mounted on my account and I have full administrative rights on my Dropbox team, I'm not seeing the expected "path_display" value for my Dropbox folders.

The script calls several API endpoints (such as sharing/get_shared_link_metadata, sharing/list_folders, sharing/list_mountable_folders, and sharing/get_folder_metadata) to try to obtain the complete folder path, but the path_display property remains empty in my sheet.

I need to retrieve path_display because it lets me move folders in Dropbox while still keeping the most recent path in my map. The script is set to run weekly so that any folder moves are captured and my map stays up to date.

Below is the complete script (with sensitive credentials, paths, and other details replaced):


I tried this script in Google Apps Script: 

```
// =====================================================
// CONFIGURATION & CONSTANTS
// =====================================================

// Google Maps API Key (for geocoding)
const API_KEY = "YOUR_GOOGLE_MAPS_API_KEY";

// OAuth2 Dropbox: Replace with your Dropbox credentials
const DROPBOX_CLIENT_ID = 'YOUR_DROPBOX_CLIENT_ID';
const DROPBOX_CLIENT_SECRET = 'YOUR_DROPBOX_CLIENT_SECRET';
const DROPBOX_SCOPES = 'sharing.read files.metadata.read files.team_metadata.read team_data.member';

// Replace with your Dropbox team member ID (the member on which you want to operate)
// Example: "dbmid:example_member_id"
const DROPBOX_TEAM_MEMBER_ID = 'YOUR_DROPBOX_TEAM_MEMBER_ID';

const BATCH_SIZE = 100;
const DELAY_BETWEEN_REQUESTS = 1000;

// =====================================================
// OAUTH2 FOR DROPBOX (using the OAuth2 library)
// =====================================================

function getDropboxService() {
  return OAuth2.createService('Dropbox')
    .setAuthorizationBaseUrl('https://www.dropbox.com/oauth2/authorize')
    .setTokenUrl('https://api.dropboxapi.com/oauth2/token')
    .setClientId(DROPBOX_CLIENT_ID)
    .setClientSecret(DROPBOX_CLIENT_SECRET)
    .setCallbackFunction('authCallback')
    .setPropertyStore(PropertiesService.getUserProperties())
    .setScope(DROPBOX_SCOPES)
    // Request a refresh token
    .setParam('token_access_type', 'offline');
}

function authCallback(request) {
  const service = getDropboxService();
  const authorized = service.handleCallback(request);
  return HtmlService.createHtmlOutput(authorized ? 'Success! You can close this tab.' : 'Access denied.');
}

function authorizeDropbox() {
  const service = getDropboxService();
  if (!service.hasAccess()) {
    const authorizationUrl = service.getAuthorizationUrl();
    Logger.log('Open the following URL and authorize the app, then re-run the script: %s', authorizationUrl);
    return authorizationUrl;
  } else {
    Logger.log('Already authorized with Dropbox.');
  }
}

// =====================================================
// FUNCTIONS FOR UPDATING COORDINATES
// =====================================================

function updateCoordinates() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  const startRow = 4;
  const addresses = sheet.getRange(`B${startRow}:B${sheet.getLastRow()}`).getValues().flat();
  const gpsData = sheet.getRange(`D${startRow}:D${sheet.getLastRow()}`).getValues().flat();

  addresses.forEach((address, index) => {
    const gps = gpsData[index];
    if (address && !gps) {
      const coordinates = getCoordinates(address);
      if (coordinates) {
        sheet.getRange(index + startRow, 4).setValue(coordinates);
      }
    }
  });
  SpreadsheetApp.flush();
}

function getCoordinates(address) {
  const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(address)}&key=${API_KEY}`;
  try {
    const response = UrlFetchApp.fetch(url);
    const json = JSON.parse(response.getContentText());
    if (json.status === "OK") {
      const latitude = json.results[0].geometry.location.lat;
      const longitude = json.results[0].geometry.location.lng;
      return `${latitude}, ${longitude}`;
    } else {
      console.error(`API error for address: ${address} - Status: ${json.status}`);
      return "API error";
    }
  } catch (error) {
    console.error(`Error fetching coordinates: ${error.message}`);
    return "API error";
  }
}

// =====================================================
// FUNCTIONS FOR UPDATING STATUS (column E) FROM PATHS (column F)
// =====================================================

function updateStatusFromPaths() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  const startRow = 4;
  const lastRow = sheet.getLastRow();
  const paths = sheet.getRange(`F${startRow}:F${lastRow}`).getValues().flat();
  const statuses = [];

  paths.forEach(path => {
    if (path) {
      if (path.includes("ActiveDeals/Active") || 
          path.includes("C:\\Path\\To\\ActiveDeals\\Active")) {
        statuses.push(["Active"]);
      } else if (path.includes("ActiveDeals/ToWatch") || 
                 path.includes("C:\\Path\\To\\ActiveDeals\\ToWatch")) {
        statuses.push(["To watch"]);
      } else if (path.includes("Properties/Owned") || 
                 path.includes("C:\\Path\\To\\Properties\\Owned")) {
        statuses.push(["Owned"]);
      } else if (path.includes("Properties/Sold") || 
                 path.includes("C:\\Path\\To\\Properties\\Sold")) {
        statuses.push(["Sold"]);
      } else if (path.includes("DeadDeals") || 
                 path.includes("C:\\Path\\To\\DeadDeals")) {
        statuses.push(["Dead"]);
      } else {
        statuses.push(["Undefined"]);
      }
    } else {
      statuses.push([""]);
    }
  });
  sheet.getRange(startRow, 5, statuses.length, 1).setValues(statuses);
}

// =====================================================
// FUNCTIONS FOR UPDATING DROPBOX INFORMATION
// =====================================================

/**
 * This script updates both the folder name (column A)
 * and the folder path (column F) by comparing the path_display returned
 * by Dropbox. Any changes are logged in column H.
 */
function updateFolderNamesInBatches() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  const startRow = 4;
  const lastRow = sheet.getLastRow();
  const scriptProperties = PropertiesService.getScriptProperties();
  
  let batchIndex = parseInt(scriptProperties.getProperty('batchIndexDropbox') || '0');
  const totalBatches = Math.ceil((lastRow - startRow + 1) / BATCH_SIZE);

  // If all batches have been processed, reset and stop
  if (batchIndex >= totalBatches) {
    scriptProperties.deleteProperty('batchIndexDropbox');
    Logger.log("All batches processed. Function stopped.");
    return;
  }

  const batchStart = startRow + (batchIndex * BATCH_SIZE);
  const batchEnd = Math.min(batchStart + BATCH_SIZE - 1, lastRow);

  // Check if the batch contains any sharing links (Column C)
  const linksRange = sheet.getRange(`C${batchStart}:C${batchEnd}`).getValues();
  let hasLink = false;
  for (let i = 0; i < linksRange.length; i++) {
    if (linksRange[i][0].toString().trim() !== "") {
      hasLink = true;
      break;
    }
  }

  // If no links are found in the batch, stop processing
  if (!hasLink) {
    scriptProperties.deleteProperty('batchIndexDropbox');
    Logger.log("No sharing links found in batch. Function stopped.");
    return;
  }

  // Clear previous statuses for the current batch (Column H)
  sheet.getRange(`H${batchStart}:H${batchEnd}`).clearContent();

  // Process the current batch
  updateBatch(batchStart, batchEnd);

  // Update the batch index
  batchIndex++;
  scriptProperties.setProperty('batchIndexDropbox', batchIndex.toString());

  // Schedule the next batch if there are more rows to process
  if (batchIndex < totalBatches) {
    ScriptApp.newTrigger('updateFolderNamesInBatches')
      .timeBased()
      .after(1000)
      .create();
  }
}

/**
 * Processes the rows in the batch,
 * compares existing names/paths with those returned by Dropbox, and updates if necessary.
 */
function updateBatch(startRow, endRow) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();

  // Read columns A (folderNames), C (sharingLinks) and F (folderPaths)
  const folderNames = sheet.getRange(`A${startRow}:A${endRow}`).getValues().flat();
  const sharingLinks = sheet.getRange(`C${startRow}:C${endRow}`).getValues().flat();
  const folderPaths = sheet.getRange(`F${startRow}:F${endRow}`).getValues().flat();

  for (let index = 0; index < sharingLinks.length; index++) {
    const link = sharingLinks[index];
    const currentRow = startRow + index;

    // Skip if the link is empty
    if (!link || link.trim() === "") {
      Logger.log(`Stopping at row ${currentRow}: No sharing link found.`);
      return; 
    }

    try {
      const metadata = getDropboxMetadata(link);
      let statusMessages = [];

      if (metadata && metadata.name) {
        // Compare folder name
        if (folderNames[index] !== metadata.name) {
          sheet.getRange(currentRow, 1).setValue(metadata.name);
          statusMessages.push("Folder name updated");
        }

        // Compare folder path
        if (metadata.path && folderPaths[index] !== metadata.path) {
          sheet.getRange(currentRow, 6).setValue(metadata.path);
          statusMessages.push("Path updated");
        }

        if (statusMessages.length === 0) {
          statusMessages.push("No changes needed");
        }

        sheet.getRange(currentRow, 8).setValue(statusMessages.join(" | "));
      } else {
        sheet.getRange(currentRow, 8).setValue("Failed: No metadata/name");
      }
    } catch (err) {
      sheet.getRange(currentRow, 8).setValue("Failed: " + err.message);
    }

    // Delay to respect rate limits
    Utilities.sleep(DELAY_BETWEEN_REQUESTS);
  }
  SpreadsheetApp.flush();
}

/**
 * getDropboxMetadata retrieves the name and path_display
 * from the Dropbox sharing link.
 * 
 * It uses multiple methods to try to obtain the full path,
 * handling various limitations of the Dropbox API.
 */
function getDropboxMetadata(sharingLink) {
  const service = getDropboxService();
  if (!service.hasAccess()) {
    throw new Error("Authorization required. Run authorizeDropbox() first.");
  }
  const accessToken = service.getAccessToken();
  
  // First call to sharing/get_shared_link_metadata
  let apiUrl = "https://api.dropboxapi.com/2/sharing/get_shared_link_metadata";
  let payload = { url: sharingLink };
  let options = {
    method: "post",
    contentType: "application/json",
    headers: { 
      Authorization: "Bearer " + accessToken,
      "Dropbox-API-Select-User": DROPBOX_TEAM_MEMBER_ID
    },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  };
  
  let response = UrlFetchApp.fetch(apiUrl, options);
  let responseText = response.getContentText();
  Logger.log("API Response for " + sharingLink + ": " + responseText);
  
  let json;
  try {
    json = JSON.parse(responseText);
  } catch (e) {
    throw new Error("Invalid JSON response: " + responseText);
  }
  
  if (json.error_summary) {
    throw new Error("Dropbox API error: " + json.error_summary);
  }
  
  // For shared folders, try a different approach to get the path
  if (!json.path_display && json['.tag'] === 'folder' && json.id) {
    // Try with sharing/list_folders endpoint if it's a shared folder
    apiUrl = "https://api.dropboxapi.com/2/sharing/list_folders";
    payload = {
      limit: 100
    };
    options.payload = JSON.stringify(payload);
    
    try {
      response = UrlFetchApp.fetch(apiUrl, options);
      responseText = response.getContentText();
      Logger.log("List Folders API Response: " + responseText);
      
      const listFolders = JSON.parse(responseText);
      if (listFolders.entries && listFolders.entries.length > 0) {
        // Look for matching entry by shared_folder_id
        const matchingFolder = listFolders.entries.find(folder => 
          folder.shared_folder_id === json.id.replace('id:', '')
        );
        
        if (matchingFolder && matchingFolder.path_lower) {
          json.path_display = matchingFolder.path_lower;
        }
      }
    } catch (e) {
      Logger.log("Fallback list_folders method failed: " + e.message);
    }
    
    // If still no path_display, try with list_mountable_folders
    if (!json.path_display) {
      apiUrl = "https://api.dropboxapi.com/2/sharing/list_mountable_folders";
      payload = {
        limit: 100
      };
      options.payload = JSON.stringify(payload);
      
      try {
        response = UrlFetchApp.fetch(apiUrl, options);
        responseText = response.getContentText();
        Logger.log("List Mountable Folders API Response: " + responseText);
        
        const listFolders = JSON.parse(responseText);
        if (listFolders.entries && listFolders.entries.length > 0) {
          // Look for matching entry by shared_folder_id
          const matchingFolder = listFolders.entries.find(folder => 
            folder.shared_folder_id === json.id.replace('id:', '')
          );
          
          if (matchingFolder && matchingFolder.path_lower) {
            json.path_display = matchingFolder.path_lower;
          }
        }
      } catch (e) {
        Logger.log("Fallback list_mountable_folders method failed: " + e.message);
      }
    }
    
    // Try one more approach with sharing/get_folder_metadata
    if (!json.path_display) {
      apiUrl = "https://api.dropboxapi.com/2/sharing/get_folder_metadata";
      payload = {
        shared_folder_id: json.id.replace('id:', '')
      };
      options.payload = JSON.stringify(payload);
      
      try {
        response = UrlFetchApp.fetch(apiUrl, options);
        responseText = response.getContentText();
        Logger.log("Get Folder Metadata API Response: " + responseText);
        
        const folderMetadata = JSON.parse(responseText);
        if (folderMetadata.path_lower) {
          json.path_display = folderMetadata.path_lower;
        }
      } catch (e) {
        Logger.log("Fallback get_folder_metadata method failed: " + e.message);
      }
    }
  }
  
  return {
    name: json.name || "",
    path: json.path_display || ""
  };
}

function startFolderNameUpdate() {
  PropertiesService.getScriptProperties().setProperty("batchIndexDropbox", "0");
  updateFolderNamesInBatches();
}

// =====================================================
// SETUP TRIGGERS
// =====================================================

function setupTriggers() {
  // Delete all existing triggers.
  ScriptApp.getProjectTriggers().forEach(function(trigger) {
    ScriptApp.deleteTrigger(trigger);
  });

  // Trigger on change to update coordinates.
  ScriptApp.newTrigger("updateCoordinates")
    .forSpreadsheet(SpreadsheetApp.getActiveSpreadsheet())
    .onChange()
    .create();

  // Trigger on change to update statuses from paths.
  ScriptApp.newTrigger("updateStatusFromPaths")
    .forSpreadsheet(SpreadsheetApp.getActiveSpreadsheet())
    .onChange()
    .create();

  // Time-based trigger to update Dropbox folder names & paths in batches (every Monday at 15:00).
  ScriptApp.newTrigger("updateFolderNamesInBatches")
    .timeBased()
    .onWeekDay(ScriptApp.WeekDay.MONDAY)
    .atHour(15)
    .create();

  Logger.log("Triggers set: onChange for updateCoordinates and updateStatusFromPaths; weekly for updateFolderNamesInBatches.");
}

// Function to manually reset the batch index.
function resetBatchIndex() {
  PropertiesService.getScriptProperties().deleteProperty('batchIndexDropbox');
  Logger.log("Batch index reset to start from the beginning (row 4)");
}

// Function to reset old Dropbox credentials.
function resetDropboxAuth() {
  PropertiesService.getUserProperties().deleteAllProperties();
  Logger.log("Dropbox authorization reset. Re-run authorizeDropbox().");
}

```

Typical Log Output:
Here are examples of the log messages I typically see when the script runs:

API Response Log:
`3 March 2025, 15:10:42    Info    API Response for https://www.dropbox.com/scl/fo/REPLACE_URL?dl=0: {"tag": "folder", "url": "https://www.dropbox.com/scl/fo/REPLACE_URL?dl=0", "id": "id:REPLACE_ID", "name": "REPLACE_NAME", "link_permissions": { ... }, "team_member_info": { ... }}`

Fallback Log:
`3 March 2025, 15:10:42    Info    Fallback API Response for https://www.dropbox.com/scl/fo/REPLACE_URL?dl=0: Error in call to API function "files/get_metadata": request body: unknown field 'shared_link'`

In these logs, all sensitive details (such as URLs, IDs, names, and team names) have been replaced with placeholders.

My question is:
Why isn't the path_display property being returned by the Dropbox API, even though my account has the necessary permissions? Retrieving this property is crucial because it allows me to move folders in Dropbox and still maintain the most recent folder path in my interactive map. This is why I run weekly updates—to ensure that the map reflects any changes in folder structure.

Any help or suggestions would be greatly appreciated!

  • Hi prjtprgn

    It looks like there are a couple of issues with your script. First, it is expected for the endpoint /sharing/get_shared_link_metadata to not return a "path_display" property.

    The next issue appears to be after a request is sent to /sharing/list_folders and /sharing/list_mountable_folders, the script seems to be trying to match the "shared_folder_id" with a "folder_id" property:

    const matchingFolder = listFolders.entries.find(folder => 
        folder.shared_folder_id === json.id.replace('id:', '')
    );

    These two values are completely different. Even though a "shared_folder_id" is a string, it is usually a numeric-only value, as such "84528192421". Whereas a "folder_id" is an alphanumeric value, prepended by the word "id", as such: "id:a4ayc_80_OEAAAAAAAAAXw".

    Additionally, /sharing/get_folder_metadata is expecting a "shared_folder_id" value, the script is sending it a "folder_id" value:

    payload = {
        shared_folder_id: json.id.replace('id:', '')
    };

    As previously mentioned, these are completely different values, even for the same folder.

    Lastly, the following fallback log:

    Fallback Log:
    `3 March 2025, 15:10:42    Info    Fallback API Response for
    https://www.dropbox.com/scl/fo/REPLACE_URL?dl=0: Error in call to API function
    "files/get_metadata": request body: unknown field 'shared_link'`

    Indicates a request was possibly sent to /files/get_metadata endpoint. This endpoint does not accept a "shared_link" as a parameter.

    To get better insight into what’s happening, I’d recommend adding additional loggers to your script. This will help you visualize all the data being returned by the API and confirm whether you’re working with the right values.

1 Reply

Replies have been turned off for this discussion
  • DB-Des's avatar
    DB-Des
    Icon for Dropbox Community Moderator rankDropbox Community Moderator
    9 months ago

    Hi prjtprgn

    It looks like there are a couple of issues with your script. First, it is expected for the endpoint /sharing/get_shared_link_metadata to not return a "path_display" property.

    The next issue appears to be after a request is sent to /sharing/list_folders and /sharing/list_mountable_folders, the script seems to be trying to match the "shared_folder_id" with a "folder_id" property:

    const matchingFolder = listFolders.entries.find(folder => 
        folder.shared_folder_id === json.id.replace('id:', '')
    );

    These two values are completely different. Even though a "shared_folder_id" is a string, it is usually a numeric-only value, as such "84528192421". Whereas a "folder_id" is an alphanumeric value, prepended by the word "id", as such: "id:a4ayc_80_OEAAAAAAAAAXw".

    Additionally, /sharing/get_folder_metadata is expecting a "shared_folder_id" value, the script is sending it a "folder_id" value:

    payload = {
        shared_folder_id: json.id.replace('id:', '')
    };

    As previously mentioned, these are completely different values, even for the same folder.

    Lastly, the following fallback log:

    Fallback Log:
    `3 March 2025, 15:10:42    Info    Fallback API Response for
    https://www.dropbox.com/scl/fo/REPLACE_URL?dl=0: Error in call to API function
    "files/get_metadata": request body: unknown field 'shared_link'`

    Indicates a request was possibly sent to /files/get_metadata endpoint. This endpoint does not accept a "shared_link" as a parameter.

    To get better insight into what’s happening, I’d recommend adding additional loggers to your script. This will help you visualize all the data being returned by the API and confirm whether you’re working with the right values.

About Dropbox API Support & Feedback

Node avatar for Dropbox API Support & Feedback
Find help with the Dropbox API from other developers.

The Dropbox Community team is active from Monday to Friday. We try to respond to you as soon as we can, usually within 2 hours.

If you need more help you can view your support options (expected response time for an email or ticket is 24 hours), or contact us on X, Facebook or Instagram.

For more info on available support options for your Dropbox plan, see this article.

If you found the answer to your question in this Community thread, please 'like' the post to say thanks and to let us know it was useful!