---
title: Upload files
description: Give a function bucket access and save uploaded files.
pagination: false
sidebar:
  icon: upload
---

Give a function access to a bucket, then write file bytes under an object key.

> **Before you begin**
>
> Complete the [bucket adapter installation and
> binding](/guides/storage/upload-product-images#configure-storage) first. The upload tutorial
> supplies the selected local profile; connected storage is a separate choice.

## Save files from a function

Import your [bucket](/docs/modules/storage/define) and add it to `dependencies.buckets`. The name `assets` makes it available as `context.buckets.assets`.

This function accepts a primary image and attachments, writes their bytes, and returns their keys:

```ts title="src/services/assets/functions/upload-assets.function.ts"
/** Validates multipart image files, writes them through the declared assets bucket and returns their uploaded names. */
import { defineFunction } from "@relkit/app/functions";
import { z } from "@relkit/app/schema";
import assets from "../buckets/assets.bucket.js";

const uploadedFile = z.file({
  maxBytes: 1024 * 1024 * 10,
  mediaTypes: ["image/jpeg", "image/png"],
});

const uploadAssets = defineFunction({
  input: z.object({
    label: z.string().min(1),
    primary: uploadedFile,
    attachments: z.array(uploadedFile),
  }),

  output: z.object({
    label: z.string(),
    files: z.array(z.string()),
  }),

  dependencies: { buckets: { assets } },

  handler: async ({ label, primary, attachments }, context) => {
    const files = [primary, ...attachments];

    // This demo uses filenames as keys; uploading the same name replaces the file.
    await Promise.all(
      files.map(async (file) =>
        context.buckets.assets.put(file.name, new Uint8Array(await file.arrayBuffer()), {
          contentType: file.type,
        }),
      ),
    );

    return { label, files: files.map(({ name }) => name) };
  },
});

export default uploadAssets;
```

`put()` takes a key, a `Uint8Array`, and optional settings such as `contentType` and `metadata`. Await it before reporting a successful upload.

## Choose keys safely

This example uses filenames as keys. Uploading `cover.png` again replaces the existing file. In a multi-user app, generate keys on the server—for example, `users/<user-id>/<random-id>.png`—and save the original display name separately.

Authorize uploads before writing. A declared content type does not prove a file is safe; inspect its contents when your app requires it.

## Connect an upload endpoint

Use a route with `accept: "multipart/form-data"`. The [image-upload tutorial](/guides/storage/upload-product-images) includes the service, route, and curl request.

The HTTP body limit covers the whole request; file-schema and bucket limits apply to individual files. Keep uploads below all applicable limits. See [HTTP uploads](/docs/modules/http/uploads) for request validation details.
