---
title: "File Upload & Processing · RUAL Documentation"
description: "Accept an upload, store it as an asset and process it safely."
canonical: https://docs.rual.nl/tutorials/file-upload-processing
language: en
---

# File Upload & Processing

Accept files through forms and APIs, store them as assets, and process them safely in the background.

Uploads come in two flavors: a user dropping a file into your page, and a system pushing a file to your API. Both end up as cluster assets, and heavy processing belongs in the queue. This tutorial covers the whole pipeline.

### Upload via a Page (User-Facing)

- Build a form with `state_form` and a file-capable input from the state elements. See the form pattern in [Block Templates](https://docs.rual.nl/block-types/block-templates#form-handling).

- On submit. Read the form with [`state_form_get`](https://docs.rual.nl/block-types/state%20ui/state_form_get); the file arrives with its name, type, and size.

- Store it via the asset upload API (`POST /api/v1/_system/assets/stream`) or a file-handling block, into `public` (world-readable) or `system_assets` (private): choose per [public vs private](https://docs.rual.nl/blueprints/assets#public-vs-private).

- Record the asset guid/path on a document so you can find it later (e.g. `photo` on a profile document).

### Upload via an API (Machine-Facing)

- Register [`on_startup_register_uri_post`](https://docs.rual.nl/block-types/http%20connection/on_startup_register_uri_post) with your upload URI. See [the upload template](https://docs.rual.nl/block-types/block-templates#file-uploads).

- Validate: content type, size limit, required metadata. Reject with a 400 before storing anything.

- Store as an asset; reply 200 with `{"guid": "...", "path": "..."}` so the caller can reference it.

- Scope it deliberately: uploads are never `*public` by accident; use `*loggedin` or a custom scope with a secret in the path.

### Processing: Do It in the Background

Resizing, parsing, importing, converting. Never inline in the request. The pattern:

- The upload flow stores the file and enqueues a processing job with [`function_custom_execute_from_queue`](https://docs.rual.nl/block-types/function%20execution/function_custom_execute_from_queue). See [Queue](https://docs.rual.nl/blueprints/queue).

- A queue worker flow picks it up: reads the file with [`file_from_assets`](https://docs.rual.nl/block-types/files/file_from_assets), transforms it (e.g. resize/convert with the image blocks), writes results back to a document or a new asset.

- Mark progress on the job document (`status: queued → processing → done|failed`) so users can poll or a page can show state.

- Log failures to the console and storage. The error-pins pattern from [Block Execution](https://docs.rual.nl/blueprints/block-execution#error-handling).

### Safety Rules

- Never trust the client's filename for the stored path, generate your own.

- Cap file size at the API (the asset API rejects oversize with `FILE_TOO_BIG`) and validate type by content, not extension.

- Keep user uploads out of `public` unless they are truly public: private folders + scoped serving, per [Serving Protected Files](https://docs.rual.nl/blueprints/assets#serving-protected-files).

- Import pipelines should be idempotent: re-running the same file updates the same output document (upsert by source guid).

### Next Steps
