> ## Documentation Index
> Fetch the complete documentation index at: https://www.c1.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Create and test functions

> Learn how to create, write, test, and publish C1 functions using the built-in code editor.

<Warning>
  **Early access.** This feature is in early access, which means it's undergoing ongoing testing and development while we gather feedback, validate functionality, and improve outputs. Contact the C1 Support team if you'd like to try it out or share feedback.
</Warning>

This guide walks you through creating your first function, from a simple "hello world" to accessing C1 data and calling external APIs.

## Use the copilot to write function code

Not sure where to start with TypeScript or the C1 API? The built-in AI code assistant can generate a working function from a plain-language description of what you want it to do — no TypeScript expertise required. Since functions start as drafts, you can try out the generated code, run it, and iterate safely before publishing.

To get started, click **Create with AI** when creating a new function, or click **Edit with AI** in the code editor of an existing function draft. Describe what you want your function to do, and the AI assistant will generate code to get you started. You can then edit the code as needed, run it with test inputs, and publish when you're ready.

## Step 1: Set up a new function

<Steps>
  <Step>
    Navigate to **Workflows** > **Functions**.
  </Step>

  <Step>
    Click **New function**.
  </Step>

  <Step>
    Enter a name and optional description, then click **Create**.
  </Step>

  <Step>
    You're taken to the detail page with a built-in code editor (Monaco, TypeScript mode). The editor includes two files: `main.ts` for your function code and `main.test.ts` for tests.
  </Step>
</Steps>

## Step 2: Write function code

Every function must export a `main` function that accepts `input` and returns a result. Both input and output are JSON objects.

<Tip>
  Visit the [functions reference](/product/admin/functions-reference) for detailed documentation on SDK namespaces, configuration options, and troubleshooting advice.
</Tip>

### Basic structure

```typescript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
import { JSONObject } from "@c1/functions-sdk";

export default async function main(input: JSONObject): Promise<JSONObject> {
    return {
        message: "Hello from my first function!",
        timestamp: new Date().toISOString()
    };
}
```

### Use input parameters

Accept input to make your function interactive:

```typescript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
import { JSONObject } from "@c1/functions-sdk";

export default async function main(input: JSONObject): Promise<JSONObject> {
    const name = input.name as string || "World";

    return {
        greeting: `Hello, ${name}!`,
        timestamp: new Date().toISOString()
    };
}
```

Test with input:

```json theme={"theme":{"light":"css-variables","dark":"css-variables"}}
{"name": "Alice"}
```

Output:

```json theme={"theme":{"light":"css-variables","dark":"css-variables"}}
{
  "greeting": "Hello, Alice!",
  "timestamp": "2026-02-09T12:07:45.272Z"
}
```

### Access C1 data

Use the pre-authenticated `sdk` object to query data from your C1 tenant.

<Note>
  Functions authenticate to the C1 API as a service principal. Before publishing, link a service principal whose roles match what your function needs to do. See [Step 5: Link a service principal for API access](#step-5-link-a-service-principal-for-api-access) below.
</Note>

### List users

```typescript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
import { JSONObject, sdk } from "@c1/functions-sdk";

export default async function main(input: JSONObject): Promise<JSONObject> {
    const users = await sdk.user.list();

    return {
        userCount: users.userServiceListResponse?.list?.length ?? 0,
    };
}
```

#### Get a specific user

```typescript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
import { JSONObject, sdk } from "@c1/functions-sdk";

export default async function main(input: JSONObject): Promise<JSONObject> {
  const userId = input.userId as string;

  const userResponse = await sdk.user.get({ id: userId });

  return {
    success: true,
    user: userResponse.user,
  };
}
```

#### Search users

```typescript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
import { JSONObject, sdk } from "@c1/functions-sdk";

export default async function main(input: JSONObject): Promise<JSONObject> {
  const searchResponse = await sdk.userSearch.search({
    query: "Engineering",
  });

  return {
    success: true,
    results: searchResponse.list,
  };
}
```

For a complete list of SDK operations, see [SDK namespaces](/product/admin/functions-reference#sdk-namespaces).

### Use secrets and configuration

Store API keys and configuration values securely in your function's config.

#### Set secrets in the UI

<Steps>
  <Step>
    Navigate to your function and click **Edit config** (in the more menu).
  </Step>

  <Step>
    In the **Secrets** section, add key/value pairs.
  </Step>

  <Step>
    Click **Save**.
  </Step>
</Steps>

#### Access secrets in code

```typescript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
import { JSONObject, functions } from "@c1/functions-sdk";

export default async function main(input: JSONObject): Promise<JSONObject> {
    const config = await functions.getConfig();
    console.log('got config', config);

    return {
      secretNumber: config.secrets.SECRET_NUMBER
    };
}
```

### Call external APIs

<Warning>
  External domains must be added to **Outbound Network Access** in your function's config before you can call them. See [Outbound network access](/product/admin/functions-reference#outbound-network-access).
</Warning>

```typescript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
import { JSONObject, functions } from "@c1/functions-sdk";
import { Octokit } from "npm:@octokit/rest@1.2.3";

export default async function main(input: JSONObject): Promise<JSONObject> {
    // 1. Get secrets from config
    const config = await functions.getConfig();
    const token = config.secrets["GITHUB_TOKEN"];

    if (!token) {
        throw new Error("GitHub Token not found in config");
    }

    // 2. Initialize external API client
    const octokit = new Octokit({ auth: token });

    try {
        // 3. Make API calls
        const { data: user } = await octokit.rest.users.getAuthenticated();

        console.log(`Authenticated as: ${user.login}`);
        return {
            username: user.login,
            fullName: user.name || "No name set",
            publicRepos: user.public_repos,
        };
    } catch (error) {
        console.error("Failed to fetch user profile:", error);
        throw error;
    }
}
```

## Step 3: Handle errors

Functions should handle errors gracefully and return useful information for debugging.

### Try-catch pattern

```typescript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
import { JSONObject, sdk } from "@c1/functions-sdk";

export default async function main(input: JSONObject): Promise<JSONObject> {
  try {
    const userResponse = await sdk.user.get({ id: input.userId as string });
    return { success: true, user: userResponse.user };
  } catch (error) {
    console.error("Failed to get user:", error);
    return {
      success: false,
      error: error.message,
    };
  }
}
```

### Input validation

```typescript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
import { JSONObject } from "@c1/functions-sdk";

export default async function main(input: JSONObject): Promise<JSONObject> {
  // Validate required fields
  if (!input.userId) {
    return { error: "userId is required" };
  }

  const userId = input.userId as string;

  // Your logic here
  return { success: true };
}
```

## Step 4: Test your function

There are two ways to test your function: manual invocation and automated tests.

### Manual invocation

<Steps>
  <Step>
    Click **Run draft** to invoke your function with test JSON input.
  </Step>

  <Step>
    Provide test input in the JSON editor, for example: `{}`
  </Step>

  <Step>
    View the output and logs in the invocation details drawer.
  </Step>
</Steps>

### Automated tests

New functions include a `main.test.ts` file with a starter template. Write tests using the `@c1/test` framework:

```typescript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
// main.test.ts
import { JSONObject } from "@c1/functions-sdk";
import { test } from "@c1/test";

export default function registerTests({handler}: {handler: (input: JSONObject) => Promise<JSONObject>}) {
  test("returns a greeting", async ({ equal }) => {
    const result = await handler({ name: "Alice" });
    equal(result.greeting, "Hello, Alice!");
  });

  test("uses default name", async ({ ok }) => {
    const result = await handler({});
    ok(result.greeting);
  });
}
```

The framework injects your `main` function as `handler`, so each test can call it with different inputs.

<Steps>
  <Step>
    Click **Test draft** (or **Run tests** when viewing published code) to execute your tests.
  </Step>

  <Step>
    View individual test results in the dialog. Each `test()` call becomes its own result with a pass/fail status.
  </Step>

  <Step>
    Click on a test result to expand assertion details and logs.
  </Step>
</Steps>

For the full assertion API and more examples, see [Testing with @c1/test](/product/admin/functions-reference#testing-with-c1test).

## Step 5: Link a service principal for API access

Functions authenticate to the C1 API as a linked service principal. Its role bindings determine what your function can do — read-only, write, or anything in between.

<Steps>
  <Step>
    If you don't already have a suitable service principal, create one. Navigate to **Directory** > **Service principals** > **New**, give it a descriptive name, and assign roles based on what your function needs (for example, **Read-Only Administrator** for a function that only reads, or **Super Administrator** for a function that creates or updates resources).
  </Step>

  <Step>
    Open your function and click **...** (the more actions menu) > **Link service principal**.
  </Step>

  <Step>
    Pick the service principal from the list and click **Save**.
  </Step>
</Steps>

The function authenticates as that service principal on its next invocation. To change what it can do later, edit the service principal's roles — no function redeploy needed.

<Note>
  **Existing functions.** Functions that existed before this change have been migrated automatically and linked to a service principal with read-only access. No action is required to keep them running. To grant write access, link them to a different service principal using the steps above.
</Note>

## Step 6: Publish your function

<Steps>
  <Step>
    Click **Save draft** to commit your changes.
  </Step>

  <Step>
    Click **Publish** to make your function available for use across C1.
  </Step>
</Steps>

Once published, your function is available for use in [automations](/product/admin/functions-automations) or via manual invocation.

## Best practices for writing functions

Follow these patterns to write maintainable, debuggable functions.

### Use TypeScript interfaces

Define clear input and output types for better code quality:

```typescript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
import { JSONObject } from "@c1/functions-sdk";

interface Input {
  userId: string;
  action: string;
}

interface Output {
  success: boolean;
  message: string;
}

export default async function main(input: JSONObject): Promise<JSONObject> {
  const { userId, action } = input as unknown as Input;

  // Your logic here

  return { success: true, message: "Done" } as Output;
}
```

### Validate inputs

Always validate required parameters before proceeding:

```typescript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
if (!input.userId) {
  return { error: "userId is required" };
}
```

### Use console logging

Log important steps for debugging. Logs are captured and viewable in the UI:

```typescript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
console.log("Processing user:", userId);
console.log("API response:", response);
```

### Handle errors gracefully

Wrap external API calls in try-catch blocks:

```typescript theme={"theme":{"light":"css-variables","dark":"css-variables"}}
try {
  const result = await externalApiCall();
  return { success: true, result };
} catch (error) {
  console.error("External API failed:", error);
  return { success: false, error: error.message };
}
```

### Keep functions focused

Each function should do one thing well. Create multiple functions for complex workflows.
