PHPackages                             aelvan/preparse-field - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Templating &amp; Views](/categories/templating)
4. /
5. aelvan/preparse-field

Abandoned → [besteadfast/craft-preparse-field](/?search=besteadfast%2Fcraft-preparse-field)Craft-plugin[Templating &amp; Views](/categories/templating)

aelvan/preparse-field
=====================

A fieldtype that parses Twig when an element is saved and saves the result as plain text.

2.1.2(2y ago)10735.9k23[7 issues](https://github.com/aelvan/Preparse-Field-Craft/issues)[2 PRs](https://github.com/aelvan/Preparse-Field-Craft/pulls)1MITPHPPHP ^8.0.2CI passing

Since Dec 2Pushed 3w ago7 watchersCompare

[ Source](https://github.com/aelvan/Preparse-Field-Craft)[ Packagist](https://packagist.org/packages/aelvan/preparse-field)[ Fund](https://www.paypal.me/jalenconner)[ GitHub Sponsors](https://github.com/jalendport)[ RSS](/packages/aelvan-preparse-field/feed)WikiDiscussions develop Synced 1w ago

READMEChangelog (10)Dependencies (2)Versions (35)Used By (1)

[![Preparse](src/icon.svg)](src/icon.svg)

Preparse
========

[](#preparse)

Preparse is a Craft CMS plugin that renders a Twig template when an element is saved and stores the result as a real field value. Values are stored as text, numbers, booleans, or dates, so they sort, filter, and resolve in GraphQL as the type they actually are. When a template changes, values can be regenerated on demand from the console, an element index, or a control panel utility.

Features
--------

[](#features)

- **Typed storage.** A value is stored as text, a number, a boolean, or a date — so numbers sort 2, 10, 100 rather than "10", "100", "2", and dates compare as dates.
- **Twig you keep where you like.** Write an inline snippet with syntax highlighting in the control panel, or point the field at a template file in your site's template folder.
- **Searchable.** Preparse values feed Craft's search index through the standard search keywords toggle.
- **Deliberate regeneration.** Reparse from the console, from an element index, or from the Preparse utility — in the background, and without resaving every element.
- **Resilient.** A template that throws doesn't have to take the save down. Keep the previous value, drop in a fallback, or block the save — the error is recorded either way and shown in the control panel.
- **Typed queries.** Element queries, condition rules, index sorting, and GraphQL all understand the field's real type, including numeric and date ranges.
- **Multi-site aware.** Values follow the field's translation method, and templates render in each site's own language.
- **A migration path off generated fields.** One command converts a Craft generated field into a Preparse field, layouts and all.

Installation
------------

[](#installation)

### Requirements

[](#requirements)

This plugin requires **Craft CMS 5.7.0 or later** and **PHP 8.2 or later**.

> Using Craft 4? Use the [2.x](https://github.com/jalendport/craft-preparse/tree/craft4) line. Using Craft 3? Use the [1.x](https://github.com/jalendport/craft-preparse/tree/craft3) line.

### Plugin Store

[](#plugin-store)

Log into your control panel and click on "Plugin Store". Search for "Preparse", then click "Install".

### Composer

[](#composer)

Open your terminal, go to your Craft project, and run:

```
composer require jalendport/craft-preparse && php craft plugin/install preparse-field
```

Usage
-----

[](#usage)

Create a **Preparse** field in Settings → Fields, write the Twig you want rendered, and add the field to a field layout. From then on, every time an element in that layout is saved, the template is rendered and the result is stored on the element.

Preparse fields are read-only by design: the value comes from your template, never from an author.

### The template context

[](#the-template-context)

Templates are rendered as Craft **object templates**, in your site's template mode. Three ways to reach the element are available, and they all refer to the same thing:

```
{{ object.title }}   {# `object` — the same variable Craft's generated fields use #}
{{ element.title }}  {# `element` — an alias, if you prefer the explicit name #}
{title}              {# shorthand — expands to {{ object.title }} #}
```

Because the context matches Craft's generated fields, a generated field template pastes into a Preparse field unchanged.

Everything else you'd expect in a site template is available too — `craft.entries`, your Twig extensions, filters, and functions:

```
{{ object.title|upper }} — {{ object.author.fullName }}
{{ craft.entries().section('news').relatedTo(object).count() }} related stories
```

Templates render in the language of the **site the element belongs to**, not the language of whoever triggered the save. A `|date` filter on a German site produces German month names even when an English-speaking author hits Save.

### Value types

[](#value-types)

Every field has a value type, and whatever your template outputs is coerced into it. Output that can't be represented in the target type is stored as **no value**, rather than a misleading zero or epoch date.

TypeStoresCoercion**Text**`string`Trimmed. Empty output is stored as no value.**Number**`int` or `float`Only genuinely numeric output is accepted. With `decimals` at `0` the value is rounded to a whole number; above `0` it's rounded to that many places.**Boolean**`bool``false`, `no`, `off`, `n`, and `0` are false; empty output is no value; anything else is true.**Date**`DateTime`Anything Craft can parse as a date. Unparseable output is no value.#### Number output must be unformatted

[](#number-output-must-be-unformatted)

The number type accepts `1234.5`. It does **not** accept `1,234.50` — a thousands separator in one locale is a decimal separator in another, and guessing wrong is worse than storing nothing. Formatted output is stored as no value.

Output the raw number, and format it where you display it:

```
{# In the field template — no formatting #}
{{ object.price * 1.2 }}

{# In your front-end template — format on output #}
{{ entry.priceWithTax|number(2) }}
```

### Parse timing

[](#parse-timing)

TimingWhen it runs**After propagate** (default)After the element and everything it owns has been saved. Nested Matrix content is visible to the template, and the value is written straight to the element's content — no second save.**Inline**During the save itself, so the value is final before other plugins see it.Inline mode has one real limitation: on a **brand-new** element, nested elements haven't been written yet when it runs, so a template that reads a Matrix field will see nothing on the first save. Use inline mode for self-contained snippets, and after propagate for anything that reaches into nested content.

### When to parse

[](#when-to-parse)

ModeBehaviour**Always** (default)The template is rendered on every save.**Only when empty**The template is rendered while the value is empty, then left alone. Useful for a value that should be captured once and then stay put.Fields set to "Only when empty" are skipped by the reparse tools too, unless you pass `--force`.

### Error handling

[](#error-handling)

A template that throws produces an error, and the "On error" setting decides what happens to the value:

ModeBehaviour**Keep previous value** (default)The stored value is left as it was.**Use fallback value**The configured fallback is stored, coerced through the same rules as a rendered result.**Block the save**The save is rolled back and the exception surfaces.All three record the error and the time it happened. Errors show up in three places: on the element's edit page, next to the value in element index tables, and in the Preparse utility's error table. An error clears itself the moment the field parses cleanly again.

Note that "Block the save" throws rather than adding a validation error. By the time an after-propagate template runs, the element has already been written and propagated, so rolling the transaction back is the only way to genuinely stop the save.

### Displaying the value

[](#displaying-the-value)

DisplayBehaviour**Hidden** (default)The field doesn't appear on element edit pages.**Value**The stored value is shown, read-only and formatted for its type.The error banner appears on the edit page whenever an error is stored, regardless of this setting.

### Querying, sorting, and filtering

[](#querying-sorting-and-filtering)

Preparse fields behave like any other typed field in an element query:

```
{# Text #}
{% set entries = craft.entries().mySummary('*deadline*').all() %}

{# Number — ranges work, because the value is a real number #}
{% set entries = craft.entries().wordCount('>= 500').all() %}
{% set entries = craft.entries().wordCount(['and', '>= 500', '< 2000']).all() %}

{# Date #}
{% set entries = craft.entries().lastReviewed('>= ' ~ now|date_modify('-1 month')|atom).all() %}

{# Boolean #}
{% set entries = craft.entries().isFeatured(true).all() %}

{# Sorting orders by the real type #}
{% set entries = craft.entries().orderBy('wordCount desc').all() %}
```

Element index sort options and condition rules are wired up automatically, with numeric and date range rules where the type supports them.

### GraphQL

[](#graphql)

Each value type resolves as its matching GraphQL type — `String`, `Number`, `Boolean`, or `DateTime`:

```
{
  entries(section: "blog", wordCount: ">= 500") {
    title
    wordCount
    lastReviewed
  }
}
```

### Reparsing

[](#reparsing)

Values are generated when an element is saved. Change a template and existing elements keep what they last stored, until you reparse them.

Reparsing renders and writes the new value directly, without running a full save — so reparsing a large site doesn't fire a save lifecycle for every element and wake every other plugin on the install.

#### Console command

[](#console-command)

```
# Everything, across every element type that has a preparse field
php craft preparse-field/reparse

# One field, one section, in the background
php craft preparse-field/reparse --fields=wordCount --section=blog --queue

# One site's values of a translatable field
php craft preparse-field/reparse --site=de

# Include fields set to only parse when empty
php craft preparse-field/reparse --force

# Run real resaves, so other plugins react to the new values
php craft preparse-field/reparse --full-save
```

OptionDescription`--fields`Comma-separated preparse field handles. Defaults to all of them.`--section`Comma-separated section handles. Entries only.`--site`Comma-separated site handles. Only these sites' values are written. Defaults to every site.`--element-type`A specific element type class. Defaults to every type that has a preparse field.`--queue`Queue the work instead of running it now.`--force`Parse fields set to only parse when empty.`--full-save`Run real resaves instead of writing content directly. Much slower; use it when another plugin needs to react.`--batch-size`Elements per batch when queueing. Defaults to `100`.#### Bulk action

[](#bulk-action)

Element indexes get a **Reparse** action wherever a preparse field is in play. Small selections run immediately; larger ones are queued.

#### Utility

[](#utility)

Utilities → **Preparse** has a scope picker for queueing a reparse across fields, sections, and sites, and a table of every stored parse error with a link to the element it belongs to. The utility's badge shows how many errors are currently stored.

Configuration
-------------

[](#configuration)

Preparse has no plugin-level settings. Everything is configured per field, in Settings → Fields.

SettingValuesDefaultDescriptionValue type`text`, `number`, `boolean`, `date``text`The type the rendered result is stored as.Decimals`int``0`Number type only. `0` stores whole numbers.Template mode`inline`, `file``inline`Whether the Twig lives in the field settings or in a template file.TemplateTwig, or a template path*(empty)*The snippet, or the path to a template in your site's template folder.Parse timing`afterPropagate`, `inline``afterPropagate`When the template renders.When to parse`always`, `whenEmpty``always`Whether to re-render on every save.On error`keepPrevious`, `fallback`, `blockSave``keepPrevious`What happens when the template throws.Fallback value`string`*(empty)*Stored when a render fails and On error is `fallback`.Parse on move`bool``false`Re-render when the element moves within a structure. Turn this on for templates that use `parent`, `level`, or anything else positional.Display`hidden`, `value``hidden`Whether the value appears on element edit pages.Search keywords are controlled by Craft's standard search keywords setting. Date values are deliberately excluded from search keywords — a formatted date is noise in a search index.

### Validating and testing a template

[](#validating-and-testing-a-template)

Field settings are validated on save: the template is tokenized and parsed without being rendered, and a syntax error is reported with its line number. A missing template file is reported with the path that was resolved.

The **Test** button renders the current settings against a sample element and shows the result — including the coercion, so a template returning `1,234.50` on a number field visibly produces no value. Pick an element, or leave the picker empty to test against the most recent entry.

Preparse vs. generated fields
-----------------------------

[](#preparse-vs-generated-fields)

Craft 5.8 added generated fields, which cover the same basic idea: render a template on save, store the result. Preparse is a superset.

Generated fieldsPreparse**Storage type**String onlyText, number, boolean, or date**Sorting**Lexicographic — 10, 100, 2Typed — 2, 10, 100**Query params**String comparisonTyped, including numeric and date ranges**Condition rules**Text onlyText, number range, date range, boolean**GraphQL**`String``String`, `Number`, `Boolean`, `DateTime`**Search index**Not searchableStandard search keywords toggle**Template source**Inline onlyInline snippet or a template file**Template errors**The save failsKeep previous, use a fallback, or block the save — with the error recorded and surfaced**Regeneration**Resave the elementsConsole command, bulk action, utility, or structure moves**Parse once**Not available"Only when empty"**Timing**After propagateAfter propagate or inlineIf you don't need anything in the right-hand column, generated fields are built in and perfectly good. Preparse exists for when you do.

### Converting a generated field

[](#converting-a-generated-field)

`preparse-field/convert` creates a matching Preparse field, swaps it into every field layout that used the generated field, removes the generated field configuration, and queues a reparse of the affected elements.

Inspect the change first:

```
php craft preparse-field/convert myGeneratedField --dry-run
```

That prints every field layout the generated field appears in, and makes no changes. When it looks right, run it for real:

```
php craft preparse-field/convert myGeneratedField
```

The command lists the affected layouts again and asks for confirmation before touching anything. Because the template context is the same, the template carries over unchanged. The new field starts as a Text field, so set its value type afterwards if it should be a number, boolean, or date, then reparse.

Upgrading from 3.x
------------------

[](#upgrading-from-3x)

Preparse 4 is a ground-up rebuild. Install it, let the migration run, and read the two breaking changes below.

Upgrading directly requires **Preparse 1.5.1 or later**. On anything older, update to a recent 3.x release first.

### Your stored values survive

[](#your-stored-values-survive)

The migration doesn't rewrite your content. Existing values keep resolving in templates, queries, and sorts exactly as before, and are rewritten into the 4.0 storage format the first time each element is parsed — on its next save, or whenever you reparse it.

### Settings are mapped for you

[](#settings-are-mapped-for-you)

3.x4.0`fieldTwig`Template, in inline mode`columnType` TEXT / MEDIUMTEXTValue type: Text`columnType` INTEGERValue type: Number, decimals `0``columnType` DECIMAL / FLOATValue type: Number, decimals carried over`columnType` DATETIMEValue type: Date`parseBeforeSave` onParse timing: Inline`parseBeforeSave` offParse timing: After propagate`parseOnMove`Parse on move (unchanged)`displayType` hiddenDisplay: Hidden`displayType` textinput / textareaDisplay: Value`allowSelect`, `textareaRows`, `showField`Removed### Breaking: the element alias is gone

[](#breaking-the-element-alias-is-gone)

3.x exposed the element under its reference handle — `entry`, `asset`, `category`, and so on. That alias no longer exists. Use `object`, `element`, or shorthand:

```
- {{ entry.title }}
+ {{ object.title }}

- {% if entry.author %}
+ {% if object.author %}
```

A find/replace across your preparse templates for `entry.` → `object.` (and the equivalent for whichever element types you use) is usually the whole job. The **Test** button on the field settings will tell you immediately if you missed one.

### Breaking: the old namespaces are gone

[](#breaking-the-old-namespaces-are-gone)

The `aelvan\preparsefield\` and `besteadfast\preparsefield\` class aliases have been removed. The migration rewrites stale references in the database and project config, but if your own modules reference those classes directly, update them to `jalendport\preparse\`.

FAQ
---

[](#faq)

### Why does my template see the wrong Matrix or Neo blocks?

[](#why-does-my-template-see-the-wrong-matrix-or-neo-blocks)

Element queries in Craft are **mutable objects**. Calling `.limit()`, `.status()`, or similar on `object.myMatrixField` changes that query for everything downstream — including code that runs after your template. Because preparse templates run in the middle of a save, a mutated query can produce wrong results elsewhere in the same request.

Clone the query, or resolve it immediately:

```
{# Fine — resolved straight away #}
{% set blocks = object.myMatrixField.all() %}

{# Fine — cloned before being narrowed #}
{% set featured = clone(object.myMatrixField).limit(3).all() %}

{# Risky — mutates the query the element itself is holding #}
{% set featured = object.myMatrixField.limit(3).all() %}
```

This isn't specific to Preparse — it's how element queries work everywhere — but a preparse template is an unusually good place to get caught by it.

### Why doesn't `{% cache %}` do anything in my template?

[](#why-doesnt--cache--do-anything-in-my-template)

Craft's non-global template caches are keyed by the **request path**. A preparse template renders during a save, so its cache entry is written under a control panel path and can never be hit from the front end. On console and queue requests, non-global template caching is switched off entirely.

Use `{% cache global %}`, which drops the path from the key and works in every context:

```
{% cache global %}
    {# expensive work #}
{% endcache %}
```

See [\#83](https://github.com/jalendport/craft-preparse/issues/83) for the background.

### Can I edit a preparse value by hand?

[](#can-i-edit-a-preparse-value-by-hand)

No. The value comes from the template, and any manual edit would be overwritten on the next save. Set Display to "Value" if you want authors to see it.

### Does it parse drafts and revisions?

[](#does-it-parse-drafts-and-revisions)

Drafts and provisional drafts are parsed like any other save. Revisions are never parsed — they're a record of what an element used to be, and re-rendering them would rewrite history.

Support
-------

[](#support)

Found a bug or need help? Open an [issue](https://github.com/jalendport/craft-preparse/issues).

---

Made by [Jalen Davenport](https://jalendport.com)

###  Health Score

56

—

FairBetter than 97% of packages

Maintenance60

Regular maintenance activity

Popularity42

Moderate usage in the ecosystem

Community26

Small or concentrated contributor base

Maturity80

Battle-tested with a long release history

 Bus Factor2

2 contributors hold 50%+ of commits

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~86 days

Recently: every ~1 days

Total

29

Last Release

763d ago

Major Versions

1.4.0 → 2.0.02022-08-08

1.4.1 → 2.0.12022-12-02

1.5.0 → 2.1.12024-07-12

1.5.1 → 3.0.0-alpha.12024-07-12

PHP version history (2 changes)2.0.0PHP ^8.0.2

3.0.0-alpha.1PHP ^8.2.0

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/2675644?v=4)[André Elvan](/maintainers/aelvan)[@aelvan](https://github.com/aelvan)

---

Top Contributors

[![jalendport](https://avatars.githubusercontent.com/u/6537823?v=4)](https://github.com/jalendport "jalendport (87 commits)")[![aelvan](https://avatars.githubusercontent.com/u/2675644?v=4)](https://github.com/aelvan "aelvan (59 commits)")[![carlcs](https://avatars.githubusercontent.com/u/7516543?v=4)](https://github.com/carlcs "carlcs (19 commits)")[![mmikkel](https://avatars.githubusercontent.com/u/298510?v=4)](https://github.com/mmikkel "mmikkel (5 commits)")[![khalwat](https://avatars.githubusercontent.com/u/7570798?v=4)](https://github.com/khalwat "khalwat (4 commits)")[![brandonkelly](https://avatars.githubusercontent.com/u/47792?v=4)](https://github.com/brandonkelly "brandonkelly (4 commits)")[![marionnewlevant](https://avatars.githubusercontent.com/u/695477?v=4)](https://github.com/marionnewlevant "marionnewlevant (3 commits)")[![bossanova808](https://avatars.githubusercontent.com/u/731309?v=4)](https://github.com/bossanova808 "bossanova808 (1 commits)")[![mildlygeeky](https://avatars.githubusercontent.com/u/273266?v=4)](https://github.com/mildlygeeky "mildlygeeky (1 commits)")[![benface](https://avatars.githubusercontent.com/u/1059139?v=4)](https://github.com/benface "benface (1 commits)")[![monachilada](https://avatars.githubusercontent.com/u/613183?v=4)](https://github.com/monachilada "monachilada (1 commits)")[![swixpop](https://avatars.githubusercontent.com/u/7113101?v=4)](https://github.com/swixpop "swixpop (1 commits)")

---

Tags

craftcmscraftcms-plugintwigcmsCraftcraftcmscraft-pluginfieldtypepreparse

### Embed Badge

![Health badge](/badges/aelvan-preparse-field/health.svg)

```
[![Health](https://phpackages.com/badges/aelvan-preparse-field/health.svg)](https://phpackages.com/packages/aelvan-preparse-field)
```

###  Alternatives

[jalendport/craft-preparse

A field type that renders a Twig template when an element is saved and stores the typed result.

1078.8k](/packages/jalendport-craft-preparse)[wbrowar/guide

A CMS Guide for Craft CMS.

6154.7k1](/packages/wbrowar-guide)[nystudio107/craft-seomatic

SEOmatic facilitates modern SEO best practices &amp; implementation for Craft CMS 5. It is a turnkey SEO system that is comprehensive, powerful, and flexible.

1741.5M73](/packages/nystudio107-craft-seomatic)[verbb/formie

The most user-friendly forms plugin for Craft.

101400.6k78](/packages/verbb-formie)[verbb/hyper

A user-friendly links field for Craft.

24153.5k14](/packages/verbb-hyper)[verbb/vizy

A flexible visual editor field for Craft.

4251.5k1](/packages/verbb-vizy)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
