Add callAppRoute to RestApiClient (#22863)

Adds a `callAppRoute` method to `RestApiClient` in
`twenty-client-sdk/rest`. It calls one of the app's own HTTP routes
using the injected `TWENTY_FUNCTIONS_URL`, resolved internally the same
way the client already resolves `TWENTY_API_URL`, so app code no longer
reads env vars or knows how function routes are hosted.

Both app runtimes already go through `RestApiClient` for route calls
(logic functions and front components), so both get this in one place;
front components keep the existing 401 token-refresh flow.

Pairs with #22825, which makes the injected `TWENTY_FUNCTIONS_URL`
callable in every topology (app custom domain -> workspace isolated
functions domain -> `SERVER_URL/s`).

Once this ships in an SDK release, Call Recorder's own-route plumbing
(logic-function and front-component utils) drops its URL resolution and
calls `client.callAppRoute(path, body)`.

---------

Co-authored-by: martmull <martmull@hotmail.fr>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
This commit is contained in:
nitin
2026-07-15 23:37:16 +05:30
committed by GitHub
parent 67ed2689ce
commit 79f3a5243a
11 changed files with 262 additions and 120 deletions
@@ -69,6 +69,7 @@ chapter.
```tsx filename="src/front-components/generate-document-form.front-component.tsx"
import { useEffect, useState } from 'react';
import { CoreApiClient } from 'twenty-client-sdk/core';
import { RestApiClient } from 'twenty-client-sdk/rest';
import { defineFrontComponent } from 'twenty-sdk/define';
import { enqueueSnackbar, useSelectedRecordIds } from 'twenty-sdk/front-component';
@@ -91,15 +92,10 @@ const GenerateDocumentForm = () => {
}, []);
const generate = async () => {
// Prefer the injected functions URL; fall back to the legacy /s prefix (self-hosted/local)
const functionsBaseUrl =
process.env.TWENTY_FUNCTIONS_URL || `${process.env.TWENTY_API_URL}/s`;
const token = process.env.TWENTY_APP_ACCESS_TOKEN ?? process.env.TWENTY_API_KEY;
const res = await fetch(`${functionsBaseUrl}/documents/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
body: JSON.stringify({ templateId, recordId }),
}).then((r) => r.json());
const res = await new RestApiClient().post<{ success: boolean }>(
'/s/documents/generate',
{ templateId, recordId },
);
await enqueueSnackbar({
message: res.success ? 'Document generated.' : 'Generation failed.',
variant: res.success ? 'success' : 'error',
@@ -180,6 +176,7 @@ helper.
```tsx filename="src/front-components/document-viewer.front-component.tsx"
import { CoreApiClient } from 'twenty-client-sdk/core';
import { RestApiClient } from 'twenty-client-sdk/rest';
import { defineFrontComponent } from 'twenty-sdk/define';
import { useFrontComponentExecutionContext } from 'twenty-sdk/front-component';
import { Markdown } from 'src/utils/markdown-to-react';
@@ -188,9 +185,9 @@ const DocumentViewer = () => {
const recordId = useFrontComponentExecutionContext((c) => c.recordId ?? null);
// ...load { content, file } for recordId, then derive the links:
const pdfUrl = document.file?.[0]?.url;
const functionsBaseUrl =
process.env.TWENTY_FUNCTIONS_URL || `${process.env.TWENTY_API_URL ?? ''}/s`;
const webUrl = `${functionsBaseUrl}/documents/view?id=${recordId}`;
const webUrl = new RestApiClient().resolveUrl('/s/documents/view', {
query: { id: recordId },
});
// Render the template body, plus quick links to the web page and the PDF.
// Links open in a new tab so they don't navigate the embedded component.
@@ -9,15 +9,10 @@ The same handler can also answer HTTP requests. We'll add two routes:
- a **POST** endpoint the UI calls to generate a document, and
- a public **GET** endpoint that renders a document as a printable web page.
Both use `httpRouteTriggerSettings`. On the local dev server, app routes are
served under the `/s` prefix (e.g. `http://localhost:2020/s/documents/generate`).
<Note>
On Twenty Cloud, routes are served on the workspace's dedicated functions domain
— the URL Twenty injects as `TWENTY_FUNCTIONS_URL`, with no `/s` prefix. The `/s`
prefix is deprecated there and only remains for self-hosted and local instances.
See [Calling a logic function](/developers/extend/apps/layout/front-components#calling-a-logic-function).
</Note>
Both use `httpRouteTriggerSettings`. App routes are addressed with an `/s/` path; on local
and self-hosted instances, that path is served by your Twenty server
(e.g. `http://localhost:2020/s/documents/generate`). On Twenty Cloud, `RestApiClient`
resolves the path through the workspace's functions domain.
## POST route — generate on demand