Filelayer

The usage report: what your deployment did, in a file you control

Filelayer sends nothing, ever. There is no telemetry and no background request, and this command does not add one.

filelayer usage-report reads your own database and writes one week of your deployment's usage, as aggregate counts, to a JSON file on your disk. You read the file. You decide whether to share it, and if you do, the place to share it is an issue on the repository. A report you share is what lets us count your deployment as a verified adopter: Filelayer runs inside your application, so without one we cannot tell that your deployment exists at all.

The file contains no file names, no user, org, file or grant identifiers, no IP addresses and no event contents. It contains the fields listed on this page and nothing else: its shape is usage-report.schema.json, which ships in the package, and the report is checked against it before it is written.

Run it

npx filelayer usage-report

It reads DATABASE_URL from the environment, as the rest of the CLI does, and writes ./filelayer-usage-report-<first 8 characters of install_id>-<week>.json for the last complete ISO week (UTC). Then it prints the same JSON, so what you see is what the file holds.

OptionWhat it does
--week YYYY-WwwReport that ISO week instead, for example --week 2026-W44.
--out <path>Write the file there instead.
--printPrint the report and write no file.

From code, the same report is buildUsageReport(db, { week }), where db is your pg.Pool or anything with the same query(), and validateUsageReport(report) returns the list of ways a value differs from the schema (empty when it matches). Here on a throwaway instance:

import { buildUsageReport, validateUsageReport, isoWeekOf } from '@filelayer/core';

await fl.files.put(new TextEncoder().encode('hello'), { org: 'acme', owner: 'ana' });

const report = await buildUsageReport(fl.store.db, { week: isoWeekOf(new Date()) });
assert.deepEqual(validateUsageReport(report), []);
assert.equal(report.ops.upload, 1);
assert.equal(report.errors, null);

What a report looks like

One week of a deployment that stored one file and read it once:

{
  "format": 1,
  "install_id": "f6528ef5-5808-45f8-b186-42687ff837cb",
  "week": "2026-W44",
  "lib_version": "<the version you installed>",
  "node_major": 22,
  "storage_kind": ["memory"],
  "schema_version": 16,
  "days_active": 1,
  "ops": {
    "upload": 1,
    "read_allowed": 1,
    "read_denied": 0,
    "share_create": 0,
    "share_revoke": 0,
    "unshare": null,
    "direct_upload": 0,
    "job_run": 0
  },
  "errors": null,
  "first_success_day": 0,
  "scale_bucket": { "files": "1-10", "orgs": "1-10" },
  "notes": ["errors_not_measured", "unshare_not_recorded"]
}

null means "not measured", never zero

A field the report cannot measure honestly is null, and notes says why with one of six fixed codes. A 0 is a measured zero: the thing was looked for and did not happen. Reading a null as 0 is the one mistake this format is built to prevent.

NoteWhen it appearsFields that are null because of it
errors_not_measuredalways, in format 1errors
unshare_not_recordedalways, in format 1ops.unshare
audit_trimmeda trim (trimAuditChain()) or an erasure (eraseActorFromAuditChain()) removed audit events from that week, so any count read from the audit log would be an undercountevery ops count except job_run, and days_active; also first_success_day when the first allowed read is no longer in the log
no_filesthe database has no file rowsstorage_kind
no_successful_readno file has ever been read successfullyfirst_success_day
lib_version_unknownthe installed package's package.json has no readable versionlib_version

What each field means

FieldMeaning
formatThe version of this schema, 1. A reader that does not know the number should not guess.
install_idA random UUID (version 4), generated on the first report and stored in filelayer_install. It is linked to nothing else in your database. It stays the same from week to week, which is what lets two reports be read as the same deployment.
weekThe ISO week reported, in UTC, as YYYY-Www.
lib_versionThe installed version of @filelayer/core.
node_majorThe major version of Node.js that ran the report.
storage_kindEvery kind of storage your file rows record, as a list drawn from memory, fs, s3, r2 and other.
schema_versionThe highest schema version applied to the database.
days_activeHow many days of that week, 0 to 7, have at least one audit event or one usage_daily row.
opsCounts for that week, below.
errorsNot measured in format 1, so always null.
first_success_dayDays from the schema being created to the first file read that was allowed, ever (not only in that week). 0 means the same day.
scale_bucket.files, scale_bucket.orgsHow many file rows and orgs the database holds when the report runs, as one of 0, 1-10, 11-1k, 1k-100k, >100k. A range, not the number.
notesThe reasons for every null, from the table above.

The ops counts come from rows the library already writes. Each is one audit action and decision, counted over the week:

ops fieldCounted from
uploadfile.create, allowed. Every file created, direct uploads included.
read_allowedfile.read, allowed. A redirect delivery is counted once, on its file.read.
read_deniedfile.read, denied, and file.access, denied. A file operation by an identity your deployment has never seen is refused before any file is looked up and recorded as file.access; most of those are reads, and the audit row cannot say which ones are not, so all of them are counted here.
share_creategrant.create, allowed.
share_revokegrant.revoke, allowed.
unshareAlways null (unshare_not_recorded): unshare() writes one grant.revoke per grant it removes and nothing of its own, so its revocations are inside share_revoke and cannot be counted apart.
direct_uploadfile.upload_complete, allowed: direct uploads that finished.
job_runRows in job_run that finished during the week: background jobs, run directly or through runMaintenance(). This one is read from job_run, not from the audit log, so a trim never makes it null.

What it does not measure

Both are in LIMITATIONS.md.