Codemation Docs
How-To Guides

Create Custom Credential

Add a typed credential with `defineCredential(...)`, then reuse it from helper-based or class-based nodes.

… stars

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:

  1. define the credential schema
  2. build a typed session from configured values
  3. 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.

  1. Create a custom node
  2. AI Agent node

On this page