Apps overview
How App steps work — one palette entry per app, a connected account, a resource and an operation.
The Apps row of the palette holds one entry per app Elizon knows: Google Calendar, Google Contacts, Google Sheets, Google Drive and — where the deployment offers it — Gmail. Drag the app you want, pick the account it should use, then pick what it should do.
Gmail may not be in your palette, and that is not a bug.
Gmail needs a Google permission that Google classifies as restricted: an app may only ask for it after a security review, and Elizon's own review is still in progress. Until it completes, Gmail is off on Elizon's hosted service and the palette does not show it.
A self-hosted Elizon using its own Google OAuth client is its own publisher and can turn Gmail
on: add google-gmail to WORKFLOW_APPS_ENABLED (and to NEXT_PUBLIC_WORKFLOW_APPS_ENABLED, so
Studio shows it) and restart. With it off, a Gmail step is not droppable, is absent from the app
list, and a Package containing one fails to install with that explanation rather than
installing a step whose every run refuses.
Each app is one entry in the palette, but they are all the same kind of step underneath — which is why a new app arrives as a new palette entry and nothing else changes. Nothing about the way you configure Google Sheets is different from the way you configure Google Calendar.
Configuration
Every App step is the same node (app) underneath:
| Field | Type | Default | Description |
|---|---|---|---|
app | string | '' | The app: google-calendar, google-contacts, google-sheets, google-drive or google-gmail. |
version | string | '' | The app definition's version the step was built against. |
resource | string | '' | What it acts on — an event, a contact, a range, a file. Each app's page lists them. |
operation | string | '' | What it does to it. Each app's page lists them, with their inputs. |
connectionId | string | '' | The connected account, from Connections. |
inputs | map | {} | One entry per input of the operation: { value, valueMode }, where valueMode is fixed or expression. |
Picking the account
The account list holds the connected accounts available to this workflow: the ones in its project, plus your organization's organization-level ones — see Connections. Only accounts for the right service are offered: a Google Calendar step offers your Google accounts and not an API key you keep for something else.
One Google account serves every Google app. If a step needs a permission that account has not granted, the node says which one before the run rather than failing with Google's own "insufficient authentication scopes" — reconnect the account under Settings → Connections and grant it.
Your tokens never leave Elizon's own backend. The step names the account; the credential is resolved, refreshed and applied where it is stored, and the run record never contains it.
Picking what it does
Two lists, in order: a resource (a calendar, an event, a contact, a spreadsheet, rows) and then an operation on it (list, get, create, update, delete, look up). Choosing the operation builds the form below it — one field per input that operation takes, with the field's own type, its help text and the same Fixed / Expression toggle the rest of the canvas uses.
Some fields are filled from the app itself. A Calendar step's Calendar field lists your own calendars by name, fetched with the account you picked. Pick the account first; until you do, the field says so rather than showing an empty list.
An operation that deletes something says so under the operation list. Elizon does not stop you — it is a step you asked for — but it will not let you place one without reading that it cannot be undone.
Output
| Field | Type | Description |
|---|---|---|
status | number | The HTTP status the app answered with. |
itemCount | number | Items returned, for a list operation. |
pages | number | Pages fetched. |
truncated | boolean | true when the page cap stopped a list while the app had more. |
app | string | The app. |
resource | string | The resource. |
operation | string | The operation. |
The answer itself is under result, and a file answer under file:
{{ upstream.<nodeId>.output.result }}result is the answer, already flattened: an event's start and end are strings rather than the
nested objects Google sends. A list operation returns an array, so
{{ upstream.<nodeId>.output.result }} feeds a Loop directly, and the expression picker offers the
fields that operation actually returns.
Beside it: itemCount, pages, and truncated. A list operation follows the app's own paging up
to a cap; truncated is true when the cap stopped it while the app still had more, so a partial
answer is never silently partial. Narrow the request (a date window, a filter) rather than raising
the cap.
A delete answers with nothing, so result is null. That is the operation working.
An operation whose answer is a file rather than data — Google Drive's Download a file — fills
{{ upstream.<nodeId>.output.file }} instead, and leaves result as null. The file is the same
kind of thing a Convert to File or a form upload produces, so anything that takes a file takes this
one: an AI Prompt attachment, a Send Email attachment, an FTP upload, a Drive upload.
It is kept for 30 days and deleted with its run, like every other file a workflow makes.
Limits and errors
- A step whose account has not granted the operation's permission says which one before the run.
- A list operation stops at its page cap and reports
truncated: truerather than returning a silently partial answer. - An app turned off on this deployment (Gmail, until Google's review completes) cannot be dropped on the canvas, and a Package containing it fails to install with that explanation.
Example
name: Next events
description: Lists the upcoming events in the primary calendar.
graph:
nodes:
- id: start
type: trigger_manual
position: { x: 0, y: 0 }
data: { description: 'Run it from Studio.' }
- id: next-events
type: app
position: { x: 280, y: 0 }
data:
app: google-calendar
version: 1.0.0
resource: event
operation: list
connection: google-account
inputs:
calendarId: { value: 'primary', valueMode: fixed }
timeMin: { value: '{{ run.startedAt }}', valueMode: expression }
edges:
- { id: start-to-next-events, source: start, target: next-events }In a Package
An App step travels whole, including its account — and it is the only integration step that does. The account becomes a credential Slot: the Package declares that it needs, say, a Google account, and whoever installs it binds one of their own already-connected accounts to that Slot in the install wizard (or names it by id from the CLI). Nothing about your account travels.
That is the difference between an account and a secret. An HTTP Request node's {{secret.KEY}}
points into your vault, so a same-named secret in someone else's organization would be a
different value — which is why it is not carried. A connected account is an object the installer
already has, so there is something real to bind.
npx @elizon/cli validate checks this offline, before you sign anything: a step bound to a Slot
the Package does not declare, a Slot that is not a credential Slot, or one that asks for the wrong
service all fail the build. And if the app's provider is missing from your compliance.providers,
validate warns — the install review is where an operator reads which third parties a Package sends
their data to, and a Google integration declaring none tells them the wrong thing.
slots:
- id: google-account
type: credential
scheme: oauth2
app: google
compliance:
providers: [google]- Travels:
app,version,resource,operation,inputs - Converted:
connectionId→connection(thecredentialSlot) - Never carried: none
The apps
- Gmail
- Google Calendar
- Google Contacts
- Google Sheets
- Google Drive
- App triggers: App triggers