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

Laravel Shortcodes Laravel Package

vedmant/laravel-shortcodes

Add WordPress-style shortcodes to your Laravel app. Register custom tags with attributes, parse them in Blade views or any content, and render dynamic output cleanly. Lightweight and easy to extend, ideal for CMS-like content and reusable snippets.

Deep Wiki
Context7

Getting Started

Because this package is small and the repository link is not available in the metadata, verify class names and method signatures against vendor/vedmant/laravel-shortcodes/src before copying snippets. The concepts below are the day-to-day patterns you will use regardless of exact naming.

Install and confirm discovery

composer require vedmant/laravel-shortcodes

Laravel should auto-discover the service provider. If the package ships a config file, publish it and inspect the defaults:

php artisan vendor:publish --provider="Vedmant\LaravelShortcodes\ShortcodesServiceProvider"

If that provider class does not exist, find the real one in vendor/vedmant/laravel-shortcodes/composer.json under extra.laravel.providers.

Where to look first

  1. vendor/vedmant/laravel-shortcodes/README.md — registration API and callback signature.
  2. vendor/vedmant/laravel-shortcodes/src/ — the parser and facade. Confirm whether the callback receives ($attributes, $content) or ($attributes, $content, $tag).
  3. The package service provider — this is where the facade alias, singleton binding, and any config merge are defined.
  4. Your own App\Providers\AppServiceProvider::boot() — the simplest place to register your first shortcode.

First use case

The natural use case is editor-authored content stored in the database: CMS pages, blog bodies, product descriptions, email templates. Editors write WordPress-style tags and your application renders them safely:

{!! Shortcode::compile($page->body) !!}

Start with one shortcode and one rendering path, confirm the output in php artisan tinker, then expand.

use Vedmant\LaravelShortcodes\Facades\Shortcode;

public function boot(): void
{
    Shortcode::register('button', function (array $attrs, ?string $content = null) {
        $url   = e($attrs['url'] ?? '#');
        $class = e($attrs['class'] ?? 'btn btn-primary');
        $label = e($content ?: ($attrs['label'] ?? 'Read more'));

        return "<a href=\"{$url}\" class=\"{$class}\">{$label}</a>";
    });
}
{!! Shortcode::compile('[button url="https://example.com" class="btn btn-lg"]Get started[/button]') !!}

Implementation Patterns

Use a dedicated service provider

Do not scatter Shortcode::register() calls across controllers. Create App\Providers\ShortcodeServiceProvider and register every shortcode in one place. This becomes your catalogue: it is easy to audit, document, and test.

class ShortcodeServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Shortcode::register('button', [ButtonShortcode::class, 'render']);
        Shortcode::register('alert',  [AlertShortcode::class, 'render']);
        Shortcode::register('embed',  [EmbedShortcode::class, 'render']);
    }
}

Render shortcodes through Blade views

Inline HTML strings become unmaintainable quickly. Delegate to a view and keep the callback thin:

Shortcode::register('alert', function (array $attrs, ?string $content = null) {
    return view('shortcodes.alert', [
        'type'    => in_array($attrs['type'] ?? 'info', ['info', 'warning', 'danger'], true)
            ? $attrs['type']
            : 'info',
        'slot'    => $content ?? '',
    ])->render();
});
{{-- resources/views/shortcodes/alert.blade.php --}}
<div class="alert alert-{{ $type }}">
    {!! $slot !!}
</div>

This gives you Blade escaping, component reuse, and normal view caching for free.

Normalize and validate attributes in one place

Attributes arrive as raw editor input. Treat them like request input:

private function attributes(array $attrs): array
{
    return [
        'url'    => filter_var($attrs['url'] ?? '', FILTER_VALIDATE_URL) ?: '#',
        'target' => ($attrs['target'] ?? '_self') === '_blank' ? '_blank' : '_self',
        'class'  => preg_replace('/[^a-zA-Z0-9 _-]/', '', $attrs['class'] ?? ''),
    ];
}

Do not rely on the parser to validate anything. It parses; your callback authorizes and escapes.

Distinguish self-closing and enclosed shortcodes

WordPress-style syntax supports both:

[embed src="https://youtube.com/watch?v=abc"]
[button url="/pricing"]See pricing[/button]

Decide explicitly whether $content is null, '', or the raw inner string for self-closing tags, and test both forms. If your callback assumes content exists, self-closing usage will produce empty output or errors.

Add a model accessor or presenter

Keep raw content in the database and render on output. A model accessor is convenient for read-only display:

public function getRenderedBodyAttribute(): string
{
    return Shortcode::compile($this->body);
}

Do not persist the compiled result back into the same column. Keep raw content editable and compiled content derived.

Expose a Blade directive or helper

If shortcode rendering is common in views, hide the facade behind one entry point:

Blade::directive('shortcodes', function ($expression) {
    return "<?php echo app('shortcode')->compile($expression); ?>";
});
@shortcodes($page->body)

This makes it trivial to swap the implementation later without touching every view.

Cache compiled content

Shortcode compilation is regex and string work on every request. If content changes rarely, cache the rendered output keyed by a content hash or model version:

public function getRenderedBodyAttribute(): string
{
    return Cache::remember(
        "page:{$this->id}:body:".md5($this->body),
        now()->addDay(),
        fn () => Shortcode::compile($this->body)
    );
}

Invalidate on save, or include the content hash in the key as above.

Document the supported tags for editors

Maintain a short reference — tag name, attributes, whether it is self-closing, and an example. Editors will paste shortcodes into a WYSIWYG field; if the catalogue is not documented, you will get malformed tags and support requests. Consider restricting the editor’s shortcode palette to a known list.

Test the rendering path directly

Shortcode rendering is pure enough to test without HTTP:

public function test_button_shortcode_renders_escaped_attributes(): void
{
    $html = Shortcode::compile('[button url="/x" class="a&quot; onclick=&quot;alert(1)"]Go[/button]');

    $this->assertStringContainsString('href="/x"', $html);
    $this->assertStringNotContainsString('onclick=', $html);
}

Add cases for unknown tags, missing attributes, self-closing tags, nested tags, and encoded quotes.

Gotchas and Tips

Shortcode output is a trust boundary. Shortcode::compile() returns HTML that you will typically echo with {!! !!}. Every callback must escape attribute values and decide deliberately whether enclosed content is trusted HTML or untrusted text. If editors are not fully trusted, sanitize the compiled result with a library such as HTML Purifier, or escape content inside callbacks.

Compile once. Running compile() on already-compiled output can re-interpret generated HTML that happens to contain square brackets, double-render nested structures, or corrupt attributes. Store raw content, compile at the edge, and avoid chaining compile calls in accessors, Blade, and controllers for the same value.

Unknown shortcodes are a product decision. Confirm how the package handles unregistered tags: strip them, leave them verbatim, or throw. Leaving them verbatim is often best for authoring because editors see their mistake; stripping is better for public output. Test [unknown]text[/unknown] explicitly and make the behavior intentional.

Attribute parsing has edge cases. WordPress-style syntax is not HTML or JSON. Quotes inside values, boolean attributes ([video autoplay]), unquoted values, and multiline attributes often behave differently than expected. Prefer quoted attributes in documentation and test each attribute type you expose.

Nested shortcodes are the usual failure point. Regex-based parsers frequently fail on same-name nesting such as [box][box]inner[/box][/box]. Verify whether nested compilation is recursive. If it is not, either flatten your markup or compile inner content in the callback before wrapping it.

Large or malformed content can be expensive. Regex parsing over multi-megabyte CMS bodies, especially unterminated tags, can be slow. Cap body length, cache compiled output, and avoid compiling inside loops over collections.

Laravel version support is a real risk. The last release is January 2023, which predates Laravel 11. Check composer.json constraints and test auto-discovery, facade aliases, and config caching on your target Laravel version. If constraints block installation, use --ignore-platform-reqs only as a temporary diagnostic, not a production strategy.

Small package means you own the risk. With 27 stars and no recent release, audit the parser source before adopting it. Wrap it behind your own ShortcodeRenderer service so you can replace it with thunderer/shortcode or a custom parser without changing views, models, or tests.

Registration order and conflicts. If two providers register the same tag, the later registration may silently overwrite the earlier one. Keep the catalogue in one provider and add a test that asserts every expected tag is registered.

Config caching. If the package merges a config file, run php artisan config:cache in deployment and confirm that registration does not depend on runtime-only values. Shortcodes registered conditionally from request data will behave differently under config caching.

Debugging workflow. Isolate failures in Tinker before touching Blade:

dump(Shortcode::compile('[button url="/x"]Go[/button]'));

If output is empty, check that the service provider booted, the tag name matches exactly (case sensitivity varies), and the callback signature matches what the parser passes. If output is partially rendered, the parser is likely splitting on an attribute value containing a bracket or quote.

Extension points to look for. Inspect the source for a parser instance bound in the container, a config-driven tag map, or stripper/compiler classes. If the package exposes the underlying parser, prefer extending it over reimplementing parsing in your callbacks. If it does not, keep your callbacks small and put all custom logic in your own classes.

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.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator