Create Custom Credential
Add a typed credential with `defineCredential(...)`, then reuse it from helper-based or class-based nodes.
Create Custom Credential
This guide starts with defineCredential(...), which keeps the common case short while preserving the full typed CredentialType<...> contract underneath.
Use this guide when
Reach for a custom credential type when:
- a node depends on a non-standard API key or token
- users need structured public fields such as
baseUrl,endpoint, or tenant IDs - you want a real
test(...)step instead of checking environment variables manually
Example scenario
Imagine you are building a my-service.apiKey credential for a custom REST API.
The goal is:
- define the credential schema
- build a typed session from configured values
- let nodes request the credential through a named slot
Step 1: define the credential
import { defineCredential } from "@codemation/core";
export const myServiceCredential = defineCredential({
key: "my-service.apiKey",
label: "My Service API key",
public: {
baseUrl: "string",
},
secret: {
apiKey: "password",
},
async createSession({ publicConfig, material }) {
return {
baseUrl: publicConfig.baseUrl,
apiKey: material.apiKey,
};
},
async test({ publicConfig, material }) {
return {
status: publicConfig.baseUrl.length > 0 && material.apiKey.length > 0 ? "healthy" : "failing",
testedAt: new Date().toISOString(),
};
},
});Step 2: register the credential type
Register it from codemation.config.ts:
import { defineCodemationApp } from "@codemation/host";
export default defineCodemationApp({
credentials: [myServiceCredential],
workflows: [supportWorkflow],
});Step 3: let a node request the credential
Helper-defined nodes can declare the credential directly:
import { defineNode } from "@codemation/core";
export const syncCustomerNode = defineNode({
key: "sync-customer",
title: "Sync customer",
input: {
field: "string",
},
credentials: {
myService: myServiceCredential,
},
async executeOne({ input }, { credentials }) {
const session = await credentials.myService();
return {
...input,
syncedWith: session.baseUrl,
};
},
});Class-based nodes can still use the lower-level getCredentialRequirements() + ctx.getCredential<T>() pattern when you need it.
Full example outcome
Once this is wired up, operators can bind a concrete credential instance to the myService slot and your node gets a typed runtime session instead of a loose secret string.
OAuth2 credentials
For credentials that go through the OAuth2 redirect flow (Microsoft Graph, Slack, GitHub, Notion, …), declare the authorize and token URLs directly on the credential's auth definition. The host's OAuth2ProviderRegistry treats those URLs as templates — substitute {publicFieldKey} placeholders against the credential's resolved public config at connect time, URL-encoded.
import type { AnyCredentialType } from "@codemation/core";
export const msGraphOAuthCredentialType: AnyCredentialType = {
definition: {
typeId: "msgraph-oauth",
displayName: "Microsoft Graph OAuth",
publicFields: [
{ key: "clientId", label: "Client ID", type: "string", required: true, order: 0 },
{ key: "tenantId", label: "Tenant ID", type: "string", required: true, order: 1 },
],
secretFields: [{ key: "clientSecret", label: "Client secret", type: "password", required: true, order: 2 }],
supportedSourceKinds: ["db", "env", "code"],
auth: {
kind: "oauth2",
// providerId is a free-form label for telemetry / DB rows / Better Auth provider naming.
// It is NOT used for any registry lookup — URLs come from authorizeUrl / tokenUrl below.
providerId: "microsoft",
// {tenantId} is substituted from publicConfig at connect time, URL-encoded.
authorizeUrl: "https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize",
tokenUrl: "https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token",
scopes: ["openid", "offline_access", "User.Read", "Mail.Read"],
},
},
async createSession(args) {
/* build a typed session from the saved access_token / refresh_token */
},
async test(args) {
/* call a cheap endpoint to verify the credential works */
},
};The host stores post-callback OAuth material with the spec's snake_case keys (access_token, refresh_token, expiry, scope, token_type). Read those keys (not camelCase) inside createSession and test.
Plugins do not need to edit OAuth2ProviderRegistry to add a provider — only google keeps a hardcoded shortcut for backwards compatibility. New plugins should always use the URL-template form so they stay self-contained.
The redirect URI returned to providers normalizes loopback IPs (127.0.0.1, [::1]) to localhost, since Azure AD and several other providers reject raw loopback IPs in registered redirect URIs.