# Creating an Action (Reference: https://docs.iqra.bot/developers/flowapp/create-action)
An **Action** represents a specific function within your App (e.g., "Book Meeting", "Get Contact", "Send SMS").
In the Script Builder, an Action appears as a **Node**. To create one, you need two files in the same directory:
1. **`CreateContact.json`**: Defines the **UI** (Fields, Validation).
2. **`CreateContact.cs`**: Defines the **Logic** (API Call).
***
1. Defining the UI (JSON Schema) [#1-defining-the-ui-json-schema]
We use standard **JSON Schema** to tell the frontend how to render the form. This allows for complex, dynamic UIs without writing React code.
Create a file named `CreateContact.json` in your Actions folder.
```json title="CreateContact.json"
{
"type": "object",
"title": "Create Contact",
"required": ["email", "firstName"],
"properties": {
"email": {
"type": "string",
"title": "Email Address",
"description": "The primary email of the contact."
},
"firstName": {
"type": "string",
"title": "First Name"
},
"ownerId": {
"type": "string",
"title": "Contact Owner",
"x-fetcher": "GetUsers"
}
}
}
```
The `x-fetcher` property tells the frontend to render a **Dynamic Dropdown** instead of a text input. Read more in the [Data Fetchers](/docs/developers/flowapp/data-fetchers) guide.
***
2. Implementing the Logic (C#) [#2-implementing-the-logic-c]
Create a class `CreateContactAction.cs`.
Iqra AI uses a **C# Source Generator** to compile your JSON schema directly into the DLL for performance.
1. Your class **must** be `partial`.
2. You do **not** need to implement `GetInputSchemaJson()` manually. The compiler does it for you by looking for the `.json` file with the matching name.
Boilerplate & Metadata [#boilerplate--metadata]
Define the identity of the action. Note the `partial` keyword.
```csharp
// [!code word:partial]
public partial class CreateContactAction : IFlowAction
{
private readonly HubSpotApp _app;
public CreateContactAction(HubSpotApp app)
{
_app = app;
}
public string ActionKey => "CreateContact";
public string Name => "Create Contact";
public string Description => "Adds a new contact to CRM.";
// GetInputSchemaJson() is auto-generated!
// Do not write it yourself.
}
```
Output Ports [#output-ports]
Define the possible exit paths for the node in the script graph.
```csharp
public IReadOnlyList GetOutputPorts()
{
return new List
{
new ActionOutputPort { Key = "success", Label = "Success" },
new ActionOutputPort { Key = "duplicate", Label = "Already Exists" },
new ActionOutputPort { Key = "error", Label = "Error" }
};
}
```
Execution Logic [#execution-logic]
Implement `ExecuteAsync`. This is where you parse inputs and call the API.
**Note:** The `resolvedInput` passed here has already had all Scriban templates (e.g., `{{ user.name }}`) resolved to their final string values.
```csharp
public async Task ExecuteAsync(
JsonElement input,
BusinessAppIntegration? integration)
{
try
{
// 1. Setup Client
var apiKey = integration?.DecryptedFields["ApiKey"];
var client = _app.CreateClient(apiKey);
// 2. Parse Input (System.Text.Json)
var email = input.GetProperty("email").GetString();
var name = input.GetProperty("firstName").GetString();
// 3. Call API
var payload = new { properties = new { email, firstname = name } };
var response = await client.PostAsJsonAsync("/crm/v3/objects/contacts", payload);
// 4. Handle Specific Logic Branches
if (response.StatusCode == HttpStatusCode.Conflict)
{
return ActionExecutionResult.SuccessPort("duplicate", new { msg = "Contact exists" });
}
if (response.IsSuccessStatusCode)
{
var data = await response.Content.ReadFromJsonAsync();
// Return data to the context
return ActionExecutionResult.SuccessPort("success", data);
}
return ActionExecutionResult.Failure("API_ERROR", response.ReasonPhrase);
}
catch (Exception ex)
{
return ActionExecutionResult.Failure("EXCEPTION", ex.Message);
}
}
```
3. Public Actions (Optional Auth) [#3-public-actions-optional-auth]
If your action does not require authentication (e.g., "Get Weather"), you can mark it as public.
```csharp
public bool RequiresIntegration => false;
public async Task ExecuteAsync(
JsonElement input,
BusinessAppIntegration? integration) // integration will be null
{
// ... logic without API key
}
```