# How the Prompt Template (Mask) System Works in NextChat: A Technical Deep Dive

> Explore NextChat's prompt template system. Learn how masks pre-define contexts and configurations for automatic chat session initialization and discover technical details of its persistent Zustand store.

- Repository: [NextChat/NextChat](https://github.com/ChatGPTNextWeb/NextChat)
- Tags: deep-dive
- Published: 2026-02-28

---

**NextChat's mask system lets users pre-define conversation contexts, model configurations, and languages that automatically initialize new chat sessions, blending built-in templates with user-created presets stored in a persistent Zustand store.**

NextChat (formerly ChatGPTNextWeb) implements a sophisticated prompt template architecture called "masks" that streamlines how users initiate AI conversations. The mask system combines static built-in templates with dynamic user-defined configurations, enabling instant application of complex system prompts and model settings. According to the NextChat source code, this system is implemented through a layered architecture involving TypeScript type definitions, persistent state management, and build-time optimization.

## The Mask Data Structure

At the core of the system is the `Mask` type defined in [`app/store/mask.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/store/mask.ts) (lines 9–23). This interface defines the complete shape of a prompt template:

```typescript
export type Mask = {
  id: string;                     // unique identifier (nanoid)
  createdAt: number;              // timestamp
  avatar: string;                 // UI icon
  name: string;                   // display name
  hideContext?: boolean;          // hide context from UI
  context: ChatMessage[];         // preset messages (system / user / assistant)
  syncGlobalConfig?: boolean;     // if true, model config follows global config
  modelConfig: ModelConfig;       // model, temperature, token limits, etc.
  lang: Lang;                     // language of the mask
  builtin: boolean;               // true for shipped masks
  plugin?: string[];              // optional plugin ids
  enableArtifacts?: boolean;      // UI flags
  enableCodeFold?: boolean;
};

```

The `context` array stores the actual prompt template—an array of `ChatMessage` objects that typically include a system message defining the AI's persona plus optional example exchanges. The `modelConfig` property overrides global settings for that specific mask, controlling parameters like temperature, max tokens, and model selection.

## Built-in Masks: Static Template Definitions

NextChat ships with pre-configured prompt templates stored as static JavaScript objects in locale-specific files. The built-in masks reside in [`app/masks/en.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/masks/en.ts), [`app/masks/cn.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/masks/cn.ts), and [`app/masks/tw.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/masks/tw.ts), each exporting an array of `BuiltinMask` objects tailored to English, Simplified Chinese, and Traditional Chinese users respectively.

Each built-in mask contains:

- **Avatar and name** – Visual identifiers for the UI
- **Context array** – Pre-loaded conversation messages (typically system prompts)
- **Model configuration** – Default model, temperature, and token limits
- **Language tag** – Locale association for organization
- **Builtin flag** – Set to `true` to distinguish from user masks

For example, the GitHub Copilot mask in [`app/masks/en.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/masks/en.ts) (lines 4–28) defines a programming assistant persona:

```typescript
{
  avatar: "1f47e",
  name: "GitHub Copilot",
  context: [{ 
    id: "Copilot-0", 
    role: "system", 
    content: "You are an AI programming assistant..." 
  }],
  modelConfig: { 
    model: "gpt-4", 
    temperature: 0.3, 
    max_tokens: 2000 
  },
  lang: "en",
  builtin: true,
  createdAt: 1688899480410,
}

```

## The Mask Store: Persistence and Runtime Logic

User-created masks and runtime operations are managed by `useMaskStore` in [`app/store/mask.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/store/mask.ts). This Zustand-based store persists data to localStorage or IndexedDB while handling the complex logic of merging user masks with built-in templates.

### Creating New Masks

When users click "New Mask," the store executes the `create()` method (lines 35–47). This function generates a unique ID using `nanoid()`, merges user-provided overrides with defaults from `createEmptyMask()`, and marks the result as non-built-in:

```typescript
const id = nanoid();
masks[id] = {
  ...createEmptyMask(),
  ...mask,          // user-provided overrides
  id,
  builtin: false,
};

```

The `createEmptyMask()` helper derives initial values from the current global application configuration (`useAppConfig.getState().modelConfig`) and the detected interface language (`getLang()`), ensuring new masks inherit sensible defaults while remaining customizable.

### Retrieving and Merging Masks

The `getAll()` method (lines 88–105) handles the sophisticated logic of presenting masks to the UI. It returns user masks first, sorted by creation date (newest to oldest), then conditionally appends built-in masks unless the `hideBuiltinMasks` global setting is enabled:

```typescript
const userMasks = Object.values(get().masks).sort(
  (a, b) => b.createdAt - a.createdAt,
);
if (!config.hideBuiltinMasks) {
  const buildinMasks = BUILTIN_MASKS.map(m => ({
    ...m,
    modelConfig: { ...config.modelConfig, ...m.modelConfig },
  }));
  return userMasks.concat(buildinMasks);
}
return userMasks;

```

Notice the `modelConfig` merge strategy: built-in masks inherit the user's global configuration, then apply their own specific overrides. This allows template-specific temperature or token limits while respecting user preferences for model selection.

## Build-Time Optimization: Generating masks.json

To optimize frontend performance, NextChat separates built-in mask definitions from the main bundle. During the production build process, [`app/masks/build.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/masks/build.ts) aggregates all locale-specific mask arrays into a single JSON file at [`public/masks.json`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/public/masks.json):

```typescript
fs.writeFile(
  dirname + "/../../public/masks.json",
  JSON.stringify(BUILTIN_MASKS, null, 4),
  ...
);

```

The frontend fetches this JSON file to populate the mask selector without importing the full TypeScript source, reducing bundle size and enabling dynamic updates to built-in templates without recompiling the application.

## Runtime Application: From Mask to Conversation

When initiating a chat session with a selected mask, the application performs two critical operations:

1. **Context Injection** – The `context` array from the mask is pre-loaded into the chat message list, establishing the system prompt and any example conversations immediately.
2. **Configuration Override** – The mask's `modelConfig` becomes the active configuration for API requests, controlling generation parameters.

If the mask's `syncGlobalConfig` property is set to `true`, the system maintains a live connection to global settings. Changes to the application's model configuration automatically propagate to the mask, ensuring consistency without manual updates.

## Code Examples

### Creating a Custom Mask Programmatically

```typescript
import { useMaskStore } from '@/app/store/mask';

const myMask = useMaskStore.getState().create({
  name: 'My Research Assistant',
  avatar: '🧠',
  context: [
    { 
      id: 'research-0', 
      role: 'system', 
      content: 'You are a concise research assistant...' 
    },
  ],
  modelConfig: { 
    model: 'gpt-3.5-turbo', 
    temperature: 0.2, 
    max_tokens: 1500 
  },
});

```

### Loading a Mask into a New Chat

```typescript
import { useMaskStore } from '@/app/store/mask';
import { useChatStore } from '@/app/store/chat';

function startChatWithMask(maskId: string) {
  const mask = useMaskStore.getState().get(maskId);
  const chat = useChatStore.getState();

  // preload the mask's context
  chat.setMessages([...mask.context]);

  // apply the mask's model config
  chat.updateConfig(mask.modelConfig);
}

```

### Fetching Built-in Masks on the Client

```typescript
// masks are served as public/masks.json
fetch('/masks.json')
  .then(r => r.json())
  .then((builtinMasks) => {
    // builtinMasks is { cn: [...], en: [...], tw: [...] }
    console.log('Available built‑in masks:', builtinMasks);
  });

```

## Summary

- **Masks are comprehensive prompt templates** defined by the `Mask` type in [`app/store/mask.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/store/mask.ts), combining conversation context with model configuration parameters.
- **Built-in masks ship as static objects** in locale files ([`app/masks/en.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/masks/en.ts), etc.) and get compiled to [`public/masks.json`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/public/masks.json) during build for efficient loading.
- **User masks persist in a Zustand store** that handles creation timestamps, unique ID generation via `nanoid()`, and merging with global configuration settings.
- **The `getAll()` method prioritizes user masks** sorted by recency, then conditionally merges built-in masks with current global settings unless hidden.
- **Runtime application injects context messages** and overrides model parameters when starting a chat, with optional synchronization to global config changes via `syncGlobalConfig`.

## Frequently Asked Questions

### What is the difference between a built-in mask and a user-created mask?

Built-in masks are static JavaScript objects shipped with the application in files like [`app/masks/en.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/masks/en.ts), marked with `builtin: true`, and compiled into [`public/masks.json`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/public/masks.json). User-created masks are generated dynamically through the `create()` method in [`app/store/mask.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/store/mask.ts), assigned unique IDs via `nanoid()`, marked with `builtin: false`, and stored persistently in the browser's localStorage or IndexedDB through the Zustand store.

### How does NextChat handle mask configuration when global settings change?

When a mask's `syncGlobalConfig` property is `true`, it dynamically inherits changes from the global application configuration. In the `getAll()` method, built-in masks explicitly merge the current global `modelConfig` with their own settings using the spread operator: `{ ...config.modelConfig, ...m.modelConfig }`. This ensures user preferences for model selection apply to all masks while preserving template-specific parameters like temperature.

### Where are custom masks stored in the browser?

Custom masks persist through the `createPersistStore` helper (defined in [`app/utils/store.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/utils/store.ts)), which wraps the Zustand store with localStorage or IndexedDB backing. The `useMaskStore` in [`app/store/mask.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/store/mask.ts) maintains the `masks` object—a dictionary keyed by mask ID—saving both user-created masks and their creation timestamps across browser sessions.

### Can masks include plugin configurations?

Yes. The `Mask` type includes an optional `plugin` property defined as `string[]`, allowing masks to specify which plugins should be active for conversations using that template. Additionally, UI-specific flags like `enableArtifacts` and `enableCodeFold` control interface behaviors independent of the AI model configuration, letting masks customize both backend processing and frontend presentation.