Build a CredentialProvider
Implement, test, package, and privately deploy custom credential lifecycle code.
This tutorial creates a provider package that mints short-lived material from one bootstrap Credential. Provider code executes as an ordinary private Driver with declared operations, egress, configuration, and recovery behavior.
What you will build
The finished package will:
- accept a non-secret service URL during provider setup;
- bind one
bootstrap-tokenCredential; - accept a per-Credential account identifier;
- mint and verify short-lived tokens;
- expose setup metadata for an intuitive Console form.
Project files
constal.credential-provider.json
package.json
schema.json
index.ts
test/provider.test.tsUse an exact @constal/sdk dependency and do not include node_modules, lifecycle scripts, or development dependencies in the archive.
Define the provider
import { credentialProvider } from "@constal/sdk";
export default credentialProvider({
id: "example-token",
version: "1.0.0",
displayName: "Example service token",
description: "Creates short-lived tokens for an Example account.",
documentationUrl: "https://example.com/docs/constal",
setup: {
service: "Example",
useCase: "Run unattended Example automation.",
credentialSlots: {
"bootstrap-token": {
title: "Bootstrap token",
description: "Create this in Example administration settings.",
input: "secret"
}
}
},
credentialSlots: ["bootstrap-token"],
configSchema: {
type: "object",
required: ["clientId"],
additionalProperties: false,
properties: {
clientId: { type: "string", title: "Application client ID" }
}
},
credentialConfigSchema: {
type: "object",
required: ["accountId"],
additionalProperties: false,
properties: {
accountId: { type: "string", title: "Account ID" }
}
},
rotation: {
mode: "auto", intervalMs: null, overlapMs: 60000,
maxAgeMs: null, refreshBeforeMs: 300000
},
egress: { rules: [{
operations: ["mint"], origin: "https://api.example.com",
pathPrefix: "/oauth/tokens", methods: ["POST"]
}] },
async mint(request, context) {
const config = context.config as { clientId: string };
const credential = request.configuration as { accountId: string };
const bootstrap = await context.secret("bootstrap-token");
const response = await context.egress({
url: "https://api.example.com/oauth/tokens",
method: "POST",
headers: {
authorization: `Bearer ${bootstrap.value}`,
"content-type": "application/json"
},
bodyBase64: btoa(JSON.stringify({
client_id: config.clientId,
account_id: credential.accountId
})),
maximumResponseBytes: 65536,
maximumRedirects: 0
});
if (response.status !== 201) throw new Error("Example token exchange failed");
const token = JSON.parse(atob(response.bodyBase64));
return {
material: token.access_token,
expiresAt: Date.now() + token.expires_in * 1000
};
}
});Never declare secret-shaped fields in configSchema. Secrets belong in named bootstrap Credential slots.
Test the contract
Test schema rejection, every operation result, retry classification, expiry handling, provider-private state, and egress boundaries. Use fixed clocks and protocol fixtures; do not call the real provider in unit tests.
Package and upload
Create a ZIP or tar.gz whose root contains the manifest, exact package file, schema, entrypoint, and source. In Add provider, choose Install a custom provider, upload the archive or provide a public repository plus full commit SHA. The managed builder type-checks, bundles, probes, and publishes the package privately to the tenant catalog.
Verify
After the build completes, the Console should continue directly into provider setup. Confirm that schema titles become fields, slot help is visible, secret material is collected separately, and the installed provider can create one test Credential.
Continue with Configuration schemas, Authorization lifecycle, and Package format.