# ByteChef Developer Guide: Architecture URL: /developer-guide/architecture Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/developer-guide/architecture.mdx How ByteChef works inside - the Atlas workflow engine, configuration vs execution, triggers and webhooks, the message broker, and how the Component SDK maps into the runtime. This page explains how ByteChef executes workflows under the hood. It is written for contributors and component authors who want to understand the moving parts: the **Atlas** engine that runs jobs, the **trigger and webhook** machinery that starts them, the **message broker** that connects everything, and the **Component SDK** whose definitions become both editor UI and executable tasks. If you only want to *build* a component, start with the [Developer Guide](/developer-guide) and the [Component Specification](/developer-guide/component-specification/component); come back here when you want to know what happens after you hit Run. ## The big picture [#the-big-picture] ByteChef is a modular Spring Boot application. Every box below is a set of Gradle modules under `server/libs/`; the arrows are either direct calls or messages on the broker. Three properties of this design are worth internalizing early: 1. **Configuration and execution never mix.** A `Workflow` definition is immutable design-time data; a `Job` is runtime state referencing it. They live in different modules with different repositories. 2. **Everything between coordinator and worker is a message.** Atlas components communicate exclusively through named broker routes, so the same code runs as one JVM (in-memory broker) or as separately scaled services (Redis/Kafka/RabbitMQ) with zero code changes. 3. **Components are data first, code second.** A component definition is a declarative object the platform maps into editor UI, REST models, and task handlers. The `perform` function is just one (optional!) attribute of it. ## Module layout [#module-layout] The server modules form strict layers - each layer only depends downward: | Area | Path | Responsibility | | ---------------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | Atlas engine | `server/libs/atlas/` | Generic workflow kernel: definitions, jobs, coordinator, worker, file storage | | Task dispatchers | `server/libs/modules/task-dispatchers/` | Control flow: `condition`, `loop`, `each`, `map`, `parallel`, `fork-join`, `branch`, `on-error`, `subflow`, `approval`, `suspend`, `terminate` | | Components | `server/libs/modules/components/` | The 200+ integrations built with the Component SDK | | Platform | `server/libs/platform/` | Everything that makes Atlas a product: component registry, connections, triggers, webhooks, scheduler, OAuth2, data storage | | Automation | `server/libs/automation/` | The workspace/project/deployment domain on top of the platform | | Embedded (EE) | `server/ee/libs/embedded/` | The embedded iPaaS domain (integrations, connected users) | | Core | `server/libs/core/` | Cross-cutting utilities: message broker abstraction, expression evaluator, encryption, file storage, tenant | | Component SDK | `sdks/backend/java/component-api/` | The definition interfaces and `ComponentDsl` component authors use | Most modules follow an `-api` / `-service` (or `-impl`) / `-config` split: interfaces and domain objects in `-api`, implementations in `-service`, Spring wiring in `-config`. Depending on the `-api` module alone is what lets a consumer be wired against a different implementation without touching its own code. ## Configuration: workflows as data [#configuration-workflows-as-data] The design-time side lives in `atlas-configuration`. A **`Workflow`** is parsed from JSON (or YAML) by `WorkflowReader` into `WorkflowTask` objects. Where definitions are stored is pluggable via `WorkflowRepository` implementations: JDBC (the default for user workflows), classpath, filesystem, and Git. ```json { "label": "Daily Invoice Capture", "inputs": [ { "name": "hourToRun", "type": "integer", "required": true } ], "triggers": [ { "name": "trigger_1", "type": "schedule/v1/everyDay", "parameters": { "hour": "${hourToRun}", "minute": 0 } } ], "tasks": [ { "name": "searchEmail_1", "type": "googleMail/v1/searchEmail", "parameters": { "q": "invoice has:attachment" } } ] } ``` Two things in this JSON drive everything else: * **The task `type` string** - `component/vVERSION/operation`. It is parsed by `WorkflowNodeType.ofType("googleMail/v1/searchEmail")` into `(name=googleMail, version=1, operation=searchEmail)` and doubles as the task-handler registry key at execution time. * **`${...}` expressions** - evaluated lazily against the accumulated job context, not at save time (see [Expression evaluation](#expression-evaluation)). Above Atlas, the automation layer (`automation-configuration`) adds the product concepts: **projects** version workflows, **deployments** bind a published project version to an environment, per-workflow **connections** and **inputs**. Enabling a deployment workflow is what activates its triggers. ## Execution: the Atlas engine [#execution-the-atlas-engine] The runtime side lives in `atlas-execution` (state), `atlas-coordinator` (orchestration), and `atlas-worker` (task execution). ### Core domain [#core-domain] | Object | Role | Lifecycle | | --------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | `Job` | One run of one workflow | `CREATED → STARTED → COMPLETED / FAILED / STOPPED`; tracks `currentTask` index | | `TaskExecution` | One run of one task | `CREATED → STARTED → COMPLETED / FAILED / CANCELLED`; sub-tasks carry a `parentId` | | `Context` | The evaluation context: accumulated outputs keyed by task name | Pushed as a stack per job / task execution | | `Counter` | Outstanding-children counter for fan-out joins (`parallel`, `fork-join`, ...) | | Task outputs, job outputs, and context values are **not stored inline in PostgreSQL rows**. They go through `TaskFileStorage`, which serializes them to the file-storage subsystem and stores only a `FileEntry` handle on the entity. This keeps rows small no matter how large a task's payload is. ### How a job runs [#how-a-job-runs] Every hop below is an event on a named broker route - the class names and route names are real, so you can grep for them. Step by step: 1. **Launch.** A caller (deployment trigger, webhook, editor test run, API) invokes `JobFacade.createJob(JobParametersDTO)`. The job row is created, the initial context (the job inputs) is pushed, and a `StartJobEvent` is published. 2. **Event-to-message bridge.** Atlas publishes plain Spring application events; `MessageEventListener` (a `@TransactionalEventListener(AFTER_COMMIT)`) forwards each one onto its broker route. Publishing after commit guarantees a consumer never sees a message for state that isn't in the database yet. 3. **Coordinator starts the job.** `TaskCoordinator.onStartJobEvent` marks the job `STARTED` and calls `JobExecutor.execute(job)`, which reads the current context, builds the next `TaskExecution` from `workflow.getTasks().get(job.getCurrentTask())`, **evaluates its `${...}` parameters**, persists it, and hands it to the dispatcher. 4. **Dispatch.** The primary dispatcher is a `TaskDispatcherChain`. Control-flow dispatchers (condition, loop, ...) get first refusal; if none claims the task, `DefaultTaskDispatcher` publishes it to the worker route. Tasks can target named worker pools: a task with `"node": "gpu"` routes to `task.gpu_task_execution_events` instead of `task.default_task_execution_events`. 5. **Execute.** `TaskWorker` resolves a `TaskHandler` through the `TaskHandlerRegistry` - keyed by the exact task type string (`googleMail/v1/searchEmail`) - invokes it (24h default timeout), stores the output via `TaskFileStorage`, and publishes `TaskExecutionCompleteEvent`. 6. **Complete and advance.** Back on the coordinator, the `TaskCompletionHandlerChain` gives control-flow completion handlers first refusal; `DefaultTaskCompletionHandler` handles top-level tasks: it merges the output into the job context under the task's name, then either dispatches the next task or completes the job (evaluating declared workflow `outputs` against the final context). Stop and resume follow the same pattern (`StopJobEvent` on `task.stop_job_events`, `ResumeJobEvent` on `task.resume_job_events`), as do errors (`task.error_events`). ### Expression evaluation [#expression-evaluation] `${...}` references are resolved by `SpelEvaluator` (Spring SpEL with a custom map property accessor, in `core/evaluator`) - plus a formula syntax where a parameter value starting with `=` is evaluated as an expression with built-in functions (`concat`, `join`, `range`, `now`, `uuid`, ...). Evaluation happens **at dispatch time**, on the coordinator, against the context accumulated so far - that's why `${searchEmail_1.id}` works in task 3 but not in task 1. Control-flow dispatchers register *deferred evaluation keys* (e.g. `condition` defers `caseTrue`/`caseFalse`) so expressions inside not-yet-taken branches survive verbatim until the branch is actually dispatched. ### Control flow: task dispatchers [#control-flow-task-dispatchers] Control flow is not built into the engine - each construct is a pluggable extension in `server/libs/modules/task-dispatchers/` following one pattern: Each dispatcher module contributes three pieces, registered through factory beans: * a **`TaskDispatcher`** that recognizes its task type, evaluates its parameters (which branch? how many iterations?), and dispatches child `TaskExecution`s back through the chain; * a **`TaskCompletionHandler`** that intercepts child completions and decides what's next (next iteration, next branch task, or "parent done"); * a **Spring configuration** registering both, plus deferred-evaluation keys for its nested-task parameters. Fan-out constructs (`parallel`, `fork-join`, `each`, `map`) additionally use the `Counter` service to know when all children have landed. `subflow` is the outlier: it launches a whole child `Job` linked via `parentTaskExecutionId`. ### The message broker [#the-message-broker] The broker abstraction lives in `core/message`: a `MessageRoute` (name + exchange), a `MessageBroker.send(route, message)`, and per-provider implementations selected by `bytechef.message-broker.provider` - `memory` (default), `amqp` (RabbitMQ), `jms`, `kafka`, `redis`, and AWS SQS (EE). Because coordinator and worker only ever talk through routes, scaling out means moving off the in-process broker to a shared one and running more instances - the Atlas code is identical either way. Workers subscribe to routes by node name (`bytechef.worker.task.subscriptions`), which is how a task can be pinned to a dedicated queue. There is one deliberate bypass: `JobSyncExecutor` (`platform/platform-job-sync`) hand-wires a private coordinator + worker around an in-process broker to run a workflow synchronously start-to-finish. That's what powers the editor's test runs and streamed executions, without touching the shared queues. ## Triggers [#triggers] Triggers start jobs. A component trigger declares one of these types (`TriggerDefinition.TriggerType` in the SDK): | Type | Meaning | Enabled by | | ----------------- | -------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | | `STATIC_WEBHOOK` | Provider calls a fixed ByteChef URL; no registration API | `webhookEnable` (bookkeeping only) | | `DYNAMIC_WEBHOOK` | ByteChef registers the callback URL with the provider at enable time; registration may expire and be refreshed | `webhookEnable` → output persisted, refresh scheduled | | `LISTENER` | Component opens its own long-lived subscription and emits events itself | `listenerEnable` | | `POLLING` | Platform periodically invokes the component's `poll` function | scheduler registration | | `HYBRID` | Webhook registration + polled execution | `webhookEnable` + poll | The **Schedule component** is a `LISTENER`: its `listenerEnable` registers a cron job with the platform scheduler, and each cron firing emits a trigger event. ### Lifecycle [#lifecycle] When a deployment workflow is enabled, `ProjectDeploymentFacadeImpl.enableWorkflowTriggers` builds a `WorkflowExecutionId` for each trigger and calls `TriggerLifecycleFacade.executeTriggerEnable`, which branches on the type: The `WorkflowExecutionId` deserves a note: it is the base64-encoded tuple `tenantId : type : jobPrincipalId : workflowUuid : triggerName`. It is the webhook URL path segment, the scheduler job identity, and the trigger-state key - one durable address for "this trigger of this workflow in this deployment". `TriggerStateService` persists whatever a trigger needs to remember between invocations: the webhook registration output for dynamic webhooks, and the **`closureParameters`** cursor for polling triggers. The scheduler (`platform-scheduler`) is Quartz-backed by default (DB-persistent), with an AWS EventBridge implementation in EE. It runs three kinds of jobs: `PollingTriggerJob` (fires every poll period, default 5 minutes), `ScheduleTriggerJob` (cron, for the Schedule component), and `DynamicWebhookTriggerRefreshJob` (one-shot at the registration's expiration). ### From trigger event to job [#from-trigger-event-to-job] All trigger sources converge on the `TriggerCoordinator`, which mirrors the task pipeline: dispatch to a `TriggerWorker` over the broker, execute the trigger function, then hand the completed `TriggerExecution` to `TriggerCompletionHandler`, which creates the job(s): * trigger output is a **list and the trigger is not batch** → one job **per element**; * **batch** → one job with the whole list as the trigger's output; * single value → one job. The trigger's output lands in the job inputs under the trigger's name - which is why workflow expressions reference `${trigger_1.someField}`. For **polling** triggers the worker loops the component's `poll` function: each call returns `PollOutput(records, closureParameters, pollImmediately)`; the records accumulate (safety caps: 10,000 records / 100 iterations per run) and the final `closureParameters` - typically a "last seen" timestamp or cursor - are persisted as trigger state and passed back on the next poll. Deduplication is cursor-based, not record-hash based. ## Webhooks [#webhooks] The webhook endpoint is the platform's front door for external events: Details that matter: * **Request capture.** The raw HTTP request (method, headers, query, body - including multipart, stored to temp file storage) is normalized into a `WebhookRequest` and attached to the `TriggerExecution`, so the component's `webhookRequest` function sees the full request regardless of broker hops. * **Sync execution** is opt-in per trigger (`workflowSyncExecution` flag on the trigger definition). The response body comes from whichever task in the workflow produced an output tagged `WEBHOOK_RESPONSE` - that's what the Webhook component's "response" actions set. * **`JobCompletionAwaiter`** is how a synchronous HTTP response works when the job does not run on the receiving thread: the node that received the request parks a `CompletableFuture` keyed by job id and completes it when the job-status event arrives over the broker - so the job may well have executed elsewhere. A race guard checks whether the job is already terminal before parking. * **Validation flags** (`workflowSyncValidation`, `workflowSyncOnEnableValidation`) let a trigger validate a request (e.g. a provider's URL-verification handshake) synchronously before any job is created. * There is also an SSE variant (`/webhooks/:id/sse`) that streams job progress events back to the caller. ## The Component SDK [#the-component-sdk] Components are authored against `sdks/backend/java/component-api` - a dependency-free definition model plus the `ComponentDsl` fluent builder: ```java @AutoService(ComponentHandler.class) public final class SlackComponentHandler implements ComponentHandler { private static final ComponentDefinition COMPONENT_DEFINITION = component("slack") .title("Slack") .icon("path:assets/slack.svg") .categories(ComponentCategory.COMMUNICATION) .connection(SlackConnection.CONNECTION_DEFINITION) .actions(SlackSendChannelMessageAction.ACTION_DEFINITION) .triggers(SlackAnyEventTrigger.TRIGGER_DEFINITION) .version(1); @Override public ComponentDefinition getDefinition() { return COMPONENT_DEFINITION; } } ``` `@AutoService` generates the `META-INF/services` entry, and the platform discovers handlers with `ServiceLoader`. No Spring, no annotations beyond that one - a component is a plain object graph describing itself. ### What `Optional` means in the definition interfaces [#what-optional-means-in-the-definition-interfaces] Nearly every getter on the SDK definition interfaces returns an `Optional`: ```java public interface ActionDefinition { String getName(); // mandatory - set by action(name) Optional getTitle(); // author may set it Optional getDescription(); Optional> getProperties(); Optional getOutputDefinition(); Optional getPerform(); } ``` The rule: **an `Optional` getter marks an attribute the component author *may or may not* have set in the fluent definition.** A present value means the author called that builder method; an empty `Optional` means they didn't, and the platform substitutes a fallback or treats the capability as absent. (Mechanically, every DSL builder getter is literally `Optional.ofNullable(field)` - the field stays `null` until the corresponding builder method runs.) The non-`Optional` getters are the mandatory attributes the DSL factory itself guarantees: `component(name)` sets the name, `action(name)` sets the action name, versions default to `1`. What "empty" concretely means, per attribute: | Getter | Empty means | Platform behavior | | ----------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `getTitle()` | No display title authored | Falls back to the name: `getTitle().orElse(getName())` | | `getDescription()` | No description authored | Falls back to title, then name | | `getProperties()` | The action/trigger takes no input parameters | Treated as `List.of()` | | `getConnection()` (component) | Component needs no connection | No connection UI; actions run connectionless | | `getOutputDefinition()` | No output declared | Editor can't offer typed data pills for this node | | `getPerform()` | No hand-written execute function | Legitimate for OpenAPI components - the loader synthesizes one (see below). If still empty at execution: `getPerform().orElseThrow(...)` fails the task | `OutputDefinition` itself illustrates the pattern one level deeper: it holds *either* a static schema/sample (`getOutputResponse()`) *or* a function that computes the schema at runtime against live parameters (`getOutput()`) - both `Optional`, and which one is present changes how the editor resolves the node's output shape. ### From definition to editor and runtime [#from-definition-to-editor-and-runtime] The SDK definition is deliberately *not* what the rest of the platform consumes. The `platform-component` module maps it into two other shapes: * **The registry** (`ComponentDefinitionRegistry`) is the single catalog. It is fully lazy: at startup nothing is loaded; a build-time index (`META-INF/bytechef/component-index.json`, generated by the `generateComponentIndex` Gradle task) serves the components-*list* view from lightweight stubs, and the first deep read triggers a one-time `ServiceLoader` sweep. If the index is absent, the registry transparently falls back to full loading. * **The domain model** (`com.bytechef.platform.component.domain.*`) is the flattened, `Optional`-free, function-free mirror used by services and serialized to the REST API - this is where the fallbacks from the table above are applied, once, eagerly. * **Task handlers**: `ComponentTaskHandlerProvider` registers one `ComponentTaskHandler` per action under the key `component/vN/actionName` - exactly the workflow task `type` string. That is the whole trick connecting workflow JSON to component code. At execution time the path is: The perform function receives three arguments: * **`Parameters inputParameters`** - the task's evaluated parameters (expressions already resolved by the coordinator); * **`Parameters connectionParameters`** - the decrypted parameters of the resolved connection (token, base URI, ...); * **`ActionContext context`** - the capability surface: `context.http(...)` (the HTTP client - how components call external APIs), `context.json(...)`, `context.file(...)` (file storage in/out via `FileEntry`), `context.log(...)`, `context.data(...)` (key/value storage scoped to execution / workflow / deployment / account), plus `event` progress publishing and `suspend` for human-in-the-loop pauses. Note what this means architecturally: connections are bound to deployments and resolved **server-side at dispatch** - connection ids never appear in the workflow definition, and credentials never leave the facade layer except as the already-scoped `connectionParameters`. ### OpenAPI-generated components [#openapi-generated-components] Components scaffolded from an OpenAPI spec (via the CLI: `component init --name=... --open-api-path=...`) implement `OpenApiComponentHandler`. Their generated actions declare **only properties and metadata** (HTTP method, path, where each property goes: `PATH` / `QUERY` / `HEADER` / `BODY`) - and no `perform`. At load time, `OpenApiComponentHandlerLoader` wraps each such action and injects a generic perform that assembles the HTTP call from that metadata. This is the clearest demonstration of the `Optional` contract: `getPerform()` being empty isn't an error, it's a signal the loader acts on. Hand-written `modify*` hooks let authors adjust the generated definitions without touching generated code. ## AI & Agents [#ai--agents] AI is built on the same component and connection model described above - an agent is a component; a deployed agent is a generated `Workflow`. Nothing here bypasses the Atlas engine or Component SDK. | Capability | Module | What it does | | ------------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **AI Agent component** | `server/libs/modules/components/ai/agent` | A cluster-root workflow node. The model, chat memory, retrieval (RAG), guardrails, and tools are separate components attached as cluster-element children, resolved through the same `ComponentDefinitionRegistry` as any other component. | | **Deployable agents** *(coming soon)* | `automation-ai-agent` | A chat-first alternative to hand-authoring a workflow: configuring an `AiAgent` (model, tools, skills, sub-agents) generates a real `Workflow` (`branch_in` on the trigger → `aiAgent/v1/streamChat` → `branch_out` on the reply channel) inside a hidden `__AI_AGENT__`-prefixed system project, which then runs through the exact same pipeline described in [Execution: the Atlas engine](#execution-the-atlas-engine). | | **MCP servers** | `automation-ai-mcp-*` | Expose a workflow's `workflow/newWorkflowCall` trigger as a tool an MCP client can call - the workflow itself is unmodified, only the trigger surface changes. | | **AI Copilot** *(coming soon)* | `ai-copilot-service` (CE) / `automation-ai-copilot` (EE) | A chat client wired into the editor that authors workflows, components, and code by calling the same facades a human editing session would. | | **Guardrails** | `server/libs/modules/components/ai/agent/guardrails` | Guardrails are cluster elements attached to an AI Agent node - PII and secret detection, keyword and regex rules, jailbreak and NSFW checks, topical alignment - so each agent node declares the checks it runs. | | **Workspace-wide guardrail policy** *(coming soon)* | `platform-ai-guardrails` (EE) | A Spring AI `CallAdvisor`/`StreamAdvisor` (`AiGuardrailsAdvisor`) registered at `HIGHEST_PRECEDENCE`, ahead of the node-level elements above - redaction, blocked terms, moderation, and prompt-injection detection applied as a floor underneath every agent surface, not just the nodes a workflow author remembered to wire up. | {/* @coming-soon Commented out until these ship; tracked in .agents/coming-soon-inventory.md. The MCP row above previously covered A2A as well. | **Agentic AI** *(coming soon)* | `server/libs/modules/components/ai/agentic-ai` (opt-in, `agentic` profile) | Wraps Embabel's goal-oriented action planning (GOAP) engine: instead of a fixed task sequence, the planner chooses which actions to run and in what order to reach a declared goal, using the canvas-selected model for both action prompts and goal evaluation. | | **AI Gateway** *(coming soon, Enterprise)* | `automation-ai-gateway` | Routes every AI call through one place for model selection, budget/rate limits, and per-project cost tracking, independent of which surface (canvas AI Agent, AI Hub, Copilot) issued the call. | | **MCP / A2A servers** *(A2A coming soon)* | `automation-ai-mcp-*` / `automation-ai-a2a` (+ CE protocol core `platform-ai-a2a`) | Expose a workflow's `workflow/newWorkflowCall` trigger as a tool an MCP client can call, or as a skill an A2A-speaking agent can delegate to - the workflow itself is unmodified, only the trigger surface changes. | */} ## Putting it together [#putting-it-together] One last trace, end to end - a scheduled Gmail-to-Drive workflow like the one from the configuration example: 1. Deployment enabled → `TriggerLifecycleFacade` sees the Schedule trigger is a `LISTENER` → its `listenerEnable` registers a Quartz cron keyed by the `WorkflowExecutionId`. 2. Cron fires → `TriggerListenerEvent` → `TriggerCoordinator` stores the trigger output and hands it to `TriggerCompletionHandler` → `createJob` with inputs `{ trigger_1: {...}, ...deployment inputs }`. 3. `StartJobEvent` → `TaskCoordinator` → `JobExecutor` evaluates `searchEmail_1`'s parameters against the context → `DefaultTaskDispatcher` → worker route. 4. `TaskWorker` resolves the handler under `googleMail/v1/searchEmail` → `ComponentTaskHandler` → facade resolves the Gmail connection bound to this deployment → the component's `perform` runs with `context.http(...)`. 5. Output goes to file storage; `TaskExecutionCompleteEvent` returns to the coordinator; the output is merged into context as `searchEmail_1`; the loop dispatcher takes over for the next task; and so on until the job completes. ## Multi-tenancy [#multi-tenancy] A single ByteChef installation can serve one tenant or many. `BYTECHEF_TENANT_MODE` selects which (`SINGLE`, the default, or `MULTI`); multi-tenant mode is an Enterprise Edition capability. **Isolation is schema-per-tenant, not database-per-tenant.** There is one PostgreSQL database and one connection pool. Every tenant gets its own schema named `bytechef_`, and a `DataSource` wrapper issues `SET search_path TO ` on each connection it hands out, based on the tenant bound to the current context. The practical guarantee is the same one a separate database would give for query isolation - a query for "workflows" in tenant A cannot return tenant B's rows, because they are not in the same table - without the connection-pool multiplication that per-tenant pools imply. Consequences worth planning for: * **Migrations run per tenant.** Liquibase is applied once per tenant schema at startup, so the migration window grows with the number of tenants. * **The scheduler is tenant-aware, not tenant-partitioned.** There is one shared Quartz job store. Schedule and polling trigger keys are built from a `WorkflowExecutionId` that carries the tenant id, and connection-refresh keys are prefixed with it, so those are addressable and cancellable per tenant - but they are not stored separately. The prefixing is not universal: one-time tasks (the delayed wake-ups behind a suspended run) are keyed on the bare job id, with no tenant component. * **Internal service calls carry the tenant.** Where one ByteChef process calls another over `/remote/**`, the tenant id travels in a `CURRENT_TENANT_ID` header alongside the internal service token, and the receiver validates both. * **No cross-tenant queries.** The model assumes you never join across tenants; cross-tenant analytics belong in a warehouse fed by per-tenant exports. * **The tenant id reaches the logs.** `TenantContext` puts `tenantId` into the SLF4J MDC, so it is available for filtering in every log line. Encryption is *not* per tenant: the platform holds one instance-wide encryption key (see [Encryption of stored credentials](/platform/use-bytechef/self-hosted/configuration#encryption-of-stored-credentials)). Tenant isolation is enforced by the schema boundary and the request-scoped tenant context, not by separate key material. ## Deployment shapes [#deployment-shapes] The same binary set runs in two shapes, and what stays identical across them is the point: the database schema, the component model, the workflow definition, and the observability pipeline. A component definition and a workflow JSON behave the same either way. | Shape | What it is | Where to read more | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | **The server** | One `server-app` process containing the coordinator, the workers, every domain service, and the HTTP API. Run one, or several against the same database behind a shared broker. | [Installation](/platform/use-bytechef/self-hosted/installation/local-docker) | | **Single-shot** | `runtime-job-app`: boots, runs exactly one workflow, exits. No database, no broker, no triggers. Enterprise Edition. | [Runtime job runner](/platform/use-bytechef/self-hosted/runtime-job) | ## Notifications and alerts [#notifications-and-alerts] The engine itself is notification-agnostic - nothing under `server/libs/atlas/` sends email or webhooks. Instead, every job status transition publishes a `JobStatusApplicationEvent`, and the coordinator's listener fan-out (in `platform-coordinator`) turns those events into deliveries. `platform-notification` is the central registry for notification channels (`EMAIL`, `WEBHOOK`, `SLACK`); other features reference `Notification` rows as delivery targets instead of defining their own channel entities. Alert rules (Enterprise) are workspace-scoped rows that own the **when** - consecutive failures, failure rate, error count, latency threshold/spike, cost threshold, usage threshold, no activity - while the notification registry owns the **where and how**. Rule evaluation state lives on the rule row and is updated per terminal job event; time-based rules (no-activity, usage-threshold) fire from scheduled monitors. ## Related reading [#related-reading] * [Build a component](/developer-guide/build-component/initial-setup) - the hands-on authoring guide * [Component specification](/developer-guide/component-specification/component) - every DSL method, one by one * [Working with triggers](/developer-guide/working-with-triggers) - trigger authoring specifics * [AI Agent](/platform/automation/build/workflows/ai/agent) - the cluster-root component in depth, including cluster-element slots * [Cloud](/platform/use-bytechef/cloud) - run this architecture without managing any of it yourself * [Self-hosting configuration](/platform/use-bytechef/self-hosted/configuration/environment-variables) - broker/worker/scheduler properties # ByteChef Developer Guide: Get Started URL: /developer-guide Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/developer-guide/index.mdx Build ByteChef components by hand or generate them from an OpenAPI specification, and understand how the platform executes what you author. ## Introduction [#introduction] The Developer Guide is for engineers who want to extend ByteChef with new **components** (connectors) or understand how the platform runs the workflows they power. Everything here builds on the same Java Component SDK (`sdks/backend/java`) that ships the 260+ built-in components listed in the [component reference](/reference/components); most of them live under `server/libs/modules/components/`. ## Ways to build a component [#ways-to-build-a-component] There are two supported paths, and they are complementary: * **[Build a component by hand](/developer-guide/build-component/initial-setup)** - start from the `example` component template and write the definition, actions, triggers, connection, and tests yourself. Best for components with custom logic, non-REST protocols, or bespoke behavior. * **[Generate a component from OpenAPI](/developer-guide/generate-component)** - point the CLI at an OpenAPI specification and let it scaffold the actions, properties, and connection for you, then customize the generated code. Best for REST APIs that already ship an OpenAPI spec. Both paths produce the same artifact: a component module that the platform discovers and surfaces in the workflow editor's component panel. ## Reference [#reference] * **[Component specification](/developer-guide/component-specification/component)** - every DSL method for the [component](/developer-guide/component-specification/component), [action](/developer-guide/component-specification/action), [trigger](/developer-guide/component-specification/trigger), [connection](/developer-guide/component-specification/connection), and [property](/developer-guide/component-specification/property) builders, method by method. * **[Working with triggers](/developer-guide/working-with-triggers)** - configuring ngrok and the local webhook URL so provider callbacks reach your machine while developing webhook triggers. ## Understand the internals [#understand-the-internals] * **[Architecture deep dive](/developer-guide/architecture)** - how the Atlas engine, message broker, trigger machinery, and Component SDK fit together, and what happens between hitting **Run** and a task's `perform` function executing. # ByteChef Developer Guide: Working with Triggers URL: /developer-guide/working-with-triggers Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/developer-guide/working-with-triggers.mdx Working with Triggers as a developer ## Download and Set Up ngrok [#download-and-set-up-ngrok] 1. Download ngrok * Visit [ngrok's download page](https://ngrok.com/download) and download the appropriate version for your operating system 2. Start ngrok * Open a terminal and run the following command to start ngrok: ```bash ngrok http http://127.0.0.1:9555 ``` * Copy the first address listed under "Forwarding". This will be used as your webhook URL. ngork example ## Configure the webhook URL [#configure-the-webhook-url] Where the ngrok URL goes depends on how you run the server. 1. Open Bytechef Codebase: * Navigate to bytechef/server/apps/server-app/src/main/resources/config. 2. Create Local Configuration: * Create a file named `application-local.yml`. * Note: `application-local.yml` is optional, git-ignored, and corresponds to the `local` Spring profile. Ensure the `local` profile is activated on Spring Boot startup. 3. Configure Webhook URL: * Add the following configuration to `application-local.yml`, replacing `(first address under Forwarding)` with the copied ngrok URL: ``` bytechef: webhook-url: (first address under Forwarding)/webhooks/{id} ``` 4. Activate Local Profile: * Ensure that the `local` profile is added to active profiles in your IntelliJ configuration. intellij-scr 5. Start the ByteChef application. 1. Navigate to the `BYTECHEF_HOME/server` directory create a file named `local.env` 2. Add the following configuration, replacing (first address under Forwarding) with the ngrok URL you copied: ``` BYTECHEF_WEBHOOK_URL=(first address under Forwarding)/webhooks/{id} ``` 3. Start the ByteChef application. **Note:** Docker has to be restarted in order to update any changes made to `BYTECHEF_HOME/server/local.env` file. # openapi: API Reference URL: /openapi Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/openapi/index.mdx Public REST APIs you can call from your application - generated from ByteChef's OpenAPI specifications. **Automation** The workspace-level API - workflow executions, deploying a code-based project, and pulling one from git. Not yet documented here. **Embedded** The public REST API your application calls to manage and run integrations on behalf of your end users. Each embedded resource is reachable two ways, and the reference documents them separately because the credential decides which one you can use. **Backend - API Key** Called from your own server. The connected user is named by the `{externalUserId}` path segment. * **[Actions](/openapi/backend/embedded-action)** - execute a component action for a connected user. * **[Tools](/openapi/backend/embedded-tool)** - list a connected user's available tools, and execute one. * **[Integrations](/openapi/backend/embedded-configuration-integration)** - the integrations available to your customers. * **[Integration Instances](/openapi/backend/embedded-configuration-integration-instance)** - a connected user's instance of an integration. * **[Instance Workflows](/openapi/backend/embedded-configuration-integration-instance-workflow)** - enable, disable and configure an instance's workflows. * **[Connections](/openapi/backend/embedded-configuration-connection)** - list a connected user's connections. * **[Connected Users](/openapi/backend/embedded-configuration-connected-user)** - update a connected user. * **[User Workflows](/openapi/backend/embedded-configuration-connected-user-project-workflow)** - a connected user's own workflows, including generation from a prompt. * **[Workflow Catalog](/openapi/backend/embedded-configuration-automation-workflow-project)** - the catalog projects available to connected users. * **Workflow Executions** - fetch a tenant's workflow executions. Served by the internal API rather than a public spec, so it has no reference page; read executions in the ByteChef admin UI. * **Tool Invocations** - fetch a connected user's tool and action invocation history. No public spec yet, so it has no reference page. **Frontend - Signing Key JWT** Called from the browser with a short-lived token your backend signs. The connected user comes from the token's `sub` claim, so these paths carry no user id. * **[Integrations](/openapi/frontend/embedded-configuration-integration)** - the integrations available to the signed-in user. * **[Integration Instances](/openapi/frontend/embedded-configuration-integration-instance)** - their instance of an integration. * **[Instance Workflows](/openapi/frontend/embedded-configuration-integration-instance-workflow)** - enable, disable and configure that instance's workflows. * **[Connections](/openapi/frontend/embedded-configuration-connection)** - list their connections. * **[Connected Users](/openapi/frontend/embedded-configuration-connected-user)** - update the signed-in user. * **[User Workflows](/openapi/frontend/embedded-configuration-connected-user-project-workflow)** - their own workflows, including generation from a prompt. * **[Workflow Catalog](/openapi/frontend/embedded-configuration-automation-workflow-project)** - the catalog projects available to them. * **[App Events](/openapi/frontend/embedded-webhook-app-event-trigger)** - fire an App Event to start every subscribed workflow. * **[Request Trigger](/openapi/frontend/embedded-webhook-request-trigger)** - execute a single workflow synchronously and return its result. **Platform** Deploying a custom component to the platform. Not yet documented here. *** ## Base URL [#base-url] Every operation path you see on a reference page is relative to `/api/embedded/v1`. Prefix it with that, then with your ByteChef host - e.g. `https://your-bytechef-host.example.com/api/embedded/v1`. *** ## Authentication [#authentication] Every request carries a bearer token in the `Authorization` header, and there are two kinds. An **API Key** authenticates *you*. It is the general ByteChef credential - the Automation and Platform APIs take one too - and on the embedded API it acts for whichever connected user the `{externalUserId}` path segment names. A **Signing Key JWT** is specific to the embedded API. It authenticates *one of your end users* directly from the browser: your backend signs a short-lived token whose `sub` claim is that user, so the operations it reaches carry no user id in their path. That is what the two sections of this reference correspond to: | | **Backend** | **Frontend** | | ---------------------- | ----------------------------------- | ----------------------------------------------- | | Credential | An **API Key** | A short-lived JWT signed with a **Signing Key** | | Identifies the user by | the `{externalUserId}` path segment | the token's `sub` claim | | Called from | your own server | the browser | It also explains why the paths differ. An API Key says nothing about which of your end users a call is for, so those operations carry the id in the path. A JWT already names the user, so its operations do not - which in turn means an API Key cannot be used against them: there would be no user to act for, and the request is rejected. **Actions** and **Tools** are the exception that proves the rule. Their paths carry `{externalUserId}`, so either credential resolves a user and both are accepted; they are listed under Backend because that is how they are normally called. Mint frontend tokens as described in [Installing the SDK](/platform/embedded/get-started/initial-setup/installing-the-sdk#2-generate-a-user-token-in-your-backend) * the private key never reaches the browser, and the token is short-lived. See [Signing Keys](/platform/embedded/administration/signing-keys) and [Embedded API Keys](/platform/embedded/administration/api-keys) for issuing each credential. Pass an optional `X-Environment` header (`DEVELOPMENT`, `STAGING`, or `PRODUCTION`) to scope the request to a specific environment. If omitted, ByteChef uses `PRODUCTION`. # ByteChef Reference: Expressions URL: /reference/expressions Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/reference/expressions.md Expressions in ByteChef provide a powerful way to dynamically access, transform, and manipulate data within your workflows. They allow you to reference data from previous steps, perform calculations, implement conditional logic, and create dynamic configurations for your automation processes. # Expression Cheat Sheet [#expression-cheat-sheet] ### Expressions and types [#expressions-and-types] Formula and text expressions used in ByteChef are primarily written using SpEL (Spring Expression language) with some constraints - simple, yet powerful expression language. SpEL is based on Java ([reference documentation](https://docs.spring.io/spring-framework/reference/core/expressions.html)), but no prior Java knowledge is needed to use it. The easiest way to learn SpEL is looking at examples which are further down this page. Some attention should be paid to data types, described in more detail in the next section. ## Data types and structures [#data-types-and-structures] The data types used in the execution engine, SpEL expressions and data structures are Java based. These are also the data type names that appear in code completion hints. In most cases ByteChef can automatically convert between Java data types and JSON formats. Below is the list of the most common data types. In Java types column package names are omitted for brevity, they are usually: * Primitive types and basic objects: [`java.lang`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/package-summary.html) * Collections: [`java.util`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/package-summary.html) * Date/Time: [`java.time`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/package-summary.html) ### Basic (primitive data types) [#basic-primitive-data-types] | Java type | ByteChef Type | Description | | ------------------------------------------------------------------------------------------------------------ | ------------- | ---------------------------------------------------------------------------- | | `null` | `nullable` | Represents the absence of a value | | [`String`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html) | `string` | UTF-8 encoded text | | [`Boolean`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Boolean.html) | `bool` | Represents `true` or `false` values | | [`Integer`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Integer.html) | `integer` | 32-bit signed integer | | [`Long`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Long.html) | `integer` | 64-bit signed integer | | [`Float`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Float.html) | `number` | 32-bit floating point number | | [`Double`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Double.html) | `number` | 64-bit floating point number | | [`LocalTime`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/LocalTime.html) | `time` | Time without timezone (HH:MM:SS) | | [`LocalDate`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/LocalDate.html) | `date` | Date without timezone (YYYY-MM-DD) | | [`LocalDateTime`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/LocalDateTime.html) | `date-time` | Date and time without timezone | | [`UUID`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/UUID.html) | `string` | Universally Unique Identifier (e.g., `123e4567-e89b-12d3-a456-426614174000`) | ### Objects/Maps [#objectsmaps] In ByteChef, objects are implemented as `Map`s, which store key-value pairs. The keys are strings, and values can be of any type. This is similar to JSON objects. Example: ```java { "name": "John", "age": 30, "active": true } ``` ### Arrays/Lists [#arrayslists] In ByteChef, arrays are implemented using `List`s, which are ordered collections that can contain duplicate elements. The `Collection` interface is the root interface for all collection types in Java, including `List`, `Set`, and `Queue`. Example: ```java [1, 2, 3, 4, 5] ["apple", "banana", "cherry"] ``` ### Date/Time [#datetime] See [Handling data/time](#handling-data-time) for detailed description of how to deal with date and time in ByteChef. ## Expression syntax [#expression-syntax] ### Formula expressions [#formula-expressions] In ByteChef `formula` expression starts with `=` and `${}` is used to access values. For example: ``` =${httpClient_1.body.amount} + 1 ``` will access value defined in `${httpClient_1.body.amount}` nested structure and increase it for `1`. ### Text expressions [#text-expressions] Also, it is allowed to write a `text` expression like ``` ${httpClient_1.body.amount} is total amount ``` where value defined in `${httpClient_1.body.amount}` nested structure will be merged with `' is total amount'` literal part. The same expression can be written as a formula expression: ``` =${httpClient_1.body.amount} + ' is total amount' ``` ## Basics [#basics] Most of the literals are similar to JSON ones, in fact in many cases JSON structure is valid SpEL. There are a few notable exceptions: * Lists are written using curly braces: `{"firstElement", "secondElement"}`, as `[]` is used to access elements in array * Strings can be quoted with either `'` or `"` * Field names in maps do not need to be quoted (e.g., `{name: "John"}` is valid SpEL, but not valid JSON) | Expression | Result | Type | | ---------------------- | --------------------------------- | --------------------------------------------- | | `'Hello World'` | `"Hello World"` | `String` | | `true` | `true` | `Boolean` | | `null` | `null` | `Null` | | `{}` | an empty list | `List[Unknown]` | | `{1,2,3,4}` | a list of integers from 1 to 4 | `List[Integer]` | | `{:}` | an empty object | `Map{}` | | `{john:300, alex:400}` | an object (name-value collection) | `Map{alex: Integer(400), john: Integer(300)}` | | `'AA' + 'BB'` | `"AABB"` | `String` | ## Arithmetic Operators [#arithmetic-operators] The `+`, `-`, `*` arithmetic operators work as expected. | Operator | Equivalent symbolic operator | Example expression | Result | | -------- | ---------------------------- | ------------------ | ------ | | `div` | `/` | `7 div 2` | `3` | | `div` | `/` | `7.0 div 2` | `3.5` | | `mod` | `%` | `23 mod 7` | `2` | ## Conditional Operators [#conditional-operators] | Expression | Result | Type | | -------------------------------------------- | ----------- | --------- | | `2 == 2` | `true` | `Boolean` | | `2 > 1` | `true` | `Boolean` | | `true AND false` | `false` | `Boolean` | | `true && false` | `false` | `Boolean` | | `true OR false` | `true` | `Boolean` | | `true \|\| false` | `true` | `Boolean` | | `2 > 1 ? 'a' : 'b'` | `"a"` | `String` | | `2 < 1 ? 'a' : 'b'` | `"b"` | `String` | | `nonNullVar == null ? 'Unknown' : 'Success'` | `"Success"` | `String` | | `nullVar == null ? 'Unknown' : 'Success'` | `"Unknown"` | `String` | | `nullVar?:'Unknown'` | `"Unknown"` | `String` | | `'john'?:'Unknown'` | `"john"` | `String` | ## Relational Operators [#relational-operators] | Operator | Equivalent symbolic operator | Example expression | Result | | -------- | ---------------------------- | ------------------ | ------- | | `lt` | `<` | `3 lt 5` | `true` | | `gt` | `>` | `4 gt 4` | `false` | | `le` | `<=` | `3 le 5` | `true` | | `ge` | `>=` | `4 ge 4` | `true` | | `eq` | `==` | `3 eq 3` | `true` | | `ne` | `!=` | `4 ne 2` | `true` | | `not` | `!` | `not true` | `false` | ## String Operators [#string-operators] | Expression | Result | Type | | ------------- | -------- | -------- | | `'AA' + 'BB'` | `"AABB"` | `String` | ## Method Invocations [#method-invocations] As ByteChef uses Java types, some objects contain additional methods, but they are not allowed to be called directly on those types. For example, this is **not** allowed: ``` 'someValue'.substring(4) ``` Instead, use the built-in function: ``` substring('someValue', 4) ``` ByteChef provides built-in functions to help with various type operations. Built-in functions are only resolved in **formula** expressions - the ones that start with `=`. A plain `${...}` text expression accepts a value accessor and nothing else, so `substring(...)` inside `${}` is left unresolved. Write `=substring(${step_1.text}, 4)` instead. ## Accessing Elements of a List or a Map [#accessing-elements-of-a-list-or-a-map] | Expression | Result | Type | | ----------------------------------------------------------------------- | --------------------------------------- | ------------------- | | `{1,2,3,4}[0]` | `1` | `Integer` | | `{jan:300, alex:400}[alex]` | a value of field 'alex', which is `400` | `Integer` | | `{jan:300, alex:400}['alex']` | `400` | `Integer` | | `{jan:{age:24}, alex:{age: 30}}['alex']['age']` | `30` | `Integer` | | `{foo: 1L, bar: 2L, tar: 3L}.?[#this.key == "foo" OR #this.value > 2L]` | `{'tar': 3, 'foo': 1}` | `Map[String, Long]` | Attempting to access non-present elements fails. Both cases are detected when the expression is evaluated, not before - there is no pre-deployment expression validation that resolves map keys. When evaluation fails, ByteChef leaves the expression unresolved and passes the raw text through rather than aborting the step, so an unexpected literal in a step's input is the symptom to look for. | Expression | Error | | ----------------------------- | ------------------------- | | `{1,2,3,4}[4]` | Index out of bounds | | `{jan:300, alex:400}['anna']` | No property 'anna' in map | ## Filtering Lists [#filtering-lists] Special variable `#this` is used to operate on a single element of a list. * Filtering all elements uses the syntax: `.?[condition]` * To get the first matching element: `.^[condition]` * To get the last matching element: `.$[condition]` | Expression | Result | Type | | ---------------------------------------- | ------------------------- | --------------- | | `{1,2,3,4}.?[#this ge 3]` | `{3, 4}` | `List[Integer]` | | `usersList.?[#this.firstName == 'john']` | the matching user objects | `List[Map]` | | `{1,2,3,4}.^[#this ge 3]` | `3` | `Integer` | | `{1,2,3,4}.$[#this ge 3]` | `4` | `Integer` | ## Transforming Lists [#transforming-lists] Special variable `#this` is used to operate on a single element of a list. For the examples below, assume `listOfPersons` contains: ```json [ {"name": "Alex", "age": 42}, {"name": "John", "age": 24} ] ``` | Expression | Result | Type | | ------------------------------------------------------------- | -------------------------------------------------------------- | ---------------------- | | `{1,2,3,4}.![#this * 2]` | `{2, 4, 6, 8}` | `List[Integer]` | | `listOfPersons.![#this.name]` | `{'Alex', 'John'}` | `List[String]` | | `listOfPersons.![#this.age]` | `{42, 24}` | `List[Integer]` | | `listOfPersons.![7]` | `{7, 7}` | `List[Integer]` | | `listOfPersons.![{key: #this.name, value: #this.age}]` | `[{"key": "Alex", "value": 42}, {"key": "John", "value": 24}]` | `List[Record]` | | `toMap(listOfPersons.![{key: #this.name, value: #this.age}])` | `{Alex: 42, John: 24}` | `Map[String, Integer]` | Note: `toMap()` function can be applied to lists of maps where each map contains `key` and `value` properties. For other list operations, see the List Functions section. ## Safe Navigation [#safe-navigation] When accessing nested structures, handle null fields to avoid errors. SpEL's safe navigation operator (`?.`) is a shorthand for the conditional operator: `someVar?.b` is equivalent to `someVar != null ? someVar.b : null`. | Expression | `var` value | Result | Type | | ---------- | ----------- | -------------------------------- | -------------------------------- | | `var.foo` | `{foo: 5}` | `5` | `Integer` | | `var.foo` | `null` | `java.lang.NullPointerException` | `java.lang.NullPointerException` | | `var?.foo` | `{foo: 5}` | `5` | `Integer` | | `var?.foo` | `null` | `null` | `Null` | ## Invoking Static Methods [#invoking-static-methods] ByteChef does not allow calling static Java methods directly. For example, this is **not** allowed: ``` T(java.lang.Math).PI ``` Instead, use the equivalent built-in functions provided by ByteChef. ## Chaining with Dot Operator [#chaining-with-dot-operator] | Expression | Result | Type | | ------------------------------------------------------------ | ----------- | --------------- | | `{1, 2, 3, 4}.?[#this > 1].![#this > 2 ? #this * 2 : #this]` | `{2, 6, 8}` | `List[Integer]` | ## Type Conversions [#type-conversions] Type conversion in ByteChef can be done either implicitly or explicitly. ### Explicit Conversions [#explicit-conversions] Explicit conversions are available as built-in functions. See the [Type Conversion Functions](#type-conversion-functions) section for details. ### Implicit Conversions [#implicit-conversions] SpEL provides many built-in implicit conversions that are also available in ByteChef. These include conversions between various numeric types and between `String` and other value types. Implicit conversion occurs when an input value of one type is used in a context that expects a different type. The system will automatically attempt to convert the value to the expected type. #### Common Implicit Conversions [#common-implicit-conversions] | Input value | Input type | Converts to | | ---------------------------------------- | ---------- | --------------- | | `12.34f` | `Float` | `Double` | | `42` | `Integer` | `Long` | | `'Europe/Warsaw'` | `String` | `ZoneId` | | `'+01:00'` | `String` | `ZoneOffset` | | `'09:00'` | `String` | `LocalTime` | | `'2020-07-01'` | `String` | `LocalDate` | | `'2020-07-01T09:00'` | `String` | `LocalDateTime` | | `'en_GB'` | `String` | `Locale` | | `'ISO-8859-1'` | `String` | `Charset` | | `'USD'` | `String` | `Currency` | | `'bf3bb3e0-b359-4e18-95dd-1d89c7dc5135'` | `String` | `UUID` | #### Usage Examples [#usage-examples] | Expression | Input value | Input type | Target type | | -------------------------------- | ----------------- | ---------- | ----------- | | `atZone(now(), 'Europe/Warsaw')` | `'Europe/Warsaw'` | `String` | `ZoneId` | | `'' + 42` | `'42'` | `Integer` | `String` | ## Built-in functions [#built-in-functions] ### Type Conversion Functions [#type-conversion-functions] | Function | Description | | -------------- | -------------------------------- | | boolean(value) | Converts a value to a boolean. | | byte(value) | Converts a value to a byte. | | char(value) | Converts a value to a character. | | float(value) | Converts a value to a float. | | double(value) | Converts a value to a double. | | int(value) | Converts a value to an integer. | | long(value) | Converts a value to a long. | | short(value) | Converts a value to a short. | ### String Functions [#string-functions] | Function | Description | | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | concat(str1, str2) | Concatenates two strings or two lists. | | contains(str, substr) | Checks if a string contains a substring. | | equalsIgnoreCase(str1, str2) | Compares two strings for equality, ignoring case differences. | | format(formatStr, args...) | Formats a string using a format string and arguments (similar to String.format). | | indexOf(str, substr, \[fromIndex]) | Returns the index of the first occurrence of a substring in a string, optionally starting at `fromIndex`. | | join(separator, list) | Joins the values of a list into one string, separated by `separator`. Note the separator comes first: `=join(',', ${list})`. | | lastIndexOf(str, substr, \[fromIndex]) | Returns the index of the last occurrence of a substring in a string. | | length(str) | Returns the length of a string. | | split(str, delimiter) | Splits a string by a delimiter, which is treated as a regular expression, and returns a list of strings. | | substring(str, beginIndex, \[endIndex]) | Returns a substring starting at `beginIndex` (inclusive). Without `endIndex` it runs to the end of the string; with it, `endIndex` is exclusive. | ### Date and Time Functions [#date-and-time-functions] | Function | Description | | ------------------------------------- | ------------------------------------------------------------------------------------- | | atZone(instant, zoneId) | Converts an instant to a zoned date-time with the specified time zone. | | format(date, \[format]) | Formats a date using the specified format. If no format is provided, uses ISO format. | | minusDays(date, days) | Subtracts the specified number of days from a date. | | minusHours(date, hours) | Subtracts the specified number of hours from a date. | | minusMicros(date, micros) | Subtracts the specified number of microseconds from a date. | | minusMillis(date, millis) | Subtracts the specified number of milliseconds from a date. | | minusMinutes(date, minutes) | Subtracts the specified number of minutes from a date. | | minusMonths(date, months) | Subtracts the specified number of months from a date. | | minusSeconds(date, seconds) | Subtracts the specified number of seconds from a date. | | minusWeeks(date, weeks) | Subtracts the specified number of weeks from a date. | | minusYears(date, years) | Subtracts the specified number of years from a date. | | now() | Returns the current instant, in UTC. | | parseDate(dateStr, \[format]) | Parses a string into a date. If no format is provided, uses ISO format. | | parseDateTime(dateTimeStr, \[format]) | Parses a string into a date-time. If no format is provided, uses ISO format. | | plusDays(date, days) | Adds the specified number of days to a date. | | plusHours(date, hours) | Adds the specified number of hours to a date. | | plusMicros(date, micros) | Adds the specified number of microseconds to a date. | | plusMillis(date, millis) | Adds the specified number of milliseconds to a date. | | plusMinutes(date, minutes) | Adds the specified number of minutes to a date. | | plusMonths(date, months) | Adds the specified number of months to a date. | | plusSeconds(date, seconds) | Adds the specified number of seconds to a date. | | plusWeeks(date, weeks) | Adds the specified number of weeks to a date. | | plusYears(date, years) | Adds the specified number of years to a date. | | timestamp() | Returns the current time as a Unix timestamp in milliseconds. | ### List Functions [#list-functions] | Function | Description | | ------------------------- | ---------------------------------------------------------------------------- | | add(list, element) | Adds an element to a list and returns a new list. | | addAll(list1, list2) | Adds all elements from list2 to list1 and returns a new list. | | concat(list1, list2) | Concatenates two lists. | | contains(list, element) | Returns true if list contains the specified element. | | flatten(list) | Flattens a list of lists into a single list. | | range(start, end) | Creates a list of integers from start to end (inclusive). | | remove(list, element) | Removes an element from a list and returns the modified list. | | set(list, index, element) | Sets an element at a specific index in a list and returns the modified list. | | size(list) | Returns the size of a list. If list is null returns -1. | | sort(list) | Sorts a collection in natural order and returns a new list. | ### Map Functions [#map-functions] | Function | Description | | -------------------- | ----------------------------------------------------------------------- | | put(map, key, value) | Adds a key-value pair to a map and returns a new map. | | putAll(map1, map2) | Adds all key-value pairs from map2 to map1 and returns a new map. | | remove(map, key) | Removes a key-value pair from a map and returns the modified map. | | toMap(list) | Converts a list of maps with "key" and "value" entries to a single map. | ### Utility Functions [#utility-functions] | Function | Description | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | config(propertyName) | Reads a server configuration property by name. It is gated by an allowlist of property-name prefixes (`bytechef.workflow.config.allowed-prefixes`) that is empty by default, so on a stock deployment every call fails and the function reads nothing. | | uuid() | Generates a random UUID (version 4). | # ByteChef Reference: Overview URL: /reference Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/reference/index.mdx Generated documentation for every ByteChef component and flow control, plus the expression syntax. ## Overview \[toc] [#overview-toc] The Reference section documents the building blocks a workflow is made of: * **[Components](/reference/components)**: A page per built-in component, listing each of its actions and triggers with their properties and outputs. * **[Flow Controls](/reference/flow-controls)**: The steps that shape how a workflow runs - branching, looping, parallelism, error handling, sub-workflows, and approvals. * **[Expressions](/reference/expressions)**: The expression syntax used to reference data from earlier steps, plus the built-in functions available to transform it. These pages describe what each building block accepts and returns. For task-oriented guidance, start with the [Automation guide](/platform/automation/get-started). The component and flow-control pages are generated from the definitions in the ByteChef repository rather than written by hand, so do not edit them directly. # platform: Glossary URL: /platform/glossary Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/platform/glossary.mdx Terms used in ByteChef platform. In this document, you will find a list of terms used in ByteChef's platform. Here are the definitions: ## Projects [#projects] Projects are containers that hold one or more workflows. Projects help organize and manage workflows, making it easier to group related automation processes together. Users can create, edit, duplicate, import, and delete projects to streamline their automation efforts. ## Workflows [#workflows] Workflows are a series of actions and triggers that automate processes within the ByteChef platform. Workflows can be created by combining various components and configuring them to work together seamlessly. Users can also customize workflows by adding conditions, loops, and other logic to create more complex automation processes. ## Components [#components] In ByteChef, a component is a modular building block that encapsulates specific functionalities within the platform. Each component is designed to interact with external services or perform particular tasks, making it an essential part of creating workflows. A component is made up of actions and triggers. Most also define a connection, which holds the credentials and settings its actions and triggers need to reach the external service, and some expose cluster elements - the models, tools, and memory an AI agent step can be wired to. Every built-in component is listed, action by action, in the [component reference](/reference/components). ## Connections [#connections] Connections are used to connect a component to an external service or application. They provide the necessary information for the component to interact with the external service, such as authentication details, base URL, and other properties. ## Triggers [#triggers] These are events that initiate a workflow. Triggers listen for specific occurrences, such as receiving a new email, a change in a database, or a scheduled time event, and start the workflow when these events occur. ## Actions [#actions] These are operations that a component can perform. Actions typically involve sending data to an external service, retrieving information, or manipulating data within the workflow. For example, an action might send an email, update a database record, or fetch user details from an API. # platform: Quick Start URL: /platform Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/platform/index.mdx Get Started with ByteChef ## Introduction [#introduction] ByteChef is an open-source platform for building and running workflow automations and AI agents across the applications, APIs, and databases you already use. ## Choose how to use ByteChef [#choose-how-to-use-bytechef] Run ByteChef on our managed, hosted deployment - no infrastructure to manage. Deploy and operate ByteChef on your own infrastructure - Docker, Kubernetes, AWS, Azure, Google Cloud, or DigitalOcean. ## Pick your path [#pick-your-path] Easily build and run smart workflows that connect your apps, automate routine tasks, and include AI-driven decisions-no heavy coding needed. Bring the power of ByteChef directly into your product-embed the workflow builder, run automations behind the scenes, and let your users create workflows inside your app. ## Explore more [#explore-more] Build a component by hand or generate one from an OpenAPI specification, and learn the DSL for actions, triggers, connections, and properties. Every component and flow control, plus the expression syntax and its built-in functions. ## Community support [#community-support] For help, you can use one of these channels to ask a question: * [Discord](https://discord.gg/VKvNxHjpYx) - Discussions with the community and the team. * [GitHub](https://github.com/bytechefhq/bytechef/issues) - For bug reports and feature requests. * [X/Twitter](https://twitter.com/bytechefhq) - Get the product updates easily. ## Roadmap [#roadmap] Check out our [roadmap](https://github.com/bytechefhq/bytechef/milestones) to get informed of the latest features released and the upcoming ones. ### Contributing [#contributing] If you'd like to contribute, kindly read our [Contributing Guide](https://github.com/bytechefhq/bytechef/blob/master/CONTRIBUTING.md) to learn and understand about our development process, how to propose bug fixes and improvements, and how to build and test your changes to ByteChef. # platform: What is ByteChef? URL: /platform/what-is-bytechef Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/platform/what-is-bytechef.mdx What ByteChef is, what it can do, and where to start. ByteChef is an open-source platform for building AI agents, automating workflows, and integrating applications - SaaS tools, APIs, and databases alike. You can run it as your own automation platform, or embed it in your product so your customers build workflows inside your app. ## Core Capabilities [#core-capabilities] **Workflow automation** Connect your applications and services in a visual editor: handle repetitive tasks, transform data, and express business logic without writing code. **AI in workflows** Call chat and embedding models from a workflow step, build AI agents that pick their own tools, and ground answers in your own documents with a knowledge base. **260+ Integrations** Connect to applications including Slack, Salesforce, HubSpot, Google Workspace, and Microsoft Teams. The full list is in the [component reference](/reference/components). **Cloud or self-hosted** Run ByteChef on the [managed deployment](/platform/use-bytechef/cloud), or [host it yourself](/platform/use-bytechef/self-hosted) on Docker, Kubernetes, AWS, Azure, Google Cloud, or DigitalOcean. ## What else it gives you [#what-else-it-gives-you] **Open source** The source is public, so you can read it, change it, and run your own build. **Governance for teams** Workspaces, workspace roles and permission scopes, audit events, single sign-on, and metrics and traces over OTLP. Some of these need an Enterprise license - see the [Enterprise index](/platform/enterprise). **An extension path** Write your own components against the Java Component SDK, or generate one from an OpenAPI specification. See the [Developer Guide](/developer-guide). ## Use Cases [#use-cases] **Sales & Marketing Automation** Capture leads from multiple sources, enrich data, qualify prospects, and nurture them through your sales pipeline. Keep data in sync across your CRM and marketing tools. **Customer Support Automation** Route support tickets intelligently, generate responses with AI, track issues across channels, and keep customers informed automatically. **Data Integration & Synchronization** Keep databases, CRMs, data warehouses, and analytics platforms in sync. Eliminate manual data entry and ensure consistency across systems. **Content & Publishing Workflows** Automate content creation, approval workflows, distribution, and scheduling across multiple channels. Streamline your publishing pipeline. **Finance & Operations** Automate invoicing, expense tracking, financial reporting, and reconciliation. Reduce errors and free up your team for strategic work. **HR & People Operations** Automate onboarding, offboarding, leave management, and employee communications. Keep HR processes running smoothly. ## Key Features [#key-features] * **Visual workflow builder** - Drag and drop steps onto a canvas; no code required * **[Expressions and data mapping](/reference/expressions)** - Reference earlier steps' output and transform it with SpEL expressions and built-in functions * **Error handling and retries** - Catch a failure with the `on-error` flow control, or set `maxRetries` on a task * **Execution monitoring** - Run history with per-step inputs and outputs * **Scheduling and triggers** - Time-based, polling, webhook, and listener triggers * **Conditional logic** - Branch on data with the `condition` and `branch` flow controls * **Loops and iterations** - Iterate with `each`, `loop`, `map`, `parallel`, and `fork-join` * **Sub-workflows** - Call one workflow from another with `subflow` * **Public REST APIs** - Drive deployments and read execution history programmatically; see the [API reference](/openapi) ## Get Started [#get-started] Choose your path: * **[Quick Start](/platform/automation/get-started/quick-start/build-first-workflow)** - Build your first workflow in minutes * **[Automation Guide](/platform/automation/get-started)** - Learn workflow fundamentals * **[Browse Integrations](/reference/components)** - Explore the 260+ pre-built components * **[Developer Guide](/developer-guide)** - Create custom components and extend ByteChef * **[Deploy](/platform/automation/deploy/workflows)** - Move from testing to production ## How It Works [#how-it-works] 1. **Create a Project** - Organize your automations into logical groups 2. **Build Workflows** - Connect components, add logic, and configure data mapping 3. **Test & Iterate** - Run workflows immediately and see results in real-time 4. **Deploy** - Activate workflows in production with monitoring and observability 5. **Monitor & Optimize** - Track execution, debug issues, and improve performance ## Community & Support [#community--support] Ask a question or report a problem through one of these channels: * **Discord** - [discord.gg/VKvNxHjpYx](https://discord.gg/VKvNxHjpYx), for discussion with the community and the team. * **GitHub** - [github.com/bytechefhq/bytechef/issues](https://github.com/bytechefhq/bytechef/issues), for bug reports and feature requests. # ByteChef Developer Guide: Action URL: /developer-guide/component-specification/action Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/developer-guide/component-specification/action.mdx ## Action \[toc] [#action-toc] The action definition is used to specify the behavior and UI of an action inside a component. Below is an explanation of each method that can be used in the action definition: * `action(String name)` - Builds a new `ModifiableActionDefinition` with the specified backend ID. The name is the action ID used internally and in workflow JSON. * `title(String title)` - Sets the display name of the action (shown in the UI). * `description(String description)` - Provides a short description of the action (shown in the UI and tooltips). * `deprecated(boolean deprecated)` - Marks the action as deprecated. Deprecated actions should not be used in new integrations. * `perform(PerformFunction perform)` - Defines the action logic with access to the configured connection. Several overloads exist for specialized cases (`CallableResponsePerformFunction`, `WebhookResponsePerformFunction`, `StreamPerformFunction`, `WebSocketPerformFunction`), all sharing the same three-argument shape below. Perform functions receive: * `Parameters inputParameters` - Getter for action properties (values set by the user in the Properties tab). * `Parameters connectionParameters` - Getter for connection properties (if the action uses a connection). * `ActionContext actionContext` - Utilities for implementing logic: * `encoder` - Encode/decode data. * `file` - Work with temporary files (produces/consumes FileEntry). * `data` - Read/write persistent action data between runs. * `log` - Structured logging. * `json` - JSON read/write helpers. * `http` - HTTP client for external calls. * `event` - Publish platform events (e.g. action progress) when needed. * `help(String body)` / `help(String body, String learnMoreUrl)` - Adds help text (and optional *Learn more* link) displayed in the UI. * `properties(P... properties)` - Lists the properties that the action needs to perform its task. Properties are shown in the Properties tab. See [Property](/developer-guide/component-specification/property). ### Defining the output [#defining-the-output] There are several ways to describe what the action returns. The Output tab in the UI uses this to assist mapping in subsequent steps. * `output()` - Fully dynamic output. No schema is declared up front; users will see the shape only after running the action (schema is inferred from the first execution result). * `output(OutputSchema

outputSchema)` - Declares a static output schema. The schema can be a primitive (string, number, boolean, fileEntry, integer) or a complex type (object, array). The Output tab will display the fields and auto-generate sample values. * `output(SampleOutput sampleOutput)` - Provides just a sample result (useful for quick mapping without executing). The Output tab will show the sample. * `output(Placeholder placeholder)` - Defines a structure that will be shown in the dialog for uploading sample output data. * `output(OutputSchema

outputSchema, SampleOutput sampleOutput)` - Declares both schema and sample. The Output tab shows the declared fields populated with the provided sample values. * `output(BaseOutputFunction output)` / `output(OutputFunction output)` - Advanced: compute the output schema and/or sample dynamically based on current inputs or connection parameters. Use these when the output shape depends on user selections. ### Example [#example] ```java public static final ModifiableActionDefinition ACTION_DEFINITION = action("upperCase") .title("Upper Case") .description("Convert a string to upper case.") .properties( string("text") .label("Text") .controlType(ControlType.TEXT_AREA) .required(true)) .perform((inputParameters, connectionParameters, context) -> { String text = inputParameters.getRequiredString("text"); return text.toUpperCase(); }) .output( outputSchema(string().description("Upper case string.")), sampleOutput("HELLO WORLD") ); ``` # ByteChef Developer Guide: Component URL: /developer-guide/component-specification/component Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/developer-guide/component-specification/component.mdx ## Component \[toc] [#component-toc] The component definition describes a reusable integration component (its metadata, actions, triggers, and optional connection). Below is an explanation of each method you can use when building a component with the DSL: * `component(String name)` - Builds a new `ModifiableComponentDefinition` with the specified backend ID. The name is the component ID used internally and in workflow JSON. * [`actions(A... actionDefinitions)`](/developer-guide/component-specification/action) - Declares the actions exposed by this component. * `agentChannels(ModifiableAgentChannelDefinition... agentChannels)` - Declares that this component can carry an AI agent's conversation. See [Agent channels](#agent-channels) below. * `categories(ComponentCategory... category)` / `categories(List categories)` - Assigns UI categories for grouping and discovery in the catalog. See [ComponentCategory](https://github.com/bytechefhq/bytechef/blob/master/sdks/backend/java/component-api/src/main/java/com/bytechef/component/definition/ComponentCategory.java). * [`connection(ModifiableConnectionDefinition connectionDefinition)`](/developer-guide/component-specification/connection) - Defines the connection used by actions/triggers. If not set, actions can still run without a connection if they don't require one. * `customAction(boolean customAction)` - If true, enables the Custom Action feature in the UI (typically for REST/OpenAPI‑based connectors). * `customActionHelp(Help customActionHelp)` - Adds help text for the Custom Action. Displayed as a popup in the UI next to the Custom Action. * `description(String description)` - Short description shown in the catalog and tooltips. * `icon(String icon)` - Path or resource name of the SVG icon shown in the UI. * `resources(String documentationUrl)` / `resources(String documentationUrl, Map additionalUrls)` - Links to product documentation and optional additional resources (e.g., FAQ, blog posts, templates). * `title(String title)` - Human‑readable display name (Chicago style) shown in the UI. * [`triggers(T... triggerDefinitions)`](/developer-guide/component-specification/trigger) - Declares triggers available on this component. * `clusterElements(ClusterElementDefinition... clusterElements)` - Declares reusable cluster elements (e.g., AI tools) that the component may expose. * `version(int version)` - Component version number. Increment this when you make breaking changes so existing workflows can continue to use older versions. ### Example [#example] ```java private static final ComponentDefinition COMPONENT_DEFINITION = component("textHelper") .title("Text Helper") .description("Helper component which contains operations to help you work with text.") .icon("path:assets/text-helper.svg") .categories(ComponentCategory.HELPERS) .actions(TextHelperUpperCaseAction.ACTION_DEFINITION); ``` ### Agent channels [#agent-channels] An **agent channel** is a way an AI agent can be reached and can answer: a trigger that receives an incoming message and, usually, an action that sends the reply. Declaring one makes your component selectable as a channel on any agent - no change is needed anywhere else in the platform. The channel contract is three fields. Each end of the channel states where they live on it, in its own vocabulary: | Field | On the trigger (`agentRequest()`) | On the reply action (`agentReply()`) | | ---------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `conversationId` | path into the trigger's output identifying who to answer. Defaults to `"conversationId"` | the action property that addresses the reply. **No default** - omit it when the reply is a synchronous response and needs no address | | `message` | path into the trigger's output holding the incoming text. Defaults to `"message"` | the action property the agent's answer is written to. Defaults to `"message"` | | `attachments` | path into the trigger's output holding incoming files. **No default** - omitting it means this channel carries no attachments | the action property carrying outgoing files. Reserved; not wired yet | Because the request side defaults to the contract's own names, a trigger whose output already *is* the contract (build it with `ComponentDsl.agentChannelRequest()`) needs nothing but a bare `agentRequest()`. A trigger with its own payload shape spells the paths out, dots included - e.g. `.conversationId("message.chat.id")`. Pair them on the component with `agentChannel(name, trigger[, replyAction])`. The `name` is the channel's stored key and must be unique across all components: ```java // trigger public static final ModifiableTriggerDefinition TRIGGER_DEFINITION = trigger("newChatRequest") .title("New Chat Request") .output(...) .agentRequest(agentRequest().attachments(ATTACHMENTS)) .webhookRequest(ChatNewRequestTrigger::getWebhookResult); // reply action public static final ModifiableActionDefinition ACTION_DEFINITION = action("responseToRequest") .title("Response to Request") .agentReply(agentReply().attachments(ATTACHMENTS)) .perform(ChatResponseToRequestAction::perform); // component private static final ComponentDefinition COMPONENT_DEFINITION = component("chat") .title("Chat") .agentChannels( agentChannel("chat", ChatNewRequestTrigger.TRIGGER_DEFINITION, ChatResponseToRequestAction.ACTION_DEFINITION) .title("Chat") .approvalChannel("chat")); ``` Optional on `agentChannel(...)`: * `title(String title)` / `description(String description)` - How the channel is labelled in the agent UI. Defaults to the component's own title, which is wrong whenever one component exposes a channel that is not the whole component (Twilio's channel is titled "Twilio (WhatsApp)"). * `approvalChannel(String elementName)` - Which of this component's `APPROVAL_CHANNELS` cluster elements a human approval request is delivered through on this channel. Omit it and the channel simply cannot carry approvals. * `triggerParameters(Map triggerParameters)` - Parameters pinned onto the generated trigger node, for a trigger whose output shape depends on its own input. Optional on `agentReply()`: * `channelParameter(String rowKey, String property)` - A value the user configures on the channel itself is copied into `property` on every reply. Twilio's `number` becomes the reply's `From` this way; `rowKey` must name a property the paired trigger declares. * `fixedParameter(String property, Object value)` - A reply parameter pinned by the declaration, e.g. `useTemplate = false`. Everything is validated when the component loads: a path or property name that the paired trigger or action does not declare fails fast, rather than producing an agent that silently replies nowhere. A trigger that receives an event it should not act on declines by returning an empty collection from its webhook handler - the agent run never starts. # ByteChef Developer Guide: Connection URL: /developer-guide/component-specification/connection Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/developer-guide/component-specification/connection.mdx ## Connection \[toc] [#connection-toc] The connection definition configures how a component authenticates to external systems. Below is an explanation of each method available on the connection DSL: * `connection()` - Builds a new `ModifiableConnectionDefinition` used inside a component. * [`properties(P... properties)`](/developer-guide/component-specification/property) - Declares user-configurable connection properties (e.g., region, account ID). These appear in the connection dialog. * `authorizations(ModifiableAuthorization... authorizations)` - Declares one or more authorization mechanisms supported by this connection (OAuth2, API Key, Basic Auth, etc.). * `authorizationRequired(boolean authorizationRequired)` - Indicates if actions/triggers require a successful authorization before they can run. * `baseUri(BaseUriFunction baseUri)` - Returns the base URI for all HTTP requests for this connection. If the component has `customAction(true)`, the Base URI is shown in the Connection tab. * `version(int version)` - Connection version. Bump when introducing breaking changes to connection fields or auth behavior. ## Authorizations [#authorizations] Each authorization is configured via `ModifiableAuthorization` and can drive how credentials are acquired, applied to requests, refreshed, and detected. ### Modifiable Authorization [#modifiable-authorization] * `authorization(AuthorizationType authorizationType)` - Builds a new authorization of the given type. Supported values: `API_KEY`, `BASIC_AUTH`, `BEARER_TOKEN`, `CUSTOM`, `DIGEST_AUTH`, `OAUTH2_AUTHORIZATION_CODE`, `OAUTH2_AUTHORIZATION_CODE_PKCE`, `OAUTH2_CLIENT_CREDENTIALS`, `OAUTH2_IMPLICIT_CODE`, `OAUTH2_RESOURCE_OWNER_PASSWORD`. * `title(String title)` - Display name shown in the UI. * `description(String description)` - Optional help text for this auth method. * [`properties(P... properties)`](/developer-guide/component-specification/property) - Additional fields needed for the chosen auth (e.g., Client ID/Secret, tenant, audience). Use constants from `Authorization` when applicable: `USERNAME`, `PASSWORD`, `TOKEN`, `CLIENT_ID`, `CLIENT_SECRET`, `VALUE`. * `apply(ApplyFunction apply)` - Inject credentials into outgoing requests. Return `Authorization.ApplyResponse` using helpers: * `ApplyResponse.ofHeaders(Map> headers)` * `ApplyResponse.ofQueryParameters(Map> queryParameters)` * `authorizationUrl(AuthorizationUrlFunction authorizationUrl)` - For OAuth2 flows, compute the authorization URL. * `authorizationCallback(AuthorizationCallbackFunction authorizationCallback)` - Handle the redirect from the OAuth2 server and exchange the code for tokens. * `tokenUrl(TokenUrlFunction tokenUrl)` / `refreshUrl(RefreshUrlFunction refreshUrl)` - Provide OAuth2 token/refresh endpoints when needed. * `refresh(RefreshFunction refresh)` / `refreshToken(RefreshTokenFunction refreshTokenFunction)` - Implement custom refresh logic or provide only the refresh token value. * `scopes(ScopesFunction scopes)` - Defines the list of OAuth2 scopes to request. * `oAuth2AuthorizationExtraQueryParameters(...)` - Add extra query parameters to the OAuth2 authorization URL. ### Examples [#examples] Different auth types require different inputs. Below are practical examples. #### Basic Auth [#basic-auth] Basic Auth is a simple authentication scheme built into the HTTP protocol. It requires a username and password, which are sent with each request. ```java authorization(AuthorizationType.BASIC_AUTH) .title("Basic Auth") .properties( string(USERNAME) .label("Username") .required(true), string(PASSWORD) .label("Password") .required(true)) ``` #### Bearer Token [#bearer-token] Bearer Token authentication involves sending a token with each request. This token is typically obtained from an authorization server and represents the user's identity. ```java authorization(AuthorizationType.BEARER_TOKEN) .title("Bearer Token") .properties( string(TOKEN) .label("Token") .required(true)) ``` #### OAuth2 Authorization [#oauth2-authorization] OAuth2 Authorization Code is a robust authorization framework that allows third-party applications to obtain limited access to a web service. It involves redirecting the user to an authorization server to obtain an authorization code, which is then exchanged for an access token. ```java authorization(AuthorizationType.OAUTH2_AUTHORIZATION_CODE) .title("OAuth2 Authorization Code") .properties( string(CLIENT_ID) .label("Client Id") .required(true), string(CLIENT_SECRET) .label("Client Secret") .required(true)) .authorizationUrl((connectionParameters, context) -> "authorization url") .scopes((connection, context) -> List.of("scope1", "scope2")) .tokenUrl((connectionParameters, context) -> "token url") .refreshUrl((connectionParameters, context) -> "refresh url") ``` # ByteChef Developer Guide: Property URL: /developer-guide/component-specification/property Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/developer-guide/component-specification/property.mdx Properties describe the inputs a component's action, trigger, or connection collects from the user. Each property type below has a dedicated factory method (`string`, `integer`, `array`, ...) plus type-specific options, but they all share a common set of methods inherited from the base property builder. Two further factory methods exist alongside the ones documented below: `nullable(String name)` for a property whose only value is null, and `dynamicProperties(String name)` for a placeholder whose properties are computed at configuration time. ### Common Property Methods [#common-property-methods] These methods are available on every value property (`string`, `integer`, `number`, `bool`, `date`, `dateTime`, `time`, `array`, `object`, `fileEntry`): * `label(String label)` - Human-readable label shown above the field in the UI. * `description(String description)` - Help text describing the property (shown as a tooltip). * `placeholder(String placeholder)` - Placeholder text shown inside an empty field. * `required(boolean required)` - Marks the property as required; the workflow cannot run until it is set. * `hidden(boolean hidden)` - Hides the property from the UI (useful for constant values set via `defaultValue`). * `advancedOption(boolean advancedOption)` - Moves the property under the collapsible **Advanced** section in the Properties tab. * `displayCondition(String displayCondition)` - Shows the property only when the given expression evaluates to true, so a field can depend on what the user picked in another field. * `expressionEnabled(boolean expressionEnabled)` - Controls whether the field accepts `${...}` data-pill expressions (enabled by default). * `metadata(String key, String value)` / `metadata(Map metadata)` - Attaches arbitrary metadata to the property. ### Array Property [#array-property] The `ModifiableArrayProperty` class is a customizable property type designed to handle array values within a component. * `array(String name)` - Initializes a new `ModifiableArrayProperty` with the specified name. * `defaultValue(T... defaultValue)` - Sets the default value for the property using various data types such as Boolean, Integer, Long, Float, Double, String, or Map. * `exampleValue(T... exampleValue)` - Provides an example value for illustrative purposes using various data types. * `items(P.. properties)` - Specifies the properties that define the items in the array. * `optionsLookupDependsOn(String... optionsLookupDependsOn)` - Defines dependencies for option lookups. * `maxItems(long maxItems)` - Sets the maximum number of items allowed in the array. * `minItems(long minItems)` - Sets the minimum number of items required in the array. * `multipleValues(boolean multipleValues)` - Indicates whether the array can contain multiple values. * `options(Option... options)` - Specifies a list of options for the property. * `options(OptionsFunction optionsFunction)` - Defines a function to dynamically generate options. ### Boolean Property [#boolean-property] The `ModifiableBooleanProperty` class is a customizable property type designed to handle boolean values within a component. * `bool(String name)` - Initializes a new `ModifiableBooleanProperty` with the specified name. * `defaultValue(boolean defaultValue)` - Sets the default value for the property. * `exampleValue(boolean exampleValue)` - Provides an example value for illustrative purposes. ### Date Property [#date-property] The `ModifiableDateProperty` class is a customizable property type designed to handle date values within a component. * `date(String name)` - Initializes a new `ModifiableDateProperty` with the specified name. * `defaultValue(LocalDate defaultValue)` - Sets the default value for the property. * `exampleValue(LocalDate exampleValue)` - Provides an example value for illustrative purposes. * `optionsLookupDependsOn(String... optionsLookupDependsOn)` - Defines dependencies for option lookups. * `options(Option... options)` - Specifies a list of options for the property. * `options(OptionsFunction optionsFunction)` - Defines a function to dynamically generate options. ### Date Time Property [#date-time-property] The `ModifiableDateTimeProperty` class is a customizable property type designed to handle date-time values within a component. * `dateTime(String name)` - Initializes a new `ModifiableDateTimeProperty` with the specified name. * `defaultValue(LocalDateTime defaultValue)` - Sets the default value for the property. * `exampleValue(LocalDateTime exampleValue)` - Provides an example value for illustrative purposes. * `optionsLookupDependsOn(String... optionsLookupDependsOn)` - Defines dependencies for option lookups. * `options(Option... options)` - Specifies a list of options for the property. * `options(OptionsFunction optionsFunction)` - Defines a function to dynamically generate options. ### File Entry Property [#file-entry-property] The `ModifiableFileEntryProperty` class is a customizable property type designed to handle file entry values within a component. * `fileEntry(String name)` - Initializes a new `ModifiableFileEntryProperty` with the specified name. ### Integer Property [#integer-property] The `ModifiableIntegerProperty` class is a customizable property type designed to handle integer values within a component. * `integer(String name)` - Initializes a new `ModifiableIntegerProperty` with the specified name. * `defaultValue(long value)` - Sets the default value for the property. * `exampleValue(long exampleValue)` - Provides an example value for illustrative purposes. * `optionsLookupDependsOn(String... optionsLookupDependsOn)` - Defines dependencies for option lookups. * `maxValue(long maxValue)` - Sets the maximum allowable value for the property. * `minValue(long minValue)` - Sets the minimum allowable value for the property. * `options(Option... options)` - Specifies a list of options for the property. * `options(List> options)` - Sets a list of options for the property. * `options(OptionsFunction optionsFunction)` - Defines a function to dynamically generate options. ### Number Property [#number-property] The `ModifiableNumberProperty` class is a customizable property type designed to handle numeric values within a component. * `number(String name)` - Initializes a new `ModifiableNumberProperty` with the specified name. * `defaultValue(...)` - Sets the default value for the property using various numeric types such as int, long, float, or double. * `exampleValue(...)` - Provides an example value for illustrative purposes using various numeric types. * `optionsLookupDependsOn(String... optionsLookupDependsOn)` - Defines dependencies for option lookups. * `maxNumberPrecision(Integer maxNumberPrecision)` - Sets the maximum precision for the number. * `maxValue(double maxValue)` - Sets the maximum allowable value for the property. * `minNumberPrecision(Integer minNumberPrecision)` - Sets the minimum precision for the number. * `minValue(double minValue)` - Sets the minimum allowable value for the property. * `numberPrecision(Integer numberPrecision)` - Specifies the precision for the number. * `options(Option... options)` - Specifies a list of options for the property. * `options(OptionsFunction optionsFunction)` - Defines a function to dynamically generate options. ### Object Property [#object-property] The `ModifiableObjectProperty` class is a customizable property type designed to handle object values within a component. * `object(String name)` - Initializes a new `ModifiableObjectProperty` with the specified name. * `defaultValue(Map defaultValue)` - Sets the default value for the property. * `exampleValue(Map exampleValue)` - Provides an example value for illustrative purposes. * `additionalProperties(...)` - Specifies additional properties that can be included in the object. * `optionsLookupDependsOn(String... optionsLookupDependsOn)` - Defines dependencies for option lookups. * `multipleValues(boolean multipleValues)` - Indicates whether the object can contain multiple values. * `options(Option... options)` - Specifies a list of options for the property. * `options(OptionsFunction optionsFunction)` - Defines a function to dynamically generate options. * `properties(...)` - Specifies the properties that define the structure of the object. ### String Property [#string-property] The `ModifiableStringProperty` class is a customizable property type designed to handle string values within a component. * `string(String name)` - Initializes a new `ModifiableStringProperty` with the specified name. * `controlType(ControlType controlType)`- Sets the control type for the property (e.g., TEXT, SELECT). * `defaultValue(String value)`- Specifies the default value for the property. * `exampleValue(String exampleValue)`- Provides an example value for illustrative purposes. * `languageId(String languageId)`- Sets the language identifier for the property. * `optionsLookupDependsOn(String... optionsLookupDependsOn)`- Defines dependencies for option lookups. * `maxLength(int maxLength)`- Sets the maximum length allowed for the string value. * `minLength(int minLength)`- Sets the minimum length required for the string value. * `options(Option... options)`- Specifies a list of options for the property. * `options(List> options)`- Sets a list of options for the property. * `options(OptionsFunction optionsFunction)`- Defines a function to dynamically generate options. * `regex(String regex)`- Defines a regular expression that will be used on the string value. ### Time Property [#time-property] The `ModifiableTimeProperty` class is a customizable property type designed to handle time values within a component. * `time(String name)` - Initializes a new `ModifiableTimeProperty` with the specified name. * `defaultValue(LocalTime defaultValue)`- Specifies the default value for the property. * `exampleValue(LocalTime exampleValue)`- Provides an example value for illustrative purposes. * `optionsLookupDependsOn(String... optionsLookupDependsOn)`- Defines dependencies for option lookups. * `options(Option... options)`- Specifies a list of options for the property. * `options(List> options)`- Sets a list of options for the property. * `options(OptionsFunction optionsFunction)`- Defines a function to dynamically generate options. # ByteChef Developer Guide: Trigger URL: /developer-guide/component-specification/trigger Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/developer-guide/component-specification/trigger.mdx The trigger definition is used to specify the properties of a trigger. Below is an explanation of each method that can be used in the trigger definition: * `trigger(String name)` - Builds new `ModifiableTriggerDefinition` with the specified name. The name defines the trigger key (backend ID). * `description(String description)` - Provides a short description of the trigger. * `output` - Defines the output of the trigger. * `properties(P... properties)` - Lists the properties that the trigger needs to perform its task. Properties will be shown in the Properties tab. For more information, refer to the [Property](/developer-guide/component-specification/property). * `title(String title)` - Sets the name of the trigger that will be displayed in the UI. Use Chicago Style. * `type(TriggerType type)` - Sets the type of the trigger. Possible types are `CALLABLE`, `DYNAMIC_WEBHOOK`, `HYBRID`, `LISTENER`, `POLLING`, `STATIC_WEBHOOK` and `WEBSOCKET`. * `poll(PollFunction poll)` - Required for `POLLING` triggers. This method is called at specified intervals to check for new data. * `webhookEnable(WebhookEnableFunction webhookEnable)` - Required for `DYNAMIC_WEBHOOK` triggers. This method is called when the trigger is enabled to register the webhook with the third-party service. * `webhookDisable(WebhookDisableConsumer webhookDisable)` - Required for `DYNAMIC_WEBHOOK` triggers. This method is called when the trigger is disabled to unregister the webhook from the third-party service. * `webhookRequest(WebhookRequestFunction webhookRequest)` - Required for `DYNAMIC_WEBHOOK` and `STATIC_WEBHOOK` triggers. This method is called when a webhook request is received to process the incoming data. * `listenerEnable(ListenerEnableConsumer listenerEnable)` - Required for `LISTENER` triggers. This method is called when the trigger is enabled to start listening for events. * `listenerDisable(ListenerDisableConsumer listenerDisable)` - Required for `LISTENER` triggers. This method is called when the trigger is disabled to stop listening for events. * `webhookValidate(WebhookValidateFunction webhookValidate)` - Optional for webhook triggers. Used to validate the incoming webhook request (e.g., checking signatures) before a job is created. * `webhookValidateOnEnable(WebhookValidateFunction webhookValidateOnEnable)` - Optional. Validates the request during the provider's URL-verification handshake performed at enable time (e.g., echoing back a challenge token). * `webhookRawBody(boolean webhookRawBody)` - When `true`, the raw request body is passed to `webhookRequest` without being parsed (needed for signature verification over the exact bytes). * `workflowSyncExecution(boolean workflowSyncExecution)` - When `true`, the workflow runs synchronously and the HTTP caller receives the workflow's response (used by request-response webhook patterns). * `batch(boolean batch)` - When `true` and the trigger output is a list, a single job runs with the whole list; when `false`, one job runs per list element. * `deduplicate(DeduplicateFunction deduplicate)` - Optional for polling triggers. Provides a key used to drop records that were already seen on a previous poll. * `dynamicWebhookRefresh(DynamicWebhookRefreshFunction dynamicWebhookRefresh)` - Optional for `DYNAMIC_WEBHOOK` triggers whose registration expires. Called before expiration to renew the webhook and return a fresh output. ## Trigger Type [#trigger-type] * **CALLABLE**: A trigger that has no inbound endpoint of its own - the workflow is invoked directly, by another workflow or by a caller that holds a reference to it. `workflow/v1/newWorkflowCall` is the built-in example, and it is also the trigger that makes a workflow exposable as an MCP or A2A tool. * **DYNAMIC\_WEBHOOK**: A trigger that listens for incoming HTTP requests at a dynamically generated URL. * **HYBRID**: Combines features of both polling and webhook triggers. It can listen for events via webhooks and also poll for updates, providing flexibility in handling different event sources. * **LISTENER**: A trigger that continuously listens for specific events or messages from a source, such as a message queue or event stream, and activates when those events occur. * **POLLING**: Regularly checks a data source at specified intervals to detect changes or new data. This type is suitable for systems that do not support webhooks or real-time notifications. * **STATIC\_WEBHOOK**: A trigger that listens for incoming HTTP requests at a fixed URL. This type is ideal for scenarios where the endpoint URL does not change and can be predefined. * **WEBSOCKET**: A trigger fired by a WebSocket upgrade rather than an HTTP request; the platform registers no HTTP webhook controller for it. `browser/v1/voiceSession` is the built-in example. # ByteChef Developer Guide: Add Connection URL: /developer-guide/build-component/add-connection Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/developer-guide/build-component/add-connection.mdx How to create connection for a component. ## Add Connection \[toc] [#add-connection-toc] In the `server/libs/modules/components/newcomponent/src/main/java/com/bytechef/component/newcomponent/connection` package, the `NewComponentConnection` class defines the connection. The `CONNECTION_DEFINITION` constant contains all the details about the connection, including its base URI, authorizations, properties, and more. If your component uses OAuth2 authorization, you can define the authorization type, properties, authorization URL, and scopes in the `authorizations` method of the `CONNECTION_DEFINITION` constant. The `properties` method allows you to define the properties that are required for the connection, such as Client ID and Client Secret. Here is an example of a connection with OAuth2 authorization: ```java public static final ModifiableConnectionDefinition CONNECTION_DEFINITION = connection() .baseUri((connectionParameters, context) -> "base url") .authorizations( authorization(AuthorizationType.OAUTH2_AUTHORIZATION_CODE) .title("OAuth2 Authorization Code") .properties( string(CLIENT_ID) .label("Client Id") .required(true), string(CLIENT_SECRET) .label("Client Secret") .required(true)) .authorizationUrl((connectionParameters, context) -> "authorization url") .scopes((connection, context) -> List.of("scope1", "scope2")) .tokenUrl((connectionParameters, context) -> "token url") .refreshUrl((connectionParameters, context) -> "refresh url")); ``` If another type of authorization is used, such as Basic or API Key, you can define it in the `authorizations` method of the `CONNECTION_DEFINITION` constant. For more information, refer to the [connection documentation](/developer-guide/component-specification/connection). # ByteChef Developer Guide: Create Action URL: /developer-guide/build-component/create-action Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/developer-guide/build-component/create-action.mdx How to create an action for a component. ## Create Action \[toc] [#create-action-toc] In `server/libs/modules/components/newcomponent/src/main/java/com/bytechef/component/newcomponent/action` package, the `NewComponentDummyAction` class defines the action. The `ACTION_DEFINITION` constant contains all the details about the action, including its name, title, description, properties and others. ```java public static final ModifiableActionDefinition ACTION_DEFINITION = action("dummy") .title("Dummy Action") .description("Action description.") .properties( string("name") .label("label") .description("Property description.") .minLength(1) .maxLength(255) .required(true)) .output( outputSchema( string())) .perform(NewComponentDummyAction::perform); ``` The `perform` method contains the logic for the action. Here is the simplest example of the `perform` method that returns the value of the `name` property. ```java public static String perform(Parameters inputParameters, Parameters connectionParameters, Context context) { return inputParameters.getRequiredString("name"); } ``` For more information about any method in the `ACTION_DEFINITION`, refer to the [action documentation](/developer-guide/component-specification/action). # ByteChef Developer Guide: Create Component Definition URL: /developer-guide/build-component/create-component-definition Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/developer-guide/build-component/create-component-definition.mdx How to create a new component definition. In `server/libs/modules/components/newcomponent/src/main/java/com/bytechef/component/newcomponent` package, the `NewComponentComponentHandler` class defines the component. The `COMPONENT_DEFINITION` constant contains all the details about the component, including its name, title, description, icon, categories, connection, actions, triggers and others. ```java @AutoService(ComponentHandler.class) public class NewComponentComponentHandler implements ComponentHandler { private static final ComponentDefinition COMPONENT_DEFINITION = component("newComponent") .title("New Component") .description("New component description.") .icon("path:assets/newcomponent.svg") .categories(ComponentCategory.HELPERS) .connection(NewComponentConnection.CONNECTION_DEFINITION) .actions(NewComponentDummyAction.ACTION_DEFINITION) .triggers(NewComponentDummyTrigger.TRIGGER_DEFINITION); @Override public ComponentDefinition getDefinition() { return COMPONENT_DEFINITION; } } ``` ### How the platform finds your handler [#how-the-platform-finds-your-handler] `@AutoService(ComponentHandler.class)` registers the class with the JDK `ServiceLoader`, which is how the platform discovers components. A handler discovered this way is instantiated by the platform, not by Spring, so it has **no dependency injection** - everything it needs must be a constant or come from the `Context` passed into `perform`. If your component genuinely needs Spring beans (for example an AI model registry), annotate the handler with `@Component("newComponent_v1_ComponentHandler")` instead and use constructor injection - Spring-managed handlers are collected by type and merged with the ServiceLoader ones. Keep the `_v_ComponentHandler` bean-name shape; it is the convention every such handler in the codebase follows. Only a handful of built-in components need this; `server/libs/modules/components/ai/agent/utils/.../AiAgentUtilsComponentHandler.java` is one example. ### Icon [#icon] Find and download a user interface icon in .svg format for your component and place it in `server/libs/modules/components/newcomponent/src/main/resources/assets/newcomponent.svg` - the file name has to match the `icon("path:assets/…")` value above. The `title`, `description`, and `icon` you set here are exactly what the workflow editor shows when the component is discovered: once the module is on the classpath and the server is running, the component appears in the editor's component panel and can be dropped into a workflow. {/* TODO screenshot: the newly built component (with its title and SVG icon) appearing in the workflow editor's right-hand component panel / node picker */} For more information about any method in the `COMPONENT_DEFINITION`, refer to the [component documentation](/developer-guide/component-specification/component). # ByteChef Developer Guide: Create Trigger URL: /developer-guide/build-component/create-trigger Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/developer-guide/build-component/create-trigger.mdx How to create a trigger for a component. ## Create Trigger \[toc] [#create-trigger-toc] In `server/libs/modules/components/newcomponent/src/main/java/com/bytechef/component/newcomponent/trigger` package, the `NewComponentDummyTrigger` class defines the trigger. The `TRIGGER_DEFINITION` constant contains all the details about the trigger, including its name, title, description, properties and others. ### Polling Trigger Example [#polling-trigger-example] Polling triggers regularly check a data source for changes. You must implement the `poll` method. ```java public static final ModifiableTriggerDefinition TRIGGER_DEFINITION = trigger("dummyTrigger") .title("Dummy Trigger") .description("Polls a dummy API for new data.") .type(TriggerType.POLLING) .output(outputSchema(string())) .poll(NewComponentDummyTrigger::poll); protected static PollOutput poll( Parameters inputParameters, Parameters connectionParameters, Parameters closureParameters, TriggerContext triggerContext) { // Implementation logic to fetch data and determine what is new // ... return new PollOutput(newRecords, closureParameters, false); } ``` ### Dynamic Webhook Trigger Example [#dynamic-webhook-trigger-example] Dynamic webhooks require registering a URL with a third-party service. You must implement `webhookEnable`, `webhookDisable`, and `webhookRequest`. ```java public static final ModifiableTriggerDefinition TRIGGER_DEFINITION = trigger("dummyTrigger") .title("Dummy Trigger") .description("Triggers on a dynamic URL.") .type(TriggerType.DYNAMIC_WEBHOOK) .output(outputSchema(string())) .webhookEnable(NewComponentDummyTrigger::webhookEnable) .webhookDisable(NewComponentDummyTrigger::webhookDisable) .webhookRequest(NewComponentDummyTrigger::webhookRequest); protected static WebhookEnableOutput webhookEnable( Parameters inputParameters, Parameters connectionParameters, String webhookUrl, String workflowExecutionId, TriggerContext triggerContext) { // Logic to register webhookUrl with the third-party API return new WebhookEnableOutput(Map.of(), null); } protected static void webhookDisable( Parameters inputParameters, Parameters connectionParameters, Parameters outputParameters, String workflowExecutionId, TriggerContext triggerContext) { // Logic to unregister the webhook using the ID stored during enable } protected static Object webhookRequest( Parameters inputParameters, Parameters connectionParameters, HttpHeaders headers, HttpParameters parameters, WebhookBody body, WebhookMethod method, Parameters output, TriggerContext triggerContext) { // Logic to process the incoming webhook request body return body.getContent(); } ``` ### Static Webhook Trigger Example [#static-webhook-trigger-example] Static webhooks use a fixed URL and only require the `webhookRequest` method to be implemented. ```java public static final ModifiableTriggerDefinition TRIGGER_DEFINITION = trigger("dummyTrigger") .title("Dummy Trigger") .description("Triggers on a fixed URL.") .type(TriggerType.STATIC_WEBHOOK) .output(outputSchema(string())) .webhookRequest(NewComponentDummyTrigger::webhookRequest); protected static Map webhookRequest( Parameters inputParameters, Parameters connectionParameters, HttpHeaders headers, HttpParameters parameters, WebhookBody body, WebhookMethod method, Parameters output, TriggerContext context) { return body.getContent(new TypeReference<>() {}); } ``` ### Listener Trigger Example [#listener-trigger-example] Listener triggers stay active and wait for events (e.g., from a message queue). You must implement `listenerEnable` and `listenerDisable`. ```java public static final ModifiableTriggerDefinition TRIGGER_DEFINITION = trigger("dummyTrigger") .title("Dummy Trigger") .description("Triggers on a message queue.") .type(TriggerType.LISTENER) .output(outputSchema(string())) .listenerEnable(NewComponentDummyTrigger::listenerEnable) .listenerDisable(NewComponentDummyTrigger::listenerDisable); protected static void listenerEnable( Parameters inputParameters, Parameters connectionParameters, String workflowExecutionId, ListenerEmitter listenerEmitter, TriggerContext context) { // Logic to start listening (e.g., connect to message broker and set up callback) // When a message arrives: // listenerEmitter.emit(data); } protected static void listenerDisable( Parameters inputParameters, Parameters connectionParameters, String workflowExecutionId, TriggerContext context) { // Logic to stop listening and close connections } ``` For more information about any method in the `TRIGGER_DEFINITION`, refer to the [trigger documentation](/developer-guide/component-specification/trigger). # ByteChef Developer Guide: Initial Setup URL: /developer-guide/build-component/initial-setup Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/developer-guide/build-component/initial-setup.mdx Learn how to create a new component To create a new component, we will use the `example` component as a template. 1. **Copy the example component** from `server/libs/modules/components/example` into a new package `server/libs/modules/components/newcomponent`, where `newcomponent` is the name of your new component. 2. **Update Settings**: * Open `bytechef/settings.gradle.kts`. * Add the following line to include your new component in the build process: ```kotlin include("server:libs:modules:components:newcomponent") ``` 3. **Load Gradle Changes**: * Refresh or reload the Gradle project in IntelliJ IDEA. * This step ensures that IntelliJ recognizes your new component as a Java module, allowing you to work with it seamlessly within the IDE. 4. **Rename Package and Classes**: * Inside the newly created package, rename the `example` subpackage to `newcomponent`. * Additionally, rename all classes within this package that start with `Example` to start with `NewComponent`. # ByteChef Developer Guide: Write Tests URL: /developer-guide/build-component/write-tests Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/developer-guide/build-component/write-tests.mdx How to write tests for your component. ## Overview [#overview] When you build a component, add tests to validate both: * The component definition (actions, triggers, properties, connection) stays consistent over time. * The business logic in your actions and triggers behaves as expected. ## Component Handler Test [#component-handler-test] This test serializes your component definition to JSON and compares it with a snapshot file under your module's test resources. If the file is missing, it will be auto-created. * Location: `src/test/resources/definition/_v.json` * Naming: Use your component name and version (for example: notion\_v1.json) * Purpose: Catch unintentional breaking changes to your component definition (titles, properties, output schemas, etc.). Example: ```java import com.bytechef.test.jsonasssert.JsonFileAssert; import org.junit.jupiter.api.Test; class MyComponentHandlerTest { @Test void testGetDefinition() { JsonFileAssert.assertEquals("definition/my-component_v1.json", new MyComponentHandler().getDefinition()); } } ``` How it works: * `JsonFileAssert.assertEquals` writes the file under `src/test/resources/` when it is missing, then reads the copy on the **test classpath** (`build/resources/test/`) and performs a strict JSON comparison against it. * Because the comparison reads the build output, deleting only the source file is not enough. To intentionally update the snapshot, delete **both**: * `src/test/resources/definition/.json` * `build/resources/test/definition/.json` Then re-run the test. The run that finds no file writes a fresh one; run the test once more to compare against it, and commit the regenerated JSON. Tip: Every component module should have one ComponentHandlerTest like this. ## Unit Testing Actions [#unit-testing-actions] Action logic lives in perform(...). You can unit test it by providing Parameters via MockParametersFactory and mocking ActionContext with Mockito. Example (simplified boolean computation): ```java import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; import com.bytechef.component.definition.ActionContext; import com.bytechef.component.definition.Parameters; import com.bytechef.component.test.definition.MockParametersFactory; import java.util.Map; import org.junit.jupiter.api.Test; class MyActionTest { private final ActionContext actionContext = mock(ActionContext.class); @Test void testPerform() { Parameters input = MockParametersFactory.create(Map.of("text", "hello")); Object result = MyAction.perform(input, input, actionContext); assertEquals("HELLO", result); } } ``` Notes: * Use MockParametersFactory.create(Map.of(...)) to build Parameters quickly for inputs and, if needed, connection parameters. * Mock ActionContext and verify interactions if your code uses context.http(), context.file(), context.data(), etc. ## Unit Testing Triggers [#unit-testing-triggers] For polling triggers, test the poll(...) function by mocking TriggerContext and its HTTP executor. For webhook triggers, write small tests around enable/disable/request handlers by mocking ctx.http() calls and asserting returned data structures. Remember: If you change your component definition, remember to update the snapshot JSON: * Delete `src/test/resources/definition/_v.json` **and** `build/resources/test/definition/_v.json` * Re-run the Component Handler Test to regenerate # openapi: Deploy a new code based project URL: /openapi/automation-project-code-workflow/deployProject Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/openapi/automation-project-code-workflow/deployProject.mdx Deploy a new code based project. {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # openapi: Automation Projects URL: /openapi/automation-project-code-workflow Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/openapi/automation-project-code-workflow/index.mdx Deploy a code-based project. {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # ByteChef Developer Guide: Create Trigger URL: /developer-guide/generate-component/create-trigger Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/developer-guide/generate-component/create-trigger.mdx How to create a trigger for a component. ## Create Trigger \[toc] [#create-trigger-toc] When creating a trigger for generated component, it must be added manually. Follow the [instructions in Build Component section](/developer-guide/build-component/create-trigger) to create `NewComponentDummyTrigger` class that defines the trigger. Once this class is created, update Component Handler as follows: In `NewComponentComponentHandler`, override the `getTriggers()` method: ```java @Override public List getTriggers() { return List.of(NewComponentDummyTrigger.TRIGGER_DEFINITION); } ``` # ByteChef Developer Guide: Customize Component URL: /developer-guide/generate-component/customize-component Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/developer-guide/generate-component/customize-component.mdx ### Connector Icon and Category [#connector-icon-and-category] 1. Find an Icon: * Search for a suitable user interface icon for your component in `.svg` format. 2. Save the Icon: * Place the icon in the following directory: `server/libs/modules/components/newcomponent/src/main/resources/assets/newcomponent.svg`. 3. Choose a Category: * Select a category for your component. Available categories can be found in [ComponentCategory](https://github.com/bytechefhq/bytechef/blob/master/sdks/backend/java/component-api/src/main/java/com/bytechef/component/definition/ComponentCategory.java). 4. Update Component Handler: * In `NewComponentComponentHandler`, override the `modifyComponent(ModifiableComponentDefinition modifiableComponentDefinition)` method: ```java @Override public ModifiableComponentDefinition modifyComponent(ModifiableComponentDefinition modifiableComponentDefinition) { return modifiableComponentDefinition .icon("path:assets/newcomponent.svg") .categories(ComponentCategory.HELPERS); } ``` The icon and category you set here determine how the generated component is presented in the workflow editor: once the module is on the classpath and the server is running, it appears (with its icon) under the chosen category in the editor's component panel. {/* TODO screenshot: the generated component listed with its icon under its chosen category in the workflow editor's component panel */} ### Connection [#connection] If your component requires custom authentication parameters, override the `modifyConnection(ModifiableConnectionDefinition modifiableConnectionDefinition)` method in `NewComponentComponentHandler`. Refer to examples like [`ShopifyComponentHandler`](https://github.com/bytechefhq/bytechef/blob/master/server/libs/modules/components/shopify/src/main/java/com/bytechef/component/shopify/ShopifyComponentHandler.java#L72), [`DiscordComponentHandler`](https://github.com/bytechefhq/bytechef/blob/master/server/libs/modules/components/discord/src/main/java/com/bytechef/component/discord/DiscordComponentHandler.java#L92), or [`PipelinerComponentHandler`](https://github.com/bytechefhq/bytechef/blob/master/server/libs/modules/components/pipeliner/src/main/java/com/bytechef/component/pipeliner/PipelinerComponentHandler.java#L57) for guidance. #### API Key Labels via OpenAPI `x-title` [#api-key-labels-via-openapi-x-title] You can customize user‑friendly labels for API key properties in the generated connection definition by using the `x-title` OpenAPI extension on security scheme properties. When present, ByteChef’s generator uses `x-title` as the label for the corresponding property. Example OpenAPI snippet: ```yaml components: securitySchemes: ApiKeyQuery: type: "apiKey" in: "query" name: "appid" x-title: "App ID" ``` Expected generated code: ```java public static final ComponentDsl.ModifiableConnectionDefinition CONNECTION_DEFINITION = connection() .authorizations(authorization(AuthorizationType.API_KEY) .title("API Key") .properties( string(KEY) .label("Key") .required(true) .defaultValue("appid") .hidden(true), string(VALUE) .label("App ID") // comes from x-title .required(true), string(ADD_TO) .label("Add to") .required(true) .defaultValue(ApiTokenLocation.QUERY_PARAMETERS.name()) .hidden(true) )); ``` #### Bearer Token Labels via OpenAPI `x-title` [#bearer-token-labels-via-openapi-x-title] You can also customize the label of the bearer token field using the same `x-title` OpenAPI extension. When the generator processes a Bearer Token authorization scheme, it maps `x-title` to the `.label(...)` of the generated token property. Example OpenAPI snippet: ```yaml components: securitySchemes: BearerAuth: type: "http" scheme: "bearer" x-title: "Access Token" ``` Expected generated code: ```java public static final ComponentDsl.ModifiableConnectionDefinition CONNECTION_DEFINITION = connection() .authorizations( authorization(AuthorizationType.BEARER_TOKEN) .title("Bearer Token") .properties( string(TOKEN) .label("Access Token") // comes from x-title .required(true) ) ); ``` ### Action [#action] If some actions require properties not specified in the OpenAPI schema, override the `modifyActions(ModifiableActionDefinition... actionDefinitions)` method in `NewComponentComponentHandler`. Refer to examples like [`DiscordComponentHandler`](https://github.com/bytechefhq/bytechef/blob/master/server/libs/modules/components/discord/src/main/java/com/bytechef/component/discord/DiscordComponentHandler.java#L66) or [`ClickupComponentHandler`](https://github.com/bytechefhq/bytechef/blob/master/server/libs/modules/components/clickup/src/main/java/com/bytechef/component/clickup/ClickupComponentHandler.java#L60). ### Dynamic Options [#dynamic-options] 1. Define Dynamic Options in OpenAPI Schema: * Add `x-dynamic-options: true` to the parameter in your OpenAPI schema to indicate that it requires dynamic options. * If the dynamic options depend on another parameter, include `x-dynamic-options-dependency` with the relevant parameter name. 2. Regenerate the Component: * Run the following command to regenerate the component with updated dynamic options: ```bash ./bytechef.sh component init --open-api-path ../../server/libs/modules/components/newcomponent/openapi.yaml --output-path ../../server/libs/modules/components --name newcomponent ``` 3. For each parameter with dynamic `options`, the options() and `optionsLookupDependsOn()` methods are automatically generated in the `ModifiableActionDefinition` class. 4. The `AbstractNewComponentUtils` class is generated, providing methods to retrieve dynamic options for various properties within the component. 5. Override the appropriate method in the `NewComponentUtils` class to load the correct options based on your specific requirements. For implementation details, refer to examples from existing components such as [`Shopify`](https://github.com/bytechefhq/bytechef/blob/master/server/libs/modules/components/shopify), [`Airtable`](https://github.com/bytechefhq/bytechef/blob/master/server/libs/modules/components/airtable), and [`Hubspot`](https://github.com/bytechefhq/bytechef/blob/master/server/libs/modules/components/hubspot). ### Dynamic Properties [#dynamic-properties] 1. Define Dynamic Properties in OpenAPI Schema: * Add `x-dynamic-properties: true` to the parameter in your OpenAPI schema to mark it as dynamic. * If the dynamic property depends on another parameter, include` x-dynamic-properties-dependency` with the relevant parameter name. 2. Regenerate the Component: * Run the following command to regenerate the component with updated dynamic properties: ```bash ./bytechef.sh component init --open-api-path ../../server/libs/modules/components/newcomponent/openapi.yaml --output-path ../../server/libs/modules/components --name newcomponent ``` 3. For each dynamic property, the `properties()` and `propertiesLookupDependsOn()` methods are generated in the `ModifiableActionDefinition` class. 4. The `AbstractNewComponentUtils` class is generated, offering methods to retrieve dynamic properties for various parameters within the component. 5. Override the necessary method in the `NewComponentUtils` class to load the correct properties based on your specific needs. For implementation details, refer to examples from existing components such as [`Airtable`](https://github.com/bytechefhq/bytechef/blob/master/server/libs/modules/components/airtable). ### Dynamic Output [#dynamic-output] 1. Define Dynamic Output in OpenAPI Schema: * Add `x-dynamic-output: true` to the response in your OpenAPI schema to indicate that output is fully dynamic. For implementation details, refer to examples from existing components such as [`Airtable`](https://github.com/bytechefhq/bytechef/blob/master/server/libs/modules/components/airtable). ### AI Agent Tool [#ai-agent-tool] 1. Define the AI Agent Tool in OpenAPI Schema: * Add `x-ai-agent-tool: true` to the individual endpoint in your OpenAPI schema to indicate that it is an AI Agent Tool. 2. Regenerate the Component: * Run the following command to regenerate the component: ```bash ./bytechef.sh component init --open-api-path ../../server/libs/modules/components/newcomponent/openapi.yaml --output-path ../../server/libs/modules/components --name newcomponent ``` For implementation details, refer to examples from existing components such as [`Airtable`](https://github.com/bytechefhq/bytechef/blob/master/server/libs/modules/components/airtable). # ByteChef Developer Guide: Generate Component URL: /developer-guide/generate-component Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/developer-guide/generate-component/index.mdx Scaffold a new component from an OpenAPI specification using the ByteChef CLI. ## Generate Component \[toc] [#generate-component-toc] In the following steps, we will learn how to generate a new component from an OpenAPI specification using the ByteChef CLI (`cli/cli-app`). The CLI reads your API's OpenAPI definition and scaffolds a full component module - actions, properties, and a connection - so you don't have to write them by hand. OpenAPI, formerly known as Swagger, is a specification for building APIs that allows developers to define their API's structure in a standardized format. By using the CLI, you can streamline the process of creating components by automatically generating code based on your OpenAPI definitions. ## Steps [#steps] **[Initial setup](/developer-guide/generate-component/initial-setup)** - create the component package and register it in the Gradle build. **[OpenAPI specification](/developer-guide/generate-component/open-api-specification)** - add your `openapi.yaml` and run the CLI `component init` command to generate the component. **[Customize the component](/developer-guide/generate-component/customize-component)** - set the icon and category, adjust the connection and actions, and enable dynamic options, dynamic properties, dynamic output, and AI Agent tools via OpenAPI extensions. **[Create a trigger](/developer-guide/generate-component/create-trigger)** - triggers are not generated from OpenAPI, so add them manually and wire them into the generated handler. The result is the same kind of component module you would author by hand; once it is on the classpath, it appears in the workflow editor's component panel alongside the built-in connectors. > Generating a component only scaffolds the code. For APIs with non-standard behavior, or for triggers, you will still edit the generated classes as described in [Customize the component](/developer-guide/generate-component/customize-component) and [Create a trigger](/developer-guide/generate-component/create-trigger). If you prefer to write everything yourself, see [Build a component by hand](/developer-guide/build-component/initial-setup) instead. # ByteChef Developer Guide: Initial Setup URL: /developer-guide/generate-component/initial-setup Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/developer-guide/generate-component/initial-setup.mdx ## Initial Setup \[toc] [#initial-setup-toc] This section provides a clear step-by-step guide for setting up a new component in your project, ensuring it is properly integrated into the build system and recognized by the IDE. 1. Create a New Package: * Navigate to `server/libs/modules/components/`. * Create a new package with the name of your component, e.g., `newcomponent`. 2. Update Settings: * Open `bytechef/settings.gradle.kts`. * Add the following line to include your new component in the build process: ```kotlin include("server:libs:modules:components:newcomponent") ``` 3. Load Gradle Changes: * Refresh or reload the Gradle project in IntelliJ IDEA. * This step ensures that IntelliJ recognizes your new component as a Java module, allowing you to work with it seamlessly within the IDE. # ByteChef Developer Guide: OpenAPI Specification URL: /developer-guide/generate-component/open-api-specification Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/developer-guide/generate-component/open-api-specification.mdx ## OpenAPI Specification \[toc] [#openapi-specification-toc] 1. Create OpenAPI Specification: * Inside your new package, create an `openapi.yaml` file. * Write the [OpenAPI specification](https://swagger.io/specification/) for your component to define its API structure and endpoints. 2. Navigate to CLI Directory: * Change your working directory to the `BYTECHEF_HOME/cli/cli-app` folder. 3. Generate Component: * Execute the following command to generate the `NewComponent` in the `newcomponent` directory: ```bash ./bytechef.sh component init --open-api-path ../../server/libs/modules/components/newcomponent/openapi.yaml --output-path ../../server/libs/modules/components --name newcomponent --version 1 ``` * This command initializes the component based on the OpenAPI specification, placing the generated files in the specified output path. * `bytechef.sh` is a thin wrapper around `./gradlew -p cli/cli-app run`. Its working directory is `cli/cli-app`, which is why the paths above are relative to it, and it appends `--internal-component true` so the generated module is laid out as a component that ships with the platform. `component init` accepts: | Option | Meaning | | ---------------------- | ------------------------------------------------------------------------------------------------ | | `--name`, `-n` | Component name (required). Lower-cased before use. | | `--output-path`, `-o` | Directory the generated module is written to (required). | | `--open-api-path` | Path or URL of the OpenAPI specification. Without it the command does nothing. | | `--version`, `-v` | Component version. Defaults to `1`. | | `--base-package-name` | Package for the generated classes. Defaults to `com.bytechef.component`. | | `--internal-component` | Whether the component ships with the platform. Defaults to `false`; `bytechef.sh` passes `true`. | If you would rather run the CLI as a standalone binary - for a component you keep outside this repository - build it once with `./gradlew :cli:cli-app:installDist` and call `cli/cli-app/build/install/bytechef/bin/bytechef`. The binary runs from your current working directory, so paths behave as you expect, and it does **not** pass `--internal-component`. # openapi: Automation Git URL: /openapi/automation-project-git Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/openapi/automation-project-git/index.mdx Pull a project from its configured git repository. {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # openapi: Pulls project from git repository. URL: /openapi/automation-project-git/pullProjectFromGit Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/openapi/automation-project-git/pullProjectFromGit.mdx Pulls project from git repository. {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # openapi: Deploy a new custom component URL: /openapi/custom-components/deployCustomComponent Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/openapi/custom-components/deployCustomComponent.mdx Deploy a new custom component. {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # openapi: Custom Components URL: /openapi/custom-components Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/openapi/custom-components/index.mdx Public REST API for deploying a custom component to the platform. {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # ByteChef Reference: Branch URL: /reference/flow-controls/branch_v1 Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/reference/flow-controls/branch_v1.mdx Executes one and only one branch of execution based on the `expression` value. Type: branch/v1
## Properties [#properties] | Name | Label | Type | Description | Required | | :--------: | :--------: | :----: | :-----------------------------------------------------------------------------: | :------: | | expression | Expression | STRING | Defines expression upon which evaluation the proper branch continues execution. | false | # ByteChef Reference: Condition URL: /reference/flow-controls/condition_v1 Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/reference/flow-controls/condition_v1.mdx Directs a stream based on true/false results of comparisons. Type: condition/v1
## Properties [#properties] | Name | Label | Type | Description | Required | | :-----------: | :------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------: | :------: | | rawExpression | Raw Expression | BOOLEAN
Options true, true
| Set condition as raw expression or list of conditions. | true | | conditions | OR Conditions | ARRAY
Items \[\[\{STRING(type), BOOLEAN(value1), STRING(operation), BOOLEAN(value2)}($boolean), {STRING\(type), DATE_TIME\(value1), STRING\(operation), DATE_TIME\(value2)}\($dateTime), \{STRING(type), NUMBER(value1), STRING(operation), NUMBER(value2)}($number), {STRING\(type), STRING\(value1), STRING\(operation), STRING\(value2), STRING\(value2)}\($string)]]
| OR Condition array that contains AND Condition arrays | false | | expression | Expression | STRING | The raw expression. | true |
# Additional Instructions [#additional-instructions] The Condition flow control evaluates a logical expression and routes execution down one of two paths: **true** or **false**. Use it for binary decisions - threshold checks, validation, or simple yes/no branching. ## Usage [#usage] ### Step 1: Add Condition to your workflow [#step-1-add-condition-to-your-workflow] Select **Condition** from the **Flows** component panel and add it where the workflow needs to branch. The node creates two paths, **True** and **False**. ### Step 2: Choose how to express the condition [#step-2-choose-how-to-express-the-condition] The **Raw Expression** toggle decides which of two editors you get. See [Expressions](/reference/expressions) for the full expression language. **Raw Expression enabled** - write the expression directly in the **Expression** field: ``` ${order_1.total} > 1000 ${user_1.verified} == true && ${user_1.age} >= 18 ``` Values like `${order_1.total}` come from data pills - output produced by an earlier component or trigger, or a value you configured by hand. **Raw Expression disabled** - build the condition visually. Conditions are structured as **OR** groups, each containing one or more **AND** conditions: 1. Click **Add OR Condition** to create a group. 2. Inside a group, click **Add AND Condition** to add a comparison. 3. Pick the comparison type: Boolean, Date Time, Number, or String. Each comparison takes **Value 1**, an **Operation**, and (for every operation except `Empty`) **Value 2**. ### Step 3: Build the two paths [#step-3-build-the-two-paths] Add the components that should run when the condition is true to the **True** path, and those for the false case to the **False** path. ## Available operations [#available-operations] The operations offered depend on the comparison type you pick. ### String [#string] | Operation | Meaning | | -------------------- | ------------------------------------------------- | | `Equals` | Exact match | | `Equals Ignore Case` | Case-insensitive match | | `Not Equals` | Not equal to | | `Contains` | Value 1 includes Value 2 | | `Not Contains` | Value 1 does not include Value 2 | | `Starts With` | Value 1 begins with Value 2 | | `Ends With` | Value 1 ends with Value 2 | | `Regex` | Value 1 matches the regular expression in Value 2 | | `Empty` | Value 1 is an empty string - takes no Value 2 | ### Number [#number] | Operation | Meaning | | ------------------- | ------------------------------------------- | | `Equals` | Value 1 equals Value 2 | | `Not Equals` | Value 1 does not equal Value 2 | | `Greater` | Value 1 is greater than Value 2 | | `Greater or Equals` | Value 1 is greater than or equal to Value 2 | | `Less` | Value 1 is less than Value 2 | | `Less or Equals` | Value 1 is less than or equal to Value 2 | | `Empty` | Value 1 is absent - takes no Value 2 | ### Boolean [#boolean] | Operation | Meaning | | ------------ | ------------------------------ | | `Equals` | Value 1 equals Value 2 | | `Not Equals` | Value 1 does not equal Value 2 | ### Date Time [#date-time] | Operation | Meaning | | --------- | ------------------------------- | | `After` | Value 1 is later than Value 2 | | `Before` | Value 1 is earlier than Value 2 | # ByteChef Reference: Each URL: /reference/flow-controls/each_v1 Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/reference/flow-controls/each_v1.mdx Iterates over each item in list, in parallel. Note, that since it iterates over each item in parallel, there is no guarantee of completion order. Type: each/v1
## Properties [#properties] | Name | Label | Type | Description | Required | | :---: | :-----------: | :-------------------------------------------------------: | :----------------------------: | :------: | | items | List of items | ARRAY
Items \[]
| List of items to iterate over. | false | # ByteChef Reference: Fork/Join URL: /reference/flow-controls/fork-join_v1 Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/reference/flow-controls/fork-join_v1.mdx Executes each branch in parallel (list of tasks) as a separate and isolated sub-flow. Branches are executed internally in sequence. Type: fork-join/v1
# ByteChef Reference: Graph URL: /reference/flow-controls/graph_v1 Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/reference/flow-controls/graph_v1.mdx Directs execution across a set of named nodes connected by dynamic transitions, executing each node's tasks in sequence until a node with no next transition is reached. Type: graph/v1
## Properties [#properties] | Name | Label | Type | Description | Required | | :------------: | :-------------: | :-----: | :-----------------------------------------------------------------------------------------------------------------: | :------: | | startNode | Start Node | STRING | The name of the node execution begins from. Defaults to the first declared node when left empty. | false | | maxTransitions | Max Transitions | INTEGER | The maximum number of node-to-node transitions allowed before the graph is halted, to guard against infinite loops. | false | # ByteChef Reference: Flow Controls URL: /reference/flow-controls Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/reference/flow-controls/index.mdx Branching, looping, and error handling for workflow definitions. # ByteChef Reference: Loop Break URL: /reference/flow-controls/loop-break_v1 Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/reference/flow-controls/loop-break_v1.mdx Breaks the loop execution. Type: loopBreak/v1

# Additional Instructions [#additional-instructions] **Loop** and **Loop Break** are two halves of one mechanism, and this section covers both. Loop repeats a set of actions - either once for each item in a list, or continuously; Loop Break is the statement that ends a continuous loop from inside its body. ## Using Loop [#using-loop] ### Step 1: Add Loop to your workflow [#step-1-add-loop-to-your-workflow] Select **Loop** from the **Flows** component panel and add it where execution should repeat. ### Step 2: Choose the loop mode [#step-2-choose-the-loop-mode] **Iterate over a list** - leave **Loop Forever** off, and set **List of Items** to the collection to walk. The value normally comes from a data pill produced by an earlier step: ``` ${googleSheets_1.rows} ${salesforce_1.accounts} ${webhook_1.orders} ``` The loop ends on its own when the list is exhausted. **Loop forever** - switch **Loop Forever** on. No list is needed; the loop runs until a **Loop Break** inside its body ends it. ### Step 3: Add the loop body [#step-3-add-the-loop-body] Click **+** inside the loop to add the components that should run on every pass. When iterating over a list, the current element is available as `${item}`, and its fields as `${item.email}`, `${item.status}`, and so on. ## Using Loop Break [#using-loop-break] Loop Break takes no properties. Add it inside a loop body, normally on a branch of a [Condition](/reference/flow-controls/condition_v1), and reaching it ends the enclosing loop immediately - the rest of the current pass does not run. **Loop Forever needs a reachable break.** With **Loop Forever** on and no Loop Break that can actually fire, the loop never terminates; it consumes resources until the run hits its execution timeout. Confirm the body contains a break condition that will be met before enabling it. # ByteChef Reference: Loop URL: /reference/flow-controls/loop_v1 Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/reference/flow-controls/loop_v1.mdx Loops sequentially over list of items. Type: loop/v1
## Properties [#properties] | Name | Label | Type | Description | Required | | :---------: | :-----------: | :--------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------: | :------: | | items | List of items | ARRAY
Items \[]
| List of items to iterate over. | false | | loopForever | Loop Forever | BOOLEAN
Options true, true
| Should loop iterate until condition set by 'Loop Break' statement is met. | false |
# Additional Instructions [#additional-instructions] **Loop** and **Loop Break** are two halves of one mechanism, and this section covers both. Loop repeats a set of actions - either once for each item in a list, or continuously; Loop Break is the statement that ends a continuous loop from inside its body. ## Using Loop [#using-loop] ### Step 1: Add Loop to your workflow [#step-1-add-loop-to-your-workflow] Select **Loop** from the **Flows** component panel and add it where execution should repeat. ### Step 2: Choose the loop mode [#step-2-choose-the-loop-mode] **Iterate over a list** - leave **Loop Forever** off, and set **List of Items** to the collection to walk. The value normally comes from a data pill produced by an earlier step: ``` ${googleSheets_1.rows} ${salesforce_1.accounts} ${webhook_1.orders} ``` The loop ends on its own when the list is exhausted. **Loop forever** - switch **Loop Forever** on. No list is needed; the loop runs until a **Loop Break** inside its body ends it. ### Step 3: Add the loop body [#step-3-add-the-loop-body] Click **+** inside the loop to add the components that should run on every pass. When iterating over a list, the current element is available as `${item}`, and its fields as `${item.email}`, `${item.status}`, and so on. ## Using Loop Break [#using-loop-break] Loop Break takes no properties. Add it inside a loop body, normally on a branch of a [Condition](/reference/flow-controls/condition_v1), and reaching it ends the enclosing loop immediately - the rest of the current pass does not run. **Loop Forever needs a reachable break.** With **Loop Forever** on and no Loop Break that can actually fire, the loop never terminates; it consumes resources until the run hits its execution timeout. Confirm the body contains a break condition that will be met before enabling it. # ByteChef Reference: Map URL: /reference/flow-controls/map_v1 Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/reference/flow-controls/map_v1.mdx Produces a new collection of values by mapping each value in list through defined task, in parallel. When execution is finished on all items, the `map` task will return a list of execution results in an order which corresponds to the order of the source list. Type: map/v1
## Properties [#properties] | Name | Label | Type | Description | Required | | :---: | :-----------: | :-------------------------------------------------------: | :----------------------------: | :------: | | items | List of items | ARRAY
Items \[]
| List of items to iterate over. | false | # ByteChef Reference: Error Handler URL: /reference/flow-controls/on-error_v1 Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/reference/flow-controls/on-error_v1.mdx Triggers an error branch with an error object if an exception occurs in the main branch. Type: on-error/v1
# ByteChef Reference: Parallel URL: /reference/flow-controls/parallel_v1 Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/reference/flow-controls/parallel_v1.mdx Run collection of tasks in parallel, without waiting until the previous function has completed. Type: parallel/v1
# ByteChef Reference: Subflow URL: /reference/flow-controls/subflow_v1 Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/reference/flow-controls/subflow_v1.mdx Starts a new job as a sub-flow of the current job. Output of the sub-flow job is the output of the task. Type: subflow/v1
## Properties [#properties] | Name | Label | Type | Description | Required | | :----------: | :------: | :-----------------------------------------------------------------------------------: | :----------------------------------------: | :------: | | workflowUuid | Workflow | STRING | The sub-workflow to execute. | false | | inputs | null | DYNAMIC\_PROPERTIES
Depends On workflowUuid
| The input parameters for the sub-workflow. | false | # ByteChef Reference: Stop Job URL: /reference/flow-controls/terminate_v1 Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/reference/flow-controls/terminate_v1.mdx Stops a job execution with specified status and message. Type: terminate/v1
## Properties [#properties] | Name | Label | Type | Description | Required | | :-----: | :---: | :----: | :--------------------------: | :------: | | message | null | STRING | Reason for stopping the job. | false | # ByteChef Reference: Wait for Approval URL: /reference/flow-controls/wait-for-approval_v1 Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/reference/flow-controls/wait-for-approval_v1.mdx Allows a person to review and either approve or reject requests. Type: waitForApproval/v1
# ByteChef Reference: Accelo URL: /reference/components/accelo_v1 Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/reference/components/accelo_v1.mdx Accelo is a cloud-based platform designed to streamline operations for service businesses by integrating project management, CRM, and billing functionalities into one unified system. Categories: CRM, Project Management Type: accelo/v1
## Connections [#connections] Version: 1 ### OAuth2 Authorization Code [#oauth2-authorization-code] #### Properties [#properties] | Name | Label | Type | Description | Required | | :----------: | :-----------: | :----: | :----------------------------------------------------------------------------------------------: | :------: | | deployment | Deployment | STRING | Actual deployment identifier or name to target a specific deployment within the Accelo platform. | true | | clientId | Client Id | STRING | | true | | clientSecret | Client Secret | STRING | | true | ## Connection Setup [#connection-setup] Follow these steps to connect Accelo to ByteChef using OAuth 2.0 (Authorization Code): 1. Determine your Accelo deployment (subdomain) * Your deployment is the subdomain you use to access Accelo. * Example: if you sign in at `https://acme.api.accelo.com`, your deployment is `acme`. 2. Register an app in Accelo * In Accelo, go to: Configuration → API → Register Application. * Choose the application type: Web Application (required for OAuth2 Authorization Code). * Give it a name (e.g., **ByteChef Accelo Integration**). 3. Configure the redirect (callback) URL * Add the ByteChef OAuth callback URL to your app: * `https://app.bytechef.io/callback` (Cloud) * `http://127.0.0.1:5173/callback` (Local development) 4. Copy the generated Client ID and Client Secret from your Accelo app.
## Actions [#actions] ### Create Company [#create-company] Name: createCompany `Creates a new company.` #### Properties [#properties-1] | Name | Label | Type | Description | Required | | :------: | :------: | :----: | :---------------------------------------------: | :------: | | name | Name | STRING | The name of the company. | true | | website | Website | STRING | The company's website. | false | | phone | Phone | STRING | A contact phone number for the company. | false | | comments | Comments | STRING | Any comments or notes made against the company. | false | #### Example JSON Structure [#example-json-structure] ```json { "label" : "Create Company", "name" : "createCompany", "parameters" : { "name" : "", "website" : "", "phone" : "", "comments" : "" }, "type" : "accelo/v1/createCompany" } ``` #### Output [#output] Type: OBJECT #### Properties [#properties-2] | Name | Type | Description | | :------: | :----------------------------------------------------------------------------------------------------------------: | :---------: | | response | OBJECT
Properties \{STRING(id), STRING(name)}
| | | meta | OBJECT
Properties \{STRING(more\_info), STRING(status), STRING(message)}
| | #### Output Example [#output-example] ```json { "response" : { "id" : "", "name" : "" }, "meta" : { "more_info" : "", "status" : "", "message" : "" } } ``` ### Create Contact [#create-contact] Name: createContact `Creates a new contact.` #### Properties [#properties-3] | Name | Label | Type | Description | Required | | :---------: | :--------: | :----: | :----------------------------------------------------------------------: | :------: | | firstname | First Name | STRING | The first name of the contact. | false | | surname | Last Name | STRING | The last name of the contact. | false | | company\_id | Company ID | STRING | ID of the company to which the newly affiliated contact will be linked. | true | | phone | Phone | STRING | The contact's phone number in their role in the associated company. | false | | email | Email | STRING | The contact's position in the associated company. | false | #### Example JSON Structure [#example-json-structure-1] ```json { "label" : "Create Contact", "name" : "createContact", "parameters" : { "firstname" : "", "surname" : "", "company_id" : "", "phone" : "", "email" : "" }, "type" : "accelo/v1/createContact" } ``` #### Output [#output-1] Type: OBJECT #### Properties [#properties-4] | Name | Type | Description | | :------: | :---------------------------------------------------------------------------------------------------------------------------: | :---------: | | response | OBJECT
Properties \{STRING(id), STRING(firstname), STRING(lastname), STRING(email)}
| | | meta | OBJECT
Properties \{STRING(more\_info), STRING(status), STRING(message)}
| | #### Output Example [#output-example-1] ```json { "response" : { "id" : "", "firstname" : "", "lastname" : "", "email" : "" }, "meta" : { "more_info" : "", "status" : "", "message" : "" } } ``` ### Create Task [#create-task] Name: createTask `Creates a new task.` #### Properties [#properties-5] | Name | Label | Type | Description | Required | | :-----------: | :---------------: | :--------------------------------------------------------------------------------------------------: | :-----------------------------------------: | :------: | | title | Title | STRING | | true | | against\_type | Against Type | STRING
Options company, prospect
| The type of object the task is against. | true | | against\_id | Against Object ID | STRING
Depends On against\_type
| ID of the object the task is against. | true | | date\_started | Start Date | DATE | The date the task is is scheduled to start. | true | #### Example JSON Structure [#example-json-structure-2] ```json { "label" : "Create Task", "name" : "createTask", "parameters" : { "title" : "", "against_type" : "", "against_id" : "", "date_started" : "2021-01-01" }, "type" : "accelo/v1/createTask" } ``` #### Output [#output-2] Type: OBJECT #### Properties [#properties-6] | Name | Type | Description | | :------: | :----------------------------------------------------------------------------------------------------------------: | :---------: | | response | OBJECT
Properties \{STRING(id), STRING(title)}
| | | meta | OBJECT
Properties \{STRING(more\_info), STRING(status), STRING(message)}
| | #### Output Example [#output-example-2] ```json { "response" : { "id" : "", "title" : "" }, "meta" : { "more_info" : "", "status" : "", "message" : "" } } ``` ## What to do if your action is not listed here? [#what-to-do-if-your-action-is-not-listed-here] If this component doesn't have the action you need, you can use **Custom Action** to create your own. Custom Actions empower you to define HTTP requests tailored to your specific requirements, allowing for greater flexibility in integrating with external services or APIs. To create a Custom Action, simply specify the desired HTTP method, path, and any necessary parameters. This way, you can extend the functionality of your component beyond the predefined actions, ensuring that you can meet all your integration needs effectively. # ByteChef Reference: ActiveCampaign URL: /reference/components/active-campaign_v1 Source: https://raw.githubusercontent.com/bytechefhq/bytechef/refs/heads/main/apps/docs/content/docs/reference/components/active-campaign_v1.mdx ActiveCampaign is a customer experience automation platform that offers email marketing, marketing automation, sales automation, and CRM tools. Categories: CRM, Marketing Automation Type: activeCampaign/v1
## Connections [#connections] Version: 1 ### API Key [#api-key] #### Properties [#properties] | Name | Label | Type | Description | Required | | :------: | :----------: | :----: | :-------------------------------------------------------------: | :------: | | username | Account name | STRING | Your account name, e.g. https\://\{youraccountname}.api-us1.com | true | | key | Key | STRING | | true | | value | API Key | STRING | | true | ## Connection Setup [#connection-setup] ### Find API Key [#find-api-key] 1. Navigate to your [ActiveCampaign](https://www.activecampaign.com/) dashboard. 2. Click on **Setting**. 3. Click on **Developer**. 4. Here you can see your **API Access Key** and your **API Access URL** from which you can read out your account name. 5. Done 🚀.