Skip to main content

Preprocessing Rules & Auto-Match Conditions

Codex provides powerful tools to customize how metadata is searched and when auto-matching occurs. This guide covers title preprocessing rules, search query templates, auto-match conditions, and external ID tracking.

Overview

When Codex auto-matches series with metadata plugins, it follows this flow:

                        SCAN TIME
┌────────────────────────────────────────────────────────────────┐
│ Directory Name: "One Piece (Digital)" │
│ ↓ │
│ series.name = "One Piece (Digital)" (preserved for files) │
│ ↓ │
│ Library title_preprocessing_rules applied │
│ ↓ │
│ series_metadata.title = "One Piece" (cleaned for display) │
└────────────────────────────────────────────────────────────────┘

AUTO-MATCH TIME
┌────────────────────────────────────────────────────────────────┐
│ 1. Check library auto_match_conditions → skip if fails │
│ 2. Check plugin auto_match_conditions → skip if fails │
│ 3. If use_existing_external_id && external ID exists: │
│ → Call plugin.get(external_id) directly │
│ 4. Apply plugin search_query_template (Handlebars) │
│ 5. Apply plugin search_preprocessing_rules │
│ 6. Call plugin.search(query) │
│ 7. Save external ID for future use │
└────────────────────────────────────────────────────────────────┘

Title Preprocessing Rules

Preprocessing rules use regex patterns to clean up series titles. They're applied during library scanning to transform directory names into clean display titles.

Configuration Location

  • Library Settings → Preprocessing tab
  • Applied when new series are created during scan

Library Preprocessing Settings

Rule Structure

[
{
"pattern": "\\s*\\(Digital\\)$",
"replacement": "",
"description": "Remove (Digital) suffix",
"enabled": true
}
]
FieldTypeRequiredDescription
patternstringYesRegex pattern (Rust regex syntax)
replacementstringYesReplacement text (supports $1, $2 for capture groups)
descriptionstringNoHuman-readable description
enabledbooleanNoWhether rule is active (default: true)

Common Patterns

Remove "(Digital)" Suffix

{
"pattern": "\\s*\\(Digital\\)$",
"replacement": "",
"description": "Remove (Digital) suffix"
}

Before: One Piece (Digital)After: One Piece

Remove "[Author]" Prefix

{
"pattern": "^\\[[^\\]]+\\]\\s*",
"replacement": "",
"description": "Remove [Author] prefix"
}

Before: [Toriyama Akira] Dragon BallAfter: Dragon Ball

Normalize Hyphens to Spaces

{
"pattern": "-",
"replacement": " ",
"description": "Replace hyphens with spaces"
}

Before: My-Hero-AcademiaAfter: My Hero Academia

Remove Volume Information

{
"pattern": "\\s*[Vv]ol\\.?\\s*\\d+.*$",
"replacement": "",
"description": "Remove volume suffix"
}

Before: Batman Vol. 1 - The Court of OwlsAfter: Batman

Remove Year in Parentheses

{
"pattern": "\\s*\\(\\d{4}\\)$",
"replacement": "",
"description": "Remove year suffix"
}

Before: Spider-Man (2018)After: Spider-Man

Extract Series from "Series - Subtitle" Format

{
"pattern": "^([^-]+)\\s*-.*$",
"replacement": "$1",
"description": "Extract series name before hyphen"
}

Before: Batman - The Dark Knight ReturnsAfter: Batman

Rule Order

Rules are applied in order. Use this to chain transformations:

[
{
"pattern": "\\s*\\(Digital\\)$",
"replacement": "",
"description": "1. Remove (Digital)"
},
{
"pattern": "\\s*\\(\\d{4}\\)$",
"replacement": "",
"description": "2. Remove year"
},
{
"pattern": "\\s+",
"replacement": " ",
"description": "3. Normalize whitespace"
}
]

Search Query Templates

Plugins can use Handlebars templates to customize the search query sent to metadata providers.

Configuration Location

  • Admin SettingsPlugins → Edit plugin → Search Template

Plugin Search Template Settings

Template Syntax

Templates use Handlebars syntax with access to series metadata.

Simple Template

{{metadata.title}}

Uses the preprocessed series title as-is.

Include Year

{{metadata.title}} {{metadata.year}}

Result: One Piece 1997

Conditional Year

{{metadata.title}}{{#if metadata.year}} ({{metadata.year}}){{/if}}

Result with year: One Piece (1997) Result without year: One Piece

Available Fields

Both templates and conditions have access to the same unified series context. Fields use camelCase format.

FieldTypeDescription
metadata.titlestringSeries title (after preprocessing)
metadata.titleSortstringSort title
metadata.yearnumberPublication year
metadata.publisherstringPublisher name
metadata.languagestringLanguage code
metadata.statusstringPublication status
metadata.genresarrayGenre names
metadata.tagsarrayTag names
bookCountnumberNumber of books in series
externalIds.<source>objectExternal ID info for a source
customMetadata.<field>anyCustom metadata field

Available Helpers

Codex provides these Handlebars helpers (matching the frontend helpers):

HelperDescriptionExample
lowercaseConvert to lowercase{{lowercase metadata.title}}
uppercaseConvert to uppercase{{uppercase metadata.title}}
trimRemove leading/trailing whitespace{{trim value}}
truncateLimit string length{{truncate value 50 "..."}}
replaceReplace substring{{replace value "old" "new"}}
splitSplit string to array{{split value ","}}
joinJoin array with separator{{join array ", "}}
defaultProvide fallback value{{default value "Unknown"}}
urlencodeURL-encode string{{urlencode value}}
padStartPad string start{{padStart value 3 "0"}}
lengthGet string/array length{{length value}}
lookupDynamic property access{{lookup object key}}
existsCheck if value exists{{#exists value}}...{{/exists}}
ifEqualsConditional equality{{#ifEquals a b}}...{{/ifEquals}}
ifNotEqualsConditional inequality{{#ifNotEquals a b}}...{{/ifNotEquals}}
gtGreater than{{#gt a b}}...{{/gt}}
ltLess than{{#lt a b}}...{{/lt}}
andLogical AND{{#and a b}}...{{/and}}
orLogical OR{{#or a b}}...{{/or}}
mathArithmetic{{math a "+" b}}
includesArray contains{{#includes array value}}...{{/includes}}
capitalizeCapitalize first letter{{capitalize value}}

Template Examples

Provider-Specific Formatting

{{lowercase (replace metadata.title " " "-")}}

Result: one-piece

Include Publisher for Disambiguation

{{metadata.title}}{{#if metadata.publisher}} {{metadata.publisher}}{{/if}}

Result: Batman DC Comics

Search Preprocessing Rules

Additional regex rules can be applied to the search query after template rendering. These work the same as title preprocessing rules but are applied at search time.

Configuration Location

  • Admin SettingsPlugins → Edit plugin → Search Preprocessing

Plugin Search Preprocessing Settings

Use Cases

  • Remove characters that break specific provider searches
  • Transliterate or normalize text for better matching
  • Apply plugin-specific formatting requirements

Example

For a provider that doesn't handle colons well:

[
{
"pattern": ":",
"replacement": "",
"description": "Remove colons for provider compatibility"
}
]

Auto-Match Conditions

Control when auto-matching should occur based on series state. Conditions prevent unnecessary API calls and give you fine-grained control over the matching process.

Configuration Locations

  • Library Settings → Auto-match conditions (applies to all plugins)
  • Admin SettingsPlugins → Edit plugin → Conditions (applies to this plugin only)

Library Auto-Match Conditions

Plugin Auto-Match Conditions

Condition Structure

{
"mode": "all",
"rules": [
{
"field": "externalIds.plugin:mangabaka",
"operator": "is_null"
},
{
"field": "bookCount",
"operator": "gte",
"value": 1
}
]
}
Field Path Format

Condition field paths use camelCase format (e.g., bookCount, externalIds, metadata.titleSort). For backwards compatibility, snake_case paths are also supported (e.g., book_count, external_ids, metadata.title_sort), but camelCase is recommended as it matches the JSON output format.

Condition Modes

ModeBehavior
allAll rules must pass (AND logic)
anyAt least one rule must pass (OR logic)

Available Fields

The series context provides access to all series data for condition evaluation. Fields use camelCase format.

Basic Fields

FieldTypeDescription
bookCountnumberNumber of books in series

External ID Fields

FieldTypeDescription
externalIds.countnumberTotal number of external IDs
externalIds.<source>string/nullExternal ID for specific source

External ID sources use the format plugin:<name> (e.g., plugin:mangabaka).

Metadata Fields

FieldTypeDescription
metadata.titlestringSeries title
metadata.titleSortstringSort title
metadata.summarystringSeries summary/description
metadata.yearnumberPublication year
metadata.languagestringLanguage code
metadata.statusstringPublication status
metadata.publisherstringPublisher name
metadata.imprintstringPublisher imprint
metadata.ageRatingnumberAge rating
metadata.readingDirectionstringReading direction (ltr/rtl)
metadata.totalBookCountnumberExpected total book count
metadata.genresarrayGenre names
metadata.tagsarrayTag names

Lock Fields

FieldTypeDescription
metadata.titleLockbooleanTitle is locked
metadata.summaryLockbooleanSummary is locked
metadata.genresLockbooleanGenres are locked
metadata.tagsLockbooleanTags are locked
metadata.*LockbooleanAny metadata field lock

Custom Metadata Fields

FieldTypeDescription
customMetadata.<field>anyCustom metadata field
customMetadata.<nested>.<field>anyNested custom field

Available Operators

Null Checks

OperatorValueDescription
is_null-Field is null, empty, or missing
is_not_null-Field has a value

Equality

OperatorValueDescription
equalsanyExact match
not_equalsanyNot equal

Numeric Comparisons

OperatorValueDescription
gtnumberGreater than
gtenumberGreater than or equal
ltnumberLess than
ltenumberLess than or equal

String Comparisons

OperatorValueDescription
containsstringContains substring
not_containsstringDoes not contain substring
starts_withstringStarts with prefix
ends_withstringEnds with suffix
matchesstringMatches regex pattern

Array Operators

OperatorValueDescription
inarrayValue is in the list
not_inarrayValue is not in the list

Condition Examples

Only Match Unmatched Series

Skip series that already have an external ID for this plugin:

{
"mode": "all",
"rules": [
{
"field": "externalIds.plugin:mangabaka",
"operator": "is_null"
}
]
}

Require Minimum Books

Only match series with at least 3 books (reduces false matches for new series):

{
"mode": "all",
"rules": [
{
"field": "bookCount",
"operator": "gte",
"value": 3
}
]
}

Skip Locked Metadata

Don't auto-match series where the user has locked the title:

{
"mode": "all",
"rules": [
{
"field": "metadata.titleLock",
"operator": "equals",
"value": false
}
]
}

Match Specific Languages

Only match English or Japanese series:

{
"mode": "all",
"rules": [
{
"field": "metadata.language",
"operator": "in",
"value": ["en", "ja"]
}
]
}

Match by Genre

Only match series with specific genres:

{
"mode": "all",
"rules": [
{
"field": "metadata.genres",
"operator": "contains",
"value": "Action"
}
]
}

Complex Conditions

Match only if: (no external ID) AND (has books) AND (not locked):

{
"mode": "all",
"rules": [
{
"field": "externalIds.plugin:mangabaka",
"operator": "is_null"
},
{
"field": "bookCount",
"operator": "gte",
"value": 1
},
{
"field": "metadata.titleLock",
"operator": "not_equals",
"value": true
}
]
}

External ID Tracking

Codex tracks external IDs from metadata providers, enabling efficient re-matching and preventing duplicate searches.

How It Works

  1. When a plugin successfully matches a series, Codex stores the external ID
  2. The ID is associated with a source (e.g., plugin:mangabaka)
  3. Future scans can use this ID to fetch updates directly

Viewing External IDs

External IDs are displayed on the series detail page in the metadata section.

External ID Sources

Source PatternDescription
plugin:<name>From a metadata plugin
comicinfoFrom ComicInfo.xml in files
epubFrom EPUB metadata
manualManually added by user

Use Existing External ID

Enable this option on a plugin to skip searching when an external ID already exists:

Admin SettingsPlugins → Edit plugin → Use Existing External ID

When enabled:

  • If series has an external ID for this plugin, use it directly
  • Call plugin.get(external_id) instead of searching
  • Useful for refreshing metadata without re-matching

Best Practices

Preprocessing Rules

  1. Order matters: Place more specific rules before general ones
  2. Test patterns: Use a regex tester before deploying
  3. Document rules: Use the description field for clarity
  4. Disable, don't delete: Set enabled: false to temporarily disable rules

Conditions

  1. Start simple: Begin with "unmatched only" condition
  2. Add gradually: Add conditions as you identify edge cases
  3. Library vs Plugin: Use library conditions for broad rules, plugin conditions for specific cases
  4. Monitor results: Check scan logs to verify conditions work as expected

Templates

  1. Keep it simple: Most cases only need {{metadata.title}}
  2. Escape special chars: Use urlencode for URL-sensitive providers
  3. Test thoroughly: Different series may have different metadata available

Troubleshooting

Preprocessing Not Working

  1. Rules only apply to new series during scan
  2. Run a deep scan to re-process all series
  3. Check regex syntax (Rust regex, not JavaScript)

Conditions Always Failing

  1. Check field paths use the correct format (camelCase recommended, e.g., bookCount, metadata.titleSort)
  2. Verify the field has data (use is_not_null first)
  3. Check operator matches value type (numeric vs string)
  4. Both camelCase and snake_case paths are supported for backwards compatibility

Template Errors

  1. Check for unclosed blocks ({{#if}} needs {{/if}})
  2. Verify field names exist in available fields
  3. Check helper syntax matches documentation

Next Steps