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

The router exposes a type-safe backend client through router.client. The HTTP handler and backend client share the same provider. EdgeStore is the default; chain .provider(myProvider) on the router to use a different provider.

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 { initEdgeStore } from '@edgestore/server';
import { createEdgeStoreNextHandler } from '@edgestore/server/adapters/next/app';

const es = initEdgeStore.create();

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

export const handler = createEdgeStoreNextHandler({
  router,
});

export const backendClient = router.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 and Azure Blob Storage support upload, get, delete, deleteMany, and private-read createSignedUrl / createSignedUrls, accepting key or URL references. Unsupported operations, such as listing and confirmation, remain absent from each provider's client type.

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

Save the upload result's ID/key and URL in your database, then confirm the temporary file. Hosted EdgeStore accepts confirmation during processing; a preceding get() is unnecessary and can return 404 until processing finishes. If saving fails, leave the file temporary.

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 router.client.publicFiles.upload
>[0];

type UploadOutput = Awaited<
  ReturnType<typeof router.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 helpers infer capabilities and provider-specific types directly from the router, including custom providers. Use EdgeStoreClient<typeof router> for the complete backend client type.

On this page