Follow a document through storage, application access and delivery. Keep the file private while making the next step clear to the person using your app.
Choose a file, preview an upload or connect the example app.
Local example · No uploads, keys or account changes · Private buckets require an enabled installation
Start with the person who needs the file.
A useful upload flow is more than a successful PUT. A person adds a document, your application records who owns it, another authorized person requests it, and the app returns the right file without exposing everyone else's objects. Design that path before choosing an endpoint. A private bucket gives the application storage access; the application still decides which user may act on each object.
Use private objects for customer documents, generated exports, attachments and original uploads that need a permission check. Use public Project files for intentionally public assets such as a product image or downloadable brochure. Existing Worker R2 folders use the Project files model. Their familiar binding names do not make the files private. Private buckets are available on hosted Fortly and open first in Storage. Choose Project files only for assets you intend to serve publicly.
Create one resource. Connect one app.
Open your project, choose Storage, then create a private bucket. Give it a useful display name such as App uploads. Fortly assigns the stable S3 identity. Upload a small test file in Files, then refresh the list. The console and S3 tools work with the same objects, so you can verify the integration without maintaining two copies of the data.
In Connect, choose Read only for a consumer or Read and write for an uploader. Write access also allows replacement and deletion. Connecting creates a dedicated credential and encrypted runtime variables together. A variable prefix lets one app use another bucket without overwriting the first connection. If a variable already exists, resolve the conflict deliberately rather than replacing a secret behind the application's back.
The connection result tells you what was saved and whether your application needs a deployment or restart to use it. View code example uses the saved prefix. Bucket secrets are runtime values and are not sent to the build. The upload and serving guide includes server-side client configuration, path-style addressing and the gateway region.
Choose where the bytes travel.
Server upload
Your app writes the object
Authenticate the user, validate the file and select its object key. The server uses its scoped bucket credential to upload the bytes. This keeps the storage secret out of the browser and puts the authorization decision beside the application's business rules.
Direct browser upload
Your app signs a request
After authorizing an exact upload, the server issues a short-lived signed PUT URL. The browser sends the file directly to S3. Configure an explicit CORS rule for its origin, method and headers. Fortly checks the signature, credential permission, supported request size and shared capacity.
A signed PUT is not an HTML form-policy upload. Send the file as the request body and preserve the URL and signed headers. Your application remains responsible for checking the resulting content before trusting it. A client-provided file name or a successful application login is not sufficient authorization to write any object key.
Return access, not a permanent public shortcut.
When a user requests a protected document, your application authenticates that user and checks ownership or sharing rules. It can then return a temporary signed GET URL. The browser downloads from the Fortly S3 endpoint while the bucket remains private. Anyone who holds an unexpired link can use it, so choose an appropriate expiry and keep it out of analytics and request logs.
The console's temporary links use a separate read-only signer. Revoking console links does not retire app credentials, and rotating an app connection does not revoke every other key. Give each integration its own key, verify the replacement before retiring an old credential, and use immediate revocation when a secret is compromised. The access guide explains the complete lifecycle.
Built-in bucket CDN delivery and public bucket URLs are not enabled. Copying a private document to Project files to obtain a shareable URL changes its access model. Use an authorized application route or temporary download instead. If an application deliberately publishes an asset, make that decision explicit and preserve private caching rules for protected responses.
Make capacity part of the happy path.
Private objects and public Project files draw from one account object-storage allowance. Incomplete multipart data, transfer reservations and pending cleanup count too. A selected bucket can look nearly empty while other projects occupy most of the shared pool. Uploading through another SDK or creating another bucket does not provide more capacity.
Reserve room for replacements: the previous object stays available while the new copy arrives. Deleting a file removes it from the list before cleanup necessarily releases the physical bytes. When capacity cannot cover a write, Fortly refuses the upload rather than silently purchasing an add-on. Authorized downloads and deletion remain available. A plan reduction or Storage Pack removal must still fit stored plus reserved bytes; otherwise the current tier and packs remain unchanged.
Activity this month separates read/write attempts and processed transfer bytes from stored capacity. Retries are separate requests, multipart uploads involve several writes, and a ranged read does not pretend to transfer the full object. These totals are measurements, not automatic usage invoices. Payment collection and prepaid balances are not connected. Use the usage guide to understand the exact boundaries.
Verify one complete user journey.
Test an allowed upload, then inspect its key and size through Files. Authorize a download for its owner, and confirm another user is refused before a URL is issued. Exercise an expired link and a revoked key. Test a denied write with a read-only credential. A working console session proves neither the app's end-user rules nor a browser's CORS configuration.
Keep an independent copy when your retention policy requires one. PostgreSQL point-in-time recovery does not restore objects, and permanent bucket deletion has no recovery window. Review compatibility before importing a production client; this is a documented S3 subset, not a promise of every S3 feature. Once the small path works, plan multipart behavior, concurrency and shared capacity before the larger migration.
When delivery is enabled, open the bucket's Delivery section and choose signed links for protected files. Your server can sign with its existing bucket-scoped key after checking the app user. A cache hit still checks current permission, key state, object version and delivery budget before returning bytes. A S3 presigned URL uses a different token format and continues to bypass caching.
For assets intended for everyone, select a folder such as public/, review its files and confirm that scope. Future uploads into that prefix also become public. Purge selects a fresh cache generation, while pause blocks subsequent delivery requests without deleting files. Stored objects count once; edge requests and bytes have a separate budget. Follow the CDN guide and review delivery behavior before launch.
Before you build
A few useful answers.
Can a bucket replace user authorization?
No. A bucket key represents an integration, not an end user. Your application authenticates the user and checks access to the specific object before reading it or signing a request.
Should I send an S3 secret to a browser?
No. Keep the secret on the server. After authorizing the request, your server can issue a short-lived signed GET or PUT URL. Browser calls also need an appropriate CORS rule.
Can I copy a private file into Project files to share it?
That would publish it at a public URL. Use a temporary signed download or an authorized application route for a protected object instead.