VAULT PROVIDER
A vault provider is a TypeScript module that implements secret storage and
retrieval for a custom backend. The file lives in extensions/vaults/ and
exports a vault object that swamp loads at runtime.
For vault configuration and usage (creating vaults, storing secrets, CEL integration), see the Vaults reference.
Export Shape
import { z } from "npm:zod@4";
export const vault = {
type: "@mycollective/my-vault",
name: "My Vault",
description: "Store secrets in my backend",
configSchema: z.object({
region: z.string().describe("Backend region"),
}),
createProvider(
name: string,
config: Record<string, unknown>,
): VaultProvider {
// validate config, return provider instance
},
};Fields
| Field | Type | Description |
|---|---|---|
type |
string |
Unique identifier in @collective/name format. Reserved collectives: swamp, si. |
name |
string |
Human-readable display name. |
description |
string |
Short description shown in swamp vault type search. |
configSchema |
z.ZodTypeAny |
Zod schema that validates the config object passed at vault creation time. |
createProvider |
(name: string, config: Record<string, unknown>) => VaultProvider |
Factory that returns a provider instance. name is the vault's user-assigned name. |
VaultProvider Interface
The object returned by createProvider must implement four methods.
| Method | Signature | Description |
|---|---|---|
get |
(secretKey: string) => Promise<string> |
Retrieve a secret value by key. Throw if the key does not exist. |
put |
(secretKey: string, secretValue: string) => Promise<void> |
Store a secret value, creating or overwriting the key. |
list |
() => Promise<string[]> |
List all secret key names. Values are never returned. |
getName |
() => string |
Return the vault's user-assigned name. |
VaultAnnotationProvider Interface
Vault providers can optionally support secret annotations — metadata (URL,
notes, labels) attached to individual secrets. A provider opts in by
implementing the VaultAnnotationProvider interface alongside VaultProvider.
Swamp detects annotation support at runtime via duck typing: if the object
returned by createProvider has getAnnotation, putAnnotation,
deleteAnnotation, and listAnnotations methods, the provider supports
annotations. No schema or adapter changes are needed.
| Method | Signature | Description |
|---|---|---|
getAnnotation |
(secretKey: string) => Promise<VaultAnnotation | null> |
Retrieve the annotation for a secret. Returns null if unset. |
putAnnotation |
(secretKey: string, annotation: VaultAnnotation) => Promise<void> |
Store an annotation for a secret, creating or overwriting it. |
deleteAnnotation |
(secretKey: string) => Promise<void> |
Remove the annotation for a secret. |
listAnnotations |
() => Promise<Map<string, VaultAnnotation>> |
List all annotations keyed by secret name. |
VaultAnnotation
The annotation value object passed to and returned from the provider methods.
| Field | Type | Required | Description |
|---|---|---|---|
url |
string |
No | URL associated with this secret |
notes |
string |
No | Free-text notes |
labels |
Record<string, string> |
No | Key=value metadata labels |
updatedAt |
Date |
Yes | Timestamp of the last update |
Minimal Example
import { z } from "npm:zod@4";
interface VaultProvider {
get(secretKey: string): Promise<string>;
put(secretKey: string, secretValue: string): Promise<void>;
list(): Promise<string[]>;
getName(): string;
}
const configSchema = z.object({
endpoint: z.string().url().describe("Backend API endpoint"),
token: z.string().describe("Authentication token"),
});
export const vault = {
type: "@mycollective/kv-vault",
name: "KV Vault",
description: "Store secrets in a key-value API",
configSchema,
createProvider(
name: string,
config: Record<string, unknown>,
): VaultProvider {
const { endpoint, token } = configSchema.parse(config);
const headers = { Authorization: `Bearer ${token}` };
return {
async get(secretKey: string): Promise<string> {
const res = await fetch(`${endpoint}/secrets/${secretKey}`, {
headers,
});
if (!res.ok) throw new Error(`Secret "${secretKey}" not found`);
const data = await res.json();
return data.value;
},
async put(secretKey: string, secretValue: string): Promise<void> {
await fetch(`${endpoint}/secrets/${secretKey}`, {
method: "PUT",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ value: secretValue }),
});
},
async list(): Promise<string[]> {
const res = await fetch(`${endpoint}/secrets`, { headers });
const data = await res.json();
return data.keys;
},
getName(): string {
return name;
},
};
},
};Adding Annotation Support
A provider that implements all four VaultAnnotationProvider methods alongside
VaultProvider is recognized as annotation-capable. Detection is via duck
typing — no registration or schema change is required.
import { z } from "npm:zod@4";
interface VaultAnnotation {
url?: string;
notes?: string;
labels: Record<string, string>;
updatedAt: Date;
}
interface VaultProvider {
get(secretKey: string): Promise<string>;
put(secretKey: string, secretValue: string): Promise<void>;
list(): Promise<string[]>;
getName(): string;
}
interface VaultAnnotationProvider {
getAnnotation(secretKey: string): Promise<VaultAnnotation | null>;
putAnnotation(secretKey: string, annotation: VaultAnnotation): Promise<void>;
deleteAnnotation(secretKey: string): Promise<void>;
listAnnotations(): Promise<Map<string, VaultAnnotation>>;
}
const configSchema = z.object({
endpoint: z.string().url().describe("Backend API endpoint"),
token: z.string().describe("Authentication token"),
});
export const vault = {
type: "@mycollective/kv-vault",
name: "KV Vault",
description: "Store secrets in a key-value API",
configSchema,
createProvider(
name: string,
config: Record<string, unknown>,
): VaultProvider & VaultAnnotationProvider {
const { endpoint, token } = configSchema.parse(config);
const headers = { Authorization: `Bearer ${token}` };
return {
async get(secretKey: string): Promise<string> {
const res = await fetch(`${endpoint}/secrets/${secretKey}`, {
headers,
});
if (!res.ok) throw new Error(`Secret "${secretKey}" not found`);
const data = await res.json();
return data.value;
},
async put(secretKey: string, secretValue: string): Promise<void> {
await fetch(`${endpoint}/secrets/${secretKey}`, {
method: "PUT",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ value: secretValue }),
});
},
async list(): Promise<string[]> {
const res = await fetch(`${endpoint}/secrets`, { headers });
const data = await res.json();
return data.keys;
},
getName(): string {
return name;
},
async getAnnotation(
secretKey: string,
): Promise<VaultAnnotation | null> {
const res = await fetch(
`${endpoint}/secrets/${secretKey}/annotation`,
{ headers },
);
if (res.status === 404) return null;
const data = await res.json();
return { ...data, updatedAt: new Date(data.updatedAt) };
},
async putAnnotation(
secretKey: string,
annotation: VaultAnnotation,
): Promise<void> {
await fetch(`${endpoint}/secrets/${secretKey}/annotation`, {
method: "PUT",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({
...annotation,
updatedAt: annotation.updatedAt.toISOString(),
}),
});
},
async deleteAnnotation(secretKey: string): Promise<void> {
await fetch(`${endpoint}/secrets/${secretKey}/annotation`, {
method: "DELETE",
headers,
});
},
async listAnnotations(): Promise<Map<string, VaultAnnotation>> {
const res = await fetch(`${endpoint}/annotations`, { headers });
const data = await res.json();
const map = new Map<string, VaultAnnotation>();
for (const [key, ann] of Object.entries(data)) {
const a = ann as Record<string, unknown>;
map.set(key, {
url: a.url as string | undefined,
notes: a.notes as string | undefined,
labels: (a.labels as Record<string, string>) ?? {},
updatedAt: new Date(a.updatedAt as string),
});
}
return map;
},
};
},
};Packaging
Declare the vault in your extension's manifest:
vaults:
- kv_vault.tsThe file path is relative to the extensions/vaults/ directory within the
extension root.