Skip to main content
ClaudeWave

User files for SaaS. Ownership, access, sharing, expiry, revocation and an audit trail for your application's files — on your own Postgres and your own bucket. Apache-2.0.

ToolsOfficial Registry0 stars0 forks● TypeScriptApache-2.0Updated today
ClaudeWave Trust Score
95/100
✓ Verified
Passed
  • ✓Open-source license (Apache-2.0)
  • ✓Actively maintained (<30d)
  • ✓Clear description
  • ✓Topics declared
  • ✓Documented (README)
Last scanned: 10/13/2026
Get started
Method: Clone
Terminal
git clone https://github.com/filelayer/filelayer
1. Clone the repository.
2. Follow the README for installation and usage instructions.
Use cases

Tools overview

# Filelayer

[![CI](https://github.com/filelayer/filelayer/actions/workflows/ci.yml/badge.svg)](https://github.com/filelayer/filelayer/actions/workflows/ci.yml)
[![npm](https://img.shields.io/npm/v/@filelayer/core.svg)](https://www.npmjs.com/package/@filelayer/core)
[![node](https://img.shields.io/node/v/@filelayer/core.svg)](https://nodejs.org)
[![license](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/filelayer/filelayer/blob/main/LICENSE)

<!-- Every URL on this page is rooted at github.com/filelayer/filelayer and
     npmjs.com/package/@filelayer/core, so the badges resolve on their own with
     no edit here. -->

> **Alpha — developer preview, not production software.** Nobody is running it
> in production, including us. [**Should you depend on this?**](https://github.com/filelayer/filelayer/blob/main/TRUST.md)
> has the real numbers, including the ones that are zero, and exactly what would
> change them. [Limitations](https://github.com/filelayer/filelayer/blob/main/LIMITATIONS.md) is the complete list of what it
> does not do.
>
> **We are looking for three to five design partners.** If you are building
> something with private user files, we will help you integrate this and stay on
> hand while you do, in exchange for telling us where it breaks. That is the only
> thing on our [roadmap to 1.0](https://github.com/filelayer/filelayer/blob/main/ROADMAP.md) we cannot build ourselves.
> [Open an issue](https://github.com/filelayer/filelayer/issues) or say hello.

**The file layer for SaaS applications.** Public files and private files, with
one authorization model behind both.

You tell Filelayer who the caller is. Filelayer decides what they may do with a
file, serves the bytes with the right headers, and writes the audit event. For
file access that goes through Filelayer, that decision is made in one place,
`packages/core/src/authz.ts`, so you write no ownership checks in route
handlers, no presigned-URL expiry logic, and no per-route access rules of your
own.

**Filelayer is authorization middleware, not row-level security.** It runs in
your application process, in front of Postgres and your bucket. Code that
queries these tables directly does not go through it. The schema does enforce a
set of invariants against *every* writer: cross-tenant grants, cross-project
identities, and delegation that amplifies authority or subject breadth are
refused by constraints and triggers, so a migration or a `psql` session cannot
write them. But there is no RLS policy in `schema.sql`, and a direct `SELECT` is
not filtered by anything. If you need enforcement that survives arbitrary
database clients, use database-level enforcement; Filelayer is the layer above
it, and the two compose.

<!-- doccheck-setup
import { Filelayer } from '@filelayer/core';
const fl = await Filelayer.quickstart({ baseUrl: 'http://localhost:3000' });
const bytes = new TextEncoder().encode('hello');
-->

```ts
// Public avatar
const { url } = await fl.files.put(bytes, { public: true });

// Private, user-owned
const { id } = await fl.files.put(bytes, { owner: 'user_123' });
const file  = await fl.files.get(id, { as: 'user_123' });

// Multi-tenant, role-controlled
await fl.files.put(bytes, { org: 'acme', owner: 'user_123' });

// Shared with an expiry, a password and a download cap, and revocable
const share = await fl.shares.create(id, {
  as: 'user_123', expiresIn: 3600, maxDownloads: 3, password: 'hunter2',
});
await fl.shares.revoke(share.grantId, { as: 'user_123' });   // this link, now

// Shared with a named user, and taken back. `unshare` is the one you want for
// a person: `create()` is not idempotent, so revoking a single grant id you
// happen to be holding can leave an earlier one live.
await fl.shares.create(id, { as: 'user_123', withUser: 'user_456' });
await fl.shares.unshare(id, { as: 'user_123', user: 'user_456' });   // all of it
```

**Start at whichever line matches your problem.** Complexity is incremental:
each tier adds one concept, and no tier makes you pay for a concept you are not
using. → [`docs/QUICKSTART.md`](https://github.com/filelayer/filelayer/blob/main/docs/QUICKSTART.md)

---

## The three layers

```
your application          fl.files.put / get / list, fl.shares, fl.orgs
                          you pass YOUR user id; nothing here knows a uuid
   ───────────────────────────────────────────────────────────────────────
Filelayer                 ONE authorization function answers every request
                          grants, roles, expiry, download caps, revocation
                          the audit event, written in the decision's own
                          transaction so the log cannot disagree with it
   ───────────────────────────────────────────────────────────────────────
your Postgres             9 tables. Filelayer owns the schema, you own the
your bucket               database. Bytes go to S3, R2, B2 or a directory
                          behind one adapter; storage is replaceable
```

Filelayer sits between the two things you already run. It brings no database and
no storage of its own, and **zero runtime dependencies**.

**It is authorization middleware, not row-level security.** It decides for calls
made through it, in your process. The schema additionally refuses cross-tenant
grants, cross-project identities and delegation that amplifies authority from
*every* writer, including a `psql` session — those are constraints and triggers.
But there is no RLS policy in `schema.sql`, and a direct `SELECT` against these
tables is filtered by nothing. Filelayer composes with database-level
enforcement; it does not replace it.

---

## What it is for, and what it is not for

**Worth the overhead when:** files are private, belong to specific people or
tenants, and their permissions change over time. Documents, contracts,
attachments, exports, anything with a share link you might later want back.

**Not worth the overhead when:**

| You need | Use instead | Why |
|---|---|---|
| Public images at CDN volume | a CDN-backed bucket | Default delivery proxies every byte. Redirect delivery (below) removes the proxy but is opt-in and narrow. |
| Video or audio at scale | a CDN / media service | Seeking works — the shipped routes answer `Range` with `206`. But default delivery proxies every byte through your server, and there is no CDN on that path. |
| Browser → storage upload with your server out of the data path, in development | a real bucket | `FsStorage` mints the credential so the client code is identical, but it receives the bytes itself and says so: `via: 'server'`. Only S3 and R2 keep your process out of the path. |
| Resumable or multipart direct upload | presigned S3 directly / tus | One signed PUT, one object. There is no resume. |
| Thumbnails, transforms, format negotiation | Cloudinary / imgix | We have none. |

### Against the two things you would otherwise do

| | Hand-rolled presigned URLs | Supabase Storage | Filelayer |
|---|---|---|---|
| Revoke a link somebody already holds | **No.** A signature is valid until its clock runs out; your options are a short TTL or proxying the bytes yourself | **No.** Signed URLs are signed with an internal key and cannot be revoked | **Yes**, on the next request, transitively through the delegation chain |
| "Everyone in this org may read" | your own join, recomputed on every join and leave | expressible as an RLS policy | one grant row; membership resolves at request time |
| Who accessed this, including refusals | whatever you wrote | not a product feature; Postgres logging is not an access log | hash-chained per tenant, written in the decision's transaction |
| Download cap | your own counter, and the race in it | none | atomic, reserved before the bytes move |
| CDN on the private path | yes, and that is why you cannot revoke | yes, same trade | **no** — the two facts are the same fact |
| Public images at volume | **yes** | **yes** | no. Use a bucket behind a CDN |
| Lines for a public avatar | ~10 | **10** | 16 |

The last two rows are losses and they are not close. We publish the full
comparison, including every case we lose, in
[`ARCHITECTURE-PROGRESSIVE.md`](https://github.com/filelayer/filelayer/blob/main/ARCHITECTURE-PROGRESSIVE.md)
§5. Short version: **for a public avatar, Supabase is 10 lines and Filelayer is
16.** If avatars are your whole problem, use Supabase.

---

## The five security properties

These are the product. Each is enforced in the schema or the authorization
engine, not by convention, and each has tests named after it.

| | Property | What it means in practice |
|---|---|---|
| **P1** | Deny by default | There is no `public` boolean anywhere in the schema. Public delivery is an explicit, revocable, auditable grant row — there is no flag to leave on by accident. **This is a property of the schema, not of your object store: Filelayer cannot make your bucket private, and a public bucket bypasses everything on this page.** That is the one configuration step it cannot do for you — [QUICKSTART §7](https://github.com/filelayer/filelayer/blob/main/docs/QUICKSTART.md). |
| **P2** | No ambient authority | Knowing an object key, a URL or a file id grants nothing. Storage location is never an input to a decision. |
| **P3** | Cross-tenant grants are structurally impossible to write | A grant's `org_id` must equal its file's `org_id`, enforced by the composite foreign key `FOREIGN KEY (file_id, org_id) REFERENCES file (id, org_id)`. No writer — including a migration or a `psql` session — can create a grant pointing at another tenant's file. This is a write-side integrity constraint, not a read filter: it makes the *row* unrepresentable. Reads are scoped by the engine, not by the database. |
| **P4** | A URL never outlives its permission | Every signed URL embeds a grant id and is re-validated on **every** request, transitively through the whole delegation chain. Revocation beats a live URL. |
| **P5** | Every 
audit-logauthorizationfile-sharingfile-uploadmulti-tenantnodejspostgress3saastypescript

What people ask about filelayer

What is filelayer/filelayer?

+

filelayer/filelayer is tools for the Claude AI ecosystem. User files for SaaS. Ownership, access, sharing, expiry, revocation and an audit trail for your application's files — on your own Postgres and your own bucket. Apache-2.0. It has 0 GitHub stars and its last recorded update is dated 2026-10-12.

How do I install filelayer?

+

You can install filelayer by cloning the repository (https://github.com/filelayer/filelayer) or following the README instructions on GitHub. ClaudeWave also provides quick install blocks on this page.

Is filelayer/filelayer safe to use?

+

Our security agent has analyzed filelayer/filelayer and assigned a Trust Score of 95/100 (tier: Verified). See the full breakdown of passed checks and flags on this page.

Who maintains filelayer/filelayer?

+

filelayer/filelayer is maintained by filelayer. The last recorded GitHub activity is dated 2026-10-12, with 0 open issues.

Are there alternatives to filelayer?

+

Yes. On ClaudeWave you can browse similar tools at /categories/tools, sorted by popularity or recent activity.

Deploy filelayer to your cloud

Ship this repo to production in minutes. Each platform spins up its own environment with editable env vars.

Maintain this repo? Add a badge to your README

Drop the badge into your GitHub README to show it's tracked on ClaudeWave. Each badge links back to this page and reflects the live Trust Score.

Featured on ClaudeWave: filelayer/filelayer
[![Featured on ClaudeWave](https://claudewave.com/api/badge/filelayer-filelayer)](https://claudewave.com/repo/filelayer-filelayer)
<a href="https://claudewave.com/repo/filelayer-filelayer"><img src="https://claudewave.com/api/badge/filelayer-filelayer" alt="Featured on ClaudeWave: filelayer/filelayer" width="320" height="64" /></a>

More Tools

filelayer alternatives