Last updated

DAM Picker SDK Integration Guide

This guide covers setting up and using the @fluid-commerce/dam-picker npm package to embed Fluid's Digital Asset Management (DAM) file picker into your application. The SDK provides a drop-in modal for browsing, searching, and uploading assets, with support for React and vanilla JavaScript.

Overview

The DAM Picker SDK lets you add a full-featured asset picker to any web application. Users can browse the DAM library, upload files from their computer, import from URLs, and search Unsplash — all within a single modal. When a user makes a selection, your application receives structured asset data including the public URL, asset code, MIME type, and optional metadata.

The SDK works with any frontend framework. First-class React bindings are included, and the vanilla JavaScript API integrates cleanly with Vue, Angular, Svelte, or plain HTML pages.

Installation

Install the package from npm:

npm install @fluid-commerce/dam-picker
pnpm add @fluid-commerce/dam-picker
yarn add @fluid-commerce/dam-picker

Prerequisites

You need a Fluid API token to authenticate the picker. This is the same token used for other Fluid API integrations. See the Authentication Guide for details on obtaining a token.

Quick Start

Vanilla JavaScript

import { DamPicker } from '@fluid-commerce/dam-picker';

const picker = new DamPicker({
  token: 'your-fluid-api-token',
  onSelect: (assets) => {
    console.log('Selected:', assets[0].url);
  },
  onCancel: () => {
    console.log('User cancelled');
  },
  onError: (error) => {
    console.error('Picker error:', error.message);
  },
});

document.getElementById('choose-file').addEventListener('click', () => {
  picker.open();
});

React (Hook)

The useDamPicker hook provides an imperative open() function that you call with callbacks:

import { useState } from 'react';
import { useDamPicker } from '@fluid-commerce/dam-picker/react';

function ImageSelector() {
  const [imageUrl, setImageUrl] = useState<string | null>(null);
  const { open, isOpen } = useDamPicker({ token: 'your-fluid-api-token' });

  const handleChooseImage = () => {
    open({
      onSelect: (assets) => setImageUrl(assets[0].url),
      onCancel: () => console.log('Cancelled'),
    });
  };

  return (
    <div>
      <button onClick={handleChooseImage} disabled={isOpen}>
        Choose Image
      </button>
      {imageUrl && <img src={imageUrl} alt="Selected" />}
    </div>
  );
}

React (Controlled Component)

The DamPickerModal component gives you declarative control over the picker's open/close state:

import { useState } from 'react';
import { DamPickerModal } from '@fluid-commerce/dam-picker/react';

function ImageSelector() {
  const [isOpen, setIsOpen] = useState(false);
  const [imageUrl, setImageUrl] = useState<string | null>(null);

  return (
    <div>
      <button onClick={() => setIsOpen(true)}>Choose Image</button>
      {imageUrl && <img src={imageUrl} alt="Selected" />}

      <DamPickerModal
        isOpen={isOpen}
        token="your-fluid-api-token"
        onSelect={(assets) => {
          setImageUrl(assets[0].url);
          setIsOpen(false);
        }}
        onCancel={() => setIsOpen(false)}
      />
    </div>
  );
}

Configuration

DamPicker Options

The DamPicker constructor and DamPickerModal component accept the following options:

OptionTypeRequiredDefaultDescription
tokenstringYesFluid API token for authentication
onSelect(assets: SelectedAsset[]) => voidYesCalled when the user confirms their selection
onCancel() => voidNoCalled when the user closes the picker without selecting
onError(error: DamPickerError) => voidNoCalled when an error occurs
onReady() => voidNoCalled when the picker iframe has loaded and is ready
maxSelectablenumberNo1Maximum number of assets the user can select (minimum: 1)
filters.assetTypesAssetType[]NoRestrict which asset types are shown
baseUrlstringNoProduction URLOverride the picker app URL (for local development)
zIndexnumberNo10000Z-index of the modal overlay

Asset Type Filtering

Restrict the picker to specific asset types using the filters.assetTypes option:

const picker = new DamPicker({
  token: 'your-token',
  filters: {
    assetTypes: ['images', 'videos'],
  },
  onSelect: (assets) => console.log(assets),
});

Available asset types:

TypeDescription
'images'Image files (jpg, png, gif, webp, svg, etc.)
'videos'Video files (mp4, webm, mov, etc.)
'documents'Documents (pdf, doc, docx, xls, xlsx, etc.)
'audio'Audio files (mp3, wav, ogg, etc.)

Multi-Select

By default, users can select one asset at a time. Set maxSelectable to allow multiple selections:

const picker = new DamPicker({
  token: 'your-token',
  maxSelectable: 10,
  onSelect: (assets) => {
    console.log(`Selected ${assets.length} assets`);
    assets.forEach((asset) => console.log(asset.url));
  },
});

With the React component:

<DamPickerModal
  isOpen={isOpen}
  token="your-token"
  maxSelectable={5}
  onSelect={(assets) => {
    setSelectedImages(assets.map((a) => a.url));
    setIsOpen(false);
  }}
  onCancel={() => setIsOpen(false)}
/>

onSelect always receives an array of SelectedAsset[], even when maxSelectable is 1. For single-select, access the asset with assets[0].

Selected Asset Structure

When a user makes a selection, onSelect receives an array of SelectedAsset objects:

interface SelectedAsset {
  /** Public URL to access the asset */
  url: string;

  /** Unique asset identifier */
  assetCode: string;

  /** Display name of the asset */
  name: string;

  /** MIME type (e.g., "image/png", "video/mp4") */
  mimeType: string;

  /** Optional metadata (dimensions, file size) */
  metadata?: {
    width?: number;
    height?: number;
    fileSize?: number;
  };

  /** ID of the selected variant, if applicable */
  variantId?: string;
}

Example: Using the Selected Asset

onSelect: (assets) => {
  const asset = assets[0];

  // Use the URL directly
  document.querySelector('img').src = asset.url;

  // Check asset type
  if (asset.mimeType.startsWith('image/')) {
    console.log(`Image: ${asset.metadata?.width}x${asset.metadata?.height}`);
  }

  // Store the asset code for API references
  saveToDatabase({ assetCode: asset.assetCode, url: asset.url });
}

Error Handling

The onError callback receives a DamPickerError with a machine-readable code and a human-readable message:

interface DamPickerError {
  code: 'AUTHENTICATION_ERROR' | 'NETWORK_ERROR' | 'TIMEOUT' | 'IFRAME_LOAD_ERROR' | 'UNKNOWN';
  message: string;
}
Error CodeCause
AUTHENTICATION_ERRORInvalid or expired API token
NETWORK_ERRORNetwork connectivity issue
TIMEOUTPicker failed to load within timeout period (10s)
IFRAME_LOAD_ERRORPicker iframe failed to load
UNKNOWNUnexpected error
const picker = new DamPicker({
  token: 'your-token',
  onSelect: (assets) => handleSelection(assets),
  onError: (error) => {
    switch (error.code) {
      case 'AUTHENTICATION_ERROR':
        refreshToken().then(() => picker.open());
        break;
      case 'TIMEOUT':
      case 'NETWORK_ERROR':
        showRetryMessage();
        break;
      default:
        console.error('Picker error:', error.message);
    }
  },
});

Instance Methods

The DamPicker class exposes these methods:

MethodReturnDescription
open()voidOpens the picker modal
close()voidCloses the picker modal
destroy()voidCleans up the picker instance and removes all DOM elements
isOpen()booleanReturns whether the picker is currently open
getState()DamPickerStateReturns the current state: 'idle', 'loading', 'ready', 'open', or 'closed'

Always call destroy() when you're done with the picker to prevent memory leaks:

const picker = new DamPicker({ token, onSelect });

// Later, when no longer needed:
picker.destroy();

Upload Behavior

When users upload a file (either from their computer or via URL), the file is uploaded to Fluid's DAM. Once the upload completes, the onSelect callback fires with the new asset — no separate upload handling is needed. The picker automatically closes after a successful upload.

Framework Integrations

Vue 3

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { DamPicker } from '@fluid-commerce/dam-picker';

const imageUrl = ref(null);
let picker = null;

onMounted(() => {
  picker = new DamPicker({
    token: 'your-fluid-api-token',
    onSelect: (assets) => {
      imageUrl.value = assets[0].url;
    },
    onError: (error) => {
      console.error('Picker error:', error.message);
    },
  });
});

onUnmounted(() => {
  picker?.destroy();
});

const openPicker = () => picker?.open();
</script>

<template>
  <div>
    <button @click="openPicker">Choose Image</button>
    <img v-if="imageUrl" :src="imageUrl" alt="Selected" />
  </div>
</template>

Angular

import { Component, OnDestroy } from '@angular/core';
import { DamPicker, SelectedAsset } from '@fluid-commerce/dam-picker';

@Component({
  selector: 'app-image-selector',
  template: `
    <button (click)="openPicker()">Choose Image</button>
    <img *ngIf="imageUrl" [src]="imageUrl" alt="Selected" />
  `,
})
export class ImageSelectorComponent implements OnDestroy {
  imageUrl: string | null = null;
  private picker: DamPicker;

  constructor() {
    this.picker = new DamPicker({
      token: 'your-fluid-api-token',
      onSelect: (assets: SelectedAsset[]) => {
        this.imageUrl = assets[0].url;
      },
    });
  }

  openPicker() {
    this.picker.open();
  }

  ngOnDestroy() {
    this.picker.destroy();
  }
}

Svelte

<script>
  import { onMount, onDestroy } from 'svelte';
  import { DamPicker } from '@fluid-commerce/dam-picker';

  let imageUrl = null;
  let picker;

  onMount(() => {
    picker = new DamPicker({
      token: 'your-fluid-api-token',
      onSelect: (assets) => {
        imageUrl = assets[0].url;
      },
    });
  });

  onDestroy(() => {
    picker?.destroy();
  });
</script>

<button on:click={() => picker?.open()}>Choose Image</button>
{#if imageUrl}
  <img src={imageUrl} alt="Selected" />
{/if}

Common Use Cases

Product Image Selector

const picker = new DamPicker({
  token: 'your-token',
  filters: { assetTypes: ['images'] },
  maxSelectable: 1,
  onSelect: (assets) => {
    updateProductImage(productId, assets[0].assetCode);
  },
});
<DamPickerModal
  isOpen={isOpen}
  token="your-token"
  filters={{ assetTypes: ['images', 'videos'] }}
  maxSelectable={20}
  onSelect={(assets) => {
    setGalleryItems((prev) => [...prev, ...assets]);
    setIsOpen(false);
  }}
  onCancel={() => setIsOpen(false)}
/>

Document Attachment

const picker = new DamPicker({
  token: 'your-token',
  filters: { assetTypes: ['documents'] },
  maxSelectable: 5,
  onSelect: (assets) => {
    assets.forEach((doc) => attachDocument(doc.url, doc.name));
  },
});

Local Development

For local development, point the SDK to your local fluid-admin instance:

const picker = new DamPicker({
  token: 'your-token',
  baseUrl: 'http://localhost:3007/dam-picker',
  onSelect: (assets) => console.log(assets),
});

Troubleshooting

Picker does not open

  • Verify your API token is valid and not expired. An invalid token triggers the onError callback with AUTHENTICATION_ERROR.
  • Check the browser console for JavaScript errors.
  • Ensure the @fluid-commerce/dam-picker package is installed and imported correctly.

Picker opens but shows a blank screen

  • Check network connectivity. The picker loads inside an iframe from admin.fluid.app.
  • If using a corporate network or VPN, ensure admin.fluid.app is not blocked.
  • The onReady callback should fire when the picker is loaded. If it doesn't, there may be a network issue.

Timeout errors

  • The picker has a 10-second load timeout. On slow connections, this may not be enough.
  • Check that admin.fluid.app is reachable from the browser.

onSelect returns an empty array

  • This should not happen under normal operation. If it does, check the browser console for errors and ensure you're on the latest SDK version.

Multi-select not working

  • Confirm you're passing maxSelectable with a value greater than 1.
  • The picker UI will show a different selection mode when maxSelectable > 1.

Content Security Policy (CSP) issues

If your application uses a strict CSP, you need to allow the picker iframe:

frame-src https://admin.fluid.app;

For local development:

frame-src https://admin.fluid.app http://localhost:3007;