PG Mode Cloud Storage
Starting with CloudBase Flutter SDK 1.3.0, PG mode cloud storage is available through app.storage.fromBucket(bucketId). Path arguments are object names inside the bucket. Do not pass a cloud:// fileID.
from() remains the classic-mode entry. The JS SDK enters a PG bucket with app.storage.from(bucketId). The Flutter SDK uses fromBucket so the classic entry keeps its name.
Key API differences between PG mode and classic mode:
| Dimension | PG mode | Classic mode |
|---|---|---|
| Entry | app.storage.fromBucket(bucketId) | app.storage.from() |
| Path | Object name inside the bucket, such as user-123/avatar.png | Relative path for upload, copy, and move; cloud:// fileID for download, delete, and signed URLs |
| Permissions | PostgreSQL RLS (Row Level Security) | Built-in permissions plus JSON security rules |
| Bucket management | createBucket / listBuckets / getBucket / updateBucket / deleteBucket | Not supported |
| File listing | list() | Not supported |
| Overwrite default | upsert defaults to false | upsert defaults to true |
| Cross-bucket | move() / copy() accept destinationBucket | Not supported |
| Download | download() returns file bytes | getDownloadUrls() / createSignedUrl() |
| Public URL | getPublicUrl() builds the URL locally and sends no request | Not supported |
| Object info | info() / exists() | Not supported |
Quick Start
final app = await CloudBase.init(
env: 'your-env-id',
accessKey: 'your-access-key',
);
final bucket = app.storage.fromBucket('avatars');
final uploaded = await bucket.upload(
'user-123/avatar.png',
fileBytes,
options: PgStorageUploadOptions(
contentType: 'image/png',
upsert: true,
),
);
if (!uploaded.isSuccess) {
print(uploaded.error?.message);
return;
}
final file = await bucket.download('user-123/avatar.png');
final signed = await bucket.createSignedUrl('user-123/avatar.png', 3600);
await bucket.remove(['user-123/avatar.png']);
API Methods
Bucket client
- fromBucket() - Select a bucket and return the object client
File operations
- upload() - Upload a file
- update() - Replace an existing file
- download() - Download file bytes
- remove() - Delete files
- move() - Move a file
- copy() - Copy a file
- list() - List objects in the bucket
URL management
- createSignedUrl() - Create a signed URL
- createSignedUrls() - Create signed URLs in batch
- getPublicUrl() - Get a public URL
- createSignedUploadUrl() - Create a signed upload URL
- uploadToSignedUrl() - Upload with a signed token
File info
Bucket management
- Bucket management - Create, read, update, and delete buckets
fromBucket
CloudBasePgStorageFileApi app.storage.fromBucket(String bucketId)
Parameters
PG bucket ID, matching `storage.buckets.id`. An empty value, a length over 256, or whitespace, `/`, `\`, `?`, `#`, `%` throws `StorageError` synchronously.
Response
Object client for a PG mode bucket.
Example
final bucket = app.storage.fromBucket('avatars');
upload
Upload a file. Same-name objects are not overwritten by default. File bytes are sent as a binary body.
Future<StorageResponse<StorageUploadResult>> upload(
String path,
List<int> fileData, {
PgStorageUploadOptions? options,
})
class PgStorageUploadOptions {
String? cacheControl; // seconds as a string, default 3600, sent as Cache-Control: max-age=<value>
String? contentType; // text/plain;charset=UTF-8 when omitted
Map<String, dynamic>? metadata; // base64(JSON), sent as x-metadata
bool upsert; // default false; only upload sends x-upsert
Map<String, String>? headers;
}
class StorageUploadResult {
String? id;
String? path;
String? fullPath; // object key returned by the server
}
Parameters
Object name inside the bucket, such as `user-123/avatar.png`. Do not pass a `cloud://` fileID.
File bytes.
Upload options. Same-name objects are not overwritten by default.
Response
Return data
Error. Includes `code`, `message`, `requestId`, and `status`.
Example
final result = await app.storage.fromBucket('avatars').upload(
'user-123/avatar.png',
fileBytes,
options: PgStorageUploadOptions(
contentType: 'image/png',
upsert: true,
metadata: {'usage': 'avatar'},
),
);
if (result.isSuccess) {
print(result.data?.fullPath);
} else {
print(result.error?.message);
}
update
Replace an existing file. The request uses PUT and does not send x-upsert.
Future<StorageResponse<StorageUploadResult>> update(
String path,
List<int> fileData, {
PgStorageUploadOptions? options,
})
Parameters
Object name inside the bucket.
New file bytes.
Upload options. `upsert` is not sent.
Response
Update result.
Error.
Example
await app.storage.fromBucket('avatars').update(
'user-123/avatar.png',
fileBytes,
options: PgStorageUploadOptions(contentType: 'image/png'),
);
download
Download a file and return its bytes.
Future<StorageResponse<List<int>>> download(
String path, {
PgStorageUrlOptions? options,
})
class PgStorageUrlOptions {
Object? download; // true appends download=; a string is the download file name
String? cacheNonce; // appended to the URL to bypass the CDN cache
}
Parameters
Object name inside the bucket.
Download query parameters.
Response
File bytes.
Error.
Example
final result = await app.storage.fromBucket('avatars').download(
'user-123/avatar.png',
options: PgStorageUrlOptions(download: 'avatar.png'),
);
if (result.isSuccess) {
print(result.data?.length);
}
remove
Delete one or more files. A single request holds at most 100 paths; larger lists are sent in batches. If any batch fails, the whole call fails.
Future<StorageResponse<List<PgRemovedObject>>> remove(List<String> paths)
class PgRemovedObject {
String name;
String bucketId;
String? ownerId;
String? id;
Map<String, dynamic>? metadata;
}
Parameters
Object names inside the bucket.
Response
Objects that were deleted.
Error.
Example
await app.storage.fromBucket('avatars').remove(['user-123/avatar.png']);
move
Move a file. The source object is deleted. The destination can be another bucket.
Future<StorageResponse<PgStorageMessage>> move(
String fromPath,
String toPath, {
PgStorageDestinationOptions? options,
})
class PgStorageDestinationOptions {
String? destinationBucket; // same as the source bucket when omitted
bool? upsert; // true allows overwriting the destination
bool? copyMetadata; // used by copy only
Map<String, dynamic>? metadata; // used by copy only
}
class PgStorageMessage {
String message;
}
Parameters
Source object name.
Destination object name.
Destination bucket and overwrite options. `copyMetadata` and `metadata` are not sent to the move API.
Response
Return data
Error.
Example
await app.storage.fromBucket('avatars').move(
'user-123/avatar.png',
'user-123/archive/avatar.png',
);
copy
Copy a file. The destination can be another bucket.
Future<StorageResponse<PgCopyResult>> copy(
String fromPath,
String toPath, {
PgStorageDestinationOptions? options,
})
class PgCopyResult {
String path; // key returned by the server
}
Parameters
Source object name.
Destination object name.
Cross-bucket, overwrite, and metadata options.
Response
Return data
Error.
Example
await app.storage.fromBucket('avatars').copy(
'user-123/avatar.png',
'user-123/avatar-copy.png',
options: PgStorageDestinationOptions(
upsert: true,
copyMetadata: true,
),
);
createSignedUrl
Create a temporary access URL.
Future<StorageResponse<PgSignedUrl>> createSignedUrl(
String path,
int expiresIn, {
PgStorageUrlOptions? options,
})
class PgSignedUrl {
String fullSignedUrl;
}
Parameters
Object name inside the bucket.
Lifetime in seconds.
`download` and `cacheNonce` appended to the signed URL.
Response
Return data
Error.
Example
final result = await app.storage.fromBucket('avatars').createSignedUrl(
'user-123/avatar.png',
3600,
options: PgStorageUrlOptions(download: 'avatar.png'),
);
print(result.data?.fullSignedUrl);
createSignedUrls
Create temporary access URLs in batch. A single request holds at most 500 paths; larger lists are sent in batches.
Future<StorageResponse<List<PgSignedUrlItem>>> createSignedUrls(
List<String> paths,
int expiresIn, {
PgStorageUrlOptions? options,
})
class PgSignedUrlItem {
String path;
String? fullSignedUrl; // null when this item fails
String? error;
}
Parameters
Object names inside the bucket.
Lifetime in seconds.
Options appended to each signed URL.
Response
Batch signed URL results. A failed item has `fullSignedUrl` null and `error` set.
Error for the whole request.
Example
final result = await app.storage.fromBucket('avatars').createSignedUrls(
['user-123/avatar.png'],
3600,
);
getPublicUrl
Build a public object URL locally. This method sends no request. Whether the object can be read depends on the bucket public flag.
StorageResponse<PgPublicUrl> getPublicUrl(
String path, {
PgStorageUrlOptions? options,
})
class PgPublicUrl {
String publicUrl;
}
Parameters
Object name inside the bucket.
`download` and `cacheNonce` appended to the public URL.
Response
Return data
Example
final result = app.storage.fromBucket('public-assets').getPublicUrl(
'logos/cloudbase.png',
);
print(result.data?.publicUrl);
createSignedUploadUrl
Create a signed upload URL and token. The call requires a signed-in session.
Future<StorageResponse<PgSignedUploadUrl>> createSignedUploadUrl(
String path, {
bool upsert = false,
})
class PgSignedUploadUrl {
String fullSignedUrl;
String token;
String path;
}
Parameters
Object name inside the bucket.
Whether overwriting a same-name object is allowed. Default `false`. `true` sends `x-upsert: true`.
Response
Return data
Error.
Example
final bucket = app.storage.fromBucket('avatars');
final signed = await bucket.createSignedUploadUrl(
'user-123/avatar.png',
upsert: true,
);
if (signed.isSuccess) {
await bucket.uploadToSignedUrl(
'user-123/avatar.png',
signed.data!.token,
fileBytes,
);
}
uploadToSignedUrl
Upload with a signed token. The request does not send the session credential.
Future<StorageResponse<PgSignedUploadResult>> uploadToSignedUrl(
String path,
String token,
List<int> fileData, {
PgStorageUploadOptions? options,
})
class PgSignedUploadResult {
String path;
String fullPath;
}
Parameters
Object name inside the bucket. It must match the path used when signing.
Token returned by `createSignedUploadUrl`.
File bytes.
Content type, cache, and metadata. This method does not send `x-upsert`.
Response
Return data
Error.
Example
await bucket.uploadToSignedUrl(
'user-123/avatar.png',
token,
fileBytes,
options: PgStorageUploadOptions(contentType: 'image/png'),
);
info
Get object metadata.
Future<StorageResponse<PgObjectInfo>> info(String path)
class PgObjectInfo {
String id;
String name;
String? version;
String bucketId;
int? size;
String? contentType;
String? cacheControl;
String? etag;
Map<String, dynamic>? metadata;
String? lastModified;
String? createdAt;
}
Parameters
Object name inside the bucket.
Response
Object metadata. Server snake_case fields are mapped to camelCase.
Error.
Example
final result = await app.storage.fromBucket('avatars').info('user-123/avatar.png');
print(result.data?.size);
exists
Check whether a file exists. HTTP 400 and 404 are treated as missing, and data is false.
Future<StorageResponse<bool>> exists(String path)
Parameters
Object name inside the bucket.
Response
Whether the object exists.
Errors other than 400 and 404.
Example
final result = await app.storage.fromBucket('avatars').exists('user-123/avatar.png');
print(result.data);
list
List objects by prefix. Omitting path lists the bucket root. The prefix keeps the form you pass, including a trailing /.
Future<StorageResponse<PgListObjectsResponse>> list([
String? path,
PgStorageListOptions? options,
])
class PgStorageListOptions {
int? limit;
String? cursor; // nextCursor from the previous page
bool? withDelimiter; // true splits folders and objects on /
PgStorageSortBy? sortBy;
}
class PgStorageSortBy {
String? column; // name, created_at, updated_at
String? order; // asc, desc
}
class PgListObjectsResponse {
List<PgListObjectsEntry> folders;
List<PgListObjectsEntry> objects;
bool hasNext;
String? nextCursor;
}
Parameters
Path prefix. Omit it to list the bucket root.
Paging, delimiter, and sort.
Response
Return data
Error.
Example
final result = await app.storage.fromBucket('avatars').list(
'user-123/',
PgStorageListOptions(limit: 20, withDelimiter: true),
);
if (result.isSuccess) {
for (final object in result.data!.objects) {
print(object.name);
}
}
Bucket management
These methods live on app.storage and are available only in a PG mode environment. bucketId follows the same character rules as fromBucket.
Future<StorageResponse<List<PgBucket>>> listBuckets({
PgListBucketsOptions? options,
})
Future<StorageResponse<PgBucket>> getBucket(String bucketId)
Future<StorageResponse<PgBucketName>> createBucket(
String bucketId, {
PgCreateBucketOptions? options,
})
Future<StorageResponse<PgStorageMessage>> updateBucket(
String bucketId,
PgUpdateBucketOptions options,
)
Future<StorageResponse<PgStorageMessage>> deleteBucket(String bucketId)
class PgCreateBucketOptions {
bool public; // default false
String? type; // set only on create; currently STANDARD
Object? fileSizeLimit; // a number is bytes, a string such as 100MB, null means no limit
Object? allowedMimeTypes; // List<String>; null means no limit
}
class PgUpdateBucketOptions {
bool? public; // omitted fields are left unchanged
Object? fileSizeLimit; // an explicit null clears the limit
Object? allowedMimeTypes;
}
class PgListBucketsOptions {
int? limit;
int? offset;
String? sortColumn; // id, name, created_at, updated_at
String? sortOrder; // asc, desc
String? search;
}
class PgBucket {
String id;
String name;
String? ownerId;
bool public;
String? type;
int? fileSizeLimit; // bytes; null means no limit
List<String>? allowedMimeTypes;
String createdAt;
String updatedAt;
}
Parameters
Bucket ID. `createBucket` uses it as both `id` and `name`.
Create, update, or list parameters. Omitted update fields are left out of the request body. `type` cannot be changed.
Response
Bucket operation result. `deleteBucket` deletes an empty bucket only. A non-empty bucket fails the request.
Error.
Example
final created = await app.storage.createBucket(
'avatars',
options: PgCreateBucketOptions(
public: false,
type: 'STANDARD',
fileSizeLimit: '100MB',
allowedMimeTypes: ['image/png', 'image/jpeg'],
),
);
final buckets = await app.storage.listBuckets(
options: PgListBucketsOptions(limit: 20, offset: 0),
);
await app.storage.updateBucket(
'avatars',
PgUpdateBucketOptions(public: true),
);
Best practices
Error handling
A failed request is returned on StorageResponse.error and is not thrown. An invalid bucketId, or an object path that contains .., throws StorageError immediately.
final result = await app.storage.fromBucket('avatars').upload(
'user-123/avatar.png',
fileBytes,
);
if (!result.isSuccess) {
print(result.error?.message);
return;
}
Object paths
Select the bucket with fromBucket(bucketId) first. Later paths are object names inside that bucket.
- Do not pass a
cloud://fileID. - The SDK strips leading and trailing
/and collapses repeated/. - A
listprefix skips that cleanup, so a trailing/is kept. - A
..segment throws.
FAQ
Path argument
fromBucket(bucketId) already selects the bucket. path is the object name inside that bucket. A cloud:// fileID is neither required nor accepted.
Access control
PG mode is controlled by RLS policies on PostgreSQL storage.objects and storage.buckets. On a 403, check the current session, object path, bucketId, and the matching policy.