Skip to main content
By default, generated images, videos, audio, and other files are uploaded to the fal CDN and returned as public fal URLs. If you need generated files to land in your own S3 bucket, send an S3 upload policy with the request using the x-app-fal-upload-policy header. This is useful when your application already has its own storage lifecycle, access controls, compliance process, or media processing pipeline.
Custom output storage is for generated output files. It does not upload local input files before a request. For model inputs, upload to the fal CDN first or pass a public or presigned input URL.
Support depends on the model. Custom output storage works on fal model endpoints that upload their generated files through fal’s output storage pipeline, which includes most popular fal model APIs. Endpoints that do not support it ignore the header without an error and return fal CDN URLs as usual, and self-deployed serverless apps do not honor it. To confirm support for an endpoint, send a request with the policy and check that the returned file URL points at your bucket.

How It Works

  1. Your backend creates an S3 presigned POST policy for the destination bucket and prefix.
  2. You send the model request with x-app-fal-upload-policy.
  3. When the model creates an output file, fal uses the policy to upload the file to your bucket.
  4. The model response contains the resulting S3 object URL instead of a fal CDN URL.
The policy must be a presigned POST policy, not a presigned PUT URL. fal sends the generated file as the POST form’s file field and includes the form fields from your policy. Your policy’s key field must contain ${filename}. fal replaces that placeholder with a unique generated filename for each uploaded output.

Header Format

The header value is a JSON object:
The returned URL is based on the S3 POST URL and the final object key, with the key URL-encoded for safe access. If your policy does not include a signed Content-Type field, fal adds the output file’s content type to the POST form. If your policy does include one, its value must exactly match the content type of the file the model generates — a mismatched signed Content-Type fails the upload. Because many models choose the output format at request time, prefer omitting Content-Type from the signed fields and restricting it with a starts-with condition instead.

Example

Create the presigned POST on a trusted backend:
Send the policy with the model request:
The headers parameter requires fal-client 0.8.1 or later. The returned URL points at your bucket, for example:
The object name comes from the ${filename} substitution: a unique id plus a generated file name whose extension follows the output format.
fal uploads the object, but it does not change your bucket policy, ACLs, CloudFront setup, or read permissions. If the object is private, the returned S3 URL may not be directly downloadable by clients. In that case, generate your own read URL or serve the object through your storage layer.

Errors

Invalid upload policies fail the request instead of falling back to the fal CDN. Common policy errors include malformed JSON, a non-HTTPS URL, an unsupported storage host, missing url or fields, or a fields.key value without ${filename}. If S3 rejects the upload or the upload cannot be completed — for example, the policy expired, a policy condition rejects the generated key or content type, or a signed Content-Type does not match the generated file — the request may still return a URL because output uploads are asynchronous from the caller’s perspective. In that case, the returned URL may not become readable. There is no automatic fallback to fal CDN once an upload policy is present.
Presigned POST policies must remain valid for the full model runtime plus the output upload time. A policy that expires while the model is still running will cause the S3 upload to fail.

Background Uploads

For most models, output uploads run in the background from the caller’s perspective, and the response can include the final S3 URL before the object is readable from your bucket. Some models wait for the upload to finish before responding; for those, a failed upload fails the request instead of returning an unreadable URL. An immediate GET against the returned URL can briefly return 404, 403, or another S3 response while the upload is still in progress or before your bucket permissions make the object readable. Clients that fetch the object immediately should retry for a short period. For workflows that must guarantee object availability, verify the object from your backend or use your S3 event pipeline before treating the file as ready.

Limitations

  • Only HTTPS S3 presigned POST upload URLs are supported. Webhooks, GCS URLs, R2 URLs, and custom S3-compatible endpoints are not supported by this header.
  • Output uploads are asynchronous from the caller’s perspective for most models. Treat the returned URL as the intended destination, not as proof that the object is already readable.
  • Each generated output is uploaded with one S3 presigned POST. S3 POST uploads have a 5 GB maximum object size, and large files can still fail earlier because of your policy conditions, request duration, or bucket configuration. For large outputs such as 1 GB videos, prefer the default fal CDN upload flow or upload to your bucket from your own endpoint code.
  • The policy applies to generated file outputs. JSON-only outputs, plain strings, and URLs returned directly by a model are not rewritten.
  • fal media expiration settings and fal CDN deletion APIs do not manage objects in your bucket. Retention and deletion are your responsibility.
  • Your policy conditions must allow the generated key, content type, and file size. If the policy is too narrow, S3 will reject the upload.
  • The returned URL is based on the S3 POST URL and object key. If you serve objects through CloudFront or another domain, translate the key to your public URL in your own application.

Security Recommendations

  • Generate upload policies on a trusted backend.
  • Use short expirations that still cover the expected model runtime and upload time.
  • Scope key conditions to a dedicated prefix such as fal-outputs/.
  • Do not grant broader bucket permissions than the single POST policy needs.