Used to create a custom extension from scratch.

The name should consist of a description and a type, where type can be "Source", "Loader", "Validator", "Transform", "Store", or "View". createFilePondExtensionSet uses the type to auto sort the extensions set.

Options are the default options available for configuring this extension, these options are passed to didSetProps when updated and are available on props.

And then there’s the extension function body which receives the extension state and methods to interact with the FilePond instance.

const MyExtension = createExtension(
    'NameType',
    {
        // options
    },
    (state, pond) => {
        // the extension state
        const { props, didSetProps } = state;

        // the internal api
        const {
            on,
            insertEntries,
            removeEntries,
            updateEntry,
            replaceEntry,
            pushTask,
            abortTasks,
            setEntryExtensionStatus,
            getEntryExtensionStatus,
        } = pond;

        // called when props are updated
        didSetProps((props) => {
            //
        });

        // a task that needs to run on an entry, these are queued, so only run if other tasks finish successfully
        async function taskCustomJob(entry) {
            //
        }

        // called when an entry is updated
        function handleUpdateEntry(entry) {
            //
        }

        // called when the entry list is updated
        function handleUpdateEntries(entries) {
            //
        }

        // when the entry list is updated we call handleUpdateEntries
        const unsubUpdateEntryList = on('updateEntries', debounce(handleUpdateEntries));

        // when an entry is updated we call handleUpdateEntry
        const unsubUpdateEntry = on('updateEntry', handleUpdateEntry);

        return {
            destroy: () => {
                unsubUpdateEntry();
                unsubUpdateEntryList();
            },
        };
    }
);

Creating a PDF Preview extension

This extension will use PDF.js to load a PDF and create a poster image of its first page. The poster image can then be displayed by the EntryListView extension.

import { defineFilePond } from 'filepond';
import { locale } from 'filepond/locales/en-gb.js';
import { createExtension } from 'filepond/extensions/index.js';
import { createFilePondEntryList, appendEntryImageView } from 'filepond/templates/index.js';

// So we can see the image preview
const template = createFilePondEntryList();
appendEntryImageView(template);

// The PDF Preview Loader extension
const PDFPreviewLoader = createExtension(
    'PDFPreviewLoader',
    {},
    (
        { props, extensionName },
        {
            on,
            updateEntry,
            pushTask,
            setEntryExtensionState,
            getEntryExtensionState,
            setEntryExtensionStatus,
            getEntryExtensionStatus,
        }
    ) => {
        // very basic pdf detection
        function isPDF(file) {
            return file.type === 'application/pdf';
        }

        // this task will generate the PDF
        async function taskGeneratePDFPreview(entry) {
            // let's start the loading indicator
            setEntryExtensionStatus(entry, {
                type: 'system',
                code: 'LOAD_BUSY',
                progress: Infinity,
            });

            // try to create a screenshot of the first page
            try {
                // dynamically load pdfjs and set its worker url
                const cdn = 'https://cdn.jsdelivr.net/npm/pdfjs-dist';
                const { getDocument, GlobalWorkerOptions } = await import(/* @vite-ignore */ cdn);
                GlobalWorkerOptions.workerSrc = cdn + '/build/pdf.worker.min.mjs';

                // let's convert the pdf to a png
                const objectURL = URL.createObjectURL(entry.file);
                const pdf = await getDocument(objectURL).promise;

                // get first page
                const page = await pdf.getPage(1);

                // get a scaled viewport for the pdf
                const viewport = page.getViewport({ scale: 1 });

                // create the target canvas to draw on
                const canvas = document.createElement('canvas');
                canvas.width = viewport.width;
                canvas.height = viewport.height;

                // ask pdfjs to draw to the canvas
                await page.render({
                    canvasContext: canvas.getContext('2d'),
                    transform: null,
                    viewport: viewport,
                }).promise;

                // we turn the canvas into a blob
                const blob = await new Promise((resolve) => canvas.toBlob(resolve));

                // release url
                URL.revokeObjectURL(objectURL);

                // update state of the entry so the poster can be displayed
                updateEntry(entry, {
                    extension: {
                        // So we know which file was used as source
                        [extensionName]: {
                            posterSrc: entry.file,
                        },
                        // the poster the EntryListView will use
                        EntryListView: {
                            poster: blob,
                        },
                    },
                });
            } catch (error) {
                setEntryExtensionStatus(entry, {
                    type: 'error',
                    code: 'LOAD_ERROR',
                    values: { error },
                });

                // so scheduler aborts  rest of tasks
                throw error;
            }

            // done!
            setEntryExtensionStatus(entry, {
                type: 'success',
                code: 'LOAD_COMPLETE',
            });
        }

        // this will be called everytime the entry is updated
        function handleUpdateEntry(entry) {
            // get extension entry props to help determine what next step to take
            const status = getEntryExtensionStatus(entry);
            const { posterSrc } = getEntryExtensionState(entry);

            // is in error state
            const hasFailed = status?.type === 'error';

            // already has generated preview
            const hasPoster = posterSrc === entry.src;

            // failed to generate preview, not ready yet, or already generated the preview
            if (hasFailed || !(entry.src instanceof File) || !isPDF(entry.src) || hasPoster) {
                return;
            }

            // let's queue our preview generation task
            pushTask(entry.id, taskGeneratePDFPreview);
        }

        const unsubUpdateEntry = on('updateEntry', handleUpdateEntry);

        return {
            destroy: () => {
                unsubUpdateEntry();
            },
        };
    }
);

// Create FilePond
const elements = defineFilePond({
    locale,
    extensions: [PDFPreviewLoader],
    EntryListView: {
        template,
    },
});