Skip to main content

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.

Differences from classic mode

Key API differences between PG mode and classic mode:

DimensionPG modeClassic mode
Entryapp.storage.fromBucket(bucketId)app.storage.from()
PathObject name inside the bucket, such as user-123/avatar.pngRelative path for upload, copy, and move; cloud:// fileID for download, delete, and signed URLs
PermissionsPostgreSQL RLS (Row Level Security)Built-in permissions plus JSON security rules
Bucket managementcreateBucket / listBuckets / getBucket / updateBucket / deleteBucketNot supported
File listinglist()Not supported
Overwrite defaultupsert defaults to falseupsert defaults to true
Cross-bucketmove() / copy() accept destinationBucketNot supported
Downloaddownload() returns file bytesgetDownloadUrls() / createSignedUrl()
Public URLgetPublicUrl() builds the URL locally and sends no requestNot supported
Object infoinfo() / exists()Not supported

See Classic Cloud Storage.

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​

URL management​

File info​

Bucket management​


fromBucket​

CloudBasePgStorageFileApi app.storage.fromBucket(String bucketId)

Parameters

bucketId
String

PG bucket ID, matching `storage.buckets.id`. An empty value, a length over 256, or whitespace, `/`, `\`, `?`, `#`, `%` throws `StorageError` synchronously.

Response

CloudBasePgStorageFileApi
Object

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

path
String

Object name inside the bucket, such as `user-123/avatar.png`. Do not pass a `cloud://` fileID.

fileData
List<int>

File bytes.

options
PgStorageUploadOptions

Upload options. Same-name objects are not overwritten by default.

Response

data
Object

Return data

error
StorageError?

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

path
String

Object name inside the bucket.

fileData
List<int>

New file bytes.

options
PgStorageUploadOptions

Upload options. `upsert` is not sent.

Response

data
StorageUploadResult?

Update result.

error
StorageError?

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

path
String

Object name inside the bucket.

options
PgStorageUrlOptions

Download query parameters.

Response

data
List<int>?

File bytes.

error
StorageError?

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

paths
List<String>

Object names inside the bucket.

Response

data
List<PgRemovedObject>?

Objects that were deleted.

error
StorageError?

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

fromPath
String

Source object name.

toPath
String

Destination object name.

options
PgStorageDestinationOptions

Destination bucket and overwrite options. `copyMetadata` and `metadata` are not sent to the move API.

Response

data
Object

Return data

error
StorageError?

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

fromPath
String

Source object name.

toPath
String

Destination object name.

options
PgStorageDestinationOptions

Cross-bucket, overwrite, and metadata options.

Response

data
Object

Return data

error
StorageError?

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

path
String

Object name inside the bucket.

expiresIn
int

Lifetime in seconds.

options
PgStorageUrlOptions

`download` and `cacheNonce` appended to the signed URL.

Response

data
Object

Return data

error
StorageError?

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

paths
List<String>

Object names inside the bucket.

expiresIn
int

Lifetime in seconds.

options
PgStorageUrlOptions

Options appended to each signed URL.

Response

data
List<PgSignedUrlItem>?

Batch signed URL results. A failed item has `fullSignedUrl` null and `error` set.

error
StorageError?

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

path
String

Object name inside the bucket.

options
PgStorageUrlOptions

`download` and `cacheNonce` appended to the public URL.

Response

data
Object

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

path
String

Object name inside the bucket.

upsert
bool

Whether overwriting a same-name object is allowed. Default `false`. `true` sends `x-upsert: true`.

Response

data
Object

Return data

error
StorageError?

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

path
String

Object name inside the bucket. It must match the path used when signing.

token
String

Token returned by `createSignedUploadUrl`.

fileData
List<int>

File bytes.

options
PgStorageUploadOptions

Content type, cache, and metadata. This method does not send `x-upsert`.

Response

data
Object

Return data

error
StorageError?

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

path
String

Object name inside the bucket.

Response

data
PgObjectInfo?

Object metadata. Server snake_case fields are mapped to camelCase.

error
StorageError?

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

path
String

Object name inside the bucket.

Response

data
bool?

Whether the object exists.

error
StorageError?

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
String?

Path prefix. Omit it to list the bucket root.

options
PgStorageListOptions

Paging, delimiter, and sort.

Response

data
Object

Return data

error
StorageError?

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

bucketId
String

Bucket ID. `createBucket` uses it as both `id` and `name`.

options
Object

Create, update, or list parameters. Omitted update fields are left out of the request body. `type` cannot be changed.

Response

data
PgBucket | List<PgBucket> | PgBucketName | PgStorageMessage

Bucket operation result. `deleteBucket` deletes an empty bucket only. A non-empty bucket fails the request.

error
StorageError?

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 list prefix 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.