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.
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
- Your backend creates an S3 presigned POST policy for the destination bucket and prefix.
- You send the model request with
x-app-fal-upload-policy. - When the model creates an output file, fal uses the policy to upload the file to your bucket.
- The model response contains the resulting S3 object URL instead of a fal CDN URL.
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:headers parameter requires fal-client 0.8.1 or later.
The returned URL points at your bucket, for example:
${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, missingurl 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.
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 return404, 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
keyconditions to a dedicated prefix such asfal-outputs/. - Do not grant broader bucket permissions than the single POST policy needs.