# EdgeStore Docs: Backend Client
URL: /docs/backend-client
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/(getting-started)/backend-client.mdx
***
title: Backend Client
description: Interact with EdgeStore from your backend.
-------------------------------------------------------
Sometimes you might want to use the EdgeStore functionality directly from your backend. Things like deleting, uploading or even listing files can be done with the use of the backend client.
## Setup
You can use your EdgeStore router to initialize the backend client.
Since Next.js doesn't allow exports in the api route, you will need to move your router to an external file.
```ts title="src/lib/edgestore-server.ts"
import { initEdgeStoreClient } from '@edgestore/server/core';
// ...
export const handler = createEdgeStoreNextHandler({
router: edgeStoreRouter,
});
// ...
// [!code ++:3]
export const backendClient = initEdgeStoreClient({
router: edgeStoreRouter,
});
```
Then you will need to update your api route to use the exported handler.
```ts title="src/app/api/edgestore/[...edgestore]/route.ts"
import { handler } from '@/lib/edgestore-server';
export { handler as GET, handler as POST };
```
You can find an example of the backend client usage in the
[next-advanced](https://github.com/edgestorejs/edgestore/tree/main/examples/next-advanced)
example.
## Backend Upload
You can use the `upload` function to upload files from your backend.
### Upload a text file
The simplest use case would be to just upload a `txt` file:
```ts
const res = await backendClient.publicFiles.upload({
content: 'some text content',
});
```
### Upload a blob
You can also upload a more complex file using the Blob object. And there are also all the other options available in the normal upload.
```ts
const res = await backendClient.publicFiles.upload({
content: {
blob: new Blob(['col1,col2,col2'], { type: 'text/csv' }),
extension: 'csv',
},
options: {
temporary: true,
},
ctx: {
userId: '123',
userRole: 'admin',
},
input: {
type: 'post',
},
});
```
### Copy an existing file
You can use an existing file's URL to copy it into the EdgeStore bucket. This can be an external file (from outside of EdgeStore) or an existing EdgeStore file.
```ts
const res = await backendClient.publicFiles.upload({
content: {
url: 'https://some-url.com/file.txt',
extension: 'txt',
},
});
```
### Confirm a temporary file upload
If you upload a temporary file, you can confirm it by using the `confirmUpload` function.
```ts
const res = await backendClient.publicFiles.confirmUpload({
url: fileUrl,
});
```
## Backend Delete
You can use the `deleteFile` function to delete files from your backend.
```ts
const res = await backendClient.publicFiles.deleteFile({
url: fileUrl,
});
```
## Backend List Files (search)
You can use the `listFiles` function to list files from your backend. It's also possible to filter the results by path, metadata or upload timing.
```ts
// simple usage
// get the first page (20 files) of all files in the bucket
const res = await backendClient.publicFiles.listFiles();
// with filter and pagination
const res = await backendClient.publicFiles.listFiles({
filter: {
metadata: {
role: 'admin',
},
path: {
type: 'post',
},
uploadedAt: {
gt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7), // past 7 days
},
},
pagination: {
currentPage: 1, // default: 1
pageSize: 50, // default: 20 (max: 100)
},
});
```
## TypeScript Helpers
Sometimes you might want to have the response type of the backend client functions, so you can use it to build your own functions. You can use the `InferClientResponse` helper type to infer the response type of the backend client.
```ts title="src/lib/edgestore.ts"
import { type InferClientResponse } from '@edgestore/server/core';
// ...
/**
* This helper type can be used to infer the response type of the backend client
*/
export type ClientResponse = InferClientResponse;
```
And you can use it like this:
```ts
export const getServerSideProps: GetServerSideProps<{
files: ClientResponse['publicFiles']['listFiles']['data'];
}> = async () => {
const res = await backendClient.publicFiles.listFiles();
return { props: { files: res.data } };
};
```
# EdgeStore Docs: Configuration
URL: /docs/configuration
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/(getting-started)/configuration.mdx
***
title: Configuration
description: Learn EdgeStore's configuration options.
-----------------------------------------------------
## Bucket Types
There are two types of file buckets: `IMAGE` and `FILE`. Both types of buckets work basically the same way, but the `IMAGE` bucket only accepts [certain mime types](#image-bucket-accepted-mime-types).
IMAGE buckets automatically generate a thumbnail version of the image file if the file is bigger than 200px in width or height. In case a thumbnail was generated, the url will be included in the response of the upload request.
```ts
const edgeStoreRouter = es.router({
publicFiles: es.fileBucket(),
publicImages: es.imageBucket(),
});
```
## Basic File Validation
You can set the maximum file size and the accepted mime types for every file bucket.
```ts
const edgeStoreRouter = es.router({
publicFiles: es.fileBucket({
maxSize: 1024 * 1024 * 10, // 10MB
accept: ['image/jpeg', 'image/png'], // wildcard also works: ['image/*']
}),
});
```
## Context
Many of the functions that you can use to configure your file buckets receive a `context` object as an argument. This object is generated by the `createContext` function that you pass to your router configuration.
```ts
import { initEdgeStore } from '@edgestore/server';
import {
type CreateContextOptions,
createEdgeStoreNextHandler,
} from '@edgestore/server/adapters/next/app';
import { z } from 'zod';
type Context = {
userId: string;
userRole: 'admin' | 'user';
};
async function createContext({ req }: CreateContextOptions): Promise {
const { id, role } = await getUserSession(req); // replace with your own session logic
return {
userId: id,
userRole: role,
};
}
const es = initEdgeStore.context().create();
// ...
export default createEdgeStoreNextHandler({
router: edgeStoreRouter,
/**
* The context is generated and saved to a cookie
* in the first load of the page.
*/
createContext,
});
```
You might need to refresh the context (e.g. when the user logs in or logs out). You can do this by calling the `reset` function from the `useEdgeStore` hook.
```tsx
const { edgestore, reset } = useEdgeStore();
async function runAfterAuthChange() {
await reset(); // this will re-run the createContext function
}
```
## Metadata & File Path
Every uploaded file can hold two types of data: `metadata` and `path`. You can use this data for access control or for filtering files. The `metadata` and `path` can be generated from the context (`ctx`) or from the `input` of the upload request.
```ts
import { initEdgeStore } from '@edgestore/server';
import {
type CreateContextOptions,
createEdgeStoreNextHandler,
} from '@edgestore/server/adapters/next/app';
import { z } from 'zod';
type Context = {
userId: string;
userRole: 'admin' | 'user';
};
async function createContext({ req }: CreateContextOptions): Promise {
const { id, role } = await getUserSession(req); // replace with your own session logic
return {
userId: id,
userRole: role,
};
}
const es = initEdgeStore.context().create();
const edgeStoreRouter = es.router({
publicFiles: es
.fileBucket()
// this input will be required for every upload request
.input(
z.object({
category: z.string(),
}),
)
// e.g. /publicFiles/{category}/{author}
.path(({ ctx, input }) => [
{ category: input.category },
{ author: ctx.userId },
])
// this metadata will be added to every file in this bucket
.metadata(({ ctx, input }) => ({
userRole: ctx.userRole,
})),
});
```
## Lifecycle Hooks
You can use the `beforeUpload` and `beforeDelete` hooks to allow or deny file uploads and deletions. The `beforeDelete` hook must be defined if you want to delete files directly from the client.
```ts
import { initEdgeStore } from '@edgestore/server';
import {
type CreateContextOptions,
createEdgeStoreNextHandler,
} from '@edgestore/server/adapters/next/app';
import { z } from 'zod';
type Context = {
userId: string;
userRole: 'admin' | 'user';
};
async function createContext({ req }: CreateContextOptions): Promise {
const { id, role } = await getUserSession(req); // replace with your own session logic
return {
userId: id,
userRole: role,
};
}
const es = initEdgeStore.context().create();
const edgeStoreRouter = es.router({
publicFiles: es
.fileBucket()
/**
* return `true` to allow upload
* By default every upload from your app is allowed.
*/
.beforeUpload(({ ctx, input, fileInfo }) => {
console.log('beforeUpload', ctx, input, fileInfo);
return true; // allow upload
})
/**
* return `true` to allow delete
* This function must be defined if you want to delete files directly from the client.
*/
.beforeDelete(({ ctx, fileInfo }) => {
console.log('beforeDelete', ctx, fileInfo);
return true; // allow delete
}),
});
```
## Access Control (Experimental)
You can use the `accessControl` function to add bucket level logic to allow or deny access to files. If you have ever used Prisma, you will probably notice that the structure of the `accessControl` function is similar to how you would write a Prisma query.
If you set the `accessControl` function, your bucket will automatically be configured as a **protected bucket**. You cannot change a protected bucket to a public bucket after it has been created. The opposite is also true, you cannot change a public bucket to a protected bucket.
To access files from a **protected bucket** the user will need a specific encrypted cookie generated in your server by the EdgeStore package. Which means that they will only be able to access the files from within your app. Sharing the url of a protected file will not work.
The access control check is performed on an edge function without running any database queries, so you won't need to worry about bad performance on your protected files.
```ts
const filesBucket = es
.fileBucket()
.path(({ ctx }) => [{ author: ctx.userId }])
.accessControl({
OR: [
{
// this will make sure that only the author of the file can access it
userId: { path: 'author' },
},
{
// or if the user is an admin
userRole: {
eq: 'admin',
}, // same as { userRole: 'admin' }
},
],
});
```
Other available operators are: `eq`, `not`, `gt`, `gte`, `lt`, `lte`, `in`, `contains`
The access control functionality uses third party cookies. Since third party cookies are not supported in localhost (without https), in development, all the protected files will be proxied through your app's api so that the cookies can be forwarded to the file request.
Also, the `` component from `next/image` does not forward the cookies in the request, so protected images won't be displayed. You will need ot use the `` tag instead.
## Limit parallel uploads
When creating the provider, you can set the maximum number of concurrent uploads.
EdgeStore's context provider will take care of queuing the uploads and will automatically upload the next file when the previous one is finished.
```ts
const { EdgeStoreProvider, useEdgeStore } =
createEdgeStoreProvider({
maxConcurrentUploads: 5, // default is 5
});
```
## Base Path
In case your app is not hosted at the root of your domain, you can specify the base path.
If you set this, make sure to set the full path to the EdgeStore API.
e.g. `/my-app/api/edgestore` or `https://example.com/my-app/api/edgestore`
```tsx
export default function App({ Component, pageProps }: AppProps) {
return (
);
}
```
## IMAGE bucket accepted mime types
| mime type |
| ------------- |
| image/jpeg |
| image/png |
| image/gif |
| image/webp |
| image/svg+xml |
| image/tiff |
| image/bmp |
| image/x-icon |
# EdgeStore Docs: Error Handling
URL: /docs/error-handling
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/(getting-started)/error-handling.mdx
***
title: Error Handling
description: Learn how to handle specific server errors in your EdgeStore application.
--------------------------------------------------------------------------------------
You might need to handle specific server errors in your application. Here is an example of how you can do that.
```tsx
import {
EdgeStoreApiClientError,
UploadAbortedError,
} from '@edgestore/react/errors';
// ...
```
## Error Codes
* `BAD_REQUEST`
* `FILE_TOO_LARGE`
* `MIME_TYPE_NOT_ALLOWED`
* `UNAUTHORIZED`
* `UPLOAD_NOT_ALLOWED`
* `DELETE_NOT_ALLOWED`
* `CREATE_CONTEXT_ERROR`
* `SERVER_ERROR`
# EdgeStore Docs: LLMs & Vibe Coding
URL: /docs/llms-vibe-coding
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/(getting-started)/llms-vibe-coding.mdx
***
title: LLMs & Vibe Coding
description: Learn how to leverage EdgeStore with Large Language Models (LLMs) and AI-powered IDEs, including VibeStack CLI support.
------------------------------------------------------------------------------------------------------------------------------------
EdgeStore has first-class support for Large Language Models (LLMs) and AI-powered IDEs.
## 1. llms.txt & llms-full.txt
EdgeStore provides an [llms.txt](/llms.txt) file that contains the links to each page in the documentation.
EdgeStore also provides an [llms-full.txt](/llms-full.txt) file that contains the whole documentation in a single markdown.
## 2. Copy Markdown
Every page in the documentation contains a "Copy Markdown" button that allows you to copy the markdown formatted content of the page, so you can directly use it in your LLM.
## 3. `.md` suffix
You can also just add a `.md` suffix to the end of the URL to get the markdown content of the page.
For example: [https://edgestore.dev/docs/quick-start.md](/docs/quick-start.md)
## 4. VibeStack CLI Support
EdgeStore supports [VibeStack](https://vibestack.app), which automatically adds framework-specific markdown instruction files to your project.
Just run this command in your project directory:
```bash
npx vibestack@latest add https://edgestore.dev/vibe.json
```
```bash
pnpm dlx vibestack@latest add https://edgestore.dev/vibe.json
```
```bash
yarn dlx vibestack@latest add https://edgestore.dev/vibe.json
```
```bash
bun x vibestack@latest add https://edgestore.dev/vibe.json
```
This will add multiple markdown instruction files specific to your framework, making it easier for AI coding assistants to understand how to implement EdgeStore in your codebase.
# EdgeStore Docs: Logging
URL: /docs/logging
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/(getting-started)/logging.mdx
***
title: Logging
description: Learn how to configure logging levels for EdgeStore server-side operations.
----------------------------------------------------------------------------------------
The EdgeStore package outputs some logs on the server-side. You can configure the log level by passing the `logLevel` option when creating the api handler. You can set it to `debug` to see in more details what is happening in the server.
```ts
const handler = createEdgeStoreNextHandler({
logLevel: 'debug', // optional. defaults to 'error' in production and 'info' in development
router: edgeStoreRouter,
});
```
## Log Levels
* `debug`
* `info`
* `warn`
* `error`
* `none`
# EdgeStore Docs: Quick Start
URL: /docs/quick-start
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/(getting-started)/quick-start.mdx
***
title: Quick Start
description: Implement file uploads in your app with EdgeStore.
---------------------------------------------------------------
## Next.js Setup
### Install
Let's start by installing the required packages.
```bash
npm install @edgestore/server @edgestore/react zod
```
```bash
pnpm add @edgestore/server @edgestore/react zod
```
```bash
yarn add @edgestore/server @edgestore/react zod
```
```bash
bun add @edgestore/server @edgestore/react zod
```
### Environment Variables
Then go to your [Dashboard](https://dashboard.edgestore.dev), create a new project and copy the keys to your environment variables.
```sh title=".env"
EDGE_STORE_ACCESS_KEY=your-access-key
EDGE_STORE_SECRET_KEY=your-secret-key
```
Make sure you add `.env` to your `.gitignore` file.
You don't want to commit your secret keys to your repository.
### Backend
Now we can create the backend code for our Next.js app.
EdgeStore is compatible with both types of Next.js apps (`pages router` and `app router`).
The example below is the simplest bucket you can create with EdgeStore. Just a simple file bucket with no validation that will be accessible by anyone with the link.
You can have multiple buckets in your app, each with its own configuration.
```ts tab="app" title="src/app/api/edgestore/[...edgestore]/route.ts"
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 edgeStoreRouter = es.router({
publicFiles: es.fileBucket(),
});
const handler = createEdgeStoreNextHandler({
router: edgeStoreRouter,
});
export { handler as GET, handler as POST };
/**
* This type is used to create the type-safe client for the frontend.
*/
export type EdgeStoreRouter = typeof edgeStoreRouter;
```
```ts tab="pages" title="src/pages/api/edgestore/[...edgestore].ts"
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 edgeStoreRouter = es.router({
publicFiles: es.fileBucket(),
});
export default createEdgeStoreNextHandler({
router: edgeStoreRouter,
});
/**
* This type is used to create the type-safe client for the frontend.
*/
export type EdgeStoreRouter = typeof edgeStoreRouter;
```
### Frontend
Now let's initiate our context provider.
```ts tab="app" title="src/lib/edgestore.ts"
'use client';
import { type EdgeStoreRouter } from '../app/api/edgestore/[...edgestore]/route';
import { createEdgeStoreProvider } from '@edgestore/react';
const { EdgeStoreProvider, useEdgeStore } =
createEdgeStoreProvider();
export { EdgeStoreProvider, useEdgeStore };
```
```ts tab="pages" title="src/lib/edgestore.ts"
'use client';
import { type EdgeStoreRouter } from '../pages/api/edgestore/[...edgestore]';
import { createEdgeStoreProvider } from '@edgestore/react';
const { EdgeStoreProvider, useEdgeStore } =
createEdgeStoreProvider();
export { EdgeStoreProvider, useEdgeStore };
```
And then wrap our app with the provider.
```tsx tab="app" title="src/app/layout.tsx"
// [!code ++]
import { EdgeStoreProvider } from '../lib/edgestore';
import './globals.css';
// ...
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
{/* [!code ++] */}
{children}
);
}
```
```tsx tab="pages" title="src/pages/_app.tsx"
import '../styles/globals.css';
import type { AppProps } from 'next/app';
// [!code ++]
import { EdgeStoreProvider } from '../lib/edgestore';
export default function App({ Component, pageProps }: AppProps) {
return (
{/* [!code ++] */}
{/* [!code ++] */}
);
}
```
### Upload file
You can use the `useEdgeStore` hook to access type-safe frontend client and use it to upload files.
```tsx
'use client';
import * as React from 'react';
import { useEdgeStore } from '../lib/edgestore';
export default function Page() {
const [file, setFile] = React.useState();
const { edgestore } = useEdgeStore();
return (
{
setFile(e.target.files?.[0]);
}}
/>
);
}
```
### 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.
```tsx
const res = await edgestore.publicFiles.upload({
file,
// [!code ++:3]
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](/docs/configuration#lifecycle-hooks) on the bucket.
```tsx
await edgestore.publicFiles.delete({
url: urlToDelete,
});
```
### 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.
```tsx
// prepare a state for the AbortController
const [abortController, setAbortController] = useState();
// ...
// 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](/docs/error-handling) page.
### 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.
```tsx
await edgestore.publicFiles.upload({
file: fileToUpload,
// [!code ++:3]
options: {
temporary: true,
},
});
```
To confirm a temporary file, you can use the `confirmUpload` method.
```tsx
await edgestore.publicFiles.confirmUpload({
url: urlToConfirm,
});
```
You can check if a file is temporary in the dashboard.
Temporary files are marked with a clock icon.
## Troubleshooting
If you have any problems using EdgeStore, please check the [Troubleshooting](./troubleshooting) page.
# EdgeStore Docs: Troubleshooting
URL: /docs/troubleshooting
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/(getting-started)/troubleshooting.mdx
***
title: Troubleshooting
description: Learn how to troubleshoot common issues with EdgeStore.
--------------------------------------------------------------------
If you followed the docs to get started with EdgeStore, but you are having issues, here are some things you can try to find out what the problem might be.
## Check if the API is correctly configured
You can try to access the `/health` endpoint of your edgestore API from the browser.
The default URL is: [http://localhost:3000/api/edgestore/health](http://localhost:3000/api/edgestore/health)
If you can see `OK` on the page, then the API is configured in the correct path.
## Set the log level to `debug`
You can set the [log level](./logging) to `debug` to see in more details what is happening in the server. (These logs are for the server-side and will not be visible in the browser console)
## Check the browser console and network tab
Open the developer tools in your browser and check the console and network tab to see if there are any helpful error messages.
## Try to run one of the example apps
You can try to run one of the example apps on your local machine to see if it works.
* clone the repo
* `git clone https://github.com/edgestorejs/edgestore.git`
* cd into the example app
* `cd examples/next-basic`
* install dependencies
* `npm install`
* add your environment variables
* `examples/next-basic/.env.local`
* run the app
* `npm run dev`
* access the app
* [http://localhost:3000](http://localhost:3000)
There are [other example apps](https://github.com/edgestorejs/edgestore/tree/main/examples) that you can try. Try to find the one that is closer to your use case.
## Tried everything and still not working?
If you tried everything and still can't figure out what is wrong, you can reach for support in the [Discord server](https://discord.gg/HvrnhRTfgQ) or [open an issue](https://github.com/edgestorejs/edgestore/issues) in the GitHub repo.
# EdgeStore Docs: Utils
URL: /docs/utils
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/(getting-started)/utils.mdx
***
title: Utils
description: Useful utilities methods for EdgeStore.
----------------------------------------------------
## Download links
Sometimes the browser shows the file directly in the browser instead of downloading it. To force the browser to download the file, you can use the `getDownloadUrl` function.
```ts
import { getDownloadUrl } from '@edgestore/react/utils';
getDownloadUrl(
url, // the url of the file
'overwrite-file-name.jpg', // optional, the name of the file to download
);
```
## Format file size
You might want to display the file size in a human-readable format. You can use the `formatFileSize` function to do that.
```ts
import { formatFileSize } from '@edgestore/react/utils';
formatFileSize(10485760); // => 10MB
```
# EdgeStore Docs: Astro
URL: /docs/adapters/astro
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/adapters/astro.mdx
***
title: Astro
description: Learn how to integrate EdgeStore with your Astro applications using the dedicated Astro adapter.
-------------------------------------------------------------------------------------------------------------
EdgeStore supports Astro applications through our dedicated Astro adapter, allowing you to use EdgeStore's file upload and management capabilities with your Astro project.
## Setup
### Install
Let's start by installing the required packages.
```bash
npm install @edgestore/server @edgestore/react zod
```
```bash
pnpm add @edgestore/server @edgestore/react zod
```
```bash
yarn add @edgestore/server @edgestore/react zod
```
```bash
bun add @edgestore/server @edgestore/react zod
```
### Environment Variables
Then go to your [Dashboard](https://dashboard.edgestore.dev), create a new project and copy the keys to your environment variables.
```sh title=".env"
EDGE_STORE_ACCESS_KEY=your-access-key
EDGE_STORE_SECRET_KEY=your-secret-key
```
Make sure you add `.env` to your `.gitignore` file.
You don't want to commit your secret keys to your repository.
### Backend
Now we can create the API endpoint in our Astro app. Create a file at `src/pages/api/edgestore/[...edgestore].ts`:
```ts
import { initEdgeStore } from '@edgestore/server';
import { createEdgeStoreAstroHandler } from '@edgestore/server/adapters/astro';
export const prerender = false;
const es = initEdgeStore.create();
const edgeStoreRouter = es.router({
publicFiles: es.fileBucket(),
});
const handler = createEdgeStoreAstroHandler({
router: edgeStoreRouter,
});
export { handler as GET, handler as POST };
export type EdgeStoreRouter = typeof edgeStoreRouter;
```
The example above is the simplest bucket you can create with EdgeStore. Just a simple file bucket with no validation that will be accessible by anyone with the link.
You can have multiple buckets in your app, each with its own configuration.
### Frontend
Now let's create our context provider:
```tsx title="src/lib/edgestore.ts"
import { createEdgeStoreProvider } from '@edgestore/react';
import type { EdgeStoreRouter } from '../pages/api/edgestore/[...edgestore].ts';
const { EdgeStoreProvider, useEdgeStore } =
createEdgeStoreProvider();
export { EdgeStoreProvider, useEdgeStore };
```
And then let's create out upload component:
```tsx title="src/components/FileUploader.tsx"
import { useState } from 'react';
import { EdgeStoreProvider, useEdgeStore } from '../lib/edgestore';
export function FileUploader() {
return (
);
}
function FileUploaderInner() {
const [file, setFile] = useState(null);
const { edgestore } = useEdgeStore();
return (
{
setFile(e.target.files?.[0] ?? null);
}}
/>
);
}
```
Finally, let's use the `FileUploader` component in our app.
```astro title="src/pages/index.astro"
---
import { FileUploader } from '../components/FileUploader';
---
{/* ... */}
EdgeStore with Astro
```
## Usage
To upload or use the other functionalities of EdgeStore, you can look at the main [Quick Start](/docs/quick-start) guide. The usage should be the same.
# EdgeStore Docs: Express
URL: /docs/adapters/express
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/adapters/express.mdx
***
title: Express
description: Learn how to integrate EdgeStore with your Express.js backend and React frontend.
----------------------------------------------------------------------------------------------
Some apps are built with React on the frontend (e.g. using `create-react-app` or `vite`) and have an `express.js` backend.
You can also use EdgeStore in these cases, even without using Next.js.
## Setup
### Install
Let's start by installing the required packages.
```bash
npm install @edgestore/server @edgestore/react zod
```
```bash
pnpm add @edgestore/server @edgestore/react zod
```
```bash
yarn add @edgestore/server @edgestore/react zod
```
```bash
bun add @edgestore/server @edgestore/react zod
```
### Environment Variables
Then go to your [Dashboard](https://dashboard.edgestore.dev), create a new project and copy the keys to your environment variables.
```sh title=".env"
EDGE_STORE_ACCESS_KEY=your-access-key
EDGE_STORE_SECRET_KEY=your-secret-key
```
Make sure you add `.env` to your `.gitignore` file.
You don't want to commit your secret keys to your repository.
### Backend
Now we can create the backend code in our express app.
The example below is the simplest bucket you can create with EdgeStore. Just a simple file bucket with no validation that will be accessible by anyone with the link.
You can have multiple buckets in your app, each with its own configuration.
```ts
import { initEdgeStore } from '@edgestore/server';
import { createEdgeStoreExpressHandler } from '@edgestore/server/adapters/express';
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import cors from 'cors';
import express from 'express';
// --- EXPRESS CONFIG ---
const PORT = process.env.PORT ?? 3001;
const app = express();
/**
* Your express app is probably running in a different port than your frontend app.
* To avoid CORS issues, we should use the cors middleware.
*/
app.use(
cors({
// Change this to your frontend origin for better security
origin: true,
credentials: true,
}),
);
/**
* EdgeStore uses cookies to store the context token.
* We need to use the cookie parser middleware to parse the cookies.
*/
app.use(cookieParser());
/**
* We need to have access to the json request body.
* We can use the body parser middleware to parse the request.
*/
app.use(bodyParser.json());
// --- EDGESTORE ROUTER CONFIG ---
const es = initEdgeStore.create();
const edgeStoreRouter = es.router({
publicFiles: es.fileBucket(),
});
export type EdgeStoreRouter = typeof edgeStoreRouter;
const handler = createEdgeStoreExpressHandler({
router: edgeStoreRouter,
});
// --- EXPRESS ROUTES ---
app.get('/', (req, res) => {
console.log(req), res.send('Hello from server!');
});
// set the get and post routes for the edgestore router
app.get('/edgestore/*', handler);
app.post('/edgestore/*', handler);
app.listen(PORT, () => {
console.log(`⚡Server is running here 👉 http://localhost:${PORT}`);
});
```
### Frontend
Now let's initiate our context provider in the frontend app.
```tsx title="src/lib/edgestore.ts"
// You can import it from the other project if it's just the type
import { type EdgeStoreRouter } from '../../../path/to/express-backend/src';
import { createEdgeStoreProvider } from '@edgestore/react';
const { EdgeStoreProvider, useEdgeStore } =
createEdgeStoreProvider();
export { EdgeStoreProvider, useEdgeStore };
```
And then wrap our app with the provider.
```tsx title="src/App.tsx"
// [!code ++]
import { EdgeStoreProvider } from '../lib/edgestore';
function App() {
return (
{/* [!code ++] */}
{/* Rest of your app */}
{/* [!code ++] */}
);
}
```
## Usage
To upload or use the other functionalities of EdgeStore, you can look the main [Quick Start](/docs/quick-start) guide. The usage should be the same.
## Limitations
For EdgeStore to work properly in your deployed production app, Your frontend and backend should be in the same domain.
If you are deploying to Vercel, you can take a look at the [Rewrites settings](https://vercel.com/docs/edge-network/rewrites). In case you are using Apache or Nginx, you can setup a reverse proxy to make sure your frontend and backend are in the same domain.
# EdgeStore Docs: Fastify
URL: /docs/adapters/fastify
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/adapters/fastify.mdx
***
title: Fastify
description: Learn how to integrate EdgeStore with your Fastify backend and React frontend.
-------------------------------------------------------------------------------------------
Some applications are built with React on the frontend (e.g. using `create-react-app` or `vite`) and have a `fastify` backend.
You can use EdgeStore in these cases, even without using Next.js.
## Setup
### Install
Let's start by installing the required packages.
```bash
npm install @edgestore/server @edgestore/react zod
```
```bash
pnpm add @edgestore/server @edgestore/react zod
```
```bash
yarn add @edgestore/server @edgestore/react zod
```
```bash
bun add @edgestore/server @edgestore/react zod
```
### Environment Variables
Then go to your [Dashboard](https://dashboard.edgestore.dev), create a new project and copy the keys to your environment variables.
```sh title=".env"
EDGE_STORE_ACCESS_KEY=your-access-key
EDGE_STORE_SECRET_KEY=your-secret-key
```
Make sure you add `.env` to your `.gitignore` file.
You don't want to commit your secret keys to your repository.
### Backend
Now we can create the backend code in our Fastify app.
The example below is the simplest bucket you can create with EdgeStore. Just a simple file bucket with no validation that will be accessible by anyone with the link.
You can have multiple buckets in your app, each with its own configuration.
```ts
import { initEdgeStore } from '@edgestore/server';
import { createEdgeStoreFastifyHandler } from '@edgestore/server/adapters/fastify';
import cors from '@fastify/cors';
import cookie from '@fastify/cookie';
import fastify from 'fastify';
// --- FASTIFY CONFIG ---
const PORT = process.env.PORT ?? 3001;
const app = fastify();
/**
* Your fastify app is probably running in a different port than your frontend app.
* To avoid CORS issues, we should use the cors plugin.
*/
await app.register(cors, {
// Change this to your frontend origin for better security
origin: true,
credentials: true,
});
/**
* EdgeStore uses cookies to store the context token.
* We need to use the cookie plugin to parse the cookies.
*/
await app.register(cookie);
// --- EDGESTORE ROUTER CONFIG ---
const es = initEdgeStore.create();
const edgeStoreRouter = es.router({
publicFiles: es.fileBucket(),
});
export type EdgeStoreRouter = typeof edgeStoreRouter;
const handler = createEdgeStoreFastifyHandler({
router: edgeStoreRouter,
});
// --- FASTIFY ROUTES ---
app.get('/', (request, reply) => {
console.log('Request received');
reply.send('Hello from server!');
});
// set the get and post routes for the edgestore router
app.get('/edgestore/*', handler);
app.post('/edgestore/*', handler);
app.listen({ port: PORT }, (err) => {
if (err) {
console.error(err);
process.exit(1);
}
console.log(`⚡Server is running here 👉 http://localhost:${PORT}`);
});
```
### Frontend
Now let's initiate our context provider in the frontend app.
```tsx title="src/lib/edgestore.ts"
// You can import it from the other project if it's just the type
import { type EdgeStoreRouter } from '../../../path/to/fastify-backend/src';
import { createEdgeStoreProvider } from '@edgestore/react';
const { EdgeStoreProvider, useEdgeStore } =
createEdgeStoreProvider();
export { EdgeStoreProvider, useEdgeStore };
```
And then wrap our app with the provider.
```tsx title="src/App.tsx"
// [!code ++]
import { EdgeStoreProvider } from '../lib/edgestore';
function App() {
return (
{/* [!code ++] */}
{/* Rest of your app */}
{/* [!code ++] */}
);
}
```
## Usage
To upload or use the other functionalities of EdgeStore, you can look at the main [Quick Start](/docs/quick-start) guide. The usage should be the same.
## Limitations
For EdgeStore to work properly in your deployed production app, Your frontend and backend should be in the same domain.
If you are deploying to Vercel, you can take a look at the [Rewrites settings](https://vercel.com/docs/edge-network/rewrites). In case you are using Apache or Nginx, you can set up a reverse proxy to make sure your frontend and backend are in the same domain.
# EdgeStore Docs: Hono
URL: /docs/adapters/hono
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/adapters/hono.mdx
***
title: Hono
description: Learn how to integrate EdgeStore with your Hono backend and React frontend.
----------------------------------------------------------------------------------------
Some applications are built with React on the frontend (e.g. using `create-react-app` or `vite`) and have a `hono` backend.
You can use EdgeStore in these cases, even without using Next.js.
## Setup
### Install
Let's start by installing the required packages.
```bash
npm install @edgestore/server @edgestore/react zod
```
```bash
pnpm add @edgestore/server @edgestore/react zod
```
```bash
yarn add @edgestore/server @edgestore/react zod
```
```bash
bun add @edgestore/server @edgestore/react zod
```
### Environment Variables
Then go to your [Dashboard](https://dashboard.edgestore.dev), create a new project and copy the keys to your environment variables.
```sh title=".env"
EDGE_STORE_ACCESS_KEY=your-access-key
EDGE_STORE_SECRET_KEY=your-secret-key
```
Make sure you add `.env` to your `.gitignore` file.
You don't want to commit your secret keys to your repository.
### Backend
Now we can create the backend code in our Hono app.
The example below is the simplest bucket you can create with EdgeStore. Just a simple file bucket with no validation that will be accessible by anyone with the link.
You can have multiple buckets in your app, each with its own configuration.
```ts
import { initEdgeStore } from '@edgestore/server';
import { createEdgeStoreHonoHandler } from '@edgestore/server/adapters/hono';
import { serve } from '@hono/node-server';
import { Hono } from 'hono';
import { cors } from 'hono/cors';
// --- HONO CONFIG ---
const PORT = process.env.PORT ?? 3001;
const app = new Hono();
/**
* Your Hono app is probably running in a different port than your frontend app.
* To avoid CORS issues, we should use the cors middleware.
*/
app.use(
'*',
cors({
// Change this to your frontend origin for better security
origin: (origin) => origin,
credentials: true,
}),
);
// --- EDGESTORE ROUTER CONFIG ---
const es = initEdgeStore.create();
const edgeStoreRouter = es.router({
publicFiles: es.fileBucket(),
});
export type EdgeStoreRouter = typeof edgeStoreRouter;
const handler = createEdgeStoreHonoHandler({
router: edgeStoreRouter,
});
// --- HONO ROUTES ---
app.get('/', (c) => {
return c.text('Hello from Hono server!');
});
// Route for EdgeStore
app.all('/edgestore/*', handler);
// Start the server
serve(
{
fetch: app.fetch,
port: Number(PORT),
},
(info) => {
console.log(`⚡Server is running here 👉 http://localhost:${info.port}`);
},
);
```
### Frontend
Now let's initiate our context provider in the frontend app.
```tsx title="src/lib/edgestore.ts"
// You can import it from the other project if it's just the type
import { type EdgeStoreRouter } from '../../../path/to/hono-backend/src';
import { createEdgeStoreProvider } from '@edgestore/react';
const { EdgeStoreProvider, useEdgeStore } =
createEdgeStoreProvider();
export { EdgeStoreProvider, useEdgeStore };
```
And then wrap our app with the provider.
```tsx title="src/App.tsx"
// [!code ++]
import { EdgeStoreProvider } from '../lib/edgestore';
function App() {
return (
{/* [!code ++] */}
{/* Rest of your app */}
{/* [!code ++] */}
);
}
```
## Usage
To upload or use the other functionalities of EdgeStore, you can look at the main [Quick Start](/docs/quick-start) guide. The usage should be the same.
## Limitations
For EdgeStore to work properly in your deployed production app, Your frontend and backend should be in the same domain.
If you are deploying to Vercel, you can take a look at the [Rewrites settings](https://vercel.com/docs/edge-network/rewrites). In case you are using Apache or Nginx, you can set up a reverse proxy to make sure your frontend and backend are in the same domain.
# EdgeStore Docs: Next Adapter
URL: /docs/adapters/next
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/adapters/next.mdx
***
title: Next Adapter
description: An overview of the EdgeStore Next.js adapters for App Router and Pages Router.
-------------------------------------------------------------------------------------------
There are two adapters available for Next.js. The `next-app` (for the app-router) and the `next-pages` (for the pages-router).
The steps to set up the adapter are in the main [Quick Start](/docs/quick-start) guide.
# EdgeStore Docs: Remix
URL: /docs/adapters/remix
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/adapters/remix.mdx
***
title: Remix
description: Learn how to integrate EdgeStore with your Remix applications, enabling robust file management capabilities.
-------------------------------------------------------------------------------------------------------------------------
React Router integrates seamlessly with EdgeStore, enabling you to build type-safe, full-stack React applications with robust file management capabilities.
## Setup
### Install
Let's start by installing the required packages.
```bash
npm install @edgestore/server @edgestore/react zod
```
```bash
pnpm add @edgestore/server @edgestore/react zod
```
```bash
yarn add @edgestore/server @edgestore/react zod
```
```bash
bun add @edgestore/server @edgestore/react zod
```
### Environment Variables
Then go to your [Dashboard](https://dashboard.edgestore.dev), create a new project and copy the keys to your environment variables.
```sh title=".env"
EDGE_STORE_ACCESS_KEY=your-access-key
EDGE_STORE_SECRET_KEY=your-secret-key
```
Make sure you add `.env` to your `.gitignore` file.
You don't want to commit your secret keys to your repository.
### Backend
In your Remix application, create an API route for EdgeStore with the following content:
```ts title="app/routes/api/edgestore.ts"
import { initEdgeStore } from '@edgestore/server';
import { createEdgeStoreRemixHandler } from '@edgestore/server/adapters/remix';
const es = initEdgeStore.create();
const edgeStoreRouter = es.router({
publicFiles: es.fileBucket(),
});
export type EdgeStoreRouter = typeof edgeStoreRouter;
const handler = createEdgeStoreRemixHandler({
router: edgeStoreRouter,
});
export { handler as loader, handler as action };
```
Add the EdgeStore API route to your routes file.
```ts title="app/routes.ts"
import { index, route, type RouteConfig } from '@react-router/dev/routes';
export default [
index('routes/home.tsx'),
// [!code ++]
route('api/edgestore/*', 'routes/api/edgestore.ts'),
] satisfies RouteConfig;
```
### Frontend
Now let's initiate our context provider in the frontend app.
```tsx title="app/lib/edgestore.ts"
import { createEdgeStoreProvider } from '@edgestore/react';
import { type EdgeStoreRouter } from '~/routes/api/edgestore';
const { EdgeStoreProvider, useEdgeStore } =
createEdgeStoreProvider();
export { EdgeStoreProvider, useEdgeStore };
```
And then wrap your app with the provider in your root component.
```tsx title="app/root.tsx"
// [!code ++]
import { EdgeStoreProvider } from '~/lib/edgestore';
// ...
export default function App() {
return (
{/* [!code ++] */}
{/* [!code ++] */}
);
}
```
## Usage
To upload or use the other functionalities of EdgeStore, you can look the main [Quick Start](/docs/quick-start) guide. The usage should be the same.
# EdgeStore Docs: TanStack Start
URL: /docs/adapters/tanstack-start
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/adapters/tanstack-start.mdx
***
title: TanStack Start
description: Learn how to integrate EdgeStore with your TanStack Start applications for type-safe, full-stack React file management.
------------------------------------------------------------------------------------------------------------------------------------
TanStack Start integrates seamlessly with EdgeStore, enabling you to build type-safe, full-stack React applications with robust file management capabilities.
## Setup
### Install
Let's start by installing the required packages.
```bash
npm install @edgestore/server @edgestore/react zod
```
```bash
pnpm add @edgestore/server @edgestore/react zod
```
```bash
yarn add @edgestore/server @edgestore/react zod
```
```bash
bun add @edgestore/server @edgestore/react zod
```
### Environment Variables
Then go to your [Dashboard](https://dashboard.edgestore.dev), create a new project and copy the keys to your environment variables.
```sh title=".env"
EDGE_STORE_ACCESS_KEY=your-access-key
EDGE_STORE_SECRET_KEY=your-secret-key
```
Make sure you add `.env` to your `.gitignore` file.
You don't want to commit your secret keys to your repository.
### Backend
In your TanStack Start application, create an API route for EdgeStore with the following content:
```ts title="app/routes/api/edgestore.$.ts"
import { initEdgeStore } from '@edgestore/server';
import { createEdgeStoreStartHandler } from '@edgestore/server/adapters/start';
import { createAPIFileRoute } from '@tanstack/start/api';
const es = initEdgeStore.create();
const edgeStoreRouter = es.router({
publicFiles: es.fileBucket(),
});
export type EdgeStoreRouter = typeof edgeStoreRouter;
const handler = createEdgeStoreStartHandler({
router: edgeStoreRouter,
});
export const APIRoute = createAPIFileRoute('/api/edgestore/$')({
GET: handler,
POST: handler,
});
```
### Frontend
Now let's initiate our context provider in the frontend app.
```tsx title="app/utils/edgestore.ts"
import { createEdgeStoreProvider } from '@edgestore/react';
import { type EdgeStoreRouter } from '../routes/api/edgestore.$';
const { EdgeStoreProvider, useEdgeStore } =
createEdgeStoreProvider();
export { EdgeStoreProvider, useEdgeStore };
```
And then wrap our app with the provider.
```tsx title="app/routes/__root.tsx"
// [!code ++]
import { EdgeStoreProvider } from '~/utils/edgestore';
// ...
function RootComponent() {
return (
{/* [!code ++] */}
{/* [!code ++] */}
);
}
```
## Usage
To upload or use the other functionalities of EdgeStore, you can look the main [Quick Start](/docs/quick-start) guide. The usage should be the same.
# EdgeStore Docs: Dropzone
URL: /docs/components/dropzone
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/components/dropzone.mdx
***
title: Dropzone
description: The Dropzone component provides a user-friendly drag-and-drop interface for file uploads that integrates seamlessly with the UploaderProvider.
-----------------------------------------------------------------------------------------------------------------------------------------------------------
import { LimitedCode } from '@/components/ui/limited-code';
import {
OpenTabs,
OpenTabsContent,
OpenTabsList,
OpenTabsTrigger,
} from '@/components/ui/open-tabs';
import { Callout } from 'fumadocs-ui/components/callout';
If you are installing the other dropzone components via the CLI, this
component will be installed automatically. You can skip the following steps.
## Installation
CLIManual
```bash
npx shadcn@latest add https://edgestore.dev/r/dropzone.json
```
```bash
pnpm dlx shadcn@latest add https://edgestore.dev/r/dropzone.json
```
```bash
yarn dlx shadcn@latest add https://edgestore.dev/r/dropzone.json
```
```bash
bun x shadcn@latest add https://edgestore.dev/r/dropzone.json
```
### Copy this component
````tsx
'use client';
import { cn } from '@/lib/utils';
import { AlertCircleIcon, UploadCloudIcon } from 'lucide-react';
import * as React from 'react';
import { useDropzone, type DropzoneOptions } from 'react-dropzone';
import { formatFileSize, useUploader } from './uploader-provider';
const DROPZONE_VARIANTS = {
base: 'relative rounded-md p-4 w-full flex justify-center items-center flex-col cursor-pointer border-2 border-dashed border-gray-400 dark:border-gray-600 transition-colors duration-200 ease-in-out',
active: 'border-blue-500 dark:border-blue-400',
disabled:
'bg-gray-100 dark:bg-gray-800 border-gray-400/50 dark:border-gray-600/50 cursor-default pointer-events-none opacity-50',
accept:
'border-blue-500 dark:border-blue-400 bg-blue-100 dark:bg-blue-900/30',
reject: 'border-red-500 dark:border-red-400 bg-red-100 dark:bg-red-900/30',
};
/**
* Props for the Dropzone component.
*
* @interface DropzoneProps
* @extends {React.HTMLAttributes}
*/
export interface DropzoneProps extends React.HTMLAttributes {
/**
* Options passed to the underlying react-dropzone component.
* Cannot include 'disabled' or 'onDrop' as they are handled internally.
*/
dropzoneOptions?: Omit;
/**
* Whether the dropzone is disabled.
*/
disabled?: boolean;
/**
* Message shown when files are being dragged over the dropzone.
*/
dropMessageActive?: string;
/**
* Default message shown when the dropzone is idle.
*/
dropMessageDefault?: string;
}
/**
* A dropzone component for file uploads that integrates with the UploaderProvider.
*
* @component
* @example
* ```tsx
*
* ```
*/
const Dropzone = React.forwardRef(
(
{
dropzoneOptions,
className,
disabled,
dropMessageActive = 'Drop files here...',
dropMessageDefault = 'drag & drop files here, or click to select',
...props
},
ref,
) => {
const { fileStates, addFiles } = useUploader();
const [error, setError] = React.useState();
const maxFiles = dropzoneOptions?.maxFiles;
const maxSize = dropzoneOptions?.maxSize;
const isMaxFilesReached = !!maxFiles && fileStates.length >= maxFiles;
const isDisabled = disabled ?? isMaxFilesReached;
const {
getRootProps,
getInputProps,
isDragActive,
isFocused,
isDragAccept,
isDragReject,
} = useDropzone({
disabled: isDisabled,
onDrop: (acceptedFiles, rejectedFiles) => {
setError(undefined);
// Handle rejections first
if (rejectedFiles.length > 0) {
if (rejectedFiles[0]?.errors[0]) {
const error = rejectedFiles[0].errors[0];
const code = error.code;
const messages: Record = {
'file-too-large': `The file is too large. Max size is ${formatFileSize(
maxSize ?? 0,
)}.`,
'file-invalid-type': 'Invalid file type.',
'too-many-files': `You can only add ${
maxFiles ?? 'multiple'
} file(s).`,
default: 'The file is not supported.',
};
setError(messages[code] ?? messages.default);
}
return; // Exit early if there are any rejections
}
// Handle accepted files
if (acceptedFiles.length === 0) return;
// Check if adding these files would exceed maxFiles limit
if (maxFiles) {
const remainingSlots = maxFiles - fileStates.length;
// If adding all files would exceed the limit, reject them all
if (acceptedFiles.length > remainingSlots) {
setError(`You can only add ${maxFiles} file(s).`);
return;
}
}
addFiles(acceptedFiles);
},
...dropzoneOptions,
});
const dropZoneClassName = React.useMemo(
() =>
cn(
DROPZONE_VARIANTS.base,
isFocused && DROPZONE_VARIANTS.active,
isDisabled && DROPZONE_VARIANTS.disabled,
isDragReject && DROPZONE_VARIANTS.reject,
isDragAccept && DROPZONE_VARIANTS.accept,
className,
),
[isFocused, isDisabled, isDragAccept, isDragReject, className],
);
return (
);
});
SingleImageDropzone.displayName = 'SingleImageDropzone';
export { SingleImageDropzone };
````
## Usage
```tsx
'use client';
import { SingleImageDropzone } from '@/components/upload/single-image';
import {
UploaderProvider,
type UploadFn,
} from '@/components/upload/uploader-provider';
import { useEdgeStore } from '@/lib/edgestore';
import * as React from 'react';
export function SingleImageDropzoneUsage() {
const { edgestore } = useEdgeStore();
const uploadFn: UploadFn = React.useCallback(
async ({ file, onProgressChange, signal }) => {
const res = await edgestore.publicImages.upload({
file,
signal,
onProgressChange,
});
// you can run some server action or api here
// to add the necessary data to your database
console.log(res);
return res;
},
[edgestore],
);
return (
);
}
```
# EdgeStore Docs: Manual Installation Setup
URL: /docs/components/manual-install
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/components/manual-install.mdx
***
title: Manual Installation Setup
description: If you're not using shadcn/ui, you can install the components manually. Follow the steps below to prepare for the components manual installation.
--------------------------------------------------------------------------------------------------------------------------------------------------------------
import {
OpenTabs,
OpenTabsContent,
OpenTabsList,
OpenTabsTrigger,
} from '@/components/ui/open-tabs';
import { Callout } from 'fumadocs-ui/components/callout';
If you're not using shadcn/ui, you can install the components manually. Follow the steps below to prepare for the components manual installation.
Components installed manually do not use shadcn/ui specific colors.
## Install dependencies
```bash
npm install tailwind-merge clsx react-dropzone lucide-react
```
```bash
pnpm add tailwind-merge clsx react-dropzone lucide-react
```
```bash
yarn add tailwind-merge clsx react-dropzone lucide-react
```
```bash
bun add tailwind-merge clsx react-dropzone lucide-react
```
## Add the cn helper
```ts title="src/lib/utils.ts"
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
```
## Start adding the components
Now you can follow the manual installation guide for the components you need.
# EdgeStore Docs: Multi-file
URL: /docs/components/multi-file
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/components/multi-file.mdx
***
title: Multi-file
description: A component for uploading multiple files with preview and progress indicators.
-------------------------------------------------------------------------------------------
import { DemoBlock } from '@/components/demo-block';
import { LimitedCode } from '@/components/ui/limited-code';
import {
OpenTabs,
OpenTabsContent,
OpenTabsList,
OpenTabsTrigger,
} from '@/components/ui/open-tabs';
import MultiFileUploaderBlock from '@/components/upload/blocks/multi-file-block'; // Assumed path
import { Step, Steps } from 'fumadocs-ui/components/steps';
## Installation
CLIManual
Use the shadcn CLI to add the component to your project.
```bash
npx shadcn@latest add https://edgestore.dev/r/file-uploader.json
```
```bash
pnpm dlx shadcn@latest add https://edgestore.dev/r/file-uploader.json
```
```bash
yarn dlx shadcn@latest add https://edgestore.dev/r/file-uploader.json
```
```bash
bun x shadcn@latest add https://edgestore.dev/r/file-uploader.json
```
### Setup for manual installation
First you will need to follow the [manual install setup](./manual-install) guide.
### Install required components
* [uploader-provider](./uploader-provider)
* [progress-bar](./progress-bar)
* [dropzone](./dropzone)
### Copy this component
````tsx
'use client';
import { cn } from '@/lib/utils';
import {
AlertCircleIcon,
CheckCircleIcon,
FileIcon,
Trash2Icon,
XIcon,
} from 'lucide-react';
import * as React from 'react';
import { type DropzoneOptions } from 'react-dropzone';
import { Dropzone } from './dropzone';
import { ProgressBar } from './progress-bar';
import { formatFileSize, useUploader } from './uploader-provider';
/**
* Displays a list of files with their upload status, progress, and controls.
*
* @component
* @example
* ```tsx
*
* ```
*/
const FileList = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes
>(({ className, ...props }, ref) => {
const { fileStates, removeFile, cancelUpload } = useUploader();
if (!fileStates.length) return null;
return (
);
},
);
ImageList.displayName = 'ImageList';
/**
* Props for the ImageDropzone component.
*
* @interface ImageDropzoneProps
* @extends {React.HTMLAttributes}
*/
export interface ImageDropzoneProps
extends React.HTMLAttributes {
/**
* Whether the dropzone is disabled.
*/
disabled?: boolean;
/**
* Options passed to the underlying Dropzone component.
* Cannot include 'disabled' or 'onDrop' as they are handled internally.
*/
dropzoneOptions?: Omit;
/**
* Ref for the input element inside the Dropzone.
*/
inputRef?: React.Ref;
}
/**
* A dropzone component specifically for image uploads.
*
* @component
* @example
* ```tsx
*
* ```
*/
const ImageDropzone = React.forwardRef(
({ dropzoneOptions, className, disabled, inputRef, ...props }, ref) => {
return (
);
},
);
ImageDropzone.displayName = 'ImageDropzone';
/**
* Props for the ImageUploader component.
*
* @interface ImageUploaderProps
* @extends {React.HTMLAttributes}
*/
export interface ImageUploaderProps
extends React.HTMLAttributes {
/**
* Maximum number of images allowed.
*/
maxFiles?: number;
/**
* Maximum file size in bytes.
*/
maxSize?: number;
/**
* Whether the uploader is disabled.
*/
disabled?: boolean;
/**
* Additional className for the dropzone component.
*/
dropzoneClassName?: string;
/**
* Additional className for the image list component.
*/
imageListClassName?: string;
/**
* Ref for the input element inside the Dropzone.
*/
inputRef?: React.Ref;
}
/**
* A complete image uploader component with dropzone and image grid preview.
*
* @component
* @example
* ```tsx
*
* ```
*/
const ImageUploader = React.forwardRef(
(
{
maxFiles,
maxSize,
disabled,
className,
dropzoneClassName,
imageListClassName,
inputRef,
...props
},
ref,
) => {
return (
);
},
);
ImageUploader.displayName = 'ImageUploader';
export { ImageList, ImageDropzone, ImageUploader };
````
## Usage
```tsx
'use client';
import { ImageUploader } from '@/components/upload/multi-image';
import {
UploaderProvider,
type UploadFn,
} from '@/components/upload/uploader-provider';
import { useEdgeStore } from '@/lib/edgestore';
import * as React from 'react';
export function MultiImageDropzoneUsage() {
const { edgestore } = useEdgeStore();
const uploadFn: UploadFn = React.useCallback(
async ({ file, onProgressChange, signal }) => {
const res = await edgestore.publicImages.upload({
file,
signal,
onProgressChange,
});
// you can run some server action or api here
// to add the necessary data to your database
console.log(res);
return res;
},
[edgestore],
);
return (
);
}
```
# EdgeStore Docs: Progress Bar
URL: /docs/components/progress-bar
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/components/progress-bar.mdx
***
title: Progress Bar
description: A horizontal progress bar component for visualizing completion percentage.
---------------------------------------------------------------------------------------
import { DemoBlock } from '@/components/demo-block';
import { LimitedCode } from '@/components/ui/limited-code';
import {
OpenTabs,
OpenTabsContent,
OpenTabsList,
OpenTabsTrigger,
} from '@/components/ui/open-tabs';
import { ProgressBarUsage } from '@/components/upload/blocks/progress-bar-block';
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
The `ProgressBar` component displays a horizontal progress indicator, commonly used to show file upload progress. It visually represents a percentage value (0-100) with a filled bar.
If you are installing the other dropzone components via the CLI, this
component will be installed automatically. You can skip the following steps.
## Installation
CLIManual
```bash
npx shadcn@latest add https://edgestore.dev/r/progress-bar.json
```
```bash
pnpm dlx shadcn@latest add https://edgestore.dev/r/progress-bar.json
```
```bash
yarn dlx shadcn@latest add https://edgestore.dev/r/progress-bar.json
```
```bash
bun x shadcn@latest add https://edgestore.dev/r/progress-bar.json
```
### Setup for manual installation
First you will need to add the cn helper following the [manual install setup](./manual-install) guide.
### Copy this component
````tsx
import * as React from 'react';
import { cn } from '@/lib/utils';
/**
* Props for the ProgressBar component.
*
* @interface ProgressBarProps
* @extends {React.HTMLAttributes}
*/
export interface ProgressBarProps extends React.HTMLAttributes {
/**
* The progress value as a percentage (0-100).
*/
progress: number;
}
/**
* A horizontal progress bar component that visualizes completion percentage.
*
* @component
* @example
* ```tsx
*
* ```
*/
const ProgressBar = React.forwardRef(
({ progress, className, ...props }, ref) => {
return (
);
},
);
ProgressBar.displayName = 'ProgressBar';
export { ProgressBar };
````
## Usage
Import the component and provide the `progress` prop.
```tsx
import { ProgressBar } from '@/components/ui/progress-bar';
function ProgressDemo() {
const progress = 75;
return (
{/* Default usage */}
{/* With custom styling */}
{/* Different progress values */}
);
}
```
# EdgeStore Docs: Progress Circle
URL: /docs/components/progress-circle
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/components/progress-circle.mdx
***
title: Progress Circle
description: A circular progress indicator component for visualizing completion percentage.
-------------------------------------------------------------------------------------------
import { DemoBlock } from '@/components/demo-block';
import { LimitedCode } from '@/components/ui/limited-code';
import {
OpenTabs,
OpenTabsContent,
OpenTabsList,
OpenTabsTrigger,
} from '@/components/ui/open-tabs';
import { ProgressCircleUsage } from '@/components/upload/blocks/progress-circle-block';
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
The `ProgressCircle` component displays a circular progress indicator, commonly used to show file upload progress. It visually represents a percentage value (0-100) with a ring and a centered text label.
If you are installing the other dropzone components via the CLI, this
component will be installed automatically. You can skip the following steps.
## Installation
CLIManual
```bash
npx shadcn@latest add https://edgestore.dev/r/progress-circle.json
```
```bash
pnpm dlx shadcn@latest add https://edgestore.dev/r/progress-circle.json
```
```bash
yarn dlx shadcn@latest add https://edgestore.dev/r/progress-circle.json
```
```bash
bun x shadcn@latest add https://edgestore.dev/r/progress-circle.json
```
### Setup for manual installation
First you will need to add the cn helper following the [manual install setup](./manual-install) guide.
### Copy this component
````tsx
import { cn } from '@/lib/utils';
import * as React from 'react';
/**
* Props for the ProgressCircle component.
*
* @interface ProgressCircleProps
* @extends {React.HTMLAttributes}
*/
export interface ProgressCircleProps
extends React.HTMLAttributes {
/**
* The progress value as a percentage (0-100).
*/
progress: number;
/**
* The diameter of the circle in pixels.
* @default 48
*/
size?: number;
/**
* The width of the progress stroke in pixels.
* @default 4
*/
strokeWidth?: number;
}
/**
* A circular progress indicator component that visualizes completion percentage.
*
* @component
* @example
* ```tsx
*
*
* ```
*/
const ProgressCircle = React.forwardRef(
({ progress, size = 48, strokeWidth = 4, className, ...props }, ref) => {
const radius = (size - strokeWidth) / 2;
const circumference = 2 * Math.PI * radius;
const offset = circumference - (progress / 100) * circumference;
return (
{/* Progress Percentage Text (centered visually) */}
{Math.round(progress)}%
);
},
);
ProgressCircle.displayName = 'ProgressCircle';
export { ProgressCircle };
````
## Usage
Import the component and provide the `progress` prop. You can optionally customize the `size` and `strokeWidth`.
```tsx
import { ProgressCircle } from '@/components/ui/progress-circle';
function ProgressDemo() {
const progress = 75;
return (
);
}
```
# EdgeStore Docs: Uploader Provider
URL: /docs/components/uploader-provider
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/components/uploader-provider.mdx
***
title: Uploader Provider
description: The Uploader Provider is a context provider that provides the uploader context to the components. You can use it as a base for creating your own components.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
import { LimitedCode } from '@/components/ui/limited-code';
import {
OpenTabs,
OpenTabsContent,
OpenTabsList,
OpenTabsTrigger,
} from '@/components/ui/open-tabs';
import { Callout } from 'fumadocs-ui/components/callout';
If you are installing the other dropzone components via the CLI, this
component will be installed automatically. You can skip the following steps.
## Installation
CLIManual
```bash
npx shadcn@latest add https://edgestore.dev/r/uploader-provider.json
```
```bash
pnpm dlx shadcn@latest add https://edgestore.dev/r/uploader-provider.json
```
```bash
yarn dlx shadcn@latest add https://edgestore.dev/r/uploader-provider.json
```
```bash
bun x shadcn@latest add https://edgestore.dev/r/uploader-provider.json
```
### Copy this component
````tsx
'use client';
import * as React from 'react';
/**
* Represents the possible statuses of a file in the uploader.
*/
export type FileStatus = 'PENDING' | 'UPLOADING' | 'COMPLETE' | 'ERROR';
/**
* Represents the state of a file in the uploader.
*/
export type FileState = {
/** The file object being uploaded */
file: File;
/** Unique identifier for the file */
key: string;
/** Upload progress (0-100) */
progress: number;
/** Current status of the file */
status: FileStatus;
/** URL of the uploaded file (available when status is COMPLETE) */
url?: string;
/** Error message if the upload failed */
error?: string;
/** AbortController to cancel the upload */
abortController?: AbortController;
/** Whether the file should be automatically uploaded */
autoUpload?: boolean;
};
/**
* Represents a file that has completed uploading.
*/
export type CompletedFileState = Omit & {
/** Status is guaranteed to be 'COMPLETE' */
status: 'COMPLETE';
/** URL is guaranteed to be available */
url: string;
};
/**
* Function type for handling file uploads.
*/
export type UploadFn = (props: {
/** The file to be uploaded */
file: File;
/** AbortSignal to cancel the upload */
signal: AbortSignal;
/** Callback to update progress */
onProgressChange: (progress: number) => void | Promise;
/** Additional options */
options?: TOptions;
}) => Promise<{ url: string }>;
/**
* Context type for the UploaderProvider.
*/
type UploaderContextType = {
/** List of all files in the uploader */
fileStates: FileState[];
/** Add files to the uploader */
addFiles: (files: File[]) => void;
/** Update a file's state */
updateFileState: (key: string, changes: Partial) => void;
/** Remove a file from the uploader */
removeFile: (key: string) => void;
/** Cancel an ongoing upload */
cancelUpload: (key: string) => void;
/** Start uploading files */
uploadFiles: (keysToUpload?: string[], options?: TOptions) => Promise;
/** Reset all files */
resetFiles: () => void;
/** Whether any file is currently uploading */
isUploading: boolean;
/** Whether files should be automatically uploaded */
autoUpload?: boolean;
};
/**
* Props for the UploaderProvider component.
*/
type ProviderProps = {
/** React children or render function */
children:
| React.ReactNode
| ((context: UploaderContextType) => React.ReactNode);
/** Callback when files change */
onChange?: (args: {
allFiles: FileState[];
completedFiles: CompletedFileState[];
}) => void | Promise;
/** Callback when a file is added */
onFileAdded?: (file: FileState) => void | Promise;
/** Callback when a file is removed */
onFileRemoved?: (key: string) => void | Promise;
/** Callback when a file upload completes */
onUploadCompleted?: (file: CompletedFileState) => void | Promise;
/** Function to handle the actual upload */
uploadFn: UploadFn;
/** External value to control the file states */
value?: FileState[];
/** Whether files should be automatically uploaded when added */
autoUpload?: boolean;
};
// Context
const UploaderContext =
React.createContext | null>(null);
/**
* Hook to access the uploader context.
*
* @returns The uploader context
* @throws Error if used outside of UploaderProvider
*
* @example
* ```tsx
* const { fileStates, addFiles, uploadFiles } = useUploader();
* ```
*/
export function useUploader() {
const context = React.useContext(UploaderContext);
if (!context) {
throw new Error('useUploader must be used within a UploaderProvider');
}
return context as UploaderContextType;
}
/**
* Provider component for file upload functionality.
*
* @component
* @example
* ```tsx
* {
* // Upload implementation
* return { url: 'https://example.com/uploads/image.jpg' };
* }}
* autoUpload={true}
* >
*
*
* ```
*/
export function UploaderProvider({
children,
onChange,
onFileAdded,
onFileRemoved,
onUploadCompleted,
uploadFn,
value: externalValue,
autoUpload = false,
}: ProviderProps) {
const [fileStates, setFileStates] = React.useState(
externalValue ?? [],
);
const [pendingAutoUploadKeys, setPendingAutoUploadKeys] = React.useState<
string[] | null
>(null);
// Sync with external value if provided
React.useEffect(() => {
if (externalValue) {
setFileStates(externalValue);
}
}, [externalValue]);
const updateFileState = React.useCallback(
(key: string, changes: Partial) => {
setFileStates((prevStates) => {
return prevStates.map((fileState) => {
if (fileState.key === key) {
return { ...fileState, ...changes };
}
return fileState;
});
});
},
[],
);
const uploadFiles = React.useCallback(
async (keysToUpload?: string[], options?: TOptions) => {
const filesToUpload = fileStates.filter(
(fileState) =>
fileState.status === 'PENDING' &&
(!keysToUpload || keysToUpload.includes(fileState.key)),
);
if (filesToUpload.length === 0) return;
await Promise.all(
filesToUpload.map(async (fileState) => {
try {
const abortController = new AbortController();
updateFileState(fileState.key, {
abortController,
status: 'UPLOADING',
progress: 0,
});
const uploadResult = await uploadFn({
file: fileState.file,
signal: abortController.signal,
onProgressChange: (progress) => {
updateFileState(fileState.key, { progress });
},
options,
});
// Wait a bit to show the bar at 100%
await new Promise((resolve) => setTimeout(resolve, 500));
const completedFile = {
...fileState,
status: 'COMPLETE' as const,
progress: 100,
url: uploadResult?.url,
};
updateFileState(fileState.key, {
status: 'COMPLETE',
progress: 100,
url: uploadResult?.url,
});
// Call onUploadCompleted when a file upload is completed
if (onUploadCompleted) {
void onUploadCompleted(completedFile);
}
} catch (err: unknown) {
if (
err instanceof Error &&
// if using with EdgeStore, the error name is UploadAbortedError
(err.name === 'AbortError' || err.name === 'UploadAbortedError')
) {
updateFileState(fileState.key, {
status: 'PENDING',
progress: 0,
error: 'Upload canceled',
});
} else {
if (process.env.NODE_ENV === 'development') {
console.error(err);
}
const errorMessage =
err instanceof Error ? err.message : 'Upload failed';
updateFileState(fileState.key, {
status: 'ERROR',
error: errorMessage,
});
}
}
}),
);
},
[fileStates, updateFileState, uploadFn, onUploadCompleted],
);
const addFiles = React.useCallback(
(files: File[]) => {
const newFileStates = files.map((file) => ({
file,
key: `${file.name}-${Date.now()}-${Math.random()
.toString(36)
.slice(2)}`,
progress: 0,
status: 'PENDING',
autoUpload,
}));
setFileStates((prev) => [...prev, ...newFileStates]);
// Call onFileAdded for each new file
if (onFileAdded) {
newFileStates.forEach((fileState) => {
void onFileAdded(fileState);
});
}
if (autoUpload) {
setPendingAutoUploadKeys(newFileStates.map((fs) => fs.key));
}
},
[autoUpload, onFileAdded],
);
const removeFile = React.useCallback(
(key: string) => {
setFileStates((prev) =>
prev.filter((fileState) => fileState.key !== key),
);
// Call onFileRemoved when a file is removed
if (onFileRemoved) {
void onFileRemoved(key);
}
},
[onFileRemoved],
);
const cancelUpload = React.useCallback(
(key: string) => {
const fileState = fileStates.find((f) => f.key === key);
if (fileState?.abortController && fileState.progress < 100) {
fileState.abortController.abort();
if (fileState?.autoUpload) {
// Remove file if it was an auto-upload
removeFile(key);
} else {
// If it was not an auto-upload, reset the file state
updateFileState(key, { status: 'PENDING', progress: 0 });
}
}
},
[fileStates, updateFileState, removeFile],
);
const resetFiles = React.useCallback(() => {
setFileStates([]);
}, []);
React.useEffect(() => {
const completedFileStates = fileStates.filter(
(fs): fs is CompletedFileState => fs.status === 'COMPLETE' && !!fs.url,
);
void onChange?.({
allFiles: fileStates,
completedFiles: completedFileStates,
});
}, [fileStates, onChange]);
// Handle auto-uploading files added to the queue
React.useEffect(() => {
if (pendingAutoUploadKeys && pendingAutoUploadKeys.length > 0) {
void uploadFiles(pendingAutoUploadKeys);
setPendingAutoUploadKeys(null);
}
}, [pendingAutoUploadKeys, uploadFiles]);
const isUploading = React.useMemo(
() => fileStates.some((fs) => fs.status === 'UPLOADING'),
[fileStates],
);
const value = React.useMemo(
() => ({
fileStates,
addFiles,
updateFileState,
removeFile,
cancelUpload,
uploadFiles,
resetFiles,
isUploading,
autoUpload,
}),
[
fileStates,
addFiles,
updateFileState,
removeFile,
cancelUpload,
uploadFiles,
resetFiles,
isUploading,
autoUpload,
],
);
return (
}>
{typeof children === 'function' ? children(value) : children}
);
}
/**
* Formats a file size in bytes to a human-readable string.
*
* @param bytes - The file size in bytes
* @returns A formatted string (e.g., "1.5 MB")
*
* @example
* ```ts
* formatFileSize(1024); // "1 KB"
* formatFileSize(1024 * 1024 * 2.5); // "2.5 MB"
* ```
*/
export function formatFileSize(bytes?: number) {
if (!bytes) return '0 B';
const k = 1024;
const dm = 2;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
}
````
## Usage
This section provides a step-by-step guide on how to use the `UploaderProvider` and the `useUploader` hook.
### 1. Setup ``
Wrap the part of your application that needs uploader functionality with `UploaderProvider`. You must provide an `uploadFn` and can optionally configure `autoUpload`.
* **`uploadFn`**: An asynchronous function that handles the actual file upload. It receives the `file`, an `onProgressChange` callback, and an `AbortSignal`. It should return an object with the uploaded file's `url`.
* **`autoUpload`**: (Optional, default: `false`) If `true`, files will start uploading immediately after being added.
```tsx
import { UploaderProvider, UploadFn } from '@/components/ui/uploader'; // Adjust import path
import { useEdgeStore } from '@/lib/edgestore'; // Adjust import path
import * as React from 'react';
function MyUploaderPage() {
const { edgestore } = useEdgeStore();
// Define the upload function
const uploadFn: UploadFn = React.useCallback(
async ({ file, onProgressChange, signal }) => {
// Example using Edge Store client
const res = await edgestore.publicFiles.upload({
file,
signal,
onProgressChange,
});
// you can run some server action or api here
// to add the necessary data to your database
console.log('Upload successful:', res);
return res; // Must return { url: string }
},
[edgestore],
);
return (
// Provide the uploadFn and configure autoUpload
{/* Your uploader components go here */}
);
}
// export default MyUploaderPage; // Assuming MyUploaderComponent is defined below
```
### 2. Use the `useUploader` Hook
Inside components nested under `UploaderProvider`, use the `useUploader` hook to access the uploader's state and control functions.
```tsx
import { useUploader } from '@/components/ui/uploader'; // Adjust import path
import * as React from 'react';
function MyUploaderComponent() {
const {
fileStates, // Array of current file states
addFiles, // Function to add files
removeFile, // Function to remove a file by key
cancelUpload, // Function to cancel an upload by key
uploadFiles, // Function to trigger uploads (all pending or specific keys)
isUploading, // Boolean indicating if any upload is in progress
} = useUploader();
// ... component logic using these values and functions ...
return
{/* UI elements */}
;
}
```
### 3. Adding Files (`addFiles`)
Typically, you'll use a standard file input. You might hide it and trigger its click event from a custom button. Get the selected `File` objects from the input's `onChange` event and pass them to `addFiles`.
```tsx
function MyUploaderComponent() {
const { addFiles } = useUploader();
const inputRef = React.useRef(null);
// Handle file selection from the input
const handleFileChange = (e: React.ChangeEvent) => {
if (e.target.files) {
addFiles(Array.from(e.target.files));
// Optional: Reset input value to allow selecting the same file again
e.target.value = '';
}
};
// Trigger the hidden input click
const handleAddClick = () => {
inputRef.current?.click();
};
return (
{/* Hidden file input */}
{/* Button to open file selector */}
{/* ... rest of the component ... */}
);
}
```
### 4. Displaying File State (`fileStates`)
The `fileStates` array contains objects representing each file. Each object includes:
* `file`: The original `File` object.
* `key`: A unique string identifier.
* `status`: `'PENDING'`, `'UPLOADING'`, `'COMPLETE'`, or `'ERROR'`.
* `progress`: Upload progress (0-100).
* `url`: (Optional) The URL after successful upload (`status === 'COMPLETE'`).
* `error`: (Optional) Error message if upload failed (`status === 'ERROR'`).
Iterate over `fileStates` to render the UI for each file.
```tsx
function MyUploaderComponent() {
const { fileStates, removeFile, cancelUpload } = useUploader();
return (
{/* ... Add files button/input ... */}
{/* List of files */}
{fileStates.length > 0 && (
{fileStates.map((fileState) => (
{fileState.file.name} ({fileState.status})
{/* Show progress during upload */}
{fileState.status === 'UPLOADING' && (
{fileState.progress}%
)}
{/* Show cancel button during upload */}
{fileState.status === 'UPLOADING' && (
)}
{/* Show remove button otherwise */}
{fileState.status !== 'UPLOADING' && (
)}
{/* Show error message */}
{fileState.status === 'ERROR' && (
{' '}
Error: {fileState.error}
)}
{/* Show link on completion */}
{fileState.status === 'COMPLETE' && fileState.url && (
View File
)}
))}
)}
);
}
```
### 5. Triggering Uploads (`uploadFiles`)
Call `uploadFiles()` to start uploading all files with status `'PENDING'`. You can optionally pass an array of specific file keys to `uploadFiles(keysToUpload)` to upload only those files. Use the `isUploading` boolean to disable the upload button during active uploads.
```tsx
function MyUploaderComponent() {
const { uploadFiles, isUploading, fileStates } = useUploader();
// Check if there are any files pending upload
const hasPendingFiles = fileStates.some((fs) => fs.status === 'PENDING');
return (
{/* ... Add files button/input and file list ... */}
{/* Upload button */}
);
}
```
### 6. Cancelling Uploads (`cancelUpload`)
Call `cancelUpload(key)` with the file's unique key to abort an ongoing upload. Your `uploadFn` must be implemented to respect the `AbortSignal` for cancellation to work correctly.
```tsx
// Example within the file list rendering (see step 4)
{
fileState.status === 'UPLOADING' && (
);
}
```
### 7. Removing Files (`removeFile`)
Call `removeFile(key)` with the file's key to remove it from the list, regardless of its status. If the file is currently uploading, this will also attempt to cancel the upload.
```tsx
// Example within the file list rendering (see step 4)
{
fileState.status !== 'UPLOADING' && (
);
}
```
### 8. Callbacks
You can pass callback props (`onChange`, `onFileAdded`, `onFileRemoved`, `onUploadCompleted`) to the `UploaderProvider` to execute logic when the uploader state changes.
```tsx
{
console.log('Files changed:', allFiles);
console.log('Completed files:', completedFiles);
}}
onFileAdded={(fileState) => console.log('File added:', fileState.file.name)}
onUploadCompleted={(completedFile) =>
console.log('Upload complete:', completedFile.url)
}
>
{/* ... */}
```
### Complete Component Example (`MyUploaderComponent`)
Here is the `MyUploaderComponent` combining the steps above:
```tsx
import { useUploader } from '@/components/ui/uploader'; // Adjust import path
import * as React from 'react';
function MyUploaderComponent() {
const {
fileStates,
addFiles,
removeFile,
cancelUpload,
uploadFiles,
isUploading,
} = useUploader();
const inputRef = React.useRef(null);
// Function to handle file selection
const handleFileChange = (e: React.ChangeEvent) => {
if (e.target.files) {
addFiles(Array.from(e.target.files));
e.target.value = ''; // Reset input
}
};
// Function to trigger the hidden file input
const handleAddClick = () => {
inputRef.current?.click();
};
const hasPendingFiles = fileStates.some((fs) => fs.status === 'PENDING');
return (
);
}
```
### Putting It All Together (`MyUploaderPage`)
Finally, use the `MyUploaderComponent` within the page component wrapped by the `UploaderProvider`.
```tsx
import { UploaderProvider, UploadFn } from '@/components/ui/uploader'; // Adjust import path
import { useEdgeStore } from '@/lib/edgestore'; // Adjust import path
import * as React from 'react';
// Assume MyUploaderComponent is defined in the same file or imported
// import { MyUploaderComponent } from './MyUploaderComponent';
function MyUploaderPage() {
const { edgestore } = useEdgeStore();
// Define the upload function (same as in step 1)
const uploadFn: UploadFn = React.useCallback(
async ({ file, onProgressChange, signal }) => {
const res = await edgestore.publicFiles.upload({
file,
signal,
onProgressChange,
});
console.log('Upload successful:', res);
return res;
},
[edgestore],
);
return (
My File Uploader
{
console.log(
`File ${completedFile.file.name} uploaded successfully to ${completedFile.url}`,
);
// Maybe trigger a database update here
}}
>
);
}
export default MyUploaderPage;
```
This provides a basic but functional file uploader using the context provider. You can style the elements and integrate them further into your application's UI.
# EdgeStore Docs: AWS
URL: /docs/providers/aws
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/providers/aws.mdx
***
title: AWS
description: Learn how to use your own AWS S3 bucket or S3-compatible services like Minio with EdgeStore using the AWS Provider.
--------------------------------------------------------------------------------------------------------------------------------
You can also use the EdgeStore package with your own AWS S3 bucket. You might want to do that in case you have strict company policies that require you to have all the data in your own AWS account.
You can also use the AWS Provider with other S3 compatible storage services like [Minio](https://min.io/).
By using this provider, you will be able to use most of the basic features of EdgeStore. However, for some of the more advanced features like access control with protected files, you will have to create your own infrastructure and logic from scratch.
## Installation
You need to install some peer dependencies to use this provider.
```bash
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
```
```bash
pnpm add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
```
```bash
yarn add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
```
```bash
bun add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
```
Then you can set the provider in the router.
```ts
import { initEdgeStore } from '@edgestore/server';
import {
createEdgeStoreNextHandler,
type CreateContextOptions,
} from '@edgestore/server/adapters/next/pages';
// [!code ++]
import { AWSProvider } from '@edgestore/server/providers/aws';
import { z } from 'zod';
// ...
export default createEdgeStoreNextHandler({
// [!code ++]
provider: AWSProvider(),
router: edgeStoreRouter,
createContext,
});
```
## Options
```ts
export type AWSProviderOptions = {
/**
* Access key for AWS credentials.
* Can also be set via the `ES_AWS_ACCESS_KEY_ID` environment variable.
*
* If unset, the SDK will attempt to use the default credentials provider chain.
*/
accessKeyId?: string;
/**
* Secret access key for AWS credentials.
* Can also be set via the `ES_AWS_SECRET_ACCESS_KEY` environment variable.
*
* If unset, the SDK will attempt to use the default credentials provider chain.
*/
secretAccessKey?: string;
/**
* AWS region to use.
* Can also be set via the `ES_AWS_REGION` environment variable.
*/
region?: string;
/**
* Name of the S3 bucket to use.
* Can also be set via the `ES_AWS_BUCKET_NAME` environment variable.
*/
bucketName?: string;
/**
* Custom endpoint for S3-compatible storage providers (e.g., MinIO).
* Can also be set via the `ES_AWS_ENDPOINT` environment variable.
*/
endpoint?: string;
/**
* Force path style for S3-compatible storage providers.
* Can also be set via the `ES_AWS_FORCE_PATH_STYLE` environment variable.
* Defaults to false for AWS S3, but should be true for most S3-compatible providers.
*/
forcePathStyle?: boolean;
/**
* Base URL to use for accessing files.
* Only needed if you are using a custom domain or cloudfront.
*
* Can also be set via the `EDGE_STORE_BASE_URL` environment variable.
*/
baseUrl?: string;
/**
* Secret to use for encrypting JWT tokens.
* Can be generated with `openssl rand -base64 32`.
*
* Can also be set via the `EDGE_STORE_JWT_SECRET` environment variable.
*/
jwtSecret?: string;
/**
* Optional function to overwrite the S3 key (object path) for uploads.
* This function receives the EdgeStore bucket name, fileInfo and the default S3 key
* and should return the desired S3 key string.
*/
overwritePath?: (args: {
esBucketName: string;
fileInfo: FileInfo;
defaultAccessPath: string;
}) => Promise | string;
};
```
## Overwriting S3 Object Keys
By default, the same logic used for generating the path for the EdgeStore Provider is used for the AWS Provider. However, sometimes you might want a cleaner path for the S3 objects that matches your use case.
For that, you can use the `overwritePath` option.
```ts
// ...
const handler = createEdgeStoreNextHandler({
createContext,
provider: AWSProvider({
// [!code ++:4]
overwritePath: ({ defaultAccessPath }) => {
// `publicFiles/_public/123/test.png` -> `123/test.png`
return defaultAccessPath.split('/_public/')[1];
},
}),
router: edgeStoreRouter,
});
```
If you are removing the `_public` folder from the path like in the example above, you might also want to disable the dev proxy in the `createEdgeStoreProvider` function.
```ts
const { EdgeStoreProvider, useEdgeStore } =
createEdgeStoreProvider({
disableDevProxy: true,
});
```
## Using with Minio
You can use the AWS Provider with Minio or other S3-compatible storage providers by setting the `endpoint` and `forcePathStyle` options.
```ts
provider: AWSProvider({
endpoint: 'http://localhost:9000', // can be set via the `ES_AWS_ENDPOINT` environment variable
forcePathStyle: true, // can be set via the `ES_AWS_FORCE_PATH_STYLE` environment variable
}),
```
# EdgeStore Docs: Azure
URL: /docs/providers/azure
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/providers/azure.mdx
***
title: Azure
description: Learn how to use your own Azure Blob Storage with EdgeStore using the Azure Provider.
--------------------------------------------------------------------------------------------------
You can also use the EdgeStore package with your own Azure Blob Storage. You might want to do that in case you have strict company policies that require you to have all the data in your own Azure account.
By using this provider, you will be able to use most of the basic features of EdgeStore. However, for some of the more advanced features like access control with protected files, you will have to create your own infrastructure and logic from scratch.
## Installation
You need to install some peer dependencies to use this provider.
```bash
npm install @azure/storage-blob
```
```bash
pnpm add @azure/storage-blob
```
```bash
yarn add @azure/storage-blob
```
```bash
bun add @azure/storage-blob
```
Then you can set the provider in the router.
```ts
import { initEdgeStore } from '@edgestore/server';
import {
createEdgeStoreNextHandler,
type CreateContextOptions,
} from '@edgestore/server/adapters/next/pages';
// [!code ++]
import { AzureProvider } from '@edgestore/server/providers/azure';
import { z } from 'zod';
// ...
export default createEdgeStoreNextHandler({
// [!code ++]
provider: AzureProvider(),
router: edgeStoreRouter,
createContext,
});
```
## Options
```ts
export type AzureProviderOptions = {
/**
* The storage account name for Azure Blob Storage
* Can also be set via the `ES_AZURE_ACCOUNT_NAME` environment variable.
*/
storageAccountName?: string;
/**
* SAS token for Azure Blob Storage
* Can also be set via the `ES_AZURE_SAS_TOKEN` environment variable.
*/
sasToken?: string;
/**
* Azure Blob Storage container name
* Can also be set via the `ES_AZURE_CONTAINER_NAME` environment variable.
*/
containerName?: string;
};
```
# EdgeStore Docs: Custom
URL: /docs/providers/custom
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/providers/custom.mdx
***
title: Custom
description: Learn how to create your own custom storage provider for EdgeStore or contribute to the project.
-------------------------------------------------------------------------------------------------------------
You can also create a custom provider for a different storage solution or just a different implementation for the existing providers.
You can use the [EdgeStore Provider](https://github.com/edgestorejs/edgestore/blob/next/packages/server/src/providers/edgestore/index.ts) as a reference implementation.
If you build a provider that you believe could be useful for others, please consider contributing it to the EdgeStore project by opening a pull request.
# EdgeStore Docs: EdgeStore
URL: /docs/providers/edgestore
Source: https://raw.githubusercontent.com/edgestorejs/edgestore/refs/heads/main/docs/content/docs/providers/edgestore.mdx
***
title: EdgeStore
description: Learn about the EdgeStore provider and its configuration options for customizing your EdgeStore integration.
-------------------------------------------------------------------------------------------------------------------------
You can optionally pass in a provider to the `createEdgeStoreNextHandler` function. This is useful if you want to use a different provider than the default one or if you want to pass some custom options to the provider.
The EdgeStore Provider is the default provider. If you followed the documentation, you already have it configured in your app.
```ts
import { initEdgeStore } from '@edgestore/server';
import {
createEdgeStoreNextHandler,
type CreateContextOptions,
} from '@edgestore/server/adapters/next/pages';
// [!code ++]
import { EdgeStoreProvider } from '@edgestore/server/providers/edgestore';
import { z } from 'zod';
// ...
export default createEdgeStoreNextHandler({
// [!code ++]
provider: EdgeStoreProvider(), // this is the default provider and can be omitted
router: edgeStoreRouter,
createContext,
});
```
## Options
```ts
export type EdgeStoreProviderOptions = {
/**
* Access key for your EdgeStore project.
* Can be found in the EdgeStore dashboard.
*
* This can be omitted if the `EDGE_STORE_ACCESS_KEY` environment variable is set.
*/
accessKey?: string;
/**
* Secret key for your EdgeStore project.
* Can be found in the EdgeStore dashboard.
*
* This can be omitted if the `EDGE_STORE_SECRET_KEY` environment variable is set.
*/
secretKey?: string;
};
```