EdgeStore

Backend Client

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

Configure the router and hosted provider once. The resulting configuredEdgeStore instance is used by the HTTP handler and exposes the type-safe backend client.

Since Next.js doesn't allow exports in the api route, you will need to move your router to an external file.

src/lib/edgestore-server.ts
import { createEdgeStore, initEdgeStore } from '@edgestore/server';
import { createEdgeStoreNextHandler } from '@edgestore/server/adapters/next/app';
import { edgestore } from '@edgestore/server/providers/edgestore';

const es = initEdgeStore.create();

export const router = es.router({
  publicFiles: es.fileBucket(),
});

export const configuredEdgeStore = createEdgeStore({
  router,
  provider: edgestore(),
});

export const handler = createEdgeStoreNextHandler({
  edgestore: configuredEdgeStore,
});

export const backendClient = configuredEdgeStore.client;

Then you will need to update your api route to use the exported handler.

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 example.

The backend client is privileged. It validates router input and applies file type, size, transform, path, and metadata rules, but it does not run accessControl, beforeUpload, or beforeDelete. Perform authorization in the server code that calls it.

The hosted edgestore() provider supports the complete backend client. S3 currently exposes get, delete, and deleteMany. Azure Blob Storage also exposes createSignedUrl and createSignedUrls for private reads. Methods such as backend upload and listing remain absent from those clients until the provider implements them.

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:

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.

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',
  },
  signal,
  onProgress: ({ percentage, phase }) => {
    console.log(phase, `${percentage}%`);
  },
});

console.log(res.id, res.key, res.sizeBytes);

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.

const res = await backendClient.publicFiles.upload({
  content: {
    url: 'https://some-url.com/file.txt',
    extension: 'txt',
  },
});

Transform a file before upload

You can transform backend uploads before EdgeStore validates and uploads them. The transform receives the resolved Blob and extension, and returns the new Blob and extension.

For example, you can use sharp to convert an image to WebP before upload:

npm install sharp
import sharp from 'sharp';

const res = await backendClient.publicImages.upload({
  content: {
    url: 'https://some-url.com/image.jpg',
    extension: 'jpg',
  },
  options: {
    transform: async ({ blob }) => {
      const input = Buffer.from(await blob.arrayBuffer());
      const output = await sharp(input).webp({ quality: 80 }).toBuffer();

      return {
        blob: new Blob([output], { type: 'image/webp' }),
        extension: 'webp',
      };
    },
  },
});

Confirm a temporary file upload

If you upload a temporary file, you can confirm it by using the confirm function.

const res = await backendClient.publicFiles.confirm({
  id: file.id,
});

File operations accept a stable file ID, storage key, or URL. Singular operations throw EdgeStoreFileMutationError when that file fails. Use the plural form when partial success should be preserved:

const result = await backendClient.publicFiles.confirmMany({
  refs: [{ id: first.id }, { key: second.key }],
});

for (const failure of result.failed) {
  console.error(failure.ref, failure.error.code);
}

Backend Delete

You can use the delete function to delete files from your backend.

const res = await backendClient.publicFiles.delete({
  id: file.id,
});

deleteMany, restore, and restoreMany use the same singular and partial-batch semantics.

You can use the list function to list files from your backend. It's also possible to filter the results by path, metadata or upload timing.

// simple usage
// get the first 20 files in the bucket
const res = await backendClient.publicFiles.list();

// with filter and pagination
const res = await backendClient.publicFiles.list({
  filter: {
    metadata: {
      role: 'admin',
    },
    path: {
      type: 'post',
    },
    uploadedAt: {
      gt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7), // past 7 days
    },
  },
  cursor: 'cursor-from-previous-response',
  limit: 50, // default: 20 (max: 100)
});

for (const file of res.items) {
  console.log(file.id, file.url);
}

if (res.hasMore) {
  console.log('Next cursor:', res.nextCursor);
}

Private read URLs

Providers with signed-read support expose createSignedUrl on protected buckets:

const access = await backendClient.privateFiles.createSignedUrl({
  url: { id: file.id },
  expiresIn: 15 * 60,
});

console.log(access.signedUrl, access.expiresAt);

Use createSignedUrls to sign several references in one provider call.

Reusing backend client types

Derive a single method's exact input or output directly from the configured client:

type UploadInput = Parameters<
  typeof configuredEdgeStore.client.publicFiles.upload
>[0];

type UploadOutput = Awaited<
  ReturnType<typeof configuredEdgeStore.client.publicFiles.upload>
>;

This includes the router's context, input, path, and metadata configuration and the provider's file and reference types.

Use InferClientInputs and InferClientOutputs when you need a reusable map of every bucket and method:

src/lib/edgestore-server.ts
import type {
  InferClientInputs,
  InferClientOutputs,
} from '@edgestore/server';

export type BackendInputs = InferClientInputs<typeof router>;
export type BackendOutputs = InferClientOutputs<typeof router>;

type UploadInput = BackendInputs['publicFiles']['upload'];
type UploadOutput = BackendOutputs['publicFiles']['upload'];

The default represents the hosted provider. Pass a custom provider as the second generic when its capabilities or associated types differ:

type BackendOutputs = InferClientOutputs<
  typeof router,
  typeof customProvider
>;

InferClientResponse remains as a deprecated alias of InferClientOutputs.

On this page