Skip to main content

Signed URLs

Beta

This feature is in beta. Core behavior is stable and ready to try, but some APIs or configuration may still evolve before general availability.

Generate time-limited URLs for private file access and upload workflows.

When to Use Signed URLs

By default, files in a bucket with a read rule that returns true are publicly accessible via bucket.getUrl(). But for private files — where the bucket's read rule requires authentication — clients need a way to access files without sending auth headers (e.g., in <img src="..."> tags, PDF viewers, or sharing links with external users).

Signed URLs solve this by embedding a time-limited token directly in the URL:

Regular URL:  https://project.edgebase.fun/api/storage/private/report.pdf      ← 403 Forbidden
Signed URL: https://project.edgebase.fun/api/storage/private/report.pdf?token={expiresAt}.{hmacHex} ← ✅ Works for 1 hour
ScenarioUse
Display a private image in <img>Signed download URL
Share a temporary download link with someoneSigned download URL (set a short expiry)
Let the client upload without sending an auth headerSigned upload URL (use multipart for large files)
Show a public file in <img>bucket.getUrl() — no signing needed
Access Rule Check

Signed download URLs check the read rule at URL creation time and remain usable until expiry. Signed upload URLs check the write rule at URL creation time, but the upload grant is single-use: it can authorize one single-file upload or be bound to one multipart session.

Active Content Is Downloaded

Signing does not bypass storage content safety. HTML, XHTML, SVG, XML, JavaScript, CSS, PDF, binary, unknown, and malformed types are returned as opaque sandboxed attachments, including byte-range responses. Only explicitly passive media types render inline at the application origin.


Signed Download URL

Create a temporary URL for downloading a private file (default expiry: 1 hour):

const bucket = client.storage.bucket('private');

const url = await bucket.createSignedUrl('report.pdf', {
expiresIn: '1h', // default: 1h
});
// url -> "https://your-project.edgebase.fun/api/storage/private/report.pdf?token=..."

Duration Format

ValueDuration
'30s'30 seconds
'10m'10 minutes
'1h'1 hour (default)
'7d'7 days

Batch Signed URLs

Create signed URLs for multiple files in a single request:

const bucket = client.storage.bucket('private');

const results = await bucket.createSignedUrls(
['report-1.pdf', 'report-2.pdf', 'report-3.pdf'],
{ expiresIn: '1h' },
);
// [
// { key: 'report-1.pdf', url: '...?token=...', expiresAt: '2026-03-01T...' },
// { key: 'report-2.pdf', url: '...?token=...', expiresAt: '2026-03-01T...' },
// { key: 'report-3.pdf', url: '...?token=...', expiresAt: '2026-03-01T...' },
// ]
info

Non-existent files are silently skipped in the response. Maximum 100 keys per request.

Signed Upload URL

Create a temporary upload URL (default expiry: 30 minutes):

const bucket = client.storage.bucket('uploads');

const signed = await bucket.createSignedUploadUrl('large-file.zip', {
expiresIn: '10m', // default: 30m
});

const formData = new FormData();
formData.append('file', file, 'large-file.zip');
formData.append('key', 'large-file.zip');

await fetch(signed.url, {
method: 'POST',
body: formData,
});

Options

OptionTypeDefaultDescription
expiresInstring'30m'Duration until the URL expires (e.g., '10m', '1h')
maxFileSizestringMaximum allowed file size as a byte-size string (e.g., '5MB', '1GB', '128KB'). The upload is rejected if the file exceeds this limit.
info

Signed upload URLs validate write rules when the URL is created.

Signed upload tokens can also be sent to multipart upload endpoints as token and key query parameters. This lets browser clients create, upload, complete, abort, and resume multipart uploads after the initial write rule check.

Each signed upload URL contains a random single-use grant. EdgeBase atomically consumes it immediately after token validation and before parsing a single-file request body. Malformed, oversized, or hook-rejected attempts therefore burn the grant instead of letting one URL replay expensive body processing. For multipart, EdgeBase claims the grant before asking R2 to create a session, then compare-and-swap binds the winning claim to the returned upload ID. Thus competing requests can create at most one R2 session. A bound token can upload, list, complete, or abort only that multipart session. Different upload IDs and switching between multipart and single-file upload are rejected. Replaying the URL is rejected even if the application deletes the first uploaded object while the URL's original TTL is still running. Create or bind failures are terminal because a storage failure can be ambiguous; an aborted or failed multipart session also does not return the grant. Request a new signed upload URL before starting over.

When maxFileSize is set, single uploads are rejected if the file is too large. Multipart uploads require a positive Content-Length on every part. Before writing a part to R2, EdgeBase atomically reserves that declared length against the grant's aggregate byte budget; concurrent parts cannot reserve more than maxFileSize. The first over-budget request terminally closes and aborts the session. Reservations are monotonic, so retries and replacements consume the budget again; request a new signed upload URL after an ambiguous part failure. Completion also verifies the tracked sizes as a final fail-closed check.

The grant must be valid when EdgeBase atomically consumes a single-upload attempt or authorizes a multipart continuation. A request that starts while the grant is valid may finish after expiresAt; new requests at or after that boundary are rejected. This request-start boundary avoids a non-atomic post-commit delete racing with and removing a newer trusted write to the same key.

Signed download URLs carry signed file metadata such as size, type, and ETag. This lets byte-range media playback seek directly to the requested range without a second metadata lookup on each seek, and range responses are marked Cache-Control: private for the remaining signed URL lifetime so the same browser can reuse cached segments.

Before issuing a single or batch signed download URL, EdgeBase evaluates the bucket read rule against the corresponding stored object's metadata. Batch requests authorize each existing key independently; permission to list a bucket does not by itself grant signed access to every object.

That metadata also binds the URL to the object version that existed when the URL was created. If a trusted writer overwrites the same key, both full and range requests using the old URL fail with 412 failed-precondition; create a new signed URL for the new object version.

REST API

EndpointDescription
POST /api/storage/:bucket/signed-urlCreate signed download URL
POST /api/storage/:bucket/signed-urlsBatch create signed download URLs
POST /api/storage/:bucket/signed-upload-urlCreate signed upload URL