Skip to content
Twilight React Playground
ثيم رائدaren

apps

objectAdvancedserverbrowserlive demo

Reads the Salla apps installed on the store: the code each one wants on the page, and each app's public settings.

import { apps, AppSnippet, AppSnippetRow, AppSettings } from '@salla.sa/twilight-theme-engine/api/apps';

In plain words

Merchants install apps from the Salla App Store, such as a review widget or a live chat. Those apps add small pieces of code (snippets) to the storefront. apps.snippets() returns what each installed app wants to add and where; apps.settings() returns the public settings each app reads.

The engine already fetches both before every page and injects the snippets for you. Read them yourself only to list or check the installed apps.

Signature

apps.snippets(): Promise<AppSnippet[]>   // GET apps/snippets, timeout 2.5 s
apps.settings(): Promise<AppSettings>    // GET apps/snippets/settings, timeout 2.5 s → { apps: data?.apps ?? {} }
apps.queries.snippets()   // key ['apps', 'snippets'], staleTime Infinity, retry 0
apps.queries.settings()   // key ['apps', 'settings'], staleTime Infinity, retry 0

interface AppSnippet {
  service: string;
  app_id: string;
  app_slug: string;
  status: string;
  id?: string;                    // encoded app id: its settings bucket key
  snippets: AppSnippetRow[];
}
interface AppSnippetRow {
  place: string;                  // e.g. 'before_body'
  content: string;                // inline HTML; may be empty when url is set
  url?: string | null;            // the script file of a CDN row
  id?: number;                    // CDN rows only
  customer_parameters?: string[];
}
interface AppSettings { apps: Record<string, Record<string, unknown>> }

Try it live

The Salla apps installed on the demo store, and where each asks to put its code. Nothing here runs that code.Try this: turn on the settings buckets: an app with no settings has an empty array, not an object.
Storefront canvas · en · LTR
fetchStatus: idle
reviews-translator (enabled)
  • before_body: a script file on the CDN
viewed-product (enabled)
  • before_body: inline HTML
  • before_body: inline HTML
Controls
Show settings buckets
What a theme writes
import { useQuery } from '@tanstack/react-query';
import { apps } from '@salla.sa/twilight-theme-engine/api/apps';

/** Lists the installed apps. The engine already injects their snippets: never render content yourself. */
export function InstalledApps() {
  const { data = [] } = useQuery(apps.queries.snippets());

  return (
    <ul>
      {data.map((app) => (
        <li key={app.app_id}>
          {app.app_slug} · {app.snippets.length} snippet(s)
        </li>
      ))}
    </ul>
  );
}

Example

app/hooks/useHasApp.ts
import { useQuery } from '@tanstack/react-query';
import { apps } from '@salla.sa/twilight-theme-engine/api/apps';

/** For example: hide the theme's own reviews block when a reviews app is installed. */
export function useHasApp(slug: string): boolean {
  const { data = [] } = useQuery(apps.queries.snippets());
  return data.some((app) => app.app_slug === slug && app.status === 'enabled');
}

How it behaves

  • The root loader awaits both queries with a .catch that logs and continues, so a slow or failing apps API costs the page its app tags, never the page. retry: 0 keeps the 2.5 s timeout the whole budget during the server render.

  • Both stay fresh forever (staleTime: Infinity), so a component reading them answers from the cache the root loader filled.

  • The engine AppsSnippets component injects the snippets, and re-queries the settings in the browser with retry: 2.

  • AppSnippet.id is the encoded app id: the key of that app's bucket in settings.apps, and the scope its snippets bind to. It is absent on payloads older than the settings API.

  • The raw functions throw on an HTTP error; only the root loader catches.

Gotchas

  • Never render content yourself: the engine already injects every snippet, and third-party scripts could run twice.

  • A settings bucket is not always an object: on the demo store an app without settings has []. Check typeof bucket === 'object' && !Array.isArray(bucket) before reading keys.

Related

Source and docs