Point0by 1gr14
  • Меню
    • GitHub
    • Блог
  • Introduction
    • Overview
    • Getting Started
    • Full Overview
    • Benchmarks
    • Points
  • Points
    • Page
    • Layout
    • Component
    • Provider
    • Mountable
    • Query
    • Infinite Query
    • Mutation
    • Action
    • Root
    • Base
    • Plugin
  • Methods
    • Validation
    • Loader
    • Context
    • Middleware
    • Loading & error
    • .with
    • Mapper
    • Transformer
    • Stage Methods
  • Core
    • Navigation
    • SSR
    • RSC
    • Request
    • Response
    • Error handling
    • Env
    • Head
    • MDX
    • Assets
    • File upload
    • OpenAPI
    • Query client
    • Events
    • Infer
  • Engine
    • Engine Config
    • Engine Runtime
    • CLI
    • Dev
    • Build
    • Compiler
    • Generator
    • Project MCP
    • Docs MCP
    • Importer
    • Public dir
    • Testing
    • Deploy
    • Bun or Vite
  • Extra
    • SsrStore
    • CookieStore
    • Basic Auth
    • CORS
    • Cache-Control
    • Compress
  • Examples
    • Basic
    • Vite
    • Better Auth
    • Capacitor
    • Expo
  • 1gr14
    • Главная
    • Start0
    • Поддержать
    • Обучение
    • Группа
    • Блог
    • Автор
  • Сообщество
    • Discord
    • Telegram
  • Опенсорс
    • Point0
    • Route0
    • Error0
    • Flat
  • Аккаунт
    • Войти
    • Регистрация
Point0by 1gr14
Создаю опенсорс во славу Господа Иисуса Христа ☦️
С любовью ко всем разработчикам на свете ❤️
Условия использованияПолитика конфиденциальностиСергей Дмитриев 2026 😎

Context

  • Раздел: Methods

On this page

  1. Which points
  2. The merge
  3. What the function receives
  4. Plain object instead of a function
  5. Redirect and error
  6. Exposing keys
  7. It runs only when the point has a loader
  8. Reference
  9. Signature
  10. Function return values
  11. Forbidden expose keys

.ctx adds values to a server-only context. The function runs on the server during a request; what it returns is merged into the running context, and every later .ctx and the .loader can read it. It's the place to resolve request-scoped values — the current user, a per-request flag — once, before the loaders. (A DB client or anything that doesn't depend on the request belongs in a plain import, not in ctx — the import is stripped from the client bundle anyway.)

.ctx(async ({ request }) => {
  return { me: await getMe({ request }) } // me is now in ctx for every loader below
})
.loader(async ({ ctx }) => {
  const ideas = await prisma.idea.findMany({ where: { authorId: ctx.me?.id } })
  return { ideas }
})

Cut from the client bundle — its body and the imports it uses are removed, so it never ships to the browser. This holds whether or not the point has ssr: true. (It runs on the server, during the request, before the loader.)

Which points

.ctx is available on every point — root, base, plugin, page, layout, component, provider, query, infinite-query, mutation, and action — while it's still being composed (before the loader). After you finalize a point, .ctx is gone; calling it on a finalized or already-loaded point is a type error and throws at runtime.

It runs only on points that issue a server request. An action always has a server loader, so its .ctx always runs when it executes. A query, infinite-query, or mutation runs its .ctx only when it has a server .loader(); a .clientLoader()-only query/mutation runs in the browser with no endpoint, so — like a loader-less page — its server-only .ctx never runs. A page or layout without a loader also makes no request — see the gotcha below.

The merge

What you return is shallow-merged onto the previous context — later keys win:

.ctx(() => ({ x: 1 }))
.ctx(({ ctx }) => ({ y: ctx.x + 1, x: 999 })) // reads x from the previous ctx
// nextCtx = { ...prevCtx, ...returned }  =>  { x: 999, y: 2 }

You can stack as many .ctx calls as you like — each sees the context built up by the ones before it. There is still only one loader per point.

Return undefined (or nothing) and the context is left untouched — useful for a .ctx that only validates or redirects:

.ctx(({ ctx }) => {
  if (!ctx.feature) return // no override
  return { extra: load() }
})

What the function receives

The callback gets one object:

.ctx(({ ctx, request, set, points }) => {
  // ctx     — the context accumulated so far (fully typed)
  // request — the incoming Request0
  // set     — response helper for headers / cookies / status
  // points  — the server-side points collection (e.g. openapi reads it to build its schema)
  return {}
})

Parsed input / params / search / body / headers / cookies are also present when the matching schema is declared above this .ctx in the chain.

Plain object instead of a function

When the values don't depend on the request, pass an object directly:

.ctx({ tenant: 'acme' })          // same as .ctx(() => ({ tenant: 'acme' }))
.ctx(() => ({ now: Date.now() }))  // a function for request-time values

Passing a function where an object is expected is a type error (Use ctx(fn) for function values). Returning an array from a .ctx function is also a type error (Ctx fn should not return array).

Redirect and error

Return (or throw) a RedirectTask to redirect — later .ctx calls and the loader don't run:

.ctx(({ ctx }) => {
  if (!ctx.me) return redirect('signIn')
  return { me: ctx.me } // narrows me to non-null for the rest of the chain
})

Return (or throw) an Error to abort the request with that error:

.ctx(({ ctx }) => {
  if (!ctx.me) throw new ErrorPoint0('Only for authorized users', { code: 'UNAUTHORIZED' })
  return { me: ctx.me }
})

ErrorPoint0 is the framework's default error class; returning it and throwing it behave the same. You can swap it for your own class of the same-or-wider shape — AppError in the rest of this page is just such a class. See Error handling.

Exposing keys

By default a .ctx value is reachable only through ctx.x. Pass expose to also spread the key at the top level of later .ctx and .loader arguments:

.ctx({ x: 1 }, true)            // expose every returned key
.loader(({ ctx, x }) => ...)   // both ctx.x and bare x are available

.ctx({ x: 1, y: 2 }, ['x'])    // expose only x
.loader(({ x }) => ...)        // x is here; y is NOT — only ctx.y

expose: true exposes all returned keys; a string array exposes only those. Exposed keys accumulate across multiple .ctx calls. These keys are reserved and can't be exposed (it's a type error): request, input, inputRaw, data, set, execute, ctx.

It runs only when the point has a loader

.ctx runs only when the point issues a server request — that is, only when it has a loader. A loader-less page or layout makes no request, so its .ctx never executes.

// This page has no loader → no request → this .ctx never runs.
export const profilePage = root.lets
  .page('/profile')
  .ctx(({ ctx }) => {
    if (!ctx.me) throw new AppError('nope', { code: 'UNAUTHORIZED' }) // NEVER fires
    return { me: ctx.me }
  })
  .page(({ props: { me } }) => <Profile me={me} />)

Do not gate access in .ctx alone — gate it in .with, which runs at render on both server and client, so it fires on the initial SSR render and on every later client-side navigation. The production pattern pairs a .ctx (for server loaders) with a .with (for the render):

export const authorizedOnlyPlugin = Point0.lets
  .plugin()
  .use(mePlugin) // an upstream plugin that puts `me` in ctx and props
  .ctx(({ ctx: { me } }) => {
    if (!me)
      throw new AppError('Only for authorized users', { code: 'UNAUTHORIZED' })
    return { me } // narrows me to non-null in ctx
  })
  .with(({ props: { me } }) => {
    if (!me)
      return new AppError('Only for authorized users', { code: 'UNAUTHORIZED' })
    return { me } // narrows me to non-null in props
  })
  .plugin()

me comes from the upstream mePlugin — props.me doesn't appear on its own. See .with and Plugin for the full gate.

Unlike .ctx, .with is server-ssr-and-client: cut from the server bundle when ssr: false (or after a .clientOnly()), kept in the client build always, and in the server build only when SSR is on. .ctx (cut from the client bundle) and .with (cut from the server bundle only when SSR is off) cover the two halves: loaders and render.

Reference

Signature

.ctx(fn)            // function: receives { ctx, request, set, points, ...parsed }
.ctx(object)        // plain object, when values don't depend on the request
.ctx(fnOrObject, true)        // expose every returned key
.ctx(fnOrObject, ['a', 'b'])  // expose only these keys

Returns the same point chain with ctx advanced. Chainable.

Function return values

ReturnEffect
a plain objectshallow-merged into ctx (later keys override earlier)
undefined / voidleaves ctx unchanged
an Error (returned or thrown)aborts the request with that error
a RedirectTask (returned or thrown)redirects; later .ctx / loader don't run
an arraytype error — Ctx fn should not return array

The function may be async; it's awaited. A plain object argument must not be a function (Use ctx(fn) for function values).

Forbidden expose keys

request, input, inputRaw, data, set, execute, ctx. Listing any in expose is a type error (Forbidden to expose ctx keys: ...).

НазадLoaderДалееMiddleware

Enjoying Point0?

Give it a star on GitHub, ask questions, and follow what's new

Star Point0

A star on GitHub helps more developers find Point0
Star on GitHub

Start0

Point0 integrated into the Start0 SaaS boilerplate — the best way to use it
Explore Start0

YouTube

Tutorials, walkthroughs, and updates
YouTube

Discord

Questions and chat in English
Join Discord

Telegram

News channel and chat in Russian
ChannelChat

Twitter

Posts and updates
Twitter