Litestore · Developer guide
Codebase

Config

This page shows you which file in config/ to edit when you want to change a fixed value in Litestore.

The config/ folder contains values you can change without touching application code. You edit these files when you want to change the store's identity and copy, which modules write an audit trail, the AI system prompts, the four themes, the pricing defaults or the checkout country list.

Each file is plain TypeScript with as const literals. There is no JSON layer and no runtime config service.

Environment variables don't go here. They are declared in env.ts.

The barrel

config/index.ts combines the storefront-facing files into one config object.

config/index.ts
export const config = {
  site: siteConfig,
  links: linksConfig,
  metadata: metadataConfig,
  search: searchConfig,
  copy: copyConfig,
  heroSlides: heroSlidesConfig,
}

The object deliberately leaves some files out. Code imports the activity, AI, theme, pricing and payments config directly from their own modules.

config/site.ts exports four objects.

siteConfig contains the store name, slug and description. It also reads the email and URL from env.NEXT_PUBLIC_SITE_EMAIL and env.NEXT_PUBLIC_SITE_URL.

siteConfig also has affiliateUrl, the domain of your short links. addSearchParams() passes links on that domain through untouched, so affiliate links don't get their ref parameters rewritten.

searchConfig.filters lists the three filters shoppers can use in search:

collection
category
collab

heroSlidesConfig defines three fixed hero slides for the homepage. Each slide has a primary and a secondary button.

metadataConfig is a Next.js Metadata object. It sets the OpenGraph image at /opengraph.png (1200×630), the summary_large_image Twitter card and the RSS alternates.

config/links.ts lists the links that point away from your store. The footer and the JSON-LD sameAs graph read its five social handles. Its feeds entry holds the two RSS URLs that pages link to for feed discovery.

A comment at the top of the file explains what was removed. The upstream template linked to its author's homepage, cross-promoted other projects, listed press mentions and included five affiliate links that paid a third party. Nothing referenced those links, so they were deleted.

config/copy.ts contains the storefront text:

  • Page titles and descriptions for managed pages, collections, categories, suppliers, blog, collabs and tags
  • The five headings for listing sections (Featured collections, Popular products, …)
  • Six messages for empty states
  • Five button labels

It exports its own type, CopyConfig.

Activity logging

config/activity.ts decides which writes get recorded in the Activity table.

Every BaseCRUD model name belongs to exactly one of four module lists:

  • CRITICAL_MODULES log every operation: order, refund, user, customer, product, campaign, serviceApiKey, channel.
  • CREATE_DELETE_MODULES log creates and deletes: media, tag, coupon, socialPost, link.
  • DELETE_ONLY_MODULES log deletes only: productVariant, emailTemplate, stockLocation, productRelationship.
  • SKIP_LOGGING_MODULES log nothing: activity, address, wishlist, cart, notification.

Every model must be in a list. The activity-coverage rule in scripts/check-discipline.ts reads this file and fails when a BaseCRUD modelName is missing from all four.

Before that rule existed, a missing module failed silently and simply got no audit trail.

Litestore always logs the operations in CRITICAL_OPERATIONS, whichever module they happen in:

delete
refund
cancel
ban
suspend

ACTIVITY_CLEANUP keeps activity for 90 days and deletes old rows in batches of 1,000.

Activity types that start with one of the DURABLE_ACTIVITY_TYPE_PREFIXES are never purged:

commission
payout
affiliatePayout
price_changed
setting
campaign
refund
return
payment
loyalty

Routine order and product changes are deliberately not kept forever. You can rebuild an order's financial history from the PaymentTransaction and Order tables.

shouldLogActivity(module, operation) applies all of these rules, and config/activity.test.ts tests it.

How activity is displayed

config/activity-registry.ts is the largest file in the folder.

ACTIVITY_MODULES lists every module you can filter the activity feed by. If a module is logged in config/activity.ts but missing here, its rows show up but can never be filtered. lib/__tests__/activity-taxonomy.test.ts checks for this.

ACTIVITY_REGISTRY gives each activity type an icon, a color, a label, its module and a sort priority.

The file exports these functions for reading the registry:

  • getActivityConfig
  • getModuleActivityTypes
  • getModuleLabel
  • getModuleIcon
  • formatActivityType
  • getModulesWithCounts

AI prompts

config/ai.ts contains five system prompts, each with its own generation settings:

  • adminChat (maxSteps 5, maxTokens 2000, temperature 0.7)
  • productDescription (1000 / 0.3)
  • emailGeneration (800 / 0.7)
  • socialPost (500 / 0.9)
  • analyticsChat (600 / 0.2)

The file contains no model names and no API keys. Litestore resolves those elsewhere.

It also contains no list of tools, and a comment explains why. Each registered tool describes itself through the AI SDK, and server/ai/agent-tools.ts is the only place that defines them.

The admin-chat prompt used to include a hand-written tool list. That list advertised tools that didn't exist and left out real ones.

Themes and palettes

config/theme.ts registers the themes and exports the functions for reading them.

THEMES has four entries. starter targets the admin. starter-web, amelia and brutal target the storefront (web). DEFAULT_ADMIN_THEME_ID is starter and DEFAULT_STOREFRONT_THEME_ID is amelia.

assertThemeTarget throws if code uses an admin theme id for the storefront, or a storefront theme id for the admin. getTheme(channelThemeId) tries the channel's theme first, then the stored active theme, then the default.

The file also exports these functions:

  • getThemeTarget
  • isStorefrontThemeId
  • isAdminThemeId
  • getDefaultTheme
  • getThemeNames
  • getThemeByName

Each theme is defined in its own file in config/themes/, next to a shared _utils.ts. That file exports three helpers:

  • createWebTheme fills in target: "web".
  • joinThemeCssBlocks joins CSS blocks.
  • createStorefrontBaseCss(themeId) builds the shared [data-theme="…"] block. It gives dialogs, popovers, badges, inputs, empty states and the cookie banner their per-theme corner radius, borders and backdrop blur.

A theme file overrides --web-* tokens on top of DEFAULT_WEB_THEME_TOKENS. Amelia has no card borders because it sets "--web-card-border-width": "0px".

scripts/check-theme-boundaries.ts makes sure theme ids and theme-specific selectors only appear in these two places. Global CSS, admin code and shared components must not name a theme.

NewLinen shirtLight, breathable, made to last.$48.00Add to cart
A theme overrides the same design tokens, so switching it changes how the storefront looks.

Color palettes

config/color-palettes.ts is separate from themes. A palette is a set of colors that one storefront block uses. It overrides the same --color-* variables the theme already sets, but only for that block.

  • PaletteColors names the 17 colors a palette can override.
  • PALETTE_COLOR_FIELDS puts them in order with labels for the admin editor.
  • paletteCssVars() turns a palette into an inline style object.
  • resolvePaletteById finds a palette by its id.

STARTER_COLOR_PALETTES ships one palette, starter-ember-noir ("Ember Noir").

The module doesn't read the database or resolve themes. Client components can import it without pulling server code into the bundle.

The first palette a user sees is read-only. ~/server/admin/settings/color-palettes builds it from the active theme. Copies that users can edit are stored in the custom_color_palettes setting.

Fonts

config/fonts.ts loads a single typeface, once.

fontSans is Figtree Variable. It is served from public/fonts/Figtree-Variable.woff2 with display: "swap", and the app reads it as --font-sans.

A comment at the top of the file explains why a second font was removed. A second localFont() call for --font-display added a duplicate @font-face and a duplicate preload, but looked no different. The variable font's weight axis already reaches 700.

The WOFF2 file is 27.8 KB compressed, compared with 62.7 KB for the TTF.

The file also exports loadGoogleFont(font, weight). It fetches a Google Fonts CSS2 stylesheet and returns the font file. Litestore uses it to render OG images with Satori, not for the app itself.

Payments and pricing

config/payments.ts exports one list, SHIPPING_ALLOWED_COUNTRIES. It contains 24 ISO 3166-1 alpha-2 country codes, typed as Stripe's AllowedCountry union. Checkout uses it to decide which countries a shopper can enter as a shipping address.

Comments next to Poland and Brazil name the local payment methods they enable: Przelewy24 and BLIK for Poland, and Pix for Brazil.

config/pricing.ts sets the default values for margins and discounts:

  • ASSUMED_COST_RATIO (0.5) is the cost ratio Litestore assumes when a product has no costPrice.
  • DEFAULT_PAYMENT_FEE_PERCENT (2.9) and DEFAULT_PAYMENT_FEE_FIXED (0.3) are the assumed payment processing fee.
  • DEFAULT_PLATFORM_FEE_PERCENT (0) and DEFAULT_MIN_MARGIN_PERCENT (5) are the default platform fee and minimum margin.
  • BEHAVIORAL_DISCOUNTS has four discount configs: first_purchase, cart_abandoner, returning_customer and high_intent. Only first_purchase is enabled. It gives 10% off, capped at 20, with priority 100.

A cart only ever gets one behavioral discount. If several apply, Litestore picks the one with the highest priority.

External services

config/external-services.ts is a lookup table of 55 third-party services. Each entry has an id, a display name, a website, a favicon URL and one of eleven categories:

payment
social
analytics
ads
email
storage
auth
shipping
ecommerce
ai
other

Every favicon loads through Google's favicon service, so the admin shows them all at the same size.

config/external-services.ts
export function getService(id: string): ExternalService | undefined {
  return EXTERNAL_SERVICES[id]
}

export function getServicesByCategory(category: ServiceCategory): ExternalService[] {
  return Object.values(EXTERNAL_SERVICES).filter(s => s.category === category)
}

The same file defines where you can import reviews from. REVIEW_IMPORT_PLATFORMS is:

amazon
aliexpress
google
trustpilot

getReviewImportSources() returns those platforms together with the supported file formats. Each source has an available flag and a configureUrl that points to the integration you need to set up first.

Where a new value goes

Put storefront wording in config/copy.ts, store identity in config/site.ts and outbound URLs in config/links.ts.

To add a theme, create a file in config/themes/ and add its entry to THEMES. To add a block palette, add an entry to config/color-palettes.ts.

When you add a module that writes to the database, put it in one of the four lists in config/activity.ts. The activity-coverage rule fails until you do. If its rows should be filterable in the activity feed, also add it to ACTIVITY_MODULES.

To add a third-party service, add it to config/external-services.ts.

Two kinds of values don't belong here. Anything that differs between environments goes in env.ts. Anything an operator changes in the admin goes in settings. For example, the color palettes users edit are stored in the custom_color_palettes setting, not in config/color-palettes.ts.

To decide, ask yourself one question:

Does changing this value require a deploy? If it doesn't, it is not config.

On this page