# Write your own endpoint (https://docs.salla.dev/twilight/react/api/write-your-own-endpoint)



Use this when the store API has an endpoint the engine has no [module](https://docs.salla.dev/twilight/react/api/modules) for. You write one file in your theme, in the same shape as the built-in modules. The example fetches a list of "gift cards"; the path and types are placeholders for your own endpoint.

<Callout title="Framework: TanStack Start">
  The loader in step 3 is TanStack Start code, the default and currently supported framework. [See what depends on the framework](https://docs.salla.dev/twilight/react/start/frameworks).
</Callout>

### Write the typed function [#write-the-typed-function]

Build on [`api`](https://docs.salla.dev/twilight/react/api/client), so headers, locale, token and scope come for free:

```ts title="app/api/gift-cards.ts"
import { api } from '@salla.sa/twilight-theme-engine/api/client';
import type { PaginatedResult } from '@salla.sa/twilight-theme-engine/api/types';

export interface GiftCard { id: number; name: string; price: number }

export interface GiftCardsParams { cursor?: string | null; signal?: AbortSignal }

async function list({ cursor, signal }: GiftCardsParams = {}): Promise<PaginatedResult<GiftCard>> {
  const searchParams = new URLSearchParams();
  if (cursor) searchParams.set('cursor', cursor);

  const res = await api
    .get('gift-cards', { searchParams, signal })
    .json<{ data: GiftCard[] | null; cursor?: { next?: string | null } }>();

  return { items: res.data ?? [], next: res.cursor?.next ?? null };
}
```

### Add `queryOptions` with a correct key [#add-queryoptions-with-a-correct-key]

The key decides when a cached answer may be reused. The API answers differently per language, so put the locale in the key. If your data also varies by the customer's branch, add the scope too (see [Authentication and scope](https://docs.salla.dev/twilight/react/api/authentication-and-scope)).

```ts title="app/api/gift-cards.ts"
import { queryOptions } from '@tanstack/react-query';
import { getTwilightContext } from '@salla.sa/twilight-theme-engine/tanstack';

export const giftCards = {
  list,
  queries: {
    list: (params: Omit<GiftCardsParams, 'signal'> = {}) =>
      queryOptions({
        queryKey: ['gift-cards', 'list', params, getTwilightContext().locale],
        queryFn: ({ signal }) => list({ ...params, signal }),
      }),
  },
};
```

### Prefetch in the loader, read in the component [#prefetch-in-the-loader-read-in-the-component]

```tsx title="app/routes/gift-cards.tsx"
import { useSuspenseQuery } from '@tanstack/react-query';
import { getTwilightContext } from '@salla.sa/twilight-theme-engine/tanstack';
import { giftCards } from '../api/gift-cards';

loader: () => getTwilightContext().queryClient.ensureQueryData(giftCards.queries.list()),

function GiftCards() {
  const { data } = useSuspenseQuery(giftCards.queries.list());
  return data.items.map((card) => <p key={card.id}>{card.name}</p>);
}
```

The loader fills the cache on the server. The component reads the same key, so the page renders with data and the browser does not fetch it again. To put this on its own URL, see [Add a page](https://docs.salla.dev/twilight/react/recipes/add-a-page).

<Callout type="warn" title="Traps">
  * **Bare `fetch`** skips the token, locale, store identifier and scope, and has no timeout or retries. It may work while you are signed out and fail for a real customer.
  * **A key without locale and scope** serves one language's or branch's data to another. The built-in `product.queries.list` includes the scope for this reason.
  * **Browser-only code in `queryFn`** (`window`, `localStorage`) crashes the server render.
  * **Forgetting `signal`** leaves cancelled queries running. Pass it through.
</Callout>

## Next [#next]

* [Errors, timeouts and retries](https://docs.salla.dev/twilight/react/api/errors-timeouts-and-retries): errors from your module behave like any other.
* [Modules](https://docs.salla.dev/twilight/react/api/modules): read a built-in module's page to copy its conventions.
