Fliplet Help

Build workflows with App Actions

How an App Action workflow is built in code: the definition, execute(context), triggers, where it runs, what comes back, limits, and errors.

An App Action is JavaScript that Fliplet runs for your project on demand, on a schedule, or in response to an event. Use actions to automate work so it happens without a person pressing a button.

This page is for developers. It applies to Fliplet Studio, the Fliplet JavaScript SDK, and the Fliplet API. You work with actions through Fliplet.App.V3.Actions or the REST routes under /v3/apps/:appId/actions, and you can see a project's actions under Actions in the project sidebar, which opens Action Builder.

Every route needs an authenticated request. REST requests send the token in the Auth-Token header, and the SDK adds the current user's token for you. Creating, changing, and deleting an action needs editor access to the project. Publishing and unpublishing need publisher access. The run route has no editor check. For what an action can read and write, see Authentication and permissions.

How a workflow fits together

An action has four parts:

  • Code: one execute(context) function. The steps of your workflow are the statements inside that function.
  • Trigger: what starts the action: manual, schedule, log, or analytics.
  • Environment: where the action runs: server, client, or any.
  • Publishing: copies the action to the published software.

Example

This action emails a team when a row is added to a data source (a table of data your software stores in Fliplet). The log trigger starts it, and the code reads the new row.

const action = await Fliplet.App.V3.Actions.create({
  name: 'notify-new-order',
  description: 'Emails the sales team when an order is added.',
  active: true,
  environment: 'server',
  triggers: [{
    trigger: 'log',
    where: { type: 'dataSource.entry.create', dataSourceId: 123 }
  }],
  dependencies: ['fliplet-datasources', 'fliplet-communicate'],
  code: `
async function execute(context) {
  try {
    const log = context.payload.log;
    const connection = await Fliplet.DataSources.connect(log.dataSourceId);
    const entry = await connection.findById(log.dataSourceEntryId);

    await Fliplet.Communicate.sendEmail({
      to: [{ email: 'sales@example.com', name: 'Sales team', type: 'to' }],
      subject: 'New order',
      html: 'New order from ' + entry.data.Customer
    });

    return { success: true };
  } catch (err) {
    return { success: false, error: err.message };
  }
}`
});

This screen code runs a server action by name, waits for it, and checks the outcome:

const outcome = await Fliplet.App.V3.Actions.runWithResult('calculate-total', { orderId: 42 });

if (!outcome || outcome.success === false) {
  console.error('The action failed', outcome);
} else {
  console.log(outcome.data);
}

Action definition

POST /v3/apps/:appId/actions creates an action, and Fliplet.App.V3.Actions.create(data) does the same from code. PUT /v3/apps/:appId/actions/:id changes one and merges your fields into the stored action. The saved action comes back with isPublished.

FieldTypeRequiredDefaultAccepts
nameStringYesNone255 characters at most: letters, numbers, dashes, and underscores. Unique in the app.
codeStringYesNoneJavaScript that defines an async execute function.
descriptionStringNoNone1,000 characters at most.
activeBooleanNofalseAn inactive action doesn't run on a schedule or from a trigger, and a run request is refused.
environmentStringNoserverserver, client, or any.
triggersArrayNoEmptyObjects with trigger and an optional where.
frequencyStringNoNoneA 5-part cron expression: minute hour day month weekday. * * * * * is refused.
timezoneStringNoNoneA timezone name such as Europe/London, used to work out the schedule.
dependenciesArrayNoEmptyFliplet package names, or https URLs of scripts.

Code that uses a Fliplet API from a package, such as Fliplet.DataSources, Fliplet.Communicate, or Fliplet.Notifications, fails validation unless that package is in dependencies. For everything else the validator checks, see Code validator rules for App Actions.

execute(context)

Write the code as a single async function execute(context).

PropertyContains
context.payloadThe object passed to the run, or {} when there's none.
context.settingsThe project's private settings (top-level keys starting with _) in a server action. It's {} everywhere else.

A value saved from a secure panel field with settingKey: 'foo' is read as settings._aiartifact_foo. For more on inputs, see Inputs, outputs and JSON contracts.

To mark a server run as failed, return { success: false, error }. A run can be repeated or overlap another run, so running it twice must leave the same data.

On the server, your code runs in a page with fliplet-runtime and fliplet-core loaded, plus your dependencies. When the definition sets environment: 'server', code that mentions document., window., $(, or jQuery( fails validation.

To call another service from a server action, use Fliplet.App.V3.Actions.request({ url, method, headers, body, timeoutMs }). The URL must be https. The request resolves with { status, headers, body }, and body is text. timeoutMs defaults to 30,000 and is capped at 50,000. In client and any actions, request() fails with the code UNAVAILABLE.

Triggers

TriggerStarts whenEnvironmentsPayload
manualYour code asks for a runserver, client, anyWhat the caller passes
scheduleThe frequency time arrivesserver, anyEmpty
logFliplet saves a log row that matchesserver, any{ trigger: 'log', log }
analyticsThe software records an analytics event on the deviceclient, anyThe event: { createdAt, type, data }

A run request doesn't check the triggers list, so code can start any active action. The exception is that REST can't run a client action.

The frequency value drives a schedule: an active action with a frequency is queued when it's saved.

For a log trigger, log is the row, including type, dataSourceId, and dataSourceEntryId. Data source changes log as dataSource.entry.create, dataSource.entry.update, and dataSource.entry.delete. Only active actions in the app attached to the write are considered. If the write has no app, none run.

Analytics events reach the trigger with the type analytics.event or analytics.pageView. Action code running on the server generates no analytics events.

Use where to filter a trigger. It's an object, and only the keys you list are compared. For log, it's compared to the log row. For analytics, use { type, data }. A where that isn't an object is ignored, so the trigger matches every event of that type. The AI chat in Fliplet Studio sends where as JSON text and parses it first, so the object form is what's stored.

> [!WARNING] > An action with a log trigger that writes a matching row can start itself again. The trigger code has no check for who made the write.

Environments

Situationserverclientany
Started by screen codeRuns on the serverRuns in the browserRuns in the browser
Started by RESTRuns on the serverRefusedRuns on the server
Started by a schedule or log triggerRuns on the serverNot allowedRuns on the server
Started by an analytics triggerNot allowedRuns in the browserRuns in the browser
context.settingsPrivate settings{}{}
request()AvailableUNAVAILABLEUNAVAILABLE

Screen code finds client and any actions in a bundle it loads for the app. An action that isn't in the bundle goes to the server.

A server run has 870 seconds and a browser run has 30 seconds. The browser limit doesn't stop your code: the error is raised when the code finishes after 30 seconds.

> [!CAUTION] > Security: The code of client and any actions is sent to the device. Keep credentials in server actions.

Run an action from code

CallWhat it does
Fliplet.App.V3.Actions.run(id, payload)Starts the action and doesn't return its result.
Fliplet.App.V3.Actions.runWithResult(id, payload)Waits for the result.
POST /v3/apps/:appId/actions/:id/runThe body is the payload. Add ?withResult to wait.

id is the action's ID or name.

What comes back

RunrunWithResult gives you
Server, completedResolves with { success: true, data }, where data is what execute returned.
Server, failedResolves with { success: false, error }. error.message holds the message.
Server, runner couldn't finish, such as a timeoutResolves with undefined.
Browser, completedResolves with the value execute returned, unwrapped.
Browser, returned { success: false }Not treated as a failure: the object comes back as is.
Browser, threw an errorThe promise rejects.
Browser, inactive actionLogs to the console and resolves with undefined.

A failed server run doesn't reject the promise, so always check the outcome. On the server path, run resolves with undefined.

REST with ?withResult responds with HTTP 200 and status: ACTION_EXECUTION_COMPLETED even when the action failed. The body has result, duration, userId, and on failure error. Without ?withResult, status is ACTION_EXECUTION_STARTED.

Each server run saves a log of type app.task.completed or app.task.failed. Read them with Fliplet.App.V3.Actions.getLogs(), and see Test and debug an App Action.

Make an action live

The project and the published software each hold their own copy of an action. Saving changes only the project's copy. Live software keeps the last published copy until you publish again.

  • The software must be published first.
  • Publishing an already published action updates that copy in place, with the same ID and run logs.
  • Unpublishing deletes the published copy. Publishing again creates one with a new ID.
  • Live software can't run an action that isn't published. The request fails with HTTP 404.
  • The published copy has its own ID, so run actions by name.
  • A log trigger runs the copy in the app attached to the write. [UNVERIFIED: which copy runs for a write from live software]

> [!WARNING] > An active action with a frequency is queued as soon as it's saved, before you publish. Publishing queues the published copy on its own schedule and doesn't remove the project copy's job, so both run.

Limits

LimitValue
Server run870 seconds
Browser run30 seconds
Payload on a server runTravels in a URL that can't exceed 16,384 characters
Private settings on a server run100 KB, or the run fails
Run requests31 per 60 seconds, per user and IP address
Create, read, update, and delete requests61 per 60 seconds, per user and IP address
Publish and unpublish requests11 per 60 seconds, per user and IP address
Bundle requests121 per 60 seconds, per user and IP address
request() body and response5 MB
request() redirects5

Over a rate limit, the response is HTTP 429 with message, referenceUrl, and nextValidRequestDate.

Queued runs, from a schedule or a log trigger, also count against per-minute and per-month plan limits. See Plans and usage limits.

When an organization is out of credits, a queued run is skipped without a charge and the schedule carries on. A run request on software that uses credits fails with HTTP 402 and error: 'insufficient_credits' when the organization doesn't have the credits. Enterprise and internal organizations aren't blocked. See Understand Fliplet credits.

Errors

These are the common errors, by status value. For the full list, see Errors and failure behavior.

StatusHTTPCause
NAME_ALREADY_EXISTS409Another action in the app has this name.
CODE_VALIDATION_FAILED400A syntax error, a blocked pattern, no execute function, or a missing dependency.
TRIGGER_NOT_ALLOWED400The trigger isn't allowed in this environment.
FREQUENCY_INVALID400frequency isn't a valid cron expression.
FREQUENCY_TOO_FREQUENT400frequency is * * * * *.
TIMEZONE_INVALID400timezone isn't a valid timezone name.
DEPENDENCIES_INVALID400A dependency couldn't be resolved.
ACTION_NOT_FOUND404The action exists in the other copy only, for example an unpublished action run from live software.
ACTION_INACTIVE400The action isn't active.
CLIENT_ACTION_NOT_RUNNABLE400REST can't run a client action.
APP_NOT_PUBLISHED400Publish the software before publishing the action.
CANNOT_UPDATE_PRODUCTION, CANNOT_DELETE_PRODUCTION403The published copy can't be changed or deleted directly.
EXECUTION_FAILED500The run request itself failed.

A name or ID that doesn't exist in the project or the published software gets HTTP 404 with the message App Task not found.

See also

  • How to use Actions: create an action with the AI chat in Fliplet Studio and make it live.
  • Create and configure an App Action: the steps to set one up.
  • Test and debug an App Action: test runs and logs.
  • App Action lifecycle and runtime differences: more on publishing and on server and browser behavior.
  • App Actions reference: every method and route.

Open this page in the app: /build/app-actions