Back to blog
Website GuidesAug 4, 2026

Creght's backend: server logic, auth and payments with Func

Your site's server-side logic, data, authentication, verification codes and payments all run inside the platform. A published Func has a public HTTPS URL and receives payment callbacks; JSON tables persist orders and registrations through ctx.db; auth defaults to useAuth, with your own rules routed through ctx.auth.login. Includes docs and boundaries for each.

"A site the AI generated" sounds like a pile of static pages: you can look at it, restyle it, ship it — but the moment you need users to log in, orders to persist, or a payment to flip a status, you switch tools and rebuild.

That assumption does not hold on Creght. Your site's server-side logic, data, authentication, payments and email all live inside the platform, in the same project, shipping with the same publish. This post covers each piece, with the documentation for every one of them.

Func: your site's own server

A Func is a function that runs on the server. You write it in your project, give it a function key, and pages call it. Secrets, third-party APIs, database reads and writes — everything that must not sit in the browser — happens here.

ctx covers data, authentication, the user directory, caching, request and response, cookies, assets, email, streaming events and diagnostics.

One thing gets misread often enough to call out: a published Func has a public HTTPS URL. So it is not only "the site calls out to someone else" — it is also an endpoint third parties can call back into. Payment notifications, message pushes and webhooks all reach it.

Build site backends with Func →

Data: JSON tables

Persistence runs on JSON tables. Declare a table at /platform/table/<key>.json, then read, write and query it from a Func through ctx.db. The file name is the table key; table IDs and project IDs never appear in your code or in browser payloads.

It is deliberately not a general-purpose database: no joins, no aggregation, no transactions, no fuzzy matching. The scope covers exactly the business data a site owns — bookings, orders, registrations, quotas, logs. Systems that need real relational queries never belonged in a site builder anyway.

Keep two things apart: editorial content — posts, products, cases — belongs in the CMS; business data belongs in JSON tables.

JSON tables: define, read and query →

Auth: use the platform's first, write your own only when you must

The platform ships an account system. Pages read the current user through useAuth(); signup, login, password reset and change are already implemented. "Email must be verified at registration" can be enforced by project policy — once on, verification becomes a separate server-side step and your page code passes no verification-code parameter at all.

Only two situations call for writing login yourself in a Func: passwordless code login that the SDK cannot express, and login-time rules of your own such as bans, identity checks or multi-tenancy. Then ctx.auth.login(ref) issues a session for an existing user.

return ctx.auth.login({ userId: user.id })

One rule you cannot get wrong: the argument to ctx.auth.login must come from a fact the server just verified. Passing a browser-supplied email straight through means "whoever the browser claims to be, is" — that is not authentication.

The session cookie is written by the platform on the response. Func code never sees the session token and cannot forge one. Inside a Func, read identity with ctx.auth.currentUser(), or reject unauthenticated callers outright with ctx.auth.requireUser().

Login in Func →Verify email at signup →Password reset →

Email and verification codes: the easy-to-break parts are already done

Connect Resend once under Backend → Integrations in the editor, then send transactional email and verification codes from a Func through ctx.email.

ctx.email.sendCode({ to: input.email, scene: "login" })

Code generation, expiry, single use, constant-time comparison, an attempt ceiling and rate limiting are all guaranteed by the platform — get any one of those six wrong and it is a real security bug that usually raises no error, it is just quietly insecure.

Send email and verification codes →The full release note →

Payments: the loop closes on your own site

The docs give a complete Alipay desktop payment implementation: RSA2 signing, optional AES content encryption, server-side trusted orders, notification signature verification, amount checks and idempotent updates.

Two boundaries worth stating up front: the amount must be decided by a server-side product table — the browser only sends a productId; and paid status may only come from a signature-verified async notification, never from the parameters on the front-end return URL.

That notification URL (notify_url) is simply a published Func address, shaped like https://your-domain.com/func/alipay.notify. This is the concrete payoff of "a Func can be a public callback endpoint" from earlier.

Accept Alipay desktop payments with Func →Accept Alipay through the integration →

Boundaries: what does not belong here

Saying what works means saying what does not:

  • JSON tables have no joins, aggregation, transactions or fuzzy matching. Systems that need those should use a real database and have a Func call it.

  • The payment channel covered by the docs today is Alipay web payments. Other channels follow the same pattern but you wire them yourself.

  • The user directory ctx.users.find/query is scoped to the whole project, not to the current caller — so "who is allowed to look up whom" is a gate you must write. The platform will not decide it for you.

Documentation index

Render diagnostics