Quick Start
Implement file uploads in your app with EdgeStore.
Set up with your agent
Read https://edgestore.dev/SKILL.md and add file uploads to this application.
Install the skill or connect MCP.
Step-by-step setup
Next.js Setup
Install
npm install @edgestore/server@rc @edgestore/react@rcEnvironment Variables
Copy your project's keys from the dashboard to the backend env file:
EDGESTORE_ACCESS_KEY=your-access-key
EDGESTORE_SECRET_KEY=your-secret-keyUse the app's existing env file, or .env.local for a new app. Keep it gitignored.
Backend
Choose your router below. This bucket's files are accessible to anyone with the link.
import { initEdgeStore } from '@edgestore/server';
import { createEdgeStoreNextHandler } from '@edgestore/server/adapters/next/app';
const es = initEdgeStore.create();
/**
* This is the main router for the EdgeStore buckets.
*/
const router = es.router({
publicFiles: es.fileBucket(),
});
const handler = createEdgeStoreNextHandler({
router,
});
export { handler as GET, handler as POST };
/**
* This type is used to create the type-safe client for the frontend.
*/
export type EdgeStoreRouter = typeof router;import { initEdgeStore } from '@edgestore/server';
import { createEdgeStoreNextHandler } from '@edgestore/server/adapters/next/pages';
const es = initEdgeStore.create();
/**
* This is the main router for the edgestore buckets.
*/
const router = es.router({
publicFiles: es.fileBucket(),
});
export default createEdgeStoreNextHandler({
router,
});
/**
* This type is used to create the type-safe client for the frontend.
*/
export type EdgeStoreRouter = typeof router;Frontend
Create the React provider:
'use client';
import { createEdgeStoreProvider } from '@edgestore/react';
import { type EdgeStoreRouter } from '../app/api/edgestore/[...edgestore]/route';
const { EdgeStoreProvider, useEdgeStore } =
createEdgeStoreProvider<EdgeStoreRouter>();
export { EdgeStoreProvider, useEdgeStore };'use client';
import { createEdgeStoreProvider } from '@edgestore/react';
import { type EdgeStoreRouter } from '../pages/api/edgestore/[...edgestore]';
const { EdgeStoreProvider, useEdgeStore } =
createEdgeStoreProvider<EdgeStoreRouter>();
export { EdgeStoreProvider, useEdgeStore };Wrap your app with the provider:
import { EdgeStoreProvider } from '../lib/edgestore';
import './globals.css';
// ...
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<EdgeStoreProvider>{children}</EdgeStoreProvider>
</body>
</html>
);
}import '../styles/globals.css';
import type { AppProps } from 'next/app';
import { EdgeStoreProvider } from '../lib/edgestore';
export default function App({ Component, pageProps }: AppProps) {
return (
<EdgeStoreProvider>
<Component {...pageProps} />
</EdgeStoreProvider>
);
}Upload file
You can use the useEdgeStore hook to access type-safe frontend client and use it to upload files.
'use client';
import * as React from 'react';
import { useEdgeStore } from '../lib/edgestore';
export default function Page() {
const [file, setFile] = React.useState<File>();
const { edgestore } = useEdgeStore();
return (
<div>
<input
type="file"
onChange={(e) => {
setFile(e.target.files?.[0]);
}}
/>
<button
onClick={async () => {
if (file) {
const res = await edgestore.publicFiles.upload({
file,
onProgressChange: (progress) => {
// you can use this to show a progress bar
console.log(progress);
},
});
// you can run some server action or api here
// to save `res.id` and `res.url` in your database
console.log(res);
}
}}
>
Upload
</button>
</div>
);
}Replace file
By passing the replaceTargetUrl option, you can replace an existing file with a new one.
It will automatically delete the old file after the upload is complete.
You can also just upload the file using the same file name, but in that case, you might still see the old file for a while because of the CDN cache.
const res = await edgestore.publicFiles.upload({
file,
options: {
replaceTargetUrl: oldFileUrl,
},
});Delete file
You can delete a file by passing its URL to the delete method.
To be able to delete a file from a client component like this, you will need to set the beforeDelete lifecycle hook on the bucket.
await edgestore.publicFiles.delete({
url: urlToDelete,
});Use deleteMany to send one request for multiple files. Storage failures are
reported per URL:
const result = await edgestore.publicFiles.deleteMany({
urls: selectedFiles.map((file) => file.url),
});
for (const failure of result.failed) {
console.error(failure.url, failure.error.code);
}EdgeStore runs beforeDelete for every file before deleting any of them. If
one file is unauthorized, the entire request is rejected without calling the
storage provider. Once authorization succeeds, the provider may still return
partial storage failures in result.failed.
Cancel upload
To cancel an ongoing file upload, you can use an AbortController the same way you would use it to cancel a fetch request.
// prepare a state for the AbortController
const [abortController, setAbortController] = useState<AbortController>();
// ...
// instantiate the AbortController and add the signal to the upload method
const abortController = new AbortController();
setAbortController(abortController);
const res = await edgestore.publicFiles.upload({
file,
signal: abortController.signal,
});
// ...
// to cancel the upload, call the controller's abort method
abortController?.abort();When you cancel an upload, an UploadAbortedError will be thrown.
You can catch this error and handle it as needed.
For more information, check the Error Handling page.
Transform files before upload
You can transform a file before EdgeStore validates and uploads it by passing
the transform option. If the transform keeps the same extension, you can
return the transformed File or Blob directly. If the transform changes the
file type, return the transformed file with its new extension.
For example, you can convert JPEG and PNG images to WebP before upload:
npm install browser-image-compressionimport imageCompression from 'browser-image-compression';
const res = await edgestore.publicImages.upload({
file,
options: {
transform: async ({ file, extension, signal }) => {
if (!['image/jpeg', 'image/png'].includes(file.type)) {
return { file, extension };
}
const compressedFile = await imageCompression(file, {
fileType: 'image/webp',
initialQuality: 0.8,
useWebWorker: true,
signal,
});
return {
file: compressedFile,
extension: 'webp',
};
},
},
});If you provide manualFileName, EdgeStore will use that exact file name. Make
sure the file name extension matches the transformed file type.
Temporary files
You can upload temporary files by passing the temporary option to the upload method.
Temporary files will be automatically deleted after 24 hours if they are not confirmed.
await edgestore.publicFiles.upload({
file: fileToUpload,
options: {
temporary: true,
},
});For forms, upload files as temporary and save their returned IDs/keys and URLs with your record before confirming them. If the database save fails, leave the files temporary. Confirmation does not require waiting for hosted processing.
Use the confirm method after saving:
await edgestore.publicFiles.confirm({
url: urlToConfirm,
});To confirm several temporary files in one request, use confirmMany:
const result = await edgestore.publicFiles.confirmMany({
urls: temporaryFiles.map((file) => file.url),
});You can check if a file is temporary in the dashboard.
Temporary files are marked with a clock icon.
Wait for processing
upload resolves as soon as the file is transferred. EdgeStore then processes
it in the background, for example to generate an image thumbnail. The file ID
and URL are final right away, so most apps can save them without waiting.
When you need processed details immediately, such as whether the image got a
thumbnail, pass waitForProcessing. The upload then resolves with the
processed file, including freshly signed URLs for buckets with
autoSignedUrls, and onPhaseChange tells you when the transfer finishes and
processing begins.
const res = await edgestore.publicImages.upload({
file,
onProgressChange: (progress) => setProgress(progress),
onPhaseChange: (phase) => {
if (phase === 'processing') setStatus('Processing…');
},
options: {
waitForProcessing: true, // or { timeoutMs: 120_000 }
},
});Processing failures reject with UploadCanceledError. If processing takes
longer than timeoutMs (60 seconds by default), the upload rejects with
UploadProcessingTimeoutError; the file may still finish processing. Both
errors include the file id. See Error Handling.
Waiting for processing does not confirm a temporary file. Confirm it separately once your app decides to keep it.
Providers without background processing, such as S3 and Azure Blob, resolve once the transfer completes.
Troubleshooting
If you have any problems using EdgeStore, please check the Troubleshooting page.