@lacneu/twenty-openclaw
v0.7.4
Published
Twenty CRM plugin for OpenClaw — full CRUD on people / companies / opportunities / notes / tasks, custom objects + fields via the Metadata API, dashboards (charts, tabs, widgets), and workflows (triggers, steps, edges, runs, logic functions) with workspac
Downloads
1,367
Maintainers
Readme
@lacneu/twenty-openclaw
Twenty CRM plugin for OpenClaw. Lets an OpenClaw agent discover, model and operate any Twenty workspace — read and write the standard entities (People, Companies, Opportunities, Notes, Tasks), create custom objects and fields on the fly via the Metadata API, then CRUD their records — with workspace whitelisting, approval gating on every destructive operation, an optional global read-only switch, and a small set of opinionated business helpers (export, dedup, bulk import, similarity search, relationship summary).
Status: P0 → P8 shipped. 83 tools, end-to-end validated live against
crm.lacneu.com(Ataraxis). The plugin currently ships:
- 1 introspection tool (
twenty_workspace_info).- 9 typed read tools (list/get on People, Companies, Opportunities, Notes, Tasks).
- 1 cross-record activities timeline (
twenty_activities_list_for).- 15 typed write tools (
create/update/deleteon the same five entities).- 6 business helpers (
export,people_find_similar,people_dedup,companies_dedup,bulk_import_csv,summarize_relationship).- 10 Twenty Metadata API tools (custom-objects + fields lifecycle).
- 5 generic record-dispatch tools that work on any entity, standard or custom (
record_list/get/create/update/delete).- 12 dashboard tools — build, modify, and inspect dashboards (PageLayouts + tabs + widgets + chart-data) directly from the chat.
- 25 workflow tools — design, version, activate, run, and report on workflows (4 trigger types, 17 action types, runs + logic functions) directly from the chat.
before_tool_callapproval hook gating 24 destructive ops by default, with per-tool context warnings on the 5 high-risk workflow ops.
Overview
| Field | Value |
|---|---|
| Plugin id | twenty-openclaw |
| npm package | @lacneu/twenty-openclaw |
| OpenClaw compat | pluginApi >= 2026.4.0, minGatewayVersion >= 2026.4.0 |
| Twenty server tested | 2.1 (REST + Metadata REST) |
| License | MIT |
| Tools prefix | twenty_* |
The plugin talks to the Twenty REST API (/rest/...) and the Twenty
Metadata REST API (/rest/metadata/...) using a single API key sent as
Authorization: Bearer <key>. It refuses to call any workspace UUID
that isn't in allowedWorkspaceIds.
Install
Via OpenClaw CLI (recommended once published)
openclaw plugins install @lacneu/twenty-openclawFrom source (local development)
git clone https://github.com/OlivierNeu/twenty-openclaw-plugin.git
cd twenty-openclaw-plugin
npm install
npm run build
# Then point your OpenClaw instance at the local checkout via
# plugins.entries["twenty-openclaw"].path.Configuration
Configuration goes under plugins.entries["twenty-openclaw"].config in
your openclaw.json. Every string field supports ${ENV_VAR}
substitution.
{
"plugins": {
"allow": ["twenty-openclaw"],
"entries": {
"twenty-openclaw": {
"config": {
"enabled": true,
"apiKey": "${TWENTY_API_KEY}",
"serverUrl": "https://crm.lacneu.com",
"allowedWorkspaceIds": ["${TWENTY_WORKSPACE_ID}"],
"defaultWorkspaceId": "${TWENTY_WORKSPACE_ID}",
"approvalRequired": [
"twenty_people_delete",
"twenty_companies_delete",
"twenty_opportunities_delete",
"twenty_notes_delete",
"twenty_tasks_delete",
"twenty_dedup_auto_merge",
"twenty_bulk_import_csv",
"twenty_bulk_delete",
"twenty_metadata_object_create",
"twenty_metadata_object_update",
"twenty_metadata_object_delete",
"twenty_metadata_field_create",
"twenty_metadata_field_update",
"twenty_metadata_field_delete",
"twenty_record_delete",
"twenty_dashboard_delete",
"twenty_dashboard_tab_delete",
"twenty_dashboard_widget_delete",
"twenty_dashboard_replace_layout",
"twenty_workflow_delete",
"twenty_workflow_version_activate",
"twenty_workflow_version_deactivate",
"twenty_workflow_version_delete",
"twenty_workflow_run"
],
"allowedImportPaths": [
"/home/node/.openclaw/",
"/tmp/"
],
"readOnly": false,
"logLevel": "info"
}
}
}
}
}| Field | Type | Default | Description |
|---|---|---|---|
| enabled | boolean | true | Master switch — disables all tools when false. |
| apiKey | string | — | Twenty API key. Sent as Authorization: Bearer <key>. |
| serverUrl | string | https://crm.lacneu.com | Base URL of the Twenty server (no trailing slash). |
| allowedWorkspaceIds | string[] | [] | Whitelist of workspace UUIDs. Empty list ⇒ every workspace call is rejected. |
| defaultWorkspaceId | string | first allowed | Workspace UUID used when a tool doesn't specify one. Must be in allowedWorkspaceIds. |
| approvalRequired | string[] | 24 destructive tool names | Triggers an approval prompt via the before_tool_call hook. |
| allowedImportPaths | string[] | ["/home/node/.openclaw/", "/tmp/"] | Host-side prefix whitelist for bulk_import_csv. Validated with fs.realpathSync to defeat symlink + .. traversal attacks. |
| readOnly | boolean | false | When true, every tool with mutates: true is rejected at the plugin layer before any HTTP call. |
| logLevel | string | info | debug includes request bodies (be mindful of PII). |
Tools
Introspection (1)
| Tool | Description | Mutates? |
|---|---|---|
| twenty_workspace_info | List all metadata objects (standard + custom) of the configured workspace, with field counts. | No |
Typed read (9 + 1 timeline)
| Tool | Description | Mutates? |
|---|---|---|
| twenty_people_list / _get | List + fetch-by-id People. Cursor pagination via pageInfo.endCursor + starting_after. | No |
| twenty_companies_list / _get | List + fetch-by-id Companies. | No |
| twenty_opportunities_list / _get | List + fetch-by-id Opportunities. | No |
| twenty_notes_list | List Notes. (Use twenty_activities_list_for for record-attached notes.) | No |
| twenty_tasks_list | List Tasks. (Use twenty_activities_list_for for record-attached tasks.) | No |
| twenty_activities_list_for | Cross-record timeline (notes + tasks) attached to a Person, Company, or Opportunity. | No |
Filter syntax follows the Twenty REST conventions, e.g.
name[ilike]:%acme%, domainName.primaryLinkUrl[ilike]:%acme.com%,
employees[gte]:50, createdAt[gte]:2026-01-01.
Typed write (15)
| Tool | Description | Mutates? |
|---|---|---|
| twenty_people_create / _update / _delete | Full CUD on People. _delete is approval-gated. | Yes |
| twenty_companies_create / _update / _delete | Full CUD on Companies. _delete is approval-gated. | Yes |
| twenty_opportunities_create / _update / _delete | Full CUD on Opportunities. _delete is approval-gated. | Yes |
| twenty_notes_create / _update / _delete | Full CUD on Notes. _delete is approval-gated. | Yes |
| twenty_tasks_create / _update / _delete | Full CUD on Tasks. _delete is approval-gated. | Yes |
Soft-delete contract. The 5 typed *_delete tools issue
DELETE /rest/<entity>/{id}?soft_delete=true. Records remain in the
database with a deletedAt timestamp and stay restorable through the
Twenty UI. Hard-delete on entity records is intentionally not exposed.
Note — restore endpoint not exposed. Twenty 2.1 declares
PATCH /rest/restore/<entity>/{id}in OpenAPI but the server returns 400 BadRequest at runtime, and the GraphQL alternative (restorePersonmutation) returnsRECORD_NOT_FOUND. The factory pattern remains in git history at tagv0.2.0and will be re-added once Twenty fixes the upstream bug. In the meantime, restore through the Twenty UI.
Business helpers (6)
| Tool | Description | Mutates? |
|---|---|---|
| twenty_export | Paginate any entity (typed or custom) to JSON or CSV. RFC 4180 escape, dot-notation flatten of nested objects (name.firstName, domainName.primaryLinkUrl, ...). | No |
| twenty_people_find_similar | Find candidate matches for a Person by exact email[ilike], then fallback to name.firstName / name.lastName ilike. Deterministic, no fuzzy library. | No |
| twenty_people_dedup | Group People sharing the same email. Read-only (no auto-merge). | No |
| twenty_companies_dedup | Group Companies sharing the same domainName.primaryLinkUrl. Read-only. | No |
| twenty_bulk_import_csv | Import a CSV in chunked POST batches (Twenty REST max 60 per call). Path is validated against allowedImportPaths with realpathSync to defeat symlink + .. bypass. Supports dry_run. Approval-gated. | Yes |
| twenty_summarize_relationship | Count notes/tasks/calendar events on a Person or Company over a configurable window (since / until). Returns counts + first/last activity timestamps. No scoring algorithm — agent reasons over the facts. | No |
Twenty Metadata API (10)
These tools call /rest/metadata/objects and /rest/metadata/fields
and let the agent shape the workspace itself without leaving the
chat.
| Tool | Description | Mutates? |
|---|---|---|
| twenty_metadata_objects_list | List standard + custom objects (alias of the introspection tool with richer filtering). | No |
| twenty_metadata_object_get | Fetch one object by id, including its full field list. | No |
| twenty_metadata_object_create | Create a custom object (nameSingular, namePlural, labelSingular, labelPlural, optional icon, description). Approval-gated. | Yes |
| twenty_metadata_object_update | Patch an existing custom object's labels/icon/description. Approval-gated. | Yes |
| twenty_metadata_object_delete | HARD delete a custom object — irreversible, drops every record. Approval-gated. | Yes |
| twenty_metadata_fields_list | List fields. With objectMetadataId filter, routes to GET /rest/metadata/objects/{id} (Twenty rejects this filter on the /fields query string). | No |
| twenty_metadata_field_get | Fetch one field by id. | No |
| twenty_metadata_field_create | Create a field (type + Twenty-validated options). Loose schema — Twenty validates the options server-side against its 25+ field types. Approval-gated. | Yes |
| twenty_metadata_field_update | Patch a field's labels/options. Approval-gated. | Yes |
| twenty_metadata_field_delete | HARD delete a field — irreversible, drops the column. Approval-gated. | Yes |
Synchronous schema regeneration. After
metadata_object_create, the new /rest/<plural> endpoint is
available within ~50 ms. The plugin does not poll — the very next
record_create call against the new entity succeeds.
Generic record dispatch (5)
CRUD on any entity (standard or custom) parameterised by entity name. Composes naturally with the Metadata tools: agent creates a custom object via P5 → populates records via P6, no plugin redeploy needed.
| Tool | Description | Mutates? |
|---|---|---|
| twenty_record_list | List records of any entity. Same filter/pagination semantics as the typed list tools. | No |
| twenty_record_get | Fetch one record by id. | No |
| twenty_record_create | Create a record. Loose body schema (additionalProperties: true). | Yes |
| twenty_record_update | Patch a record. | Yes |
| twenty_record_delete | Soft-delete a record. Always approval-gated regardless of entity. | Yes |
The entity name is regex-validated pre-network (^[a-zA-Z][a-zA-Z0-9]*$)
to reject path-traversal attempts (people/../../etc/passwd → rejected
before any HTTP call is made).
Dashboards (12 tools)
Build, modify, and inspect Twenty dashboards from the chat. Mirrors the
exact LLM contract Twenty's own internal AI agent uses (port of
twenty-server/src/modules/dashboard/tools/), so the agent gets a
proven authoring surface — without needing the agent to learn Twenty's
internals.
A Twenty dashboard is the union of a dashboards workspace record
(with title, pageLayoutId, position) and a PageLayout of
type=DASHBOARD (containing tabs, themselves containing widgets on a
12-column grid). These tools coordinate both layers transparently.
Dashboard-level (5)
| Tool | Description | Mutates? |
|---|---|---|
| twenty_dashboards_list | Paginated list of workspace dashboards (id, title, pageLayoutId, timestamps). | No |
| twenty_dashboard_get | Single call returning dashboard + PageLayout + tabs + widgets (REST + GraphQL joined). | No |
| twenty_dashboard_create_complete | Cascade creation: layout + dashboard record + first tab + N widgets in one tool call. Returns every id created. | Yes |
| twenty_dashboard_duplicate | Wraps Twenty's duplicateDashboard mutation (records, layout, tabs, widgets cloned). | Yes |
| twenty_dashboard_delete | Soft-delete the dashboard record + HARD destroy the PageLayout. Approval-gated. | Yes |
| twenty_dashboard_replace_layout | Atomic refactor via updatePageLayoutWithTabsAndWidgets. Anything not listed is destroyed. Approval-gated. | Yes |
Tab-level (3)
| Tool | Description | Mutates? |
|---|---|---|
| twenty_dashboard_tab_add | createPageLayoutTab. Auto-computes position to the next slot when omitted. | Yes |
| twenty_dashboard_tab_update | updatePageLayoutTab (title / position / layoutMode partial). | Yes |
| twenty_dashboard_tab_delete | HARD destroy a tab and every widget it contains. Approval-gated. | Yes |
Widget-level (4)
| Tool | Description | Mutates? |
|---|---|---|
| twenty_dashboard_widget_add | createPageLayoutWidget with the full configuration union — AGGREGATE_CHART (KPI), GAUGE_CHART, BAR_CHART, LINE_CHART, PIE_CHART, RECORD_TABLE, IFRAME, STANDALONE_RICH_TEXT. The tool description embeds the per-type schema decision tree so the LLM can author configurations without round-tripping. | Yes |
| twenty_dashboard_widget_update | updatePageLayoutWidget (partial patch on title / type / gridPosition / objectMetadataId / configuration / conditionalAvailabilityExpression). | Yes |
| twenty_dashboard_widget_delete | HARD destroy a widget. Approval-gated. | Yes |
| twenty_dashboard_widget_data | Compute the rendered data for a chart (BAR / LINE / PIE) by dispatching to Twenty's chart-data resolvers. KPI charts (AGGREGATE / GAUGE) return a hint pointing to the record aggregation API. Lets the agent read the same numbers the user sees on the dashboard. | No |
Grid system
12 columns (0-11). Typical sizes: KPI rowSpan 2-4, charts 6-8. Full
width = columnSpan: 12, half = 6, third = 4, quarter = 3.
Configuration recipes (excerpt)
| Chart | Required fields |
|---|---|
| AGGREGATE_CHART (KPI) | aggregateFieldMetadataId, aggregateOperation |
| BAR_CHART | + primaryAxisGroupByFieldMetadataId, layout (VERTICAL|HORIZONTAL). For RELATION/composite groupBy: also primaryAxisGroupBySubFieldName (e.g. "name"). |
| LINE_CHART | + primaryAxisGroupByFieldMetadataId (typically a date field, with primaryAxisDateGranularity) |
| PIE_CHART | + groupByFieldMetadataId (different field name from BAR/LINE!) |
| GAUGE_CHART | + rangeMin, rangeMax |
| RECORD_TABLE | + viewId (must create a TABLE view first; reusing a record-index view is forbidden) |
| IFRAME | + url |
| STANDALONE_RICH_TEXT | + body.markdown |
Aggregations available: COUNT, COUNT_UNIQUE_VALUES, COUNT_EMPTY,
COUNT_NOT_EMPTY, COUNT_TRUE, COUNT_FALSE, SUM, AVG, MIN,
MAX, PERCENTAGE_EMPTY, PERCENTAGE_NOT_EMPTY.
Date granularities for time-bucketed charts: DAY, WEEK, MONTH,
QUARTER, YEAR, DAY_OF_THE_WEEK, MONTH_OF_THE_YEAR,
QUARTER_OF_THE_YEAR.
Approval gating philosophy
Only irreversible destructions are gated by default:
dashboard_delete, dashboard_replace_layout, tab_delete,
widget_delete. Construction tools (*_add, *_update,
create_complete, duplicate) are not gated — the LLM iterates
during build (add → check → tweak), and approval prompts on every step
would cripple the flow.
Required permission
The Twenty API key must hold the LAYOUTS permission flag. Workspace-
admin keys inherit it automatically; restricted keys require an admin
to grant it explicitly through Twenty's role configuration.
Workflows (25 tools)
Design, version, activate, run, and report on Twenty workflows — the
full lifecycle from chat. Mirrors Twenty's internal workflow LLM
tooling (twenty-server/src/modules/workflow/workflow-tools/tools/).
A Twenty workflow is the union of 4 entities:
Workflow (record, REST /rest/workflows)
├─ versions[] ─→ WorkflowVersion (DRAFT/ACTIVE/DEACTIVATED/ARCHIVED)
│ ├─ trigger (JSON: DATABASE_EVENT|MANUAL|CRON|WEBHOOK)
│ └─ steps[] ─→ WorkflowAction (17 types)
│ id, name, type, valid, settings, position
├─ runs[] ─→ WorkflowRun (each execution: status + state.stepInfos)
└─ automatedTriggers[]Workflow-level (5)
| Tool | Description | Mutates? |
|---|---|---|
| twenty_workflows_list | Paginated list (id, name, statuses[], lastPublishedVersionId, timestamps). | No |
| twenty_workflow_get | Joins workflow record + every WorkflowVersion + N most recent runs in one call. | No |
| twenty_workflow_create_complete | Cascade: workflow record + version + N×steps + N×edges + (optional) activation. The big one. | Yes |
| twenty_workflow_duplicate | duplicateWorkflow mutation (clones workflow + versions + steps + edges). | Yes |
| twenty_workflow_delete | HARD destroy (cascades to versions + runs). Approval-gated. | Yes |
Version-level (6)
| Tool | Description | Mutates? |
|---|---|---|
| twenty_workflow_version_get_current | Returns lastPublishedVersionId if set, else most recent DRAFT. | No |
| twenty_workflow_version_create_draft | Fork an existing version into a new DRAFT (required before editing an ACTIVE version). | Yes |
| twenty_workflow_version_activate | Set status=ACTIVE — starts the workflow running in production. Approval-gated with explicit warning. | Yes |
| twenty_workflow_version_deactivate | Set status=DEACTIVATED. Approval-gated. | Yes |
| twenty_workflow_version_archive | Set status=ARCHIVED (reversible — not gated). | Yes |
| twenty_workflow_version_delete | HARD destroy. Approval-gated. | Yes |
Step + edge-level (9)
| Tool | Description | Mutates? |
|---|---|---|
| twenty_workflow_step_add | Add a step (one of 17 action types). For CODE, also auto-creates the underlying logicFunction. | Yes |
| twenty_workflow_step_update | Replace a step's full configuration. | Yes |
| twenty_workflow_step_delete | Remove a step (drops incoming/outgoing edges). | Yes |
| twenty_workflow_step_duplicate | Clone a step. | Yes |
| twenty_workflow_edge_add | Connect source → target. Use source="trigger" for edges from the trigger. | Yes |
| twenty_workflow_edge_delete | Remove an edge. | Yes |
| twenty_workflow_compute_step_output_schema | Pre-compute the JSON shape of a step's output (so the agent can write correct {{<step-id>.result.x}} refs in downstream steps). | No |
| twenty_workflow_trigger_update | Replace the trigger of a DRAFT version. | Yes |
| twenty_workflow_positions_update | Bulk update visual positions (cosmetic). | Yes |
None gated by default. The LLM iterates rapidly during build (add → check → tweak); approval prompts on every step would cripple the flow.
Run-level (4)
| Tool | Description | Mutates? |
|---|---|---|
| twenty_workflow_run | Execute a WorkflowVersion. Every step with side effects (SEND_EMAIL, HTTP_REQUEST, CREATE_RECORD, …) is executed for real. Approval-gated with explicit side-effects warning. | Yes |
| twenty_workflow_run_stop | Stop an in-flight run (sets status=STOPPING). | Yes |
| twenty_workflow_runs_list | List runs with multi-filter (workflow / version / status single or array / date range). Returns durationMs per run. | No |
| twenty_workflow_run_get | Full run detail formatted for reporting: per-step status + errors, aggregated stepStatusCounts, parent version snapshot. | No |
Logic functions (3)
For CODE workflow steps. Live on /metadata.
| Tool | Description | Mutates? |
|---|---|---|
| twenty_logic_function_list | List all logicFunctions in the workspace. | No |
| twenty_logic_function_update_source | Replace the TypeScript source of a function. | Yes |
| twenty_logic_function_execute | Sandboxed test run with arbitrary input. | Yes |
Trigger types and configurations
| Type | Settings | Use case |
|---|---|---|
| DATABASE_EVENT | { eventName: "objectName.action" } (action ∈ created/updated/deleted/upserted) | Auto-react to record changes |
| MANUAL | { availability: GLOBAL \| SINGLE_RECORD \| BULK_RECORDS } | User-launched (button on record / global / bulk) |
| CRON | { type: DAYS\|HOURS\|MINUTES, schedule } or { type: CUSTOM, pattern: cronExpr } | Scheduled |
| WEBHOOK | { httpMethod: GET\|POST, authentication: API_KEY\|null, expectedBody?: object } | External HTTP-triggered |
Action types — the 17 step types
Record CRUD: CREATE_RECORD, UPDATE_RECORD, UPSERT_RECORD,
DELETE_RECORD, FIND_RECORDS. Email: SEND_EMAIL, DRAFT_EMAIL.
Logic: IF_ELSE, FILTER, ITERATOR. AI: AI_AGENT. External:
HTTP_REQUEST. Code: CODE (TS function), LOGIC_FUNCTION (alias).
UX: FORM, DELAY, EMPTY. The workflow-schemas.ts file ports
each action's settings shape directly from twenty-shared/workflow/
schemas/ so the LLM sees the canonical contract.
Variable references between steps
{{trigger.object.fieldName}} — DATABASE_EVENT triggered record
{{trigger.record.fieldName}} — MANUAL with single-record availability
{{trigger.body.fieldName}} — WEBHOOK POST body
{{<step-uuid>.result.fieldName}} — earlier step's output (UUID, not name)Discover step ids via twenty_workflow_get. Pre-compute output
schemas via twenty_workflow_compute_step_output_schema before
referencing.
Required permission
The Twenty API key must be linked to a user who has the WORKFLOWS
permission flag. Settings Twenty → Members & Roles → Roles → [your
role, typically Admin] → check Workflows. Without it, Twenty
returns Forbidden resource (FORBIDDEN) on action mutations
(run/activate/deactivate/stop/createDraft/duplicate, plus the step +
edge mutations). Standard CRUD on workflow records (list, get,
create_complete, delete, runs_list, run_get) only requires entity-
level read/write.
Use case — campaigns
Concretely, an agent can build campaign workflows like:
Trigger MANUAL (with BULK_RECORDS availability on Company)
→ step FIND_RECORDS on Company filtered by tag/label
→ step ITERATOR on the result
→ SEND_EMAIL inside the iterator
→ CREATE_RECORD (Note linked to the company) for trackingtwenty_workflow_create_complete writes all this in one cascade. The
operator activates with twenty_workflow_version_activate, then
launches with twenty_workflow_run (both approval-gated). After
execution, twenty_workflow_run_get returns the run's
stepStatusCounts and per-step errors so the agent can write a report.
Custom data modelling workflow (live demo)
End-to-end flow exercised against the Ataraxis 2CF workspace:
1. Agent: "I need to track ICOPE diagnostics for our patients"
2. metadata_object_create → Diagnostic ICOPE (icopeDiagnostics)
• Approval prompt → operator approves
3. metadata_field_create → dateEvaluation (DATE)
4. metadata_field_create → scoreCognitif (NUMBER)
5. metadata_field_create → scoreMobilite (NUMBER)
6. metadata_field_create → person (RELATION many_to_one → Person)
• Twenty auto-creates the inverse field `diagnosticsIcope` on Person
7. record_create → first ICOPE diagnostic for John Doe
• Operator: "approval — yes, this is the format I want"
8. record_list → back-reference works through the inverse field
9. record_update → fix a wrong score
10. record_delete (gated) → soft-delete the demo recordEvery destructive step (metadata_*_create, *_update, *_delete,
record_delete) prompts the operator through the active OpenClaw
channel before any HTTP call.
Approval gating (before_tool_call)
Every tool name listed in approvalRequired triggers a before_tool_call
hook that returns a requireApproval directive to the OpenClaw runtime.
The runtime then surfaces the prompt to the operator via the active
channel (Telegram inline button, Control UI, ...) and only proceeds when
the operator approves. Denied or timed-out calls (10 min default) are
rejected without ever reaching Twenty.
Approval prompts include:
severity: "critical"— the operator's UI flags it appropriately.timeoutMs: 600_000(10 minutes).timeoutBehavior: "deny"— silence is refusal.- A JSON snapshot of the tool parameters (with
workspaceIdstripped).
The hook is wired automatically when the plugin loads — no extra
configuration is required on the host side. To audit or tweak the gated
list, override approvalRequired in plugins.entries.twenty-openclaw.config:
openclaw config set 'plugins.entries.twenty-openclaw.config.approvalRequired' \
'["twenty_people_delete","twenty_metadata_object_delete","twenty_record_delete"]' \
--strict-jsonPass an empty array to disable approval gating entirely (not recommended).
Note on hook policy flags. OpenClaw 2026.4.x introduced
plugins.entries.<id>.hooks.allowConversationAccess— but that toggle only governsllm_input/llm_output/agent_endhooks (raw conversation surfaces).before_tool_callis not in that family, so no manifest-level or config-level toggle is required for this plugin's approval hook.
Examples
Once the plugin is loaded, an OpenClaw agent can simply call:
twenty_workspace_info()and receive a JSON summary like:
{
"workspaceUrl": "https://crm.lacneu.com",
"objectCount": 12,
"customObjectCount": 2,
"objects": [
{ "nameSingular": "person", "namePlural": "people", "labelSingular": "Person", "isCustom": false, "isActive": true, "isSystem": false, "fieldCount": 24 },
{ "nameSingular": "company", "namePlural": "companies", "labelSingular": "Company", "isCustom": false, "isActive": true, "isSystem": false, "fieldCount": 18 },
"..."
]
}Smoke test
cp .env.smoketest .env.smoketest.local # do not commit local copy
# edit .env.smoketest.local with real values
TWENTY_API_KEY=... TWENTY_SERVER_URL=... TWENTY_WORKSPACE_ID=... npm run smoke-testThe script lives in scripts/smoke-test.mjs and runs one
twenty_workspace_info call against the configured server. It exits 0
on success, 1 on tool failure, 2 on missing env vars.
Development
npm install
npm run typecheck
npm test
npm run buildnpm test compiles src/** + test/** to dist-test/ and runs
node --test. CI matrices node 22 + node 24.
Roadmap
- P0 — repo + license + .gitignore. ✅
- P1 — bootstrap: manifest, package, single read-only tool, smoke script, CI/Release workflows. ✅
- P2 — domain read tools (list/get for the five entities + cross-record activities timeline). ✅
- P3 — typed write tools (create/update/delete on the five entities)
before_tool_callapproval gating on every destructive operation. ✅
- P4 — business helpers:
export,people_find_similar,people_dedup,companies_dedup,bulk_import_csv,summarize_relationship. ✅ (restore + enrich dropped from scope — see CHANGELOG 0.3.0.) - P5 — Twenty Metadata API: 10 tools to create / update / delete custom objects and fields. ✅
- P6 — Generic record dispatch: 5 tools to CRUD any entity (standard or custom). ✅
- P7 — Dashboards: 12 tools to build / modify / inspect dashboards (PageLayout + tabs + widgets + chart-data). ✅ (approval gates only irreversible destructions; construction stays friction-free)
- P8 — Workflows: 25 tools (5 workflow + 6 version + 9 step/edge + 4 run + 3 logic-function). Design / activate / run / report end-to- end. Approval gates only irreversible destructions + production- impact ops (activate / deactivate / run). ✅
Future
twenty_<entity>_restoreonce Twenty fixes the upstream REST/GraphQL restore bug.twenty_enrich_companyonce a concrete data provider is selected (free-tier limits + GDPR for cabinet conseil to validate).- Real OpenClaw OTEL tracing through the runtime tracer (waiting on SDK exposure).
License
MIT — see LICENSE.
