# How Fabric's Pattern System Works: From Disk Storage to Runtime Templating

> Discover how Fabric's pattern system utilizes a three-layer architecture to manage AI prompt templates, from disk storage to runtime expansion for secure and efficient prompt engineering.

- Repository: [Daniel Miessler 🛡️/fabric](https://github.com/danielmiessler/fabric)
- Tags: internals
- Published: 2026-02-28

---

**Fabric's pattern system manages reusable AI prompt templates through a three-layer architecture that stores patterns as text files in `data/patterns/`, automatically downloads them from the official repository during setup, and expands variables at runtime while protecting user input from accidental template injection.**

Fabric's pattern system serves as the central mechanism for managing reusable prompt templates (called *patterns*) across both CLI and web interfaces. As implemented in the danielmiessler/fabric repository, this system combines file-based persistence, automated Git-based updates, and a protected templating engine to deliver extensible prompt engineering workflows. The architecture supports custom pattern directories while maintaining synchronization with the official pattern repository.

## Pattern Storage and the Persistence Layer

Patterns in Fabric are ordinary text files stored in a specific directory hierarchy. Each pattern lives in its own subdirectory under `data/patterns/<pattern-name>/`, containing a [`system_prompt.txt`](https://github.com/danielmiessler/fabric/blob/main/system_prompt.txt) file that holds the actual prompt template.

The `PatternsEntity` struct in [`internal/plugins/db/fsdb/patterns.go`](https://github.com/danielmiessler/fabric/blob/main/internal/plugins/db/fsdb/patterns.go) handles all disk operations. When retrieving a pattern, the system first checks for a user-defined custom directory before falling back to the built-in location:

```go
// internal/plugins/db/fsdb/patterns.go#L15-L34
if o.CustomPatternsDir != "" {
    customPatternPath := filepath.Join(o.CustomPatternsDir, name, o.SystemPatternFile)
    if pattern, customErr := os.ReadFile(customPatternPath); customErr == nil {
        return &Pattern{Name: name, Pattern: string(pattern)}, nil
    }
}

```

If the pattern is not found in the custom directory, the same logic reads from the main patterns folder (`o.Dir`). Missing patterns trigger an error that lists all available patterns (lines 33-44). A `loaded` marker file in the patterns directory indicates successful initialization.

## Downloading and Updating Patterns

The `PatternsLoader` in [`internal/tools/patterns_loader.go`](https://github.com/danielmiessler/fabric/blob/main/internal/tools/patterns_loader.go) manages the initial setup and updates of the pattern repository. On first run, it performs four critical operations:

1. **Clone the repository**: Uses `githelper.FetchFilesFromRepo` to pull only the `data/patterns/` subtree
2. **Migrate legacy paths**: If no patterns exist, `tryPathMigration` rewrites paths to the new `data/patterns` layout
3. **Preserve custom patterns**: `PersistPatterns` copies user-defined patterns that do not exist in the fresh download
4. **Build an index**: `createUniquePatternsFile` generates [`unique_patterns.txt`](https://github.com/danielmiessler/fabric/blob/main/unique_patterns.txt), a sorted list of all pattern names

When pattern detection fails, the loader attempts automatic migration before retrying:

```go
// internal/tools/patterns_loader.go#L93-L111
if patternCount, checkErr := o.countPatternsInDirectory(o.tempPatternsFolder); checkErr != nil {
    return fmt.Errorf(i18n.T("patterns_failed_read_temp_directory"), checkErr)
} else if patternCount == 0 {
    // No patterns -> try automatic migration
    if migrationErr := o.tryPathMigration(); migrationErr != nil {
        return fmt.Errorf(i18n.T("patterns_no_patterns_migration_failed"), o.DefaultFolder.Value, migrationErr)
    }
    return o.gitCloneAndCopy() // retry after migration
}

```

The `loaded` marker file prevents re-downloading on subsequent launches, ensuring offline functionality after initial setup.

## Runtime Pattern Retrieval and Variable Expansion

When a user selects a pattern via CLI or web interface, `PatternsEntity.GetApplyVariables` orchestrates the rendering pipeline. The process follows five distinct steps:

1. **Source resolution**: `loadPattern` determines if the source is a file path or pattern name
2. **Template reading**: Fetches raw content via `getFromFile` or `getFromDB`
3. **Input placeholder validation**: `ensureInput` appends `{{input}}` if missing from the template
4. **Variable expansion**: The template engine processes `{{var}}` placeholders while protecting user input with a sentinel token
5. **Input injection**: Replaces the sentinel with the actual user content

The core flow in [`internal/plugins/db/fsdb/patterns.go`](https://github.com/danielmiessler/fabric/blob/main/internal/plugins/db/fsdb/patterns.go) (lines 29-40) delegates to `applyVariables`:

```go
// internal/plugins/db/fsdb/patterns.go#L94-L112
o.ensureInput(pattern)
withSentinel := strings.ReplaceAll(pattern.Pattern, "{{input}}", template.InputSentinel)
processed, err := template.ApplyTemplate(withSentinel, variables, input)
pattern.Pattern = strings.ReplaceAll(processed, template.InputSentinel, input)

```

The `template.ApplyTemplate` function (located in `internal/plugins/template`) recursively resolves all `{{var}}` placeholders except the protected `InputSentinel`. This sentinel-based approach prevents accidental expansion of user-provided text that might contain template-like syntax, ensuring that only explicitly defined variables are substituted while the actual input is safely injected at the final stage.

## Web UI Integration and the Pattern Store

The browser interface consumes patterns through the [`pattern-store.ts`](https://github.com/danielmiessler/fabric/blob/main/pattern-store.ts) module in [`web/src/lib/store/pattern-store.ts`](https://github.com/danielmiessler/fabric/blob/main/web/src/lib/store/pattern-store.ts). This Svelte store interacts with three backend endpoints:

- `GET /data/pattern_descriptions.json`: Loads human-readable descriptions and tags for UI hints
- `GET /api/patterns/names`: Retrieves the list of available pattern names
- `GET /api/patterns/<name>`: Fetches the raw prompt template for a specific pattern

The store initializes by loading all patterns:

```typescript
// web/src/lib/store/pattern-store.ts#L45-L60
await patternAPI.loadPatterns()
  .then(data => allPatterns.set(data))
  .catch(err => console.error('Failed to load patterns:', err));

```

The UI also filters patterns by language prefix (e.g., `en_`) using the `languageStore`, enabling localized pattern selection.

## Practical Implementation Examples

### Loading Patterns Programmatically in Go

The following snippet demonstrates initializing the patterns entity and rendering a template with variables:

```go
package main

import (
    "fmt"
    "github.com/danielmiessler/fabric/internal/plugins/db/fsdb"
)

func main() {
    patterns := &fsdb.PatternsEntity{
        StorageEntity: &fsdb.StorageEntity{
            Dir:           "data/patterns",
            ItemIsDir:     true,
            FileExtension: "",
            Label:         "Pattern",
        },
        SystemPatternFile:      "system_prompt.txt",
        UniquePatternsFilePath: "data/patterns/unique_patterns.txt",
        CustomPatternsDir:      "", // set to override with custom patterns
    }

    vars := map[string]string{"max_words": "150"}
    pat, err := patterns.GetApplyVariables("summarize", vars, "Explain quantum computing.")
    if err != nil {
        panic(err)
    }

    fmt.Println(pat.Pattern) // Rendered prompt with variables expanded
}

```

### Consuming Patterns in the Web UI

TypeScript integration through the pattern store enables dynamic pattern selection:

```typescript
import { patternAPI } from '$lib/store/pattern-store';

// Load patterns on app initialization
await patternAPI.loadPatterns();

// Select a pattern for the current session
function selectPattern(name: string) {
    patternAPI.selectPattern(name);
}

selectPattern('create_story_about_person');

```

## Summary

- **File-based storage**: Patterns reside as [`system_prompt.txt`](https://github.com/danielmiessler/fabric/blob/main/system_prompt.txt) files in `data/patterns/<name>/` directories, with optional override support via `CustomPatternsDir`
- **Automated synchronization**: The `PatternsLoader` downloads official patterns from Git, migrates legacy path structures, and maintains a [`unique_patterns.txt`](https://github.com/danielmiessler/fabric/blob/main/unique_patterns.txt) index
- **Protected templating**: The `applyVariables` function in [`internal/plugins/db/fsdb/patterns.go`](https://github.com/danielmiessler/fabric/blob/main/internal/plugins/db/fsdb/patterns.go) uses a sentinel token strategy to safely expand variables while preventing user input from being treated as template syntax
- **Dual interface support**: Both CLI tools and the Svelte-based web UI consume patterns through the same backend API, with the frontend using [`pattern-store.ts`](https://github.com/danielmiessler/fabric/blob/main/pattern-store.ts) to manage state
- **Extensibility**: Users can extend the system by placing custom patterns in a user-defined directory, which takes precedence over built-in templates

## Frequently Asked Questions

### How does Fabric handle custom user patterns?

Fabric checks the `CustomPatternsDir` configuration value before looking in the default `data/patterns/` directory. As implemented in [`internal/plugins/db/fsdb/patterns.go`](https://github.com/danielmiessler/fabric/blob/main/internal/plugins/db/fsdb/patterns.go) (lines 15-34), if a file exists at `<custom_dir>/<pattern_name>/system_prompt.txt`, that version takes precedence over the built-in pattern. This allows users to override official patterns or add private templates without modifying the core repository.

### What prevents user input from being processed as template variables?

The system uses a sentinel-based protection mechanism in the `applyVariables` method. Before expansion, the code replaces the `{{input}}` placeholder with `template.InputSentinel` (lines 94-112 in [`internal/plugins/db/fsdb/patterns.go`](https://github.com/danielmiessler/fabric/blob/main/internal/plugins/db/fsdb/patterns.go)). The `template.ApplyTemplate` engine processes all other `{{var}}` variables but ignores the sentinel value. After expansion completes, the sentinel is replaced with the actual user input, ensuring that any `{{...}}` sequences in the user's text are never interpreted as template directives.

### How does Fabric update its pattern library?

The `PatternsLoader` in [`internal/tools/patterns_loader.go`](https://github.com/danielmiessler/fabric/blob/main/internal/tools/patterns_loader.go) clones the official Fabric patterns repository on first launch, fetching only the `data/patterns/` subtree. It detects legacy installations through `tryPathMigration`, preserves user-created patterns via `PersistPatterns`, and creates a `loaded` marker file to prevent redundant downloads. The [`unique_patterns.txt`](https://github.com/danielmiessler/fabric/blob/main/unique_patterns.txt) index is regenerated during this process to enable fast pattern listing.

### Can patterns be used offline after initial setup?

Yes. Once the `PatternsLoader` completes the initial download and creates the `loaded` marker file, all pattern operations read from the local filesystem. The `PatternsEntity` methods in [`internal/plugins/db/fsdb/patterns.go`](https://github.com/danielmiessler/fabric/blob/main/internal/plugins/db/fsdb/patterns.go) serve patterns directly from disk without requiring network connectivity, making Fabric fully functional in offline environments after the first run.