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

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 file that holds the actual prompt template.

The PatternsEntity struct in 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:

// 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 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, a sorted list of all pattern names

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

// 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 (lines 29-40) delegates to applyVariables:

// 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 module in 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:

// 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:

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:

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 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 index
  • Protected templating: The applyVariables function in 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 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 (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). 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 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 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 serve patterns directly from disk without requiring network connectivity, making Fabric fully functional in offline environments after the first run.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →