Platform features
Storage
Instant Storage makes it simple to upload and serve files for your app. You can use Storage to store images, videos, documents, and any other file type.
Storage is still in beta, but you can request access here!
Uploading files
We use the db.storage.upload(pathname: string, file: File)
function to upload a file.
async function upload(files: FileList) {
const file = files[0];
// use the file's current name as the path
await db.storage.upload(file.name, file);
// or, give the file a custom name
await db.storage.upload('demo.png', file);
// or, put it in the `images` subdirectory
await db.storage.upload('images/demo.png', file);
// or, put it in a subdirectory for the current user,
// and restrict access to this file via Storage permissions
await db.storage.upload(`${currentUser.id}/demo.png`, file);
}
return <input type="file" onChange={(e) => upload(e.target.files)} />;
The pathname
determines where the file will be stored, and can be used with permissions to restrict access to certain files.
The file
should be a File
type, which will likely come from a file-type input.
Note that if the pathname
already exists in your storage directory, it will be overwritten!
You may want to include some kind of unique identifier or timestamp in your pathname
to ensure this doesn't happen.
Retrieving files
To retrieve a file URL, we use the db.storage.getDownloadUrl(pathname: string)
function.
This function returns a signed URL that will be valid for 7 days.
This is important to keep in mind in cases where you want to save this URL somewhere, as demonstrated below in Caching the URL.
const [imageUrl, setImageUrl] = React.useState<string | null>(null);
React.useEffect(() => {
db.storage
.getDownloadUrl('images/demo.png')
.then((signedUrl) => setImageUrl(signedUrl))
.catch((err) => console.error('Failed to get file URL', err));
}, []);
return <img src={imageUrl} />;
Caching the URL
You might also want to cache the URL after retrieving it, in order to avoid calling getDownloadUrl
every time you refresh the page.
Let's imagine you have an images
namespace you use to store the file metadata of your images. You can use this to keep track of the expiration time of all your file URLs, and then refresh them accordingly.
// Simple component to upload and display image files
function App() {
const { data } = db.useQuery({ images: {} });
const upload = async (files: FileList) => {
const file = files[0];
const pathname = file.name; // or whatever custom file path you'd like
const expiresAt = Date.now() + 7 * 24 * 60 * 60 * 1000; // 7 days from now
const isSuccess = await db.storage.upload(pathname, file);
const cachedUrl = await db.storage.getDownloadUrl(pathname);
db.transact(tx.images[id()].update({ cachedUrl, pathname, expiresAt }));
};
return (
<div>
<input type="file" onChange={(e) => upload(e.target.files)} />
{data.images.map((image) => (
<ImageViewer key={image.id} image={image} />
))}
</div>
);
}
Then, in your ImageViewer
component, you can use the cachedUrl
by default, and handle the expiration when necessary:
// Component to handle displaying the image URL and refreshing when necessary
function ImageViewer({ image }: { image: Schema.Image }) {
const [imageUrl, setImageUrl] = React.useState(image.cachedUrl);
React.useEffect(() => {
// If the image URL has expired, refresh the signed url
if (image.expiresAt < Date.now()) {
const expiresAt = Date.now() + 7 * 24 * 60 * 60 * 1000;
db.storage.getDownloadUrl(image.pathname).then((url) => {
// update the cached url
db.transact(
tx.images[image.id].update({
cachedUrl: url,
// reset expiration to 7 days from now
expiresAt: expiresAt,
})
);
setImageUrl(url);
});
}
}, [image.expiresAt]);
return <img src={imageUrl} />;
}
Permissions
At the moment, Storage permissions are handled in the same JSON settings as data permissions, using the special $files
keyword.
To handle permissions for uploading files, we use the create
action.
For downloading or viewing files, we use the view
action.
By default, Storage permissions are disabled. This means that until you explicitly set permissions, no uploads or downloads will be possible.
In your permissions rules, you can use auth
to access the currently authenticated user, and data
to access the file metadata.
At the moment, the only available file metadata is data.path
, which represents the file's path in Storage. (In the future we will likely include metadata such as size
and type
.)
Examples
Allow anyone to upload and retrieve files (not recommended):
{
"$files": {
"allow": {
"view": "true",
"create": "true"
}
}
}
Allow all authenticated users to view and upload files:
{
"$files": {
"allow": {
"view": "isLoggedIn",
"create": "isLoggedIn"
},
"bind": ["isLoggedIn", "auth.id != null"]
}
}
Authenticated users may only upload and view files from their own subdirectory:
{
"$files": {
"allow": {
"view": "isOwner",
"create": "isOwner"
},
"bind": ["isOwner", "data.path.startsWith(auth.id + '/')"]
}
}
Allow all authenticated users to view files, but users may only upload png
/jpeg
image files:
{
"$files": {
"allow": {
"view": "auth.id != null",
"create": "isImage"
},
"bind": [
"isImage",
"data.path.endsWith('.png') || data.path.endsWith('.jpeg')"
]
}
}
Admin SDK
The Admin SDK offers the same API for managing storage on the server, plus a few extra convenience methods for scripting.
Uploading files
Once again, we use the db.storage.upload(pathname: string, file: Buffer)
function to upload a file on the backend.
Note that unlike our browser SDK, the file
argument must be a Buffer
:
import fs from 'fs';
async function upload(filepath: string) {
const buffer = fs.readFileSync(filepath);
await db.storage.upload('images/demo.png', buffer);
// you can also optionally specify the Content-Type header in the metadata
await db.storage.upload('images/demo.png', buffer, {
contentType: 'image/png',
});
}
The pathname
determines where the file will be stored, and can be used with permissions to restrict access to certain files.
The file
should be a Buffer
type.
Note that if the pathname
already exists in your storage directory, it will be overwritten!
You may want to include some kind of unique identifier or timestamp in your pathname
to ensure this doesn't happen.
Retrieving a file URL
To retrieve a file URL, we use the db.storage.getDownloadUrl(pathname: string)
function.
This works exactly the same as our browser SDK.
const url = await db.storage.getDownloadUrl('images/demo.png');
Listing all your files
We also offer the db.storage.list()
function to retrieve a list of all your files in storage.
This can be useful for scripting, if you'd like to manage your files programmatically.
const files = await db.storage.list();
Deleting files
There are two ways to delete files:
db.storage.delete(pathname: string)
db.storage.deleteMany(pathnames: string[])
These allow you to either delete a single file, or bulk delete multiple files at a time.
These functions will permanently delete files from storage, so use with extreme caution!
const filename = 'demo.txt';
await db.storage.delete(filename);
const images = ['images/1.png', 'images/2.png', 'images/3.png'];
await db.storage.deleteMany(images);