Files
VITEC-website/packages/evo_megamenu_json
orascheandClaude Fable 5 b1357a6ffe Megamenu: ship menu images as processed 640px webp
MenuBuilder::image() returned getPublicUrl() of the ORIGINAL upload, so
the Success Stories teaser cards (and item/featured images) carried
multi-MB files into every page payload. All menu images now go through
the ImageService (maxWidth 640, webp - the search-tile convention);
width/height report the processed size, and the original URL remains
the fallback when processing fails so the menu never loses an image.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-23 14:13:56 +02:00
..

EVO Megamenu JSON

An editable megamenu for headless TYPO3. One content element holds the whole menu; the extension publishes it as JSON, the front end renders it.

Project-neutral by design — nothing in here knows which site it runs on.

What the editor gets

A plugin called Megamenu. Inside it:

Level Holds
Entry one item in the main bar: label, target page, panel layout
Column a heading (optionally a link) and its items
Item label, link, teaser line, badge, thumbnail, “page not ready” flag
Teaser card kicker, headline, text, link, image — for editorial panels
Featured one highlighted block per entry, edited on the entry itself

Three panel layouts per entry: columns for a structure menu (products, solutions, markets), teasers for a small link column next to cards (news, stories), and catalog for a data-driven panel whose content comes from the site package (see below).

Installation

composer require evomedien/evo-megamenu-json
vendor/bin/typo3 extension:setup
vendor/bin/typo3 database:updateschema "*.add,*.change"

In the site configuration, add the set EVO Megamenu (JSON) and set:

Setting Meaning

| megamenu.contentUid | uid of the Megamenu element to publish on every page | | megamenu.storagePid | alternative: folder whose first Megamenu element is used |

Leave both uid and pid at 0 and the menu is only rendered where the element sits — useful while building it.

Payload

"megaMenu": {
  "settings": {
    "stage": 3, "openOn": "hover", "closeDelay": 140, "columnsPerRow": 4,
    "panelWidth": "container", "pendingBehaviour": "fallback",
    "showCounts": true, "showViewAll": true, "viewAllLabel": "View all %s"
  },
  "entries": [
    {
      "title": "Products",
      "link": "/products",
      "layout": "columns",
      "columns": [
        {
          "title": "IP Video Streaming",
          "link": "/products/ip-video-streaming",
          "items": [
            { "title": "Appliances", "link": "/products/ip-video-streaming/appliances",
              "pending": true, "teaser": "MGW Diamond, Ace, Pico",
              "image": { "url": "/fileadmin/...", "alternative": "", "title": "", "width": 600, "height": 400 } }
          ]
        }
      ],
      "featured": { "kicker": "Datasheet", "title": "MGW Diamond-H", "text": "…", "link": "/product/…", "image": { } }
    }
  ]
}

settings travels with the menu on purpose: the front end reads hover behaviour, close delay, column count and labels from the payload instead of hard-coding them, so an editor can change them without a deploy.

Content stages

stage decides how much detail is emitted — 1 structure only, 2 adds images, teaser lines and cards, 3 adds the featured block. Later stages only add keys, so a front end written for stage 1 keeps working when the stage is raised.

pending

An item whose target page does not exist yet carries "pending": true. settings.pendingBehaviour says what to do with it: fallback (link to the column heading instead), mute (show, not clickable) or hide. This keeps a menu shippable while its deeper pages are still being built.

Layout catalog

For panels built from data rather than by hand — a product catalog with categories, groups and items, a search bar, whatever the site needs. The extension stays project-neutral: it dispatches Evomedien\EvoMegamenuJson\Event\BuildCatalogEvent for every catalog entry and emits whatever a listener sets via setCatalog() verbatim as catalog on the entry (null without a listener). The entry's teaser cards are emitted as teasers as usual and serve as the panel's featured tiles; its columns are ignored.

#[AsEventListener(identifier: 'site/megamenu-catalog')]
final class ProvideCatalog
{
    public function __invoke(BuildCatalogEvent $event): void
    {
        $event->setCatalog(['categories' => [/* … */]]);
    }
}

getEntry() hands the listener the raw entry row (to tell several catalog entries apart), getStage() the content stage.

Notes

  • Every link is a resolved URL — page, record, external or file, run through typolink at render time.
  • Translations are overlaid per record, so a menu can be localised entry by entry.
  • The renderer never throws: a broken menu returns an empty value rather than taking the page payload down.
  • Without friendsoftypo3/headless the element has no JSON envelope to render into — the extension is built for headless installations.