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

Shiki Php Laravel Package

spatie/shiki-php

Bring Shiki's beautiful syntax highlighting to PHP. A simple static call highlights code using the same language engine as popular code editors, with 100+ languages and themes plus bundled Antlers and Blade support.

View on GitHub
Deep Wiki
Context7

Getting Started

Two installs are required — the PHP side and the JavaScript side:

composer require spatie/shiki-php
npm install shiki        # or: yarn add shiki

Node 20+ must be available on the machine that renders the HTML. This is the single most common setup failure, so verify it before writing any code:

node --version

The first use case is almost always rendering fenced code blocks in a blog, docs site, or changelog page. The smallest working call:

use Spatie\ShikiPhp\Shiki;

$html = Shiki::highlight(
    code: $code,
    language: 'php',
    theme: 'github-light',
);

You get back a self-contained <pre class="shiki">…</pre> chunk with inline colors — no CSS file needed to look decent, though you'll want CSS for the line-annotation classes.

Where to look first:

  1. Spatie\ShikiPhp\Shiki — the whole public surface is a handful of static/instance methods (highlight, getAvailableLanguages, languageIsAvailable, getAvailableThemes, themeIsAvailable).
  2. The bundled Node renderer script shipped inside the package — this is the extension point if you need to tweak Shiki options that aren't exposed in PHP.
  3. If you're in Laravel and rendering Markdown, stop and look at spatie/laravel-markdown first. It wraps this package and wires it into the Markdown pipeline with zero glue code. Similarly, spatie/commonmark-shiki-highlighter is the drop-in for league/commonmark outside Laravel. Only call Shiki::highlight() directly when you're highlighting ad-hoc strings.

Implementation Patterns

Wrap it in a small service or Blade component. You rarely want Shiki::highlight() scattered through views. A single CodeHighlighter class (or a <x-code> component) gives you one place to pin the theme, add a fallback, and apply caching.

class CodeHighlighter
{
    public function render(string $code, string $language = 'php'): string
    {
        return Cache::remember(
            'shiki:' . md5($code . $language . config('shiki.theme')),
            now()->addDay(),
            fn () => Shiki::highlight(
                code: $code,
                language: $language,
                theme: config('shiki.theme', 'github-light'),
            ),
        );
    }
}

Cache aggressively. Every highlight() call spawns a Node process. On a page with twenty code blocks that's twenty process spawns. Cache keyed on a hash of code + language + theme + line annotations. For static docs, go further and pre-render at build/deploy time into the database or a flat file.

Line annotations map cleanly onto Markdown fence metadata. highlightLines, addLines, deleteLines, and focusLines all accept arrays of ints and range strings:

Shiki::highlight(
    code: $code,
    language: 'php',
    highlightLines: [1, '4-6'],
    addLines: ['8-10'],
);

So a fence like ```php{1,4-6} can be parsed into highlightLines in your Markdown extension. The output then carries the corresponding classes on each .line — style them yourself:

.shiki .line.highlighted { background: rgba(255, 220, 0, .15); }
.shiki .line.added       { background: rgba(0, 200, 0, .12); }
.shiki .line.deleted     { background: rgba(255, 0, 0, .12); }
.shiki .line.focused     { outline: 1px solid rgba(0, 0, 0, .15); }

Custom themes: pass an absolute path. Shiki accepts any VSCode theme JSON. __DIR__ . '/../resources/themes/brand.json' works; relative paths do not reliably resolve because the Node process has its own working directory. This is the clean way to make code blocks match your design system instead of shipping one of Shiki's stock themes.

Dual themes for light/dark. Since 2.2.0 you can pass two themes and get CSS-variable-driven output — the practical approach for a site with a dark-mode toggle, since it avoids re-rendering on theme switch:

Shiki::highlight(
    code: $code,
    language: 'php',
    theme: ['light' => 'github-light', 'dark' => 'github-dark'],
);

Then define the --shiki-light / --shiki-dark variables in your CSS and swap them under .dark. Note the dual-theme path had fix releases in 2.2.1, 2.2.2, and 2.4.0 — pin a recent version if you rely on it.

Validate before highlighting user input. Languages come from user-chosen dropdowns in CMS/admin contexts. Since 2.1.0 the availability checks are back after being removed in 2.0.0:

$shiki = new Shiki();

if (! $shiki->languageIsAvailable($language)) {
    $language = 'text';
}

getAvailableLanguages() and getAvailableThemes() return arrays you can feed straight into a select element.

Blade and Antlers are bundled. If you're highlighting Blade snippets in docs (very likely for a Laravel audience), use language: 'blade' directly — no extra install. Embedded languages (Blade inside HTML, PHP inside Blade) are handled since 1.1.3.

Testing. Don't hit Node in your test suite more than necessary. Either cache-mock your wrapper, or assert only on stable markers (<pre class="shiki", the presence of .line spans) rather than exact inline colors, which shift between Shiki versions.

Gotchas and Tips

Node isn't found in NVM setups. The package looks for node in /usr/local/bin and /opt/homebrew/bin. If you develop under NVM, highlight() will fail with a confusing spawn error. Symlink it:

sudo ln -s /home/you/.nvm/versions/node/v20.11.1/bin/node /usr/local/bin/node

This is the number-one "works on my machine" trap — and it bites in Docker/CI too. Your production image needs Node installed and on the PATH, even though the app is PHP. If you can't put Node in prod, pre-render code blocks at deploy time or fall back to a plain <pre><code>.

Missing shiki npm package. The README is explicit: without it, the <pre> element won't be present in the output. You'll get a bare fragment or an empty string rather than an obvious exception, which is a nasty silent failure. Verify the package exists in node_modules as part of your deploy smoke test.

Process spawn cost dominates everything. Shiki itself is fast (1.0.2 "significantly improve speed"), but proc_open per call isn't. For a docs index page rendering 50 blocks, uncached highlighting will add seconds. Measure before you blame Shiki.

Large code blocks used to blow up. Before 2.3.3, big inputs produced proc_open(): posix_spawn() failed: Argument list too long, because code was passed as a command-line argument. 2.3.3+ pipes input via stdin. If you're importing large SQL dumps or generated files, be on ≥ 2.3.3.

Version drift between PHP and JS sides. 2.3.0 upgraded to Shiki v2, 2.4.0 to v4. If your package.json pins an old shiki, you can get output that doesn't match what the PHP wrapper expects (missing dual-theme variables, changed token shape). Keep the npm shiki roughly aligned with the PHP release's shiki major, and don't ignore the changelog when bumping spatie/shiki-php.

The 2.0 removals bite upgrade guides. getAvailableLanguages, getAvailableThemes, languageIsAvailable, and themeIsAvailable were deleted in 2.0.0 and restored in 2.1.0. On 2.x below 2.1.0 those calls fatal. Modern installs are fine — just don't copy code from a tutorial written during that window.

Inline styles vs. CSP. Output uses style="…" attributes and a background-color on the <pre>. A strict Content-Security-Policy without 'unsafe-inline' for styles will strip all coloring. Either relax style-src, or post-process the output to extract inline styles into classes (more work than it sounds — token styles are per-token).

The background color is baked in. Shiki sets background-color on the <pre> from the theme. If you want your own surface color, override it in CSS with higher specificity rather than fighting the attribute.

Custom renderer scripts are the escape hatch. If you need Shiki options the PHP API doesn't expose, you can supply your own renderer script (added in 1.2.0). That's the documented extension point — use it instead of shelling out to Node yourself and duplicating the process-management logic.

Always have a fallback. Wrap highlight() in a try/catch and render escaped <pre><code> on failure. A missing Node binary shouldn't 500 your blog post.

try {
    return Shiki::highlight($code, $language, $theme);
} catch (\Throwable $e) {
    report($e);
    return '<pre><code>' . e($code) . '</code></pre>';
}

Windows is a rough edge. Path resolution and symlink guidance in the docs are Unix-centric; if you have Windows devs on the team, expect the Node lookup to fail and budget time for a per-developer PATH fix.

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.
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support