4c4a154d31
## What
Key-value storage for applications, as proposed in
twentyhq/core-team-issues#2391 — built on the existing `keyValuePair`
entity:
- Nullable `applicationId` relation on `keyValuePair` + a new
`APPLICATION_VARIABLE` type (fast instance command included)
- GraphQL CRUD on the metadata schema (`appKeyValue`, `setAppKeyValue`,
`deleteAppKeyValue`), requiring an `APPLICATION_ACCESS` token —
`applicationId` always comes from the token, never from arguments, so
apps can't touch each other's entries
- `kv.get` / `kv.set` / `kv.delete` helpers in
`twenty-sdk/logic-function`
## Scopes
- **`INSTALL`** (default): entries are private to one workspace install;
arbitrary JSON values
- **`GLOBAL`**: entries are shared across every install of the app, with
claim semantics — the value is always the claiming `workspaceId` and
only that workspace can overwrite or delete the key (guarded writes,
race-safe via insert-if-absent)
Since `applicationId` identifies an install (one row per workspace),
GLOBAL entries are stored under the registration owner workspace's
install so all installs of the same app share one namespace.
The GLOBAL scope is what enables cross-workspace webhook routing: e.g.
the Slack app's `serverRoute` resolver (running in the owner workspace)
can resolve `kv.get('slack:team:' + team_id, { scope: 'GLOBAL' })` to
find the workspace that connected that Slack team — without a workspace
being able to hijack another's mapping.
## Follow-ups
- Wire the Slack assistant PR (#22984) to write the claim at connect
time and read it in the events resolver
- `kv.*` access from front components
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23089?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
59 lines
3.5 KiB
Plaintext
59 lines
3.5 KiB
Plaintext
---
|
|
title: Overview
|
|
description: Server-side TypeScript that runs inside Twenty — triggered by HTTP routes, cron schedules, database events, AI tools, or workflow actions.
|
|
icon: "bolt"
|
|
---
|
|
|
|
A Twenty app's **logic layer** is the code that *runs* — server-side TypeScript handlers reacting to HTTP requests, cron schedules, and record changes; AI skills and agents that live inside the workspace; and OAuth connections that let your functions act on a user's behalf in third-party services.
|
|
|
|
```text
|
|
┌─ HTTP route ──┐
|
|
│ Cron schedule │
|
|
│ Database event │ ┌────────────────────┐
|
|
triggers ─┤ AI tool call ├─────▶│ Logic function │
|
|
│ Workflow action │ │ (your handler) │
|
|
│ Manual exec │ └────────────────────┘
|
|
└────────────────────┘ │
|
|
▼
|
|
┌────────────────────────────┐
|
|
│ Twenty API (records) │
|
|
│ Third-party API │
|
|
│ (via Connection token) │
|
|
└────────────────────────────┘
|
|
```
|
|
|
|
## In this section
|
|
|
|
<CardGroup cols={2}>
|
|
<Card title="Logic Functions" icon="bolt" href="/developers/extend/apps/logic/logic-functions">
|
|
The core building block — trigger types, payloads, and the typed API client.
|
|
</Card>
|
|
<Card title="Skills & Agents" icon="robot" href="/developers/extend/apps/logic/skills-and-agents">
|
|
Reusable AI agent instructions and assistants with custom system prompts.
|
|
</Card>
|
|
<Card title="Connections" icon="plug" href="/developers/extend/apps/logic/connections">
|
|
OAuth credentials your app holds for third-party services — Linear, GitHub, Slack, and more.
|
|
</Card>
|
|
<Card title="Key-Value Store" icon="database" href="/developers/extend/apps/logic/key-value-store">
|
|
Persist state between logic function runs — caches, cursors, and cross-workspace claims.
|
|
</Card>
|
|
</CardGroup>
|
|
|
|
## Trigger types at a glance
|
|
|
|
A logic function picks one or more triggers — every entry below is a separate field on `defineLogicFunction()`:
|
|
|
|
| Trigger | When it runs | Setting |
|
|
|---------|--------------|---------|
|
|
| **HTTP route** | A request hits your `/s/<path>` endpoint | `httpRouteTriggerSettings` |
|
|
| **Cron** | A CRON expression matches | `cronTriggerSettings` |
|
|
| **Database event** | A workspace record is created, updated, or deleted | `databaseEventTriggerSettings` |
|
|
| **AI tool** | A Twenty AI feature decides to call your function | `toolTriggerSettings` |
|
|
| **Workflow action** | A workflow step invokes your function | `workflowActionTriggerSettings` |
|
|
|
|
Functions run sandboxed in isolated Node.js processes and access the workspace through a typed API client scoped to the role declared on [`defineApplication()`](/developers/extend/apps/config/application).
|
|
|
|
<Note>
|
|
**Install-time hooks** — code that runs before or after the install — share this runtime but use their own define functions and live under [Config → Install Hooks](/developers/extend/apps/config/install-hooks).
|
|
</Note>
|