---
title: Custom Storage
description: Publish a third-party storage integration using the standard ChatJS registry contract
---

Your item installs one entry point at `~/lib/storage-provider.ts`. Export a
synchronous `createStorageAdapter(options)` function returning a Files SDK
`Adapter`. You can wrap an existing Files SDK provider or implement its adapter
interface. Your provider ID does not need to exist in the built-in catalog.

## Author source

For an S3-based integration with your own configuration shape:

```typescript
import { s3 } from "files-sdk/s3";

export function createStorageAdapter(options: {
  bucket: string;
  region: string;
}) {
  return s3(options);
}
```

The receiving app generates `storage-options.ts` with
`storageOptions satisfies Parameters<typeof createStorageAdapter>[0]`.
Its shared file layer passes those options to your factory and wraps the returned
adapter in `Files`. Type-check the installed app to verify both contracts.

Read custom credentials inside your adapter from its documented environment
variables. Creating the factory should not upload files or perform other remote
operations. The installer reads metadata, without executing your source.

## Declare the registry item

Use standard shadcn files and dependencies, plus this `meta.chatjs` shape:

```json
{
  "contractVersion": 1,
  "kind": "storage",
  "id": "acme-storage",
  "configKeys": ["bucket", "region"],
  "envRequirements": [{ "options": [["ACME_STORAGE_TOKEN"]] }],
  "optionalEnv": []
}
```

`configKeys` supplies configuration hints for the CLI prompt. TypeScript checks
the actual option shape. Each environment requirement must be satisfied. Its
`options` are alternatives, with every variable in one alternative required.
Declare variables your implementation actually reads. For the S3 wrapper above,
use S3's configuration and credential requirements instead of the illustrative
`ACME_STORAGE_TOKEN`.

Declare `files-sdk` and your provider's peer packages under `dependencies`.
List the source file with `type: "registry:file"` and
`target: "~/lib/storage-provider.ts"`. Extra implementation files and shared
registry dependencies are supported through standard shadcn fields.
The CLI owns `lib/storage-options.ts`, so your item should not install that file.

## Install and verify

Publish the generated JSON through your registry, then run:

```bash
npx @chat-js/cli@latest create my-app --yes \
  --storage-provider https://example.com/r/acme-storage.json \
  --storage-config '{"bucket":"uploads","region":"us-east-1"}'
```

Namespace addresses such as `@acme/storage` also work when configured in
`components.json` at the resolution location. A complete URL works before the
new app has a namespace configuration.

Run the receiving app's type checks and `check-env`, then verify upload,
download, and deletion with your adapter. Metadata validation alone cannot prove
that credentials, network access, or storage operations work.

This selection flow applies to app creation. `chat-js add` and `sync` still
handle tools. Switching a deployed app's storage requires a separate object
migration and configuration change, as described in [File Storage](../storage).
