Tutorials / Search / Backend integrations / Use LingCode Cloud in your own app
๐Ÿ“ Written โ— Intermediate Updated 2026-06-10

How do I use LingCode Cloud in my own app?

TL;DR: Load the SDK with <script src="https://lingcode.dev/sdk/lingcode-v1.js"></script>, create a client with your backend's data URL and anon key (copy both from lingcode.dev/backends โ†’ your backend โ†’ Settings / connection details), then call lingcode.from('todos').select(), .auth.signIn(...), .storage, and realtime. Inside /try the client is pre-injected as window.lingcode; in your own app you wire those two values yourself. One rule to remember: the data API is per-table CRUD, not raw SQL โ€” no JOINs, no upserts, 200 rows per page.

If you built an app in /try or the Mac IDE, the backend client was handed to you โ€” window.lingcode just existed. The moment you take that app somewhere else โ€” your own Next.js project, a plain HTML page, a Vite build โ€” you have to wire the backend up by hand. It's two values and one script tag, and this tutorial walks the whole thing, including the one design constraint that trips people coming from raw SQL.

A LingCode Cloud backend is a private Postgres database (plus auth, storage, realtime, and vector search) that you reach over HTTPS. You don't open a database connection or run SQL from the browser โ€” you talk to a small data gateway through the official client SDK, which is shaped like Supabase or Firebase so the calls feel familiar. Everything below is that SDK.

What you'll learn

The two values you need

Every backend has exactly two things a client needs:

Find both at lingcode.dev/backends โ€” open your backend, and copy the connection details from Settings (the same pair you'd paste into another MCP client). In a framework, store them as public config โ€” for Next.js, NEXT_PUBLIC_LINGCODE_BACKEND_URL and NEXT_PUBLIC_LINGCODE_BACKEND_ANON_KEY; for a bundler, whatever your import.meta.env / process.env convention is. They're public, so they belong in client config, not in a server-only secret.

Anon key vs. secret key: the anon key is public by design. Your private secrets (a Stripe secret key, a third-party API key) never go in the browser โ€” put them in the backend's Secrets and use them from a function (lingcode.functions.invoke(...)), which runs server-side. Anything the client can see, treat as public.

Load the SDK and create a client

The SDK is a single zero-dependency file served from the CDN. The plain-HTML version:

<script src="https://lingcode.dev/sdk/lingcode-v1.js"></script>
<script>
  const lingcode = LingCode.createClient(
    'https://lingcode.dev/api/cloud/be/<your-backend-id>',
    '<your-anon-key>'
  );
</script>

In a module/bundler app, load the same file and call LingCode.createClient(url, key) with your env values. (Inside /try and the Mac preview you skip this entirely โ€” the client is already there as window.lingcode, pre-wired to that project's backend. Never re-create it or hardcode keys there.)

Wait for the client to settle once on load before reading auth state โ€” it finalizes any sign-in redirect first:

await lingcode.ready;
const user = lingcode.auth.getUser(); // { id, email } | null

Read and write data

Every call returns { data, error } โ€” check error before using data. Filters chain before the verb:

// read
const { data, error } = await lingcode
  .from('todos')
  .eq('done', false)
  .order('created_at', { ascending: false })
  .limit(50)
  .select();

// write
await lingcode.from('todos').insert({ title: 'Buy milk' });
await lingcode.from('todos').eq('id', 1).update({ done: true });  // filter REQUIRED
await lingcode.from('todos').eq('id', 1).delete();                // filter REQUIRED

Filter operators: .eq .neq .gt .gte .lt .lte .like .ilike .in(col,[โ€ฆ]) .is(col,null), and .match({a:1,b:2}) for several equals at once. Multiple filters AND together. update and delete refuse to run without a filter, so you can't wipe a table by accident.

Auth, storage, realtime, vector

The one thing to know: it's table-CRUD, not raw SQL

This is the constraint that surprises people coming from a SQL client or PostgREST, and it's the difference between code that works the first time and code that 400s. The runtime data API operates on one table at a time. So:

Anything genuinely relational or heavy โ€” a multi-table report, an index, a constraint, a generated column, seed data โ€” belongs in the schema, not in a runtime call. Create a VIEW (then select() from it like a table), add the index, or run the migration from the console's SQL tab or the apply_migration tool. The gateway stays a thin, safe CRUD layer on purpose; the full power of Postgres lives one level down, in your migrations.

Migrating from Supabase?

The SDK is intentionally Supabase-shaped, so most call sites map one-to-one. The two that don't are the two rules above: rewrite .select('*, author(*)') JOINs into separate fetches, and .upsert() into insert-then-update. In the Mac IDE or /try, just ask the agent to "migrate my Supabase project to LingCode and deploy it" โ€” the migration skill does the schema, the data, and these rewrites for you.

What's next