SPFx Drag and Drop File Upload: Single & Multiple Files (Complete Guide)

If you’ve been building SharePoint Framework web parts for a while, you know one thing users always ask for: a clean drag-and-drop file upload experience. The default SharePoint upload UI works fine, but when you’re building a custom web part, you want full control. You want users to drop files onto a zone, see them listed, and hit upload. Simple.

In this tutorial, I’ll walk you through three practical methods to build drag-and-drop file uploads in SPFx, from a single file using plain HTML5, to multiple files using the react-dropzone library, and finally using the PnP DragDropFiles reusable control. I’ll cover real code, PnPjs v4 for the upload calls, and tips you’ll actually use on the job.

What We’re Building

By the end of this tutorial, you’ll have an SPFx React web part that:

  • Let users drag and drop one or multiple files onto a drop zone
  • Shows a file list before uploading
  • Uploads files to a SharePoint document library using PnPjs v3
  • Handles large files (over 10 MB) with chunked upload
  • Validates file types and sizes

Prerequisites

Before you start, make sure you have these set up:

  • Node.js v22.x
  • SPFx Yeoman generator v1.20+ (targets SharePoint Online)
  • A SharePoint Online site with a document library (I’ll use Shared Documents)
  • Basic knowledge of React and TypeScript

Setting Up Your SPFx Project

Open your terminal and scaffold a new web part:

yo @microsoft/sharepoint

When prompted:

  • Solution name: spfx-drag-drop-upload
  • Framework: React
  • Web part name: FileUploader
setting up the spfx project

Once scaffolded, install your dependencies:

npm install @pnp/sp @pnp/spfx-controls-react react-dropzone --save

Here’s what each package does:

  • @pnp/sp = PnPjs v4 for all SharePoint REST API calls (upload, read, delete)
  • @pnp/spfx-controls-react = PnP’s ready-made React controls, including DragDropFiles
  • react-dropzone = A popular, lightweight library specifically built for drag-and-drop file handling in React

Setting Up PnPjs v4

Open FileUploaderWebPart.ts and configure PnPjs in the onInit method. This is the correct pattern for PnPjs v4; you no longer use the global sp singleton from older versions.

import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
type IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import { spfi, SPFI, SPFx } from '@pnp/sp';

import * as strings from 'FileUploaderWebPartStrings';
import FileUploader from './components/FileUploader';
import { IFileUploaderProps } from './components/IFileUploaderProps';

export interface IFileUploaderWebPartProps {
libraryName: string;
}

export default class FileUploaderWebPart extends BaseClientSideWebPart<IFileUploaderWebPartProps> {

private _sp!: SPFI;

public render(): void {
const element: React.ReactElement<IFileUploaderProps> = React.createElement(
FileUploader,
{
sp: this._sp,
libraryName: this.properties.libraryName,
webServerRelativeUrl: this.context.pageContext.web.serverRelativeUrl
}
);

ReactDom.render(element, this.domElement);
}


protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('libraryName', {
label: strings.LibraryNameFieldLabel
})
]
}
]
}
]
};
}
}

Pass this._sp down as a prop to your React component. Never initialize PnPjs inside the component itself; the onInit method is the right place.

Check out PnP React Pagination Control in SharePoint Framework (SPFx) Web Part

Method 1: HTML5 Drag and Drop (No Libraries) In SPFx

This is the raw approach; no external libraries, just native browser drag-and-drop events. It’s a great way to understand what’s happening under the hood, and it works fine for single or multiple files.

spfx upload files using drag and drop

Here is the component code you can add to the .tsx file.

import * as React from 'react';
import { SPFI } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/files";
import "@pnp/sp/folders";

interface IHtmlDropZoneProps {
sp: SPFI;
libraryName: string;
}

interface IHtmlDropZoneState {
files: File[];
uploading: boolean;
message: string;
isDragging: boolean;
}

export default class HtmlDropZone extends React.Component<IHtmlDropZoneProps, IHtmlDropZoneState> {
constructor(props: IHtmlDropZoneProps) {
super(props);
this.state = { files: [], uploading: false, message: '', isDragging: false };
}

private _onDragOver = (e: React.DragEvent<HTMLDivElement>): void => {
e.preventDefault(); // This line is mandatory — without it, the drop won't work
this.setState({ isDragging: true });
}

private _onDragLeave = (): void => {
this.setState({ isDragging: false });
}

private _onDrop = (e: React.DragEvent<HTMLDivElement>): void => {
e.preventDefault();
this.setState({ isDragging: false });
const droppedFiles = Array.from(e.dataTransfer.files);
this.setState({ files: [...this.state.files, ...droppedFiles] });
}

private _uploadFiles = async (): Promise<void> => {
const { sp, libraryName } = this.props;
const { files } = this.state;

if (!files.length) return;

this.setState({ uploading: true, message: '' });

try {
for (const file of files) {
const siteRelativeUrl = `/sites/YourSiteName/${libraryName}`;

if (file.size <= 10 * 1024 * 1024) {

await sp.web
.getFolderByServerRelativePath(siteRelativeUrl)
.files.addUsingPath(file.name, file, { Overwrite: true });
} else {

await sp.web
.getFolderByServerRelativePath(siteRelativeUrl)
.files.addChunked(file.name, file, undefined, true);
}
}
this.setState({ files: [], message: `${files.length} file(s) uploaded successfully!` });
} catch (err) {
this.setState({ message: `Upload failed: ${err.message}` });
} finally {
this.setState({ uploading: false });
}
}

public render(): React.ReactElement {
const { files, uploading, message, isDragging } = this.state;

const dropZoneStyle: React.CSSProperties = {
border: `2px dashed ${isDragging ? '#0078d4' : '#ccc'}`,
borderRadius: '8px',
padding: '40px',
textAlign: 'center',
backgroundColor: isDragging ? '#f0f8ff' : '#fafafa',
cursor: 'pointer',
transition: 'all 0.2s ease'
};

return (
<div>
<div
style={dropZoneStyle}
onDragOver={this._onDragOver}
onDragLeave={this._onDragLeave}
onDrop={this._onDrop}
>
{isDragging ? 'Release to drop files here' : 'Drag and drop files here (single or multiple)'}
</div>

{files.length > 0 && (
<ul>
{files.map((f, i) => <li key={i}>{f.name} ({(f.size / 1024).toFixed(1)} KB)</li>)}
</ul>
)}

<button onClick={this._uploadFiles} disabled={uploading || !files.length}>
{uploading ? 'Uploading...' : 'Upload Files'}
</button>

{message && <p>{message}</p>}
</div>
);
}
}

A few things worth noting about this approach:

  • e.preventDefault() inside onDragOver is not optional — browsers block drops by default. Without this line, nothing will drop.
  • The 10 MB threshold is the recommended split between addUsingPath (for regular files) and addChunked (for large ones). PnPjs handles chunked uploads using SharePoint’s StartUploadContinueUpload, and FinishUpload REST APIs behind the scenes.
  • Array.from(e.dataTransfer.files) converts the FileList object (which isn’t a real array) into a proper JavaScript array so you can spread and iterate it.

Read: Fluent UI React ComboBox Control in SPFx

react-dropzone is the most popular approach for SPFx drag-and-drop uploads, and for good reason. It handles all the tricky drag-and-drop edge cases for you — browser inconsistencies, file type filtering, accept attributes, and even click-to-browse. It also works beautifully for both single and multiple files.

spfx drag and drop file upload using react-dropzone

Install Packages

npm install react-dropzone --save

Provide this in the .ts file to send props to the component file and initialize the PnJS Instance.

import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
  type IPropertyPaneConfiguration,
  PropertyPaneTextField
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import { spfi, SPFI, SPFx } from '@pnp/sp';

import * as strings from 'ReactDropzoneUploaderWebPartStrings';
import ReactDropZoneUploader from './components/ReactDropZoneUploader';
import type { IDropZoneProps } from './components/IDropZoneProps';

export interface IReactDropzoneUploaderWebPartProps {
  libraryName: string;
  acceptedTypes: string;
  maxSizeMB: string;
}

export default class ReactDropzoneUploaderWebPart extends BaseClientSideWebPart<IReactDropzoneUploaderWebPartProps> {

  private _sp!: SPFI;

  public render(): void {
    const acceptedTypes = this.properties.acceptedTypes
      ? this.properties.acceptedTypes.split(',').map(t => t.trim()).filter(t => !!t)
      : undefined;
    const maxSizeMB = Number(this.properties.maxSizeMB);

    const element: React.ReactElement<IDropZoneProps> = React.createElement(
      ReactDropZoneUploader,
      {
        sp: this._sp,
        libraryName: this.properties.libraryName,
        siteRelativeUrl: this.context.pageContext.web.serverRelativeUrl,
        acceptedTypes,
        maxSizeMB: maxSizeMB > 0 ? maxSizeMB : undefined
      }
    );

    ReactDom.render(element, this.domElement);
  }

  protected onInit(): Promise<void> {
    this._sp = spfi().using(SPFx(this.context));
    return Promise.resolve();
  }

  protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration 
    return {
      pages: [
        {
          header: {
            description: strings.PropertyPaneDescription
          },
          groups: [
            {
              groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneTextField('libraryName', {
                  label: strings.LibraryNameFieldLabel
                }),
                PropertyPaneTextField('acceptedTypes', {
                  label: strings.AcceptedTypesFieldLabel,
                  description: strings.AcceptedTypesFieldDescription
                }),
                PropertyPaneTextField('maxSizeMB', {
                  label: strings.MaxSizeFieldLabel
                })
              ]
            }
          ]
        }
      ]
    };
  }
}

Then, here is the component, you can update it on the .tsx file.

import * as React from 'react';
import { useDropzone, Accept, ErrorCode, FileError } from 'react-dropzone';
import "@pnp/sp/webs";
import "@pnp/sp/files";
import "@pnp/sp/folders";
import type { IDropZoneProps } from './IDropZoneProps';


const EXTENSION_MIME_MAP: Record<string, string> = {
'.pdf': 'application/pdf',
'.doc': 'application/msword',
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'.xls': 'application/vnd.ms-excel',
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'.ppt': 'application/vnd.ms-powerpoint',
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.txt': 'text/plain',
'.csv': 'text/csv',
'.zip': 'application/zip'
};

function buildAcceptOption(extensions: string[]): Accept {
return extensions.reduce<Accept>((acc, ext) => {
const mimeType = EXTENSION_MIME_MAP[ext.toLowerCase()] || 'application/octet-stream';
return { ...acc, [mimeType]: [...(acc[mimeType] || []), ext] };
}, {});
}


function getFriendlyRejectionMessage(error: FileError, acceptedTypes: string[] | undefined, maxSizeMB: number): string {
switch (error.code) {
case ErrorCode.FileInvalidType:
return acceptedTypes && acceptedTypes.length
? `This file type isn't supported. Accepted types: ${acceptedTypes.join(', ')}`
: `This file type isn't supported.`;
case ErrorCode.FileTooLarge:
return `File is too large. Maximum size is ${maxSizeMB} MB.`;
case ErrorCode.FileTooSmall:
return `File is too small.`;
case ErrorCode.TooManyFiles:
return `Too many files selected at once.`;
default:
return error.message;
}
}

const MAX_SINGLE_REQUEST_UPLOAD_SIZE = 10 * 1024 * 1024;

const ReactDropZoneUploader: React.FC<IDropZoneProps> = ({
sp, libraryName, siteRelativeUrl, acceptedTypes, maxSizeMB = 50
}) => {
const [files, setFiles] = React.useState<File[]>([]);
const [uploading, setUploading] = React.useState(false);
const [statusMap, setStatusMap] = React.useState<Record<string, string>>({});

const maxSize = maxSizeMB * 1024 * 1024;

const { getRootProps, getInputProps, isDragActive, fileRejections } = useDropzone({
multiple: true, // Set to false if you only want single file
maxSize: maxSize,
accept: acceptedTypes && acceptedTypes.length ? buildAcceptOption(acceptedTypes) : undefined,
onDrop: (acceptedFiles: File[]) => {
setFiles(prev => [...prev, ...acceptedFiles]);
}
});

const removeFile = (index: number): void => {
setFiles(prev => prev.filter((_, i) => i !== index));
};

const uploadFiles = async (): Promise<void> => {
if (!files.length) return;

setUploading(true);
const folder = sp.web.getFolderByServerRelativePath(`${siteRelativeUrl}/${libraryName}`);
const newStatus: Record<string, string> = {};

for (const file of files) {
try {
if (file.size <= MAX_SINGLE_REQUEST_UPLOAD_SIZE) {
await folder.files.addUsingPath(file.name, file, { Overwrite: true });
} else {
await folder.files.addChunked(file.name, file, { Overwrite: true });
}
newStatus[file.name] = '✅ Uploaded';
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);
newStatus[file.name] = `❌ Failed: ${errorMessage}`;
}
}

setStatusMap(newStatus);
setUploading(false);
setFiles([]);
};

const dropZoneStyle: React.CSSProperties = {
border: '2px dashed #0078d4',
borderRadius: '8px',
padding: '48px 24px',
textAlign: 'center',
backgroundColor: isDragActive ? '#e8f4fb' : '#f9f9f9',
cursor: 'pointer',
transition: 'background-color 0.2s ease'
};

return (
<div style={{ fontFamily: 'Segoe UI, sans-serif', maxWidth: '600px' }}>
<div {...getRootProps({ style: dropZoneStyle })}>
<input {...getInputProps()} />
{isDragActive
? <p>Drop your files here...</p>
: <p>Drag and drop files here, or click to browse</p>
}
<p style={{ fontSize: '12px', color: '#666' }}>
Max size: {maxSizeMB} MB per file
{acceptedTypes && ` | Accepted: ${acceptedTypes.join(', ')}`}
</p>
</div>

{/* Rejected files */}
{fileRejections.length > 0 && (
<div style={{ color: 'red', marginTop: '8px' }}>
{fileRejections.map(({ file, errors }) => (
<p key={file.name}>
{file.name}: {errors.map(e => getFriendlyRejectionMessage(e, acceptedTypes, maxSizeMB)).join(', ')}
</p>
))}
</div>
)}

{/* Accepted file list */}
{files.length > 0 && (
<ul style={{ listStyle: 'none', padding: 0, marginTop: '16px' }}>
{files.map((f, i) => (
<li key={i} style={{ display: 'flex', justifyContent: 'space-between', padding: '6px 0', borderBottom: '1px solid #eee' }}>
<span>{f.name} ({(f.size / 1024).toFixed(1)} KB)</span>
<button onClick={() => removeFile(i)} style={{ color: 'red', border: 'none', background: 'none', cursor: 'pointer' }}>Remove</button>
</li>
))}
</ul>
)}

{/* Upload status */}
{Object.entries(statusMap).map(([name, status]) => (
<p key={name} style={{ fontSize: '13px' }}>{name}: {status}</p>
))}

<button
onClick={uploadFiles}
disabled={uploading || !files.length}
style={{
marginTop: '16px',
padding: '10px 24px',
backgroundColor: '#0078d4',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: uploading ? 'not-allowed' : 'pointer'
}}
>
{uploading ? 'Uploading...' : `Upload ${files.length} file(s)`}
</button>
</div>
);
};

export default ReactDropZoneUploader;

A few things I really like about react-dropzone:

  • The useDropzone hook returns getRootProps and getInputProps — spread those onto your drop zone div and input. It wires up all the event handlers automatically.
  • The accept prop lets you restrict file types. For example, {'.pdf': [], '.docx': []} means only PDFs and Word documents can be dropped.
  • fileRejections gives you a ready-made list of files that were blocked (wrong type or too large) — perfect for showing error messages.
  • Setting multiple: false turns the whole thing into a single-file uploader without changing anything else.

Here, you can also see that in the property pane for Accepted File Types, I added the .pdf extension, and then tried to upload a .xlsx file, which shows an error. In this way, we can restrict file types during upload.

spfx drag and drop file upload filter file types

Check out Add Custom Controls in SPFx Property Pane

Method 3: PnP DragDropFiles Control in SPFx

If you’re already using @pnp/spfx-controls-react it in your project, and many SPFx projects do, then you have the DragDropFiles control available at no extra cost. It’s the quickest way to add a drop zone with the least code.

spfx drag and drop file upload using pnp dragdropfiles control

Install the PnP Controls

npm install @pnp/spfx-controls-react --save --save-exact

Update your component file with the following code.

import * as React from 'react';
import { DragDropFiles } from "@pnp/spfx-controls-react/lib/DragDropFiles";
import { SPFI } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/files";
import "@pnp/sp/folders";

interface IPnPDropZoneProps {
sp: SPFI;
siteRelativeUrl: string;
libraryName: string;
}

interface IPnPDropZoneState {
droppedFiles: File[];
uploading: boolean;
message: string;
}

export default class PnPDragDropUploader extends React.Component<IPnPDropZoneProps, IPnPDropZoneState> {
constructor(props: IPnPDropZoneProps) {
super(props);
this.state = { droppedFiles: [], uploading: false, message: '' };
}

private _onDrop = (files: File[]): void => {
// The DragDropFiles control returns a File[] array directly
this.setState({ droppedFiles: [...this.state.droppedFiles, ...files] });
}

private _uploadAll = async (): Promise<void> => {
const { sp, siteRelativeUrl, libraryName } = this.props;
const { droppedFiles } = this.state;
const folderPath = `${siteRelativeUrl}/${libraryName}`;

this.setState({ uploading: true, message: '' });

try {
for (const file of droppedFiles) {
if (file.size <= 10 * 1024 * 1024) {
await sp.web
.getFolderByServerRelativePath(folderPath)
.files.addUsingPath(file.name, file, { Overwrite: true });
} else {
await sp.web
.getFolderByServerRelativePath(folderPath)
.files.addChunked(file.name, file, undefined, true);
}
}
this.setState({ message: `${droppedFiles.length} file(s) uploaded!`, droppedFiles: [] });
} catch (err) {
this.setState({ message: `Error: ${err.message}` });
} finally {
this.setState({ uploading: false });
}
}

public render(): React.ReactElement {
const { droppedFiles, uploading, message } = this.state;

return (
<div style={{ fontFamily: 'Segoe UI, sans-serif' }}>
<DragDropFiles
dropEffect="copy"
enable={true}
onDrop={this._onDrop}
iconName="Upload"
labelMessage="Drop your files here to upload"
>
<div style={{ padding: '20px', color: '#555' }}>
Drag files over this area or use the zone above
</div>
</DragDropFiles>

{droppedFiles.length > 0 && (
<div style={{ marginTop: '12px' }}>
<strong>Files ready to upload:</strong>
<ul>
{droppedFiles.map((f, i) => (
<li key={i}>{f.name} — {(f.size / 1024).toFixed(1)} KB</li>
))}
</ul>
<button
onClick={this._uploadAll}
disabled={uploading}
style={{ padding: '8px 20px', backgroundColor: '#0078d4', color: '#fff', border: 'none', borderRadius: '4px', cursor: 'pointer' }}
>
{uploading ? 'Uploading...' : 'Upload All'}
</button>
</div>
)}

{message && <p style={{ marginTop: '10px', color: message.startsWith('Error') ? 'red' : 'green' }}>{message}</p>}
</div>
);
}
}

What’s nice about DragDropFiles:

  • You get a styled, accessible drop zone immediately with iconName and labelMessage props.
  • The onDrop callback hands you a clean File[] — no need to parse dataTransfer.files yourself.
  • You can nest any child content (like a ListView or form fields) inside the control. The entire nested area becomes the drop zone.
  • The dropEffect prop controls the cursor visual (copymovelink, or none).

Uploading to a Subfolder

What if users need to upload into a specific subfolder, not the root of the library? Easy — just append the folder path:

const folderPath = `/sites/YourSiteName/Shared Documents/Reports/Q1-2025`;

await sp.web
.getFolderByServerRelativePath(folderPath)
.files.addUsingPath(file.name, file, { Overwrite: true });

If the subfolder might not exist yet, create it first:

await sp.web
.getFolderByServerRelativePath(`/sites/YourSiteName/Shared Documents`)
.addSubFolderUsingPath("Reports/Q1-2025");

Handling Large Files with Chunked Upload in SPFx

For files larger than 10 MB, addUsingPath will time out. Use addChunked instead:

await sp.web
.getFolderByServerRelativePath(folderPath)
.files.addChunked(
file.name,
file,
(data) => {
console.log(`Progress: ${data.blockNumber} chunks uploaded`);
},
true // Overwrite = true
);

The third argument is an optional progress callback — handy if you want to show a progress bar to your users. PnPjs splits the file into chunks and calls SharePoint’s upload session API automatically. You don’t need to manage chunk sizes yourself.

Which Method Should You Pick?

Here’s how I think about it:

Your situationMethod to use
Quick prototype, no extra librariesMethod 1 — Plain HTML5
Need file type filtering, size validation, click-to-browseMethod 2 — react-dropzone
Already using @pnp/spfx-controls-reactMethod 3 — PnP DragDropFiles
Need to wrap an existing web part area as a drop zoneMethod 3 — PnP DragDropFiles (it accepts child components)
Building for Teams + SharePointMethod 2 — react-dropzone (most portable)

Common Issues and Fixes

Drop zone doesn’t work at all

You probably forgot e.preventDefault() in onDragOver. Browsers block drop events by default — that’s a security measure. You have to explicitly allow it.

Files show as 0 bytes or empty in SharePoint

This happens when you read the file with FileReader and try to pass the result directly. With PnPjs v4, just pass the File object directly to addUsingPath or addChunked. No need to manually read it.

“Access denied” error on upload

Your web part’s SPFx manifest needs FullControl or Write permission on the site collection. Check package-solution.json under webApiPermissionRequests, or verify the user has contribute access to the document library.

Files not appearing in the library

Double-check your siteRelativeUrl. It must be the server-relative URL — starting with /sites/YourSiteName. Using the absolute URL will cause a 400 error.

Multiple files only uploading one

If you’re using a for...of loop await, make sure the loop itself is inside an async function. If you accidentally use .forEach with an async callback, the awaits won’t block correctly.

A Quick Note on SPFx Permissions

Your web part needs the right permissions to write to SharePoint. In package-solution.json, make sure you have:

"webApiPermissionRequests": [],
"isDomainIsolated": false

SPFx web parts running in the context of a SharePoint page already inherit the current user’s permissions — so as long as the user has Contribute or higher on the library, the upload will work without extra API permissions. If you’re uploading to a different site collection, you’ll need to set up cross-site permissions explicitly.

Deploying Your SPFx Web Part

Once your web part is ready:

heft clean
heft build --production
heft package-solution --production

Upload the .sppkg file from the sharepoint/solution folder to your App Catalog. Approve any permissions prompts, then add the web part to any page.

Wrapping Up

You’ve now got three solid ways to build drag-and-drop file uploads in SPFx. My personal go-to for most projects is react-dropzone — It’s well-maintained, has good TypeScript support, and handles all the annoying edge cases. The PnP DragDropFiles control is great when you want something fast and already have the PnP controls library in your solution. And plain HTML5 is always there when you want to keep things lean.

The upload logic using PnPjs v4 is the same across all three methods — addUsingPath for small files, addChunked for anything over 10 MB. Once you have that pattern down, you can swap the UI layer freely.

Also, you may like:

Power Apps functions free pdf

30 Power Apps Functions

This free guide walks you through the 30 most-used Power Apps functions with real business examples, exact syntax, and results you can see.

Live Webinar

Create a SharePoint List & Columns from Excel Using Power Automate

Learn how to automatically create a SharePoint Online list and all its columns from an Excel file using Power Automate—without using any Premium connectors..

📅 4th Aug 2026 – 10:00 AM EST | 7:30 PM IST

Download User registration canvas app

DOWNLOAD USER REGISTRATION POWER APPS CANVAS APP

Download a fully functional Power Apps Canvas App (with Power Automate): User Registration App