Beignet is experimental alpha software. The 0.0.x package line is for early
evaluation, and APIs may change between releases while the framework settles.
Local filesystem storage provider for Beignet.
The provider installs the app-facing ctx.ports.storage port. Use it for
local development, tests that need durable files, and small deployments where a
filesystem-backed object store is enough.
createLocalStorageProvider(...) returns the stable LocalStorageProvider
type. LocalStorageConfig describes its validated config; the Zod schema
remains internal.
bun add @beignet/provider-storage-local @beignet/core
import { createLocalStorageProvider } from "@beignet/provider-storage-local";
import { createServer } from "@beignet/core/server";
const server = await createServer({
ports: basePorts,
providers: [createLocalStorageProvider()],
context: ({ ports }) => ({ ports }),
routes,
});
Environment variables:
| Variable | Description |
|---|---|
STORAGE_ROOT |
Directory where objects are written. Defaults to storage/app. |
STORAGE_PUBLIC_BASE_URL |
Optional base URL returned by publicUrl(...) for public objects. |
beignet doctor --strict checks that installed local storage providers are
registered in server/providers.ts.
STORAGE_PUBLIC_BASE_URL may be an absolute URL such as
https://assets.example.com or an app-relative path such as /storage.
The provider only returns URLs. If you use an app-relative path in a Next.js
app, serve public objects with createStorageRoute:
// app/storage/[...key]/route.ts
import { createStorageRoute } from "@beignet/next";
import { getServer } from "@/server";
export const { GET, HEAD } = createStorageRoute(
async () => (await getServer()).ports.storage,
{
basePath: "/storage",
},
);
import { createLocalStorage } from "@beignet/provider-storage-local";
const storage = createLocalStorage({
root: "storage/app",
publicBaseUrl: "/storage",
});
The provider contributes ctx.ports.storage, the standard Beignet
StoragePort. It has no provider-specific escape hatch; use the port for
reads, writes, public URLs, and metadata.
The same StoragePort works with local files, memory tests, and cloud object
stores:
await ctx.ports.storage.put("avatars/user_123.png", avatarBytes, {
contentType: "image/png",
visibility: "public",
});
const object = await ctx.ports.storage.get("avatars/user_123.png");
const url = await ctx.ports.storage.publicUrl("avatars/user_123.png");
Objects are written below root using their storage key. Object metadata is
stored in a .beignet-storage-meta sidecar directory below the same root.
Storage keys use the shared @beignet/core/ports contract: no empty strings,
control characters, empty path segments, leading or trailing /, backslashes,
or . / .. path segments. The .beignet-storage-meta path segment is an
additional local-provider restriction because it stores sidecar metadata.
When ctx.ports.devtools is installed, the provider records storage
operations under the storage watcher. Events include operation name, key,
duration, object size, visibility, and whether a lookup hit. Object bodies are
never recorded.
The provider creates directories as needed and throws for invalid object keys, metadata errors, filesystem permission failures, and failed reads or writes. Path traversal is rejected before touching the filesystem.
Use this provider for local development or integration tests that need durable
files. Use an app-owned fake or memory StoragePort for use-case tests that
only assert storage intent.
Only use local storage in deployments where the filesystem is durable and shared
with every process that needs the objects. Serverless and horizontally scaled
apps should usually use @beignet/provider-storage-s3 or another object store.
MIT