CustUp is a highly customizable JavaScript file upload library with zero dependency that can be easily adapted to a wide range of applications.
CustUp which is the short for Customizable Uploader was made to be >95% customizable if not 100%, and it is very easy to customize it to suit any kind of project you're working on.
- Choose the UI type that fits your project.
- Easily change the UI design or create your own UI just by overriding or adding to the CSS classes of the Elements.
- With the instance attachment feature, you can create multiple CustUp instances and upload all files at once together with form fields and/or additional data.
- You can get all the selected files and upload them manually.
- You don't need to worry about installing HTTP client to manage your upload requests because axios was bundled into CustUp which you can easily configure.
Check out the demo here, or check the CustUp documentation homepage to see different CustUp UIs
Install from npm
npm i custup
import CustUp from 'path/to/custup/src/custup.min.js'
const instance1 = new CustUp({
targetRootElement: '#container',
})
TypeScript
import CustUp from 'path/to/custup/'
const instance1 = new CustUp({
targetRootElement: '#container',
})
if you get Uncaught SyntaxError: import declarations may only appear at top level of a module
error add type="module"
to the javascript file where CustUp
was imported into.
OR include it via UNPKG
import CustUp from 'https://unpkg.com/custup@latest/src/custup.min.js'
// and the CSS file
<link rel="stylesheet" href="https://unpkg.com/custup@latest/src/all.min.css">
This will load the default UI which you can further customize to fit your need or the need of your application
Then include the CSS file
<!--All the CSS files combined together -->
<link rel="stylesheet" href="path/to/custup/src/all.min.css">
<!-- OR individual CSS files -->
<!-- Bare UI CSS file -->
<link rel="stylesheet" href="path/to/custup/src/bare.min.css">
<!-- Elegant UI CSS file -->
<link rel="stylesheet" href="path/to/custup/src/elegant.min.css">
<!-- CustUp default UI CSS file -->
<link rel="stylesheet" href="path/to/custup/src/custup.min.css">
<!-- Detached UI CSS file -->
<link rel="stylesheet" href="path/to/custup/src/detached.min.css">
<!-- ResumeUploader UI CSS file -->
<link rel="stylesheet" href="path/to/custup/src/resumeUploaderUI.min.css">
CustUp was developed to be fully customizable, you can customize/change CSS styles, UI order, Icons, and even remove HTML elements just by using the Options
const options = {
allowed_file_types: ['mp3', "mp4", "jpg", "png", "jpeg","pdf"],
targetRootElement: '#container',
maxNumberOfFiles: 5,
minNumberOfFiles: 3,
maximumAllowedFileSize: 10000000,
}
const uploader = new CustUp(options)
Change the UI type
const options = {
// ...
ui_type: 'detached', // the detached UI type, see more in Options sections below
}
const uploader = new CustUp(options)
To change the font style change the css_font_link
and or only set css_font_name
if you want to use the application's font name.
Note: CustUp currently only supports Google Fonts so only Google Fonts link may work.
const options = {
// ...
css_font_link: "https://fonts.googleapis.com/css2?family=Dancing+Script&display=swap",
css_font_name: "Dancing Script",
}
const uploader = new CustUp(options)
To change the styles of some elements, you can override their styles by either overriding their classname or adding a css classname to the element's class name
const options = {
// ...
default_styles_override: {
remove_file_btn: ['close_btn', true],
fileDisplayUI: ['custup_inner_container', true],
fileUIOuterContainer: ['custup_file_display_outer', true],
fileUI: ['custup_file_ui_outer', true],
file_wrapper_el: ['file_wrapper', true],
fileDetailsContainer: ['file_details', true],
custupInnerContainerWrapperEl: ['inner_container_wrapper', true],
headerContainer: 'header_cont',
footerContainer: 'footer_container'
},
}
const uploader = new CustUp(options)
To overide the default class names the provided class name should be a string or an array with false as the second item like fileUIOuterContainer: ['custup_file_display_outer', false],
and to append/add new css class name to the element the second item in the array should be true
like fileUIOuterContainer: ['custup_file_display_outer', true]
Read more on how to customize CustUp elements with properties here
File upload settings, you can also add additional data that should be sent along the files, and/or a form field data to be sent along with the upload and you can also set the axios settings
const options = {
// ...
file_upload: {
endpoint_url: 'http://localhost/fileupload', // endpoint
additional_data: { // additional data to be sent along with the
user_id: 123456789,
username: 'johndoe'
},
form_field: '#form', // HTMLFormElement
axios_settings: {
headers: {}, // configure the axios header, like add a Bearer token
configs: {}, // add a configuration to axios
}
},
}
const uploader = new CustUp(options)
To set CustUp to automatically upload files immediately after files has been added to the UI set the upload_automatically
to true
const options = {
// ...
upload_automatically: true,
}
const uploader = new CustUp(options)
CustUp file select sources is divided into External and Media Sources
- Google Drive
- Dropbox
- Box
- OpenAI DALL.E-3
- URL
Onedrive is not currently supported, will be added in the future version
- Camera
- Video Recording
- Audio Recording
- Screen Recording
You can choose which sources to allow and the order in which you want them to appear on the UI
const options = {
// ...
allowed_sources: ['dropbox_source'], // only Dropbox file source icon will be displayed on the UI
// allowed_sources: ['openai_dalle_source', 'capture_image', 'box_source'], // only OpenAI DALL.E-3, Image capture from media devices and Box icons will be displayed and ordered as listed in the array
}
const uploader = new CustUp(options)
See more sources in the Options below.
If you wants to use another HTML elements to control the upload, you can set the UI tools to not display on the default UI.
const options = {
// ...
display_ui_tools: false,
}
const uploader = new CustUp(options)
When previewing a file there are in and out animations, by default the animations are randomized but you can set which animations to use or you can also disable the animations
const options = {
// ...
file_preview_animation_types: ['slideInLeft'], // the animation previewer will only use `slideInLeft` animation type
// file_preview_animation_types: ['slideInRight', 'zoomIn', 'slideInLeft'], // this will randomly choose between the array of the animations when displaying files
// file_preview_animation_types: null, // to disable file preview animation set it to null
}
const uploader = new CustUp(options)
Even the CustUp scrolling and scrollbar are custom made so you can also customize them, to not show the scrollbar set disable_scrollbar
to false
const options = {
// ...
disable_scrollbar: false
}
const uploader = new CustUp(options)
Guess the maximum number of CustUp instances that can be on a single page, Infinity
and up to the maximum capacity of your user's device memory, so also it is also possible to attach one or more CustUp instances to another instance basically for collective file upload, that is if you have more than one CustUp instances on a single page and you would like to upload all the files added to all the instances together in a single upload.
For Example
const instance1 = new CustUp({...});
const instance2 = new CustUp({...});
const instance3 = new CustUp({...});
const instance4 = new CustUp({
//...
instance_attach: [instance1, instance2, instance3],
// single_upload: true,
});
instance4.upload();
single_upload
when set to true and used with instance_attach
it is useful if all files should be uploaded at once, if false
then upload event will be triggered for each of the files in all of the attached instances.
See more guides on the documentation page.
Option | type | default | Required | Description |
---|---|---|---|---|
targetRootElement | string |
<empty string> |
Required | The HTML element to spawn CustUp into |
_custupDefaultUploadSentence | string |
Drag files to upload or Click to select file from device | Not Required | The HTML element that shows the description on the default UI |
disable_scrollbar | boolean |
false |
Not required | Whether to disable scroll bar or not |
persist_default_ui | boolean |
false |
Not required | Whether the default UI should never be hidden |
allowed_sources | Array<'record_video' 'capture_image' 'record_audio' 'record_screen' 'link_source' 'google_drive_source' 'dropbox_source' 'box_source' 'openai_dalle_source'> |
[] |
Not required | sources for selecting and adding files to CustUp |
use_default_file_display_ui | boolean | true |
Not required | Whether to use the default file display UI, set it to false if you would like to use your own UI, if false the UI tools and scrollbar will not be shown and selected files will not be shown on the UI |
show_preview_file_btn | boolean | true |
Not Required | whether to show file preview button or not |
autoInitialize | boolean | true |
Not required | Whether to automatically initialize and add CustUp UI to the DOM |
show_file_remove_btn | boolean | true |
Not required | Whether to show file remove button or not |
show_file_details_container | boolean | true |
Not Required | Whether to show the file details container, the container that holds the file name, size and preview icon |
file_source_icons | Object with key of video_camera , capture_image , record_audio , record_screen , url_source , google_drive_source , dropbox_source , box_source , openai_dalle_source |
Default file HTML and style | Not required | To override the default file source icons HTML, for example to change the HTML and style of video_camera set file_source_icons: {file_source_icons: HTMLElement} |
css_font_link | string |
https://fonts.googleapis.com/css2?family=Lato&display=swap |
Not required | The URL to the Google Font you would like a CustUp instance to use |
css_font_name | string |
Lato | Not required | The CSS font name of the loaded font or of the application's font name if you want CustUp to use the application's font |
external_source_style_override | typeof external_sources_ui_styles |
external_sources_ui_styles | Not required | if you would like to change the CSS of the overlay UI that displays the external file sources by either overriding their CSS classnames or adding your own CSS class name |
media_capture_source_style_override | typeof media_capture_ui_styles |
media_capture_ui_styles | Not required | if you would like to change the CSS of the overlay UI that displays the media capture file sources by either overriding their CSS classnames or adding your own CSS class name |
default_styles_override | typeof ui_styles |
ui_styles | Not required | To change the styles of the default CustUp elements by either overriding their CSS classnames or adding your own CSS class name |
persist_styles_override_across_instances | boolean |
false | Not required | Whether to persists the styles that were overiding across CustUp instance after the instance |
default_icons_override | typeof icons | icons | Not required | To override icons with your own icons |
allowed_file_types | keyof file_types |
All file types | Not required | To set which file types to allow to be selected and uploaded |
maxNumberOfFiles | number |
Infinity |
Not required | The maximumn number of files that can be selected and uploaded |
minNumberOfFiles | number |
None |
Not required | The minimum number of files that must be selected before upload |
minimumAllowedFileSize | number |
Infinity |
Not required | The minimum allowed file size |
maximumAllowedFileSize | number |
None |
Not required | The minimmum file size that can be selected |
instance_attach | Array<CustUp> |
[] |
Not required | For attaching different CustUp instances together basically for joining the selected files in the attached instances to the current instance when uploading |
single_upload | boolean |
false |
Not required | If true it will upload all selected files at once |
persist_files | boolean |
false |
Not required | To persist files in the browser storage, if true it will temporarily save the selected files in the browser storage and the files will be restored on page reload, it will use sessionStorage if persist_type is set to soft which is the default and localStorage if persist_type was set to hard , CustUp uses the targetRootElement id or classname as the key to store files for an instance |
persist_type | soft or hard |
soft |
Not required | To set the persist_files storage method, if soft CustUp will use sessionStorage to temporarily store files and if hard CustUp will use localStorage to store files. |
alert_timeout_time | number |
300 milliseconds |
Not required | The timeout for CustUp alerts |
There are many more other options, read the documentation for more options
These are all the options
options = {
// Core Options
autoInitialize: true,
disable_scrollbar: false,
persist_default_ui: false,
use_default_file_display_ui: true,
position_container: undefined,
// UI type
ui_type: 'default',
_custupDefaultUploadSentence: "Drag files to upload or Click to select file from device", // the text that displays at the top of other upload options
// style customization
default_styles_override: {},
persist_styles_override_across_instances: false,
external_source_style_override: undefined,
media_capture_source_style_override: undefined,
// icons customization
default_icons_override: {},
// File display
show_preview_file_btn: true,
show_file_remove_btn: true,
show_file_details_container: true,
file_preview_animation_types: [],
// UI tools
display_ui_tools: true,
disable_drag_n_drop: false,
disable_select_files_from_device: false,
allowed_tools: [],
// File source icons customization
file_source_icons: {
video_camera: icons.video_camera,
capture_image: icons.photo_camera,
record_audio: icons.audio,
record_screen: icons.screen_recording,
url_source: icons.link,
google_drive_source: icons.google_drive,
dropbox_source: icons.dropbox,
box_source: icons.box_icon,
openai_dalle_source: icons.openai_logo
},
allowed_sources: [],
// Upload source config
file_source_config: {
video_recording: {
video_only: false,
show_image_capture_btn: true,
},
capture_image: {
},
record_audio: {
},
record_screen: {
},
url_source: {
},
google_drive_source: {
authConfig: {
client_id: '',
api_key: '',
app_id: '',
scopes: 'https://www.googleapis.com/auth/drive.metadata.readonly'
},
},
dropbox_source: {
authConfig: {
appKey: "",
},
options: {
cancel: function() {},
}
},
box_source: {
authConfig: {
developerToken: "",
cssLink: "https://cdn01.boxcdn.net/platform/elements/17.1.0/en-US/picker.css",
jsLink: "https://cdn01.boxcdn.net/platform/elements/17.1.0/en-US/picker.js",
folder_id: '0'
},
pickerConfig: {
chooseButtonLabel: 'Select Files From Box',
sortBy: 'name', // name | date
sortDirection: 'ASC', // ASC | DESC
logoUrl: '', // URL to logo
extensions: [], // e.g. png, jpg, pdf
maxSelectable: Infinity, // Infinity | number
canUpload: false, // whether to show upload features
canSetShareAccess: false, // whether to enable sharing feature
canCreateNewFolder: false, // whether user can create a new folder from the UI
sharedLink: '', // Shared link URL, required if folder is shared and the access token doesn't belong to an owner or collaborator of the file.
sharedLinkPassword: '', // Shared link password, required if shared link has a password.
modal: '', // whether to display the the content picker in a modal
size: undefined, // undefined | 'large' | 'small' - undefined make it fit to the target container
isTouch: ( navigator.maxTouchPoints > 0 ) || ( navigator.msMaxTouchPoints > 0 ), // checks and automatically enables touch mode on devices that uses touch screen
autoFocus: false,
defaultView: 'files', // 'files' or 'recents'
chooseButtonLabel: undefined, // String to re-label the Choose button
cancelButtonLabel: undefined, // String to re-label the Cancel button
requestInterceptor: undefined, // undefined | Function - like axios request interceptor
responseInterceptor: undefined, // undefined | Function - like axios response interceptor
}
},
openai_dalle_source: {
endpoint: "https://api.openai.com/v1/images/generations",
api_key: "",
size: "1024x1024", // 256x256, 512x512, or 1024x1024
n: 4,
model: "dall-e-3",
quality: "standard", // standard | hd
}
},
// Custup Setup Options
css_font_link: "https://fonts.googleapis.com/css2?family=Lato&display=swap", // link to css font and it currently supports google fonts
css_font_name: "Lato", // name of the loaded font style
allowed_file_types: [], // Allowed file types, any file can be uploaded if the `allowed_file_types` parameter is not provided
targetRootElement: undefined, // Root target element to spawn the file uploader in
maxNumberOfFiles: undefined, // maximum allowed files that can be added, any number of files can be uploaded if not provided
minNumberOfFiles: undefined, // minimum allowed files that must be added, no limit if the `minNumberOfFiles` parameter was not provided
minimumAllowedFileSize: undefined, // minimum allowed file size that can be added to be uploaded, no minimum restriction will be placed if `minimumAllowedFileSize` is not provided
maximumAllowedFileSize: undefined, // maximum allowed file size that can be added to be uploaded, no maximum restriction will be placed if `maximumAllowedFileSize` is not provided
allowMultipleUpload: true, // whether to allow multiple file selection or not
// File upload settings
file_upload_settings: {
endpoint_url: '',
files_field_name: 'file',
form_field: '',
additional_data: {},
axios_settings: {
headers: {},
configs: {}
}
},
upload_automatically: false, // whether to upload file to the server automatically
show_upload_error_overlay: true,
// Default files File | Blob | link | base64
default_files: [],
count_default_files: true,
instance_attach: [],
single_upload: false,
// File storage locally
persist_files: false,
persist_type: 'soft',
// notification
alert_timeout_time: 300
}
See all available options here
CustUp has events that can be subscribed to
uploader.addEventListener('file.beforeAdded', (ev) => {
console.log('CustUp:', ev, ev.detail)
})
Event name | Description | Returns |
---|---|---|
'library.init' |
Called after the library has finished initializing and has been painted on the UI | null |
'file.beforeAdded' |
Called just before file gets added to the memory and/or UI | {file: File, base64: string} |
'file.afterAdded' |
Called after file has been added to the memory and/or browser storage and/or UI | {file: File, element: HTMLElement, count: number} |
'file.beforePassedChecks' |
For adding additional file checks, and must return either true or false or else it is not going to be effective |
null |
'file.removed' |
Called when a file has been removed from memory, UI and/or browser storage | {file: File, files_count: number} |
'file.defaultFileRemoved' |
Called when a default loaded file is being removed | File |
'file.all_removed' |
Called when all the files both selected and/or added to the UI by default has been removed from memory and/or browser storage | Array<File> |
'video.recordingStarted' |
Called when video recording has started | {media_recorder: MediaRecorder, media_devices: MediaDevices, display_el: HTMLElement} |
'upload.progress' |
Called on each file being uploaded or when files are being uploaded collectively | {progressEvent: Event} |
'upload.success' |
Called when a file or all files has successfully been uploaded | `{data: 'being returned from the server', file: File, upload_element: HTMLElement |
'upload.error' |
Called when a file or collective upload was not successfully due to an error | `{err: Error, file: File, upload_element: HTMLELement |
'upload.retry' |
Called when an upload is about to be retried | `{file: File, file_container: HTMLElement |
And many more events, check the documentation for more events
/*
* @param { 'file.beforeAdded' |
* 'library.init' |
* 'file.afterAdded' |
* 'file.beforePassedChecks' |
* 'file.removed' |
* 'file.defaultFileRemoved' |
* 'file.all_removed' |
* 'video.recordingStarted' |
* 'video.recording' |
* 'video.recordStop' |
* 'video.recordSaved' |
* 'video.recordCancel' |
* 'image.captured' |
* 'audio.recordingStarted' |
* 'audio.recording' |
* 'audio.recordStop' |
* 'audio.recordSaved' |
* 'audio.recordCancel' |
* 'screen.recordingStarted' |
* 'screen.recording' |
* 'screen.recordStop' |
* 'screen.recordSaved' |
* 'screen.recordCancel' |
* 'upload.beforeStart' |
* 'upload.progress' |
* 'upload.success' |
* 'upload.error' |
* 'upload.retry'
* } event
*/
Check out more events here
There are several methods that can be used to customize the library to your taste, example, to dynamically call the upload
method
uploader.upload() // this will trigger upload to upload al files
It can also take in the id
of the file to upload in the case of uploading single file
uploader.upload(file_id) // file id is the id that CustUp adds to the selected file using crypto.randomUUID()
Method name | Description | param |
---|---|---|
upload | To trigger upload for all files or a single file | file_id . Not required |
retry_upload | To trigger upload retry for all files that were not uploaded due to an error or for a single file | file_id . Not required |
show_add_file_ui | The method to show the default UI for selecting new files, will not work if use_default_file_display_ui is set to false |
None |
preview_file | To preview a file, it takes the cryptographically generated id added to the file by CustUp | file_id . Required |
get_selected_files | To get all the selected files excluding the default loaded files in memory, it returns all the selected excluding the default loaded files files in memory | None |
get_all_files | To get all the selected files including the default loaded files in memory, it returns all the selected including the default loaded files files in memory | None |
clear_files | To clear all the added files from the memory, browser storage and UI | None |
get_total_file_count | Returns all total number of added files, it will only return total number of selected files if count_default_files is set to false |
None |
And many more, read more about CustUp methods in the documentation
When I was working on a freelance project that has a custom file upload UI design some years back, I searched for libraries that can fit into the design or ones that has an option to change the UI style to fit into my project and that does not have any dependencies but I couldn't find any that fit into the description and I had to write a custom implementation for the file uploader from scratch, then I decided to build a file upload library that fits into the description of the library I couldn't find then.
- Typescript version/support
- React js version
- Chunk upload
- Upload resumable
- Onedrive implementation
Contributions and PRs are welcome, learn ways you can contribute here