Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Markdown Field Type Laravel Package

anomaly/markdown-field_type

Anomaly Markdown Field Type provides a Markdown-powered field for Laravel Streams, enabling editors to write Markdown content in entries and render it cleanly. It extends Streams field types with a familiar, lightweight authoring experience.

View on GitHub
Deep Wiki
Context7

Getting Started

Pull the field type into an existing Streams Platform / PyroCMS install and it self-registers — there is no service provider to wire up manually, the addon boots from composer.json metadata.

composer require anomaly/markdown-field_type
php artisan addon:install anomaly.markdown_field_type
# if the editor assets don't appear in the CP, force a publish:
php artisan addon:publish anomaly.markdown_field_type

Where to look first, in order:

  1. src/MarkdownFieldType.php — the field type class. It almost certainly extends Anomaly\TextFieldType\TextFieldType, which tells you the storage column, validation rules, and hydration behaviour are inherited from the text field type. Only the getView()/getWrapper() methods and asset declarations differ.
  2. resources/views/ — the admin input.twig (the <textarea> the JS editor attaches to) and the wrapper.twig (label, instructions, error output).
  3. resources/js/ and resources/scss/ — the editor binding (likely EasyMDE/SimpleMDE). This is where the save behaviour lives, so read it before you debug "content disappears on save."
  4. config/config.php — editor options (toolbar, autofocus, spellchecker, preview rendering) surfaced to the view.

First use case: add a body markdown field to a posts stream via the control panel's field editor (choose Markdown as the type), then render it in a page or module view. Out of the box the field type stores raw markdown; you are responsible for converting it to HTML:

{{ entry.body|raw }}   {# WRONG unless something already parsed it #}

so the realistic day-one workflow is: install, assign the field, then add a parsed accessor or presenter (see below) before you display anything.

Implementation Patterns

Storage vs. rendering is deliberately split. Treat the field type as an input widget only. The database column holds the markdown source; conversion is a presentation concern.

The most common pattern is a generated presenter on the entry model:

use League\CommonMark\CommonMarkConverter;

class PostPresenter extends EntryPresenter
{
    public function body()
    {
        return (new CommonMarkConverter([
            'html_input' => 'escape',
            'allow_unsafe_links' => false,
        ]))->convert($this->object->body)->getContent();
    }
}

Then {{ entry.body|raw }} is safe-ish because the converter is doing the escaping. Cache the converter instance (or resolve it from the container) rather than constructing one per call if you render lists.

Assigning via migration/seeder is the pattern that survives redeploys:

$stream->createField('body', 'markdown')->setConfig([...]);
// or in a seeder: $fields->add(['slug' => 'body', 'type' => 'anomaly.field_type.markdown']);

Two-field pattern for search. Because the column is markdown source, full-text and LIKE queries will match syntax characters and produce noisy results. A frequently used workflow is to keep the markdown field for authoring and mirror a stripped plain-text version into a textarea field (or an indexed column) on save — a form/observer hook on the entry model. It costs one extra column and makes search and excerpt generation trivial.

Integration with the WYSIWYG ecosystem. Since this is a textarea superset, it composes nicely with anything that reads the entry as a string: RSS/Atom feed builders, Str::words() excerpts, meta-description generation, and email templates. It does not compose with the WYSIWYG field's HTML-specific tooling (image pickers, file manager bindings, shortcode expansion) — markdown has no equivalent, so plan for authors pasting image URLs by hand.

Editor-level customisation is done through the field's config (persisted per-field in the control panel, or via setConfig()), not by editing the addon — override the addon view in your own namespace if you need a toolbar change, so composer update doesn't eat it.

Gotchas and Tips

  • Raw markdown in your HTML is the default failure mode. {{ entry.body }} emits source text. If you see ## Heading on the page, you haven't added a converter, not a field-type bug.
  • XSS. Parsedown/CommonMark pass inline HTML through by default. Since this field type does no sanitisation of its own (its job ends at storing the string), a field that is author-editable by non-admins is an injection vector. Set html_input => 'escape' or run HTMLPurifier after conversion.
  • Column type. Inherited from the text field type, so check whether your migration gave the field a VARCHAR(255)-ish column. Markdown posts blow past that and you get silent truncation. Use TEXT/LONGTEXT — in the CP field editor, check "Max Length" is empty.
  • Editor assets not loading. Symptom: a plain <textarea> with no toolbar. Fix in this order: php artisan addon:publish, then php artisan asset:clear / view:clear, then check browser console for a 404 on the published path. If you're behind a path prefix or CDN, the published asset URLs in the field view may need overriding.
  • Content blanked on submit. Some markdown editors sync their value to the hidden textarea on change; if a form is submitted programmatically or the field is inside a hidden tab/repeater, the sync may not fire and the server receives an empty string. Force a sync before submit (editor.codemirror.save() in EasyMDE terms) or read the wrapper in the addon view to see how it binds.
  • Repeater / grid fields. A field type that initialises a JS editor on $(document).ready won't bind to rows cloned by the repeater addon. You typically need to re-init on the repeater's row-added event — this is a common reason to copy the addon's JS into your own theme.
  • Very low maintenance signal. 3 stars and 0 dependents means you are essentially the only consumer. Treat it as a readable reference implementation of a field type rather than a supported dependency: vendor-fork it if you need fixes, and pin the version. For comparison reading, anomaly/textarea-field_type (the parent) and anomaly/wysiwyg-field_type are the authoritative patterns for views, config, and asset registration.
  • Extension points. If you need behaviour beyond the editor — e.g. rendering markdown inside the CP preview pane — the cleanest hook is a custom field type of your own extending this one and overriding getView(), rather than editing the vendor addon.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource