For a runnable version of everything below, see the
embed-app example.
@textql/sdk). The React
component described here needs v1.4.0+; the server handler works from
v1.3.8. On-prem, TEXTQL_SERVER_URL is read from v1.4.1+.
Quickstart
Server — one catch-all route. This is the only place your API key lives./api/textql, so nothing
needs configuring on the happy path.
Your routes are the access control
false or throw to reject.
The React component
@textql/sdk/embed/react is a thin wrapper over the same custom element.
Prefer it to the raw tag: JSX has no type for a custom element, and React
cannot bind the events below because they are CustomEvents.
string
default:"/api/textql"
Where
createEmbedHandler is mounted. Must match the handler’s basePath.CSSProperties
The element has no intrinsic size, like an iframe. Give it a real height.
string
Applied to the host element.
(meta: TextqlAppMeta) => void
Metadata arrived:
name, screenshotUrl, and the app’s compute functions.
Depends only on your server, so it fires even if the app’s bridge never
connects — use it to title your own chrome.(meta: TextqlAppMeta | null) => void
The app’s runtime completed its handshake.
(error) => void
The app reported a runtime error. Already logged to the console by the
element.
Server-side rendering
<TextqlApp /> carries the "use client" directive and loads the custom
element in an effect, so it server-renders to an inert tag and upgrades on the
client. That also code-splits the element out of your initial bundle.
react is an optional peer dependency (^18 || ^19). Nothing else in the SDK
needs it.
Other frameworks
It is a custom element, so there is no framework binding to install.node:http predate the Web Request object, so
wrap the handler:
CustomEvents on the element:
element.meta holds the same value for a listener that attached late.
Sizing
Data Apps lay out against the full viewport — the same region they get inside TextQL — so a narrow content column breaks the app’s own layout, not the element. Full-bleed width and a real height are the safe defaults.Compute functions
Apps that compute call back through your server. The handler relays them toPOST {basePath}/compute and refuses any function name the app does not
declare, so the route cannot become a generic runner for anything your API
key can reach.
The declared names arrive as functions on onMeta. An app with none renders
exactly the same but never calls back, leaving the route unused.
TextQL rate-limits compute server-side and returns
resource_exhausted. A
production host should retry that with backoff.Options
The handler serves three routes under
basePath and returns null for
anything else, so it composes with your own routing:
On-prem: set
TEXTQL_SERVER_URL to the plain host. The SDK appends
/rpc/public itself.
Why the document is served from your origin
TextQL pins a published app to the one origin it was published for, twice over:
So
GET {basePath}/document fetches the app’s HTML server-side and re-serves it
from your origin: your response carries no frame-ancestors, and hostOrigin
is rewritten to your origin. A <base href> keeps every subresource loading
from the CDN, which already serves them with Access-Control-Allow-Origin: *.
This is a workaround, with costs worth knowing:
- Every document load proxies through your server as
no-store, so the entry document is not CDN-cached. Subresources still are. - The rewrite is pinned to a string emitted by TextQL’s app shell. If that shape changes, the handler returns a 502 telling you to upgrade rather than serving a document whose bridge will never connect.
- The response carries
content-security-policy: sandbox allow-scripts. Untrusted third-party HTML is being served from your origin, and that header is what keeps it out of your cookies andlocalStorage. If you reimplement this route, keep it.
rehostDocument: false to point the iframe straight at the signed CDN URL
instead. That is the better path, and it works once your origin is allowed
platform-side.
On-premise and restricted networks
Nothing in the integration needs a TextQL-operated origin at runtime, so long as your instance also stores rendered apps somewhere you operate. Point the handler at your instance and serve the element yourself, and every host the browser reaches is one you run:Every client reads
TEXTQL_SERVER_URL from v1.4.1+ — the embed handler,
new Textql(), and createStreamingClient. An explicit serverURL still
wins. On earlier versions the variable is ignored and the SDK silently
defaults to app.textql.com; pass the host at each construction site
instead — createEmbedHandler({ client: new Textql({ serverURL }) }).
Check that last pair before assuming air-gapped.
rehostDocument re-serves the
entry document from your origin, but the scripts and styles inside it still load
from wherever your instance stores rendered apps — your own object storage
on-prem, TextQL’s CDN against cloud.
With the element self-hosted and rehostDocument on, the host page needs
script-src 'self' and frame-src 'self' — the iframe’s src is your own
{basePath}/document — plus style-src 'unsafe-inline', because the element
writes its shadow-DOM stylesheet as an inline <style>. Add the asset origin to
img-src for the poster.