Used to create a custom extension from scratch.

The name should be unique. The type can be "source", "loader", "validator", "transform", "resource", "view", or "store". The createFilePondExtensionSet function uses this type to auto sort the extensions set.

Props 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 factory which receives the extension state and methods to interact with the FilePond instance.

const MyExtension = createExtension({
    name: 'MyResource',
    type: 'resource',
    props: {
        // default options
    },
    factory: (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, ConsoleView } 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 PDFPageResource extension, it'll read a PDF and generate a PNG of the first page
const PDFPageResource = createExtension({
    name: 'PDFPageResource',
    type: 'resource',
    props: {
        page: 1,
        pixelRatio: 1,
    },
    factory: (
        { props, extensionName },
        {
            on,
            updateEntry,
            pushTask,
            getEntryExtensionState,
            setEntryExtensionState,
            getEntryExtensionStatus,
            setEntryExtensionStatus,
        }
    ) => {
        // very basic pdf detection
        function isPDF(file) {
            return file.type === 'application/pdf';
        }

        // this task will generate the PDF
        async function taskRenderPDFPage(entry) {
            const { page: pageNr, pixelRatio: scale } = props;

            // let's start the loading indicator
            setEntryExtensionStatus(entry, {
                type: 'system',
                code: 'RESOURCE_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;

                // we've got the document, release the url
                URL.revokeObjectURL(objectURL);

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

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

                // 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));

                // update state of the entry with the rendered page
                updateEntry(entry, {
                    extension: {
                        [extensionName]: {
                            value: blob,
                        },
                    },
                });
            } catch (error) {
                setEntryExtensionStatus(entry, {
                    type: 'error',
                    code: 'RESOURCE_ERROR',
                    values: { error },
                });

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

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

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

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

            // already has generated preview
            const hasPoster = value instanceof File;

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

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

        const unsubUpdateEntryData = on('updateEntryData', handleUpdateEntryData);

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

// Create FilePond
const [element] = defineFilePond({
    locale,

    // set custom extensions
    extensions: [PDFPageResource, ConsoleView],

    // overwrite list view props
    EntryListView: {
        template,
    },
});

// we need to pass the PDF page blob to the entry list view extension
element.on('updateEntry', (entry) => {
    const { PDFPageResource, EntryListView } = entry.extension;
    const pdf = PDFPageResource?.value;
    const poster = EntryListView?.poster;

    // wait till we have a pdf or the pdf has changed, else we end up in infinite loop
    if (!pdf || pdf === poster) {
        return;
    }

    // update the entry poster
    element.updateEntry(entry.id, {
        extension: {
            EntryListView: {
                poster: pdf,
            },
        },
    });
});