CI HUBCI HUB SDK
Building an Adapter

Folder Operations

Handlers for creating, deleting, renaming, and moving folders. Each requires a capability flag returned from getFolder in the folder's capabilities object.

Capability Flags

Return these per-folder in getFolder response:

return send({
  id: folderId,
  folders: [...],
  assets: [...],
  capabilities: {
    canCreateFolder: true,
    canDeleteFolder: true,
    canRenameFolder: true,
    canMoveFolder: true
  }
})

Capabilities are per-folder — some folders (root, virtual containers) may disable operations that others allow.

createFolder

Creates a new folder inside the specified parent.

Inputs

FieldDescription
requestData.body.nameNew folder name
requestData.body.parentIdParent folder ID

Response

return send({ id: newFolderId, name: folderName })

Patterns

Example - DAM that supports both library folders and collections:

createFolder: async (locals, requestData) => {
  try {
    const result = { id: '', name: '' }
    let { name, parentId } = requestData.body

    parentId = !folderIdStartsWith(parentId || '', [ 'root', 'collection:' ]) ? parentId : undefined
    if (!parentId) {
      throw new Error('You can not create a folder in this location')
    }
    if (parentId.startsWith('collections:')) {
      parentId = parentId.replace('collections:', '')
      const collection = await callApi(locals, {
        query: queries.createCollectionMutation,
        variables: { input: { name, parentId } }
      })

      result.id = `collection:${collection.data.createCollection.collection.id}`
      result.name = collection.data.createCollection.collection.name
    } else {
      parentId = parentId.replace('library:', '')
      const folder = await callApi(locals, {
        query: queries.createFolderMutation,
        variables: { input: { name, parentId } }
      })

      result.id = folder.data.createFolder.folder.id
      result.name = folder.data.createFolder.folder.name
    }

    return send(result)
  } catch (error) {
    return sendError(error, 400)
  }
},

Example - DAM that supports path-based folder creation:

createFolder: async (locals, requestData) => {
  try {
    const { parentId, name } = requestData.body

    const parentPath = parentId === 'root' ? '' : (await callApi(locals, 'POST', '/files/get_metadata', null, { path: parentId })).path_lower

    const folderCreateResponse = await callApi(locals, 'POST', '/files/create_folder_v2', null, { path: `${parentPath}/${name}` })

    return sendResult(null, { id: folderCreateResponse.metadata.id, name: folderCreateResponse.metadata.name })
  } catch (err) {
    return sendError(err, 400)
  }
},

UI Behavior

On success, the new folder appears in the current folder view. The plugin UI uses the returned id and name for display.

Create Folder Schema


deleteFolder

Permanently removes a folder. Some platforms recursively delete contents; others require the folder to be empty.

Inputs

FieldDescription
requestData.params.folderIdFolder ID to delete

Response

return sendStatus(200)

Patterns

Example - DAM that handles collection and folder types, blocks deletion of root-level folders:

deleteFolder: async (locals, requestData) => {
  try {
    const { folderId } = requestData.params
    const id = folderIdStartsWith(folderId, [ 'root', 'collections', 'library' ]) ? undefined : folderId

    if (!id) {
      throw new Error('You can not delete this folder')
    }
    if (folderId.startsWith('collection:')) {
      const collectionId = folderId.replace('collection:', '')

      await callApi(locals, { query: queries.deleteCollectionMutation, variables: { input: { collectionId } } })
    } else {
      await callApi(locals, { query: queries.deleteFolderMutation, variables: { input: { ids: [ id ] } } })
    }
    return sendStatus(200)
  } catch (error) {
    return sendError(error, 400)
  }
},

Example - DAM that supports path-based deletion:

deleteFolder: async (locals, requestData) => {
  try {
    const { folderId } = requestData.params

    await callApi(locals, 'POST', '/files/delete_v2', null, { path: folderId })

    return sendResult(null)
  } catch (err) {
    return sendError(err, 400)
  }
},

Protect virtual/root folder IDs from deletion. Check the folderId against known non-deletable values before calling the platform API.

UI Behavior

On success, the folder disappears from the parent folder view.

Delete Folder Schema


renameFolder

Changes a folder's display name.

Inputs

FieldDescription
requestData.params.folderIdFolder ID
requestData.body.nameNew name

Response

return send({ id: folderId, name: newName })

Patterns

Example - DAM that supports both collections and library folders:

renameFolder: async (locals, requestData) => {
  try {
    const { name } = requestData.body
    const { folderId } = requestData.params
    const id = folderIdStartsWith(folderId, [ 'root', 'collections', 'library' ]) ? undefined : folderId

    if (!id) {
      throw new Error('You can not rename this folder')
    }
    const result = { id: '', name: '' }

    if (folderId.startsWith('collection:')) {
      const collectionId = folderId.replace('collection:', '')
      const collection = await callApi(locals, {
        query: queries.updateCollectionMutation,
        variables: { input: { id: collectionId, data: { name } } }
      })

      result.id = collection.data.updateCollection.collection.id ? `collection:${collection.data.updateCollection.collection.id}` : folderId
      result.name = collection.data.updateCollection.collection.name || name
    } else {
      const folder = await callApi(locals, {
        query: queries.updateFolderMutation,
        variables: { input: { id, data: { name } } }
      })

      result.id = folder.data.updateFolder.folder.id || folderId
      result.name = folder.data.updateFolder.folder.name || name
    }
    return send(result)
  } catch (error) {
    return sendError(error, 400)
  }
},

Example - DAM that supports JSON Patch:

renameFolder: async (locals, requestData) => {
  const { folderId } = requestData.params
  const { name } = requestData.body

  try {
    await callApi(locals, 'PATCH', `/categories/${folderId}`, null, [{ op: 'replace', path: '/tree/name', value: name }])

    return send({ id: folderId, name })
  } catch (err) {
    return sendError(err, 400)
  }
},

UI Behavior

On success, the folder's name updates in the folder tree and breadcrumbs.

Rename Folder Schema


moveFolder

Moves a folder to a different parent folder.

Inputs

FieldDescription
requestData.body.folderIdFolder ID to move
requestData.body.targetParentIdDestination parent folder ID

Response

return send({ id: folderId, targetParentId })

Patterns

Example - DAM that supports JSON Patch to update parent:

moveFolder: async (locals, requestData) => {
  try {
    const { folderId, targetParentId } = requestData.body
    if (typeof targetParentId !== 'string' || !targetParentId || targetParentId.startsWith('lightbox') || [ 'root', 'categories', 'lightboxes', 'home', 'mostViewed', 'recentlyUploaded' ].includes(targetParentId)) {
      return sendError('Invalid folder ID', 400)
    }

    await callApi(locals, 'PATCH', `/categories/${folderId}`, null, [{ op: 'replace', path: '/parentId', value: targetParentId }])

    return send({ id: folderId, targetParentId })
  } catch (err) {
    return sendError(err, 400)
  }
},

Validate targetParentId before calling the API. Virtual folder IDs (root, lightboxes, etc.) are not valid move destinations.

UI Behavior

On success, the folder disappears from its current location and appears under the target parent. Enable via canMoveFolder: true in the folder's capabilities.

Move Folder Schema

On this page