Signed URLs
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.dev/api/storage/private/report.pdf ← 403 Forbidden
Signed URL: https://project.edgebase.dev/api/storage/private/report.pdf?token=eyJ... ← ✅ Works for 1 hour
| Scenario | Use |
|---|---|
Display a private image in <img> | Signed download URL |
| Share a temporary download link with someone | Signed download URL (set a short expiry) |
| Let the client upload a large file directly to R2 | Signed upload URL (bypasses Worker memory limits) |
Show a public file in <img> | bucket.getUrl() — no signing needed |
Signed download URLs check the read rule at URL creation time. Signed upload URLs check the write rule at URL creation time. After that, anyone with the URL can access the file until it expires.
Signed Download URL
Create a temporary URL for downloading a private file (default expiry: 1 hour):
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- Python
- Go
- PHP
- Rust
- C#
- C++
const bucket = client.storage.bucket('private');
const url = await bucket.createSignedUrl('report.pdf', {
expiresIn: '1h', // default: 1h
});
// url -> "https://your-project.edgebase.dev/api/storage/private/report.pdf?token=..."
final bucket = client.storage.bucket('private');
final url = await bucket.createSignedUrl('report.pdf', expiresIn: '1h');
let bucket = client.storage.bucket("private")
let url = try await bucket.createSignedUrl("report.pdf", expiresIn: "1h")
val bucket = client.storage.bucket("private")
val url = bucket.createSignedUrl("report.pdf", expiresIn = "1h")
StorageBucket bucket = client.storage().bucket("private");
String url = bucket.createSignedUrl("report.pdf", "1h");
bucket = client.storage.bucket('private')
url = bucket.create_signed_url('report.pdf', expires_in='1h')
bucket := admin.Storage.Bucket("private")
url, err := bucket.CreateSignedURL("report.pdf", "1h")
$bucket = $client->storage->bucket('private');
$url = $bucket->createSignedUrl('report.pdf', '1h');
let bucket = client.storage().bucket("private");
let url = bucket.create_signed_url("report.pdf", "1h").await?;
var bucket = admin.Storage.Bucket("private");
var url = await bucket.CreateSignedUrlAsync("report.pdf", "1h");
auto bucket = client.storage().bucket("private");
auto url = bucket.createSignedUrl("report.pdf", "1h");
Duration Format
| Value | Duration |
|---|---|
'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:
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- Python
- Go
- PHP
- Rust
- C#
- C++
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...' },
// ]
final bucket = client.storage.bucket('private');
final results = await bucket.createSignedUrls(
['report-1.pdf', 'report-2.pdf', 'report-3.pdf'],
expiresIn: '1h',
);
let bucket = client.storage.bucket("private")
let results = try await bucket.createSignedUrls(
["report-1.pdf", "report-2.pdf", "report-3.pdf"],
expiresIn: "1h"
)
val bucket = client.storage.bucket("private")
val results = bucket.createSignedUrls(
listOf("report-1.pdf", "report-2.pdf", "report-3.pdf"),
expiresIn = "1h"
)
StorageBucket bucket = client.storage().bucket("private");
List<SignedUrlResult> results = bucket.createSignedUrls(
List.of("report-1.pdf", "report-2.pdf", "report-3.pdf"), "1h"
);
bucket = client.storage.bucket('private')
results = bucket.create_signed_urls(
['report-1.pdf', 'report-2.pdf', 'report-3.pdf'],
expires_in='1h',
)
bucket := admin.Storage.Bucket("private")
results, err := bucket.CreateSignedURLs(
[]string{"report-1.pdf", "report-2.pdf", "report-3.pdf"}, "1h",
)
$bucket = $client->storage->bucket('private');
$results = $bucket->createSignedUrls(
['report-1.pdf', 'report-2.pdf', 'report-3.pdf'], '1h'
);
let bucket = client.storage().bucket("private");
let results = bucket.create_signed_urls(
&["report-1.pdf", "report-2.pdf", "report-3.pdf"], "1h"
).await?;
var bucket = admin.Storage.Bucket("private");
var results = await bucket.CreateSignedUrlsAsync(
new[] { "report-1.pdf", "report-2.pdf", "report-3.pdf" }, "1h"
);
auto bucket = client.storage().bucket("private");
auto results = bucket.createSignedUrls(
{"report-1.pdf", "report-2.pdf", "report-3.pdf"}, "1h"
);
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):
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- Python
- Go
- PHP
- Rust
- C#
- C++
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,
});
final bucket = client.storage.bucket('uploads');
final signed = await bucket.createSignedUploadUrl(
'large-file.zip',
expiresIn: '10m',
);
// Upload using the signed URL
final response = await http.post(
Uri.parse(signed.url),
body: fileBytes,
);
let bucket = client.storage.bucket("uploads")
let signed = try await bucket.createSignedUploadUrl(
"large-file.zip",
expiresIn: "10m"
)
// Upload using the signed URL
var request = URLRequest(url: URL(string: signed.url)!)
request.httpMethod = "POST"
request.httpBody = fileData
let (_, response) = try await URLSession.shared.data(for: request)
val bucket = client.storage.bucket("uploads")
val signed = bucket.createSignedUploadUrl("large-file.zip", expiresIn = "10m")
// Upload using the signed URL
val response = httpClient.post(signed.url) {
setBody(fileBytes)
}
StorageBucket bucket = client.storage().bucket("uploads");
SignedUploadUrl signed = bucket.createSignedUploadUrl("large-file.zip", "10m");
// Upload using the signed URL
bucket = client.storage.bucket('uploads')
signed = bucket.create_signed_upload_url('large-file.zip', expires_in='10m')
# Upload using the signed URL
import requests
requests.post(signed.url, files={'file': file_data})
bucket := admin.Storage.Bucket("uploads")
signed, err := bucket.CreateSignedUploadURL("large-file.zip", "10m")
// Upload using signed.URL
$bucket = $client->storage->bucket('uploads');
$signed = $bucket->createSignedUploadUrl('large-file.zip', '10m');
// Upload using $signed->url
let bucket = client.storage().bucket("uploads");
let signed = bucket.create_signed_upload_url("large-file.zip", "10m").await?;
// Upload using signed.url
var bucket = admin.Storage.Bucket("uploads");
var signed = await bucket.CreateSignedUploadUrlAsync("large-file.zip", "10m");
// Upload using signed.Url
auto bucket = client.storage().bucket("uploads");
auto signed = bucket.createSignedUploadUrl("large-file.zip", "10m");
// Upload using signed.url
Signed upload URLs validate write rules when the URL is created.
REST API
| Endpoint | Description |
|---|---|
POST /api/storage/:bucket/signed-url | Create signed download URL |
POST /api/storage/:bucket/signed-urls | Batch create signed download URLs |
POST /api/storage/:bucket/signed-upload-url | Create signed upload URL |