factorialco/factorial-code-skills

fcode-ui-triggers

Surface a Factorial Code process as a button inside the Factorial product UI — the `uiTrigger` block in a process's metadata.json (location id, label, icon,…

Trending #8112

Installation

$ npx skills add https://github.com/factorialco/factorial-code-skills
Feature your product here Skill page unit · reach builders installing agent skills View pricing →

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from factorialco/factorial-code-skills · top by installs.

npx skills add https://github.com/factorialco/factorial-code-skills

Browse all from factorialco/factorial-code-skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

License LICENSE
Default branch main
Open issues 0
Status Active

History

  1. First recorded snapshot · 43 installs

SKILL.md

Factorial Code — UI triggers

A UI trigger exposes a marketplace app's process as a button inside Factorial (a page header, an actions dropdown, …). Factorial pages declare locations; an installed app's process claims a location, and Factorial renders one button per claiming process. Clicking it runs the process — or opens its form. It sits next to the webhook and form triggers in the process's metadata.json (field reference in fcode-cli).

Gotchas

  • Only installed marketplace apps get buttons. Factorial lists the triggers

of the apps the company has installed, read from their deploy workspaces. A process in a plain workspace never shows up, and a trigger reaches customers the way the rest of the app does: dev → prod → deploy through a release.

  • The location id must come from the Factorial team that owns the page.

Locations are declared in Factorial's code (e.g. calendar.header.admin), not on the platform. The platform accepts any non-blank locationId (≤ 200 chars) — an unknown one is silently dropped from the listing, so a typo means "no button", not an error.

  • The location decides the contract, not the process: which context params

are forwarded to the process, which keys of a synchronous result reach the page, and whether the location is single (one installed app at a time). Ask for those three things along with the id.

  • awaitResult is off by default: fire-and-forget. The click queues an

execution and the user sees "action started"; the return value is never shown. Turn it on only for fast work (< 50 s) whose outcome the user must see.

  • A form-enabled process opens its form instead of running. With

"form": { "enabled": true } the button opens the form in a dialog inside Factorial and awaitResult is ignored (the console hides the switch).

  • Trigger-opened forms are not authenticated. The dialog sends no Factorial

user token, and forms carry no access restriction of their own — a form behind a trigger is public like any other (see fcode-forms).

  • Never authorize on companyid / triggeredfrom_location in a form.

On the execute path they are injected server-side and trustworthy; on the form path they arrive as pre-filled, client-editable fields.

  • Uncaught errors show a generic message. A throw / crash reaches the user as

"The action could not be completed" with no detail. Return { errors: [...] } for anything the user should read.

  • Changes take up to five minutes to appear — Factorial caches the trigger

listing in the browser.

Declare a trigger

In processes/<slug>/metadata.json, then fcode push (or the Triggered from Factorial UI section of the process page in the console):

{
  "name": "Sync report",
  "tags": ["acme"],
  "uiTrigger": {
    "enabled": true,
    "locationId": "compensations.cycle.header",
    "label": "Sync to Acme",
    "icon": "Refresh",
    "awaitResult": true
  }
}
Field Meaning
enabled Turns the trigger on. locationId is required when true
locationId The Factorial location the button renders at, as given by the page's owning team
label Button text. Plain text, or fcode.i18n("key") tokens (below)
icon One of the allowlisted names (below); omit for a text-only button
awaitResult true runs the process synchronously and shows its outcome; false/omitted is fire-and-forget

fcode pull writes "uiTrigger": { "enabled": false } for every process; the other keys only appear when set, and awaitResult only when true.

An app may declare several triggers at one location — each renders its own button — but a location marked single in Factorial admits one installed app: installing a second app that claims it fails with a conflict naming the first. Don't claim a single location from an app meant to coexist with others.

What the process receives

The click runs the process with fcode.context.parameters set to:

  • the location's forwarded context params — e.g. the id of the record the

page shows (cycle_id), exactly the keys the location allowlists;

  • company_id — the Factorial company whose user clicked (trusted: injected

under a shared secret, overwriting anything the browser sent);

  • triggeredfromlocation — the location id (trusted, same way).

A context-less location (a header button with no record in scope) forwards no params at all — only the two trusted keys. The clicking user's locale is passed as the execution locale, so fcode.i18n in the process speaks their language (fcode-i18n).

const { company_id, triggered_from_location, cycle_id } = fcode.context.parameters;
if (!cycle_id) throw new Error("cycle_id is required");

Return a result (synchronous triggers)

With awaitResult: true Factorial waits for the execution and reads the return value as an envelope:

// Success — `data` is shown to the page. Only the keys the location allowlists
// (`result_keys`) get through; anything else is dropped before reaching the browser.
return { data: { synced: 42, report_url: "https://acme.example/reports/7" } };

// Controlled error — the messages render to the user, as plain text.
return {
  errors: [{ code: "missing_mapping", message: "Map the 'Bonus' concept in Acme first." }],
};
  • { data } → a success toast; the (filtered) data reaches the page's

onSuccess handler. Return { data: {} } when there is nothing to hand back.

  • { errors: [{ code, message }] } → the user reads your messages. Both fields

must be strings; entries missing either are ignored. Use it for every expected failure (bad configuration, vendor rejection, nothing to do).

  • Anything else (no envelope, a bare { message }) still counts as success

with an empty result — the execution finished.

  • A throw, a platform 4xx, or exceeding the synchronous budget (about 50 s;

the execution may still finish in the background) → a generic error the user can't act on.

With awaitResult off the return value is only visible in the execution log; long work belongs there, or behind fcode.processes.run(...) from a quick synchronous trigger (see fcode-javascript / fcode-python).

Form-backed triggers

Enable the form as usual (fcode-forms) and the button opens it in a dialog inside Factorial, rendered with the f0 theme, pre-filled with the forwarded params plus companyid and triggeredfrom_location as default values. Declare those keys in parametersSchema.json (a hidden widget) if the process needs them — and remember they are client-editable there. The submission result follows the form conventions (message, formErrors, nextProcessId, …); a { data } envelope in the final step is handed to the page like a synchronous trigger's.

{
  "name": "Export cycle",
  "form": { "enabled": true },
  "uiTrigger": { "enabled": true, "locationId": "compensations.cycle.header", "label": "Export…", "icon": "Download" }
}

The form is public and the dialog carries no user identity, so authorize inside the process, never on the forwarded params — see the gotchas above.

Labels and i18n

label may embed fcode.i18n("key") tokens — the same tokens a form schema uses — resolved with each user's locale from the workspace's locale files, with the primary-locale fallback and never a blank label (model, files and syntax in fcode-i18n):

"uiTrigger": { "enabled": true, "locationId": "calendar.header.admin", "label": "fcode.i18n(\"acme.sync.button\")" }

Icons

Factorial renders only these names (case-insensitive); anything else falls back to a text-only button, and the console shows the same list as a picker:

Bell, Calendar, Chart, Document, Download, ExternalLink, Globe, Graph, Link, Play, Refresh, Send, Settings, Sparkles, Star, Upload.

How Factorial renders them (for Factorial engineers)

The Factorial side is generic — no per-app code. In the factorial monolith:

  • A location is a YAML file under a component's app/fcode_locations/

(id, optional resource + paramkey for the record in scope, forwardparams, resultkeys, single, legacyui), validated by a CI spec. calendar/header_admin.yml is the reference.

  • The page renders

<FactorialCodeTrigger locationId="…" params={{ cycleid }} onSuccess onError /> from frontend/src/modules/factorialCode/components/FactorialCodeTrigger. It draws one button per claiming trigger (F0 outline button, or the legacy design-system button for legacyui locations), renders nothing while loading or when no installed app claims the location, and owns the form dialog. The underlying hooks (useFactorialCodeTriggers for the listing, useFactorialCodeTriggerActivation for the click) are exported for data-driven hosts such as dropdown menus.

  • Activation goes through FactorialCode::Interactors::ActivateUiTrigger

(authorizes with a policy-scoped read of the location's resource, forwards only forwardparams, filters data to resultkeys) and is proxied to the Factorial Code dashboard, which resolves the trigger live and runs the process. Everything is gated by the Factorial Code feature flag.

App developers don't touch any of this; they need the location's id and contract from the team that owns the page.

Checklist before shipping

  1. Location id, forwarded params, result_keys and single-ness confirmed with

the owning Factorial team.

  1. awaitResult on only for fast, user-visible outcomes; the process returns

{ data } / { errors }.

  1. A form-backed trigger's form is public and declares the pre-filled keys it

reads.

  1. Tested from a dev installation in Factorial, then promoted to prod- and

released so the deploy workspaces pick it up (fcode-release, fcode-ama).