PHPackages                             justinholtweb/craft-free-nav - 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. justinholtweb/craft-free-nav

ActiveCraft-plugin

justinholtweb/craft-free-nav
============================

A free, full-featured navigation menu builder for Craft CMS 5

00PHP

Since Mar 29Pushed 1mo agoCompare

[ Source](https://github.com/justinholtweb/craft-freenav)[ Packagist](https://packagist.org/packages/justinholtweb/craft-free-nav)[ RSS](/packages/justinholtweb-craft-free-nav/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependenciesVersions (1)Used By (0)

FreeNav for Craft CMS 5
=======================

[](#freenav-for-craft-cms-5)

A free, full-featured navigation menu builder for Craft CMS 5. Build complex navigation menus with a drag-and-drop interface, conditional visibility, caching, accessibility, and more — all without paying a dime.

**Developer:** Justin Holt **Website:** [craft-freenav.com](https://craft-freenav.com)**License:** [Craft License](https://craftcms.github.io/license/)

---

Why FreeNav?
------------

[](#why-freenav)

FreeNav provides everything you need to manage navigation in Craft CMS, for free:

FeatureFreeNavVerbb Navigation**Price**Free$19**Conditional Visibility**Per-node rules (user group, login state, URL, entry type)None**Built-in Cache**Tagged cache with auto-invalidationNone**Icon &amp; Badge Fields**Native on every nodeNone**Template Presets**6 presets (dropdown, sidebar, breadcrumb, footer, mega)None**JSON Import/Export**Full menu structure portabilityNone**ARIA Accessibility**Automatic `aria-current`, `aria-expanded`, `role`Manual**REST API**3 built-in endpointsNone**Mega Menu Columns**First-class column layoutNone---

Features
--------

[](#features)

- **Menu Builder** — Drag-and-drop node builder in the Control Panel
- **Multiple Node Types** — Entry, category, asset, Commerce product, custom URL, passive (no-link), and site nodes
- **Conditional Visibility** — Show/hide nodes based on user group, logged-in state, URL segments, or entry type
- **Built-in Cache** — Intelligent per-menu cache with automatic invalidation via tagged dependencies
- **Icon &amp; Badge Support** — Native icon class and badge text fields on every node
- **Template Presets** — 6 ready-to-use render presets: default, dropdown, sidebar, breadcrumb, footer, mega menu
- **JSON Import/Export** — Export and import full menu structures with element UID portability
- **ARIA Accessibility** — Built-in accessible markup with `aria-current`, `aria-expanded`, `aria-haspopup`, and `role` attributes
- **REST API** — Simple REST endpoints for headless/decoupled architectures
- **GraphQL** — Full schema with per-menu types and scoped permissions
- **Multi-site** — Configurable propagation methods (none, site group, language, all)
- **Breadcrumbs** — URL-segment breadcrumb generation with automatic Craft element resolution
- **Project Config** — Menu definitions stored in Project Config for environment portability
- **Permissions** — Granular user permissions (manage menus, edit nodes, delete nodes — per-menu)
- **Element Syncing** — Node titles and URLs auto-update when linked entries/categories change
- **Menu Field Type** — Drop a menu selector into any entry type
- **Extensible** — Event hooks for custom node types, visibility rules, active state overrides, and render modification

---

Requirements
------------

[](#requirements)

- **Craft CMS** 5.0.0+
- **PHP** 8.2+

---

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

[](#installation)

```
composer require justinholtweb/craft-free-nav
```

Then install from **Settings &gt; Plugins** in the Control Panel, or via CLI:

```
php craft plugin/install free-nav
```

---

Quick Start
-----------

[](#quick-start)

### 1. Create a Menu

[](#1-create-a-menu)

Go to **FreeNav &gt; Menus &gt; New menu**. Set a name (`Main Menu`) and handle (`mainMenu`), choose your site settings, and save.

### 2. Build Your Navigation

[](#2-build-your-navigation)

Click **Build** on your menu. Use the slide-out panel to add nodes:

- **Entry/Category/Asset** — Select a Craft element (title and URL sync automatically)
- **Custom URL** — Any URL, including environment variables (`$BASE_URL/about`)
- **Passive** — A non-linking label (great for dropdown group headings)

Drag nodes to reorder. Nest them for submenus.

### 3. Render in Templates

[](#3-render-in-templates)

```
{{ craft.freenav.render('mainMenu') }}
```

That's it. FreeNav outputs a fully accessible `` with proper ARIA attributes.

---

Template Usage
--------------

[](#template-usage)

### Auto-Render with Presets

[](#auto-render-with-presets)

```
{# Default list #}
{{ craft.freenav.render('mainMenu') }}

{# Dropdown with custom active class #}
{{ craft.freenav.render('mainMenu', {
    preset: 'dropdown',
    activeClass: 'is-active',
}) }}

{# Mega menu #}
{{ craft.freenav.render('mainMenu', { preset: 'mega' }) }}

{# Sidebar navigation #}
{{ craft.freenav.render('sidebarMenu', { preset: 'sidebar' }) }}

{# Footer columns (level 1 = column headers, level 2+ = links) #}
{{ craft.freenav.render('footerMenu', { preset: 'footer' }) }}
```

### Manual Iteration

[](#manual-iteration)

Use Craft's `{% nav %}` tag for full control:

```
{% set nodes = craft.freenav.nodes('mainMenu').visibleOnly(true).all() %}

        {% nav node in nodes %}

                {% if node.getUrl() %}

                        {{ node.getIconHtml() }}
                        {{ node.title }}
                        {{ node.getBadgeHtml() }}

                {% else %}
                    {{ node.title }}
                {% endif %}
                {% ifchildren %}{% children %}{% endifchildren %}

        {% endnav %}

```

### Breadcrumbs

[](#breadcrumbs)

```
{% set crumbs = craft.freenav.breadcrumbs({
    homeLabel: 'Home',
    includeHome: true,
    includeCurrent: true,
}) %}

        {% for crumb in crumbs %}

                {% if crumb.isCurrent %}
                    {{ crumb.title }}
                {% else %}
                    {{ crumb.title }}
                {% endif %}

        {% endfor %}

```

### Querying Nodes

[](#querying-nodes)

```
{# Get a node query #}
{% set nodes = craft.freenav.nodes('mainMenu')
    .nodeType('entry')
    .visibleOnly(true)
    .all() %}

{# Get the tree structure #}
{% set tree = craft.freenav.tree('mainMenu') %}

{# Find the currently active node #}
{% set active = craft.freenav.getActiveNode('mainMenu') %}

{# Get menu metadata #}
{% set menu = craft.freenav.getMenuByHandle('mainMenu') %}
```

---

Twig API Reference
------------------

[](#twig-api-reference)

All methods are accessed via `craft.freenav`:

MethodReturnsDescription`render(handle, options)``Markup`Render a menu as HTML using a preset`nodes(handle, criteria)``NodeQuery`Get an element query for menu nodes`tree(handle, criteria)``array`Get a hierarchical tree of nodes`breadcrumbs(options)``array`Generate breadcrumbs from the current URL`getActiveNode(handle)``?Node`Get the currently active node`getMenuByHandle(handle)``?Menu`Get a menu model by handle`getMenuById(id)``?Menu`Get a menu model by ID`getAllMenus()``Menu[]`Get all menus---

Render Options
--------------

[](#render-options)

OptionTypeDefaultDescription`preset``string``'default'`Render preset: `default`, `dropdown`, `sidebar`, `breadcrumb`, `footer`, `mega``id``string``null``` id attribute`class``string``null``` CSS class`ulClass``string``null``` CSS class`liClass``string``null``` CSS class`aClass``string``null``` CSS class`activeClass``string``'active'`CSS class for active items`hasChildrenClass``string``'has-children'`CSS class for items with children`maxLevel``int``null`Limit rendering depth`overrideTemplate``string``null`Custom Twig template path (bypasses presets)`cache``bool``true`Enable/disable caching for this render call`cacheDuration``int``3600`Cache TTL in seconds`aria``bool``true`Enable automatic ARIA attributes`visibilityCheck``bool``true`Apply conditional visibility rules---

Node Properties
---------------

[](#node-properties)

Each `Node` element provides:

Property/MethodTypeDescription`title``string`Node title (synced from element or custom)`getUrl()``?string`Resolved URL (element URL, custom URL, or null for passive)`getLink()``Markup`Full `` tag with all attributes`nodeType``string`Type: `entry`, `category`, `asset`, `product`, `custom`, `passive`, `site``getNodeType()``NodeType`Enum instance`classes``?string`CSS classes`urlSuffix``?string`URL suffix (e.g., `#section`)`newWindow``bool`Opens in new tab`icon``?string`Icon CSS class`badge``?string`Badge text`getIconHtml()``?Markup`Rendered `` icon tag`getBadgeHtml()``?Markup`Rendered badge ```isActive()``bool`Current page or has active descendant`isCurrent()``bool`Exact URL match with current page`hasActiveDescendant()``bool`Any child is current`isVisible()``bool`Passes all visibility rules`isElement()``bool`Linked to a Craft element`isCustom()``bool`Custom URL type`isPassive()``bool`No-link/label type`getLinkedElement()``?Element`The linked Craft element`hasOverriddenTitle()``bool`Title differs from linked element`getLinkAttributes(extra)``array`HTML attributes for the link`getAriaAttributes()``array`Computed ARIA attributes`getCustomAttributesArray()``array`Custom `[{key, value}]` attributes`getMenu()``Menu`Parent menu model---

Conditional Visibility
----------------------

[](#conditional-visibility)

Add visibility rules to any node to control when it appears. Rules are evaluated at render time. Multiple rules use AND logic (all must pass).

### Rule Types

[](#rule-types)

TypeOperatorsValueExample`loggedIn``is`, `isNot``true`/`false`Show only to logged-in users`userGroup``is`, `isNot`Group handle or `"guests"`Show only to "members" group`urlSegment``is`, `isNot`, `contains`, `startsWith`URL stringShow when URL contains "blog"`entryType``is`, `isNot`Entry type handleShow on "article" entriesRules are stored as JSON on the node:

```
[
    {"type": "loggedIn", "operator": "is", "value": true},
    {"type": "userGroup", "operator": "is", "value": "members"}
]
```

Disable visibility checks for a render call:

```
{{ craft.freenav.render('mainMenu', { visibilityCheck: false }) }}
```

---

Caching
-------

[](#caching)

FreeNav caches rendered HTML per menu + site + render options using Craft's cache component with `TagDependency`.

Cache is **automatically invalidated** when:

- A menu is saved or deleted
- A node is saved, deleted, or reordered
- A linked element's title, URL, or status changes
- Site settings change

### Configuration

[](#configuration)

Global defaults in **FreeNav &gt; Settings**. Per-render override:

```
{# Disable cache for this call #}
{{ craft.freenav.render('mainMenu', { cache: false }) }}

{# Custom TTL #}
{{ craft.freenav.render('mainMenu', { cacheDuration: 7200 }) }}
```

### Clear Cache Manually

[](#clear-cache-manually)

```
php craft free-nav/menus/clear-cache            # All menus
php craft free-nav/menus/clear-cache mainMenu    # Specific menu
```

---

REST API
--------

[](#rest-api)

Enable/disable in **FreeNav &gt; Settings &gt; Enable REST API**.

EndpointMethodDescription`/actions/free-nav/api/get-menus`GETList all menus`/actions/free-nav/api/get-menu?handle={handle}`GETGet a menu with all its nodes`/actions/free-nav/api/get-breadcrumbs?uri={uri}`GETGet breadcrumbs for a URIAuthenticated via Craft's standard action URL auth (session or token).

---

GraphQL
-------

[](#graphql)

FreeNav registers per-menu GQL types and scoped read permissions.

```
{
    freeNavNodes(menuHandle: "mainMenu") {
        id
        title
        url
        nodeType
        icon
        badge
        active
        children {
            id
            title
            url
        }
    }
}
```

Enable per-menu access in **Settings &gt; GraphQL &gt; Schemas** under the **FreeNav** section.

---

Import / Export
---------------

[](#import--export)

### Export

[](#export)

Go to **FreeNav &gt; Menus &gt; \[menu\] &gt; Build**, click the menu button next to "Add node", and select **Export JSON**.

### Import

[](#import)

POST a JSON file to `free-nav/import-export/import` from the CP. Element-linked nodes are exported with UIDs for cross-environment portability.

### Format

[](#format)

```
{
    "freeNav": "1.0.0",
    "menu": {
        "name": "Main Menu",
        "handle": "mainMenu",
        "propagationMethod": "all",
        "maxNodes": null,
        "maxLevels": 5
    },
    "nodes": [
        {
            "title": "Home",
            "nodeType": "custom",
            "url": "/",
            "level": 1,
            "children": []
        }
    ]
}
```

---

Console Commands
----------------

[](#console-commands)

```
# List all menus
php craft free-nav/menus

# Resave all nodes (useful after migrations or bulk changes)
php craft free-nav/menus/resave-nodes

# Resave nodes for a specific menu
php craft free-nav/menus/resave-nodes mainMenu

# Clear all FreeNav caches
php craft free-nav/menus/clear-cache

# Clear cache for a specific menu
php craft free-nav/menus/clear-cache mainMenu
```

---

Events
------

[](#events)

FreeNav fires events for extensibility:

EventConstantServicePurpose`beforeSaveMenu``EVENT_BEFORE_SAVE_MENU``Menus`Before a menu is saved`afterSaveMenu``EVENT_AFTER_SAVE_MENU``Menus`After a menu is saved`beforeDeleteMenu``EVENT_BEFORE_DELETE_MENU``Menus`Before a menu is deleted`afterDeleteMenu``EVENT_AFTER_DELETE_MENU``Menus`After a menu is deleted`nodeActive``EVENT_NODE_ACTIVE``Node`Override a node's active state`registerNodeTypes``EVENT_REGISTER_NODE_TYPES``NodeTypes`Register additional node types`registerLinkableElements``EVENT_REGISTER_LINKABLE_ELEMENTS``NodeTypes`Register linkable element types### Example: Override Active State

[](#example-override-active-state)

```
use justinholt\freenav\elements\Node;
use justinholt\freenav\events\NodeActiveEvent;

Event::on(
    Node::class,
    Node::EVENT_NODE_ACTIVE,
    function (NodeActiveEvent $event) {
        // Force a node active based on custom logic
        if ($event->node->url === '/special') {
            $event->isActive = true;
        }
    }
);
```

---

Permissions
-----------

[](#permissions)

PermissionDescription`freeNav-manageMenus`Create, edit, delete menus`freeNav-manageMenu:{uid}`Manage a specific menu`freeNav-editNodes`Edit nodes in any menu`freeNav-editNodes:{uid}`Edit nodes in a specific menu`freeNav-deleteNodes`Delete nodes from any menu`freeNav-deleteNodes:{uid}`Delete nodes from a specific menu---

Support
-------

[](#support)

- [GitHub Issues](https://github.com/justinholtweb/craft-free-nav/issues)
- [Documentation](https://craft-freenav.com/docs)

---

Made by [Justin Holt](https://craft-freenav.com)

###  Health Score

19

—

LowBetter than 10% of packages

Maintenance60

Regular maintenance activity

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

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.

### Community

Maintainers

![](https://www.gravatar.com/avatar/035cb655c55af0e9e5b96754b80fd9703e195c32dbdfc49ae9a43ab9cf8db560?d=identicon)[justinholtweb](/maintainers/justinholtweb)

---

Top Contributors

[![justinholtweb](https://avatars.githubusercontent.com/u/295903?v=4)](https://github.com/justinholtweb "justinholtweb (7 commits)")

---

Tags

craft-plugincraft5craftcmscraftcms-pluginnavigation

### Embed Badge

![Health badge](/badges/justinholtweb-craft-free-nav/health.svg)

```
[![Health](https://phpackages.com/badges/justinholtweb-craft-free-nav/health.svg)](https://phpackages.com/packages/justinholtweb-craft-free-nav)
```

PHPackages © 2026

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