# Admin Top Menu The FluentCRM admin has a top navigation bar (Dashboard, Contacts, Campaigns and so on). You can add your own item to it, with or without a dropdown of sub-items, and decide what it opens. The menu entry itself is always added in PHP, because the bar is rendered on the server. What changes is the screen it opens: ::: tip Option 1: PHP only **Add the menu item and link it anywhere.** Point it at an existing admin page, a custom WordPress admin page, or an external URL. - No build step - Best when the destination already exists [Jump to the PHP option →](#option-1-php-menu-item) ::: ::: tip Option 2: PHP menu item + Vue screen **Add the menu item, then register a Vue screen it opens** inside the FluentCRM app, with the top bar kept and the item highlighted. - Needs a build step (Vite or similar) - Best for a full screen of your own: tables, forms, charts [Jump to the Vue screen option →](#option-2-vue-screen) ::: **Not sure which to use? Start with the PHP option.** ## Option 1: PHP menu item Hook the `fluent_crm/core_menu_items` filter and append an item to the array. ### Basic Example ```php add_filter('fluent_crm/core_menu_items', function ($menuItems, $permissions, $urlBase) { // Only show the item to users who may use it. if (!in_array('fcrm_read_contacts', $permissions, true)) { return $menuItems; } $menuItems[] = [ 'key' => 'my_plugin', 'label' => __('My Plugin', 'your-plugin'), 'permalink' => admin_url('admin.php?page=my-plugin-page'), ]; return $menuItems; }, 10, 3); ``` ### Menu item fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `key` | String | Yes | Unique identifier. Use your plugin prefix. It also marks the item as active (see [Option 2](#option-2-vue-screen)). | | `label` | String | Yes | Text shown in the bar | | `permalink` | String | Yes | Where the item links to | | `sub_items` | Array | No | Dropdown entries, see below | | `layout_class` | String | No | Shows the dropdown as cards instead of a plain list. Core uses `fc_1_col_menu` for a single column. | Each entry in `sub_items` takes: | Field | Required | Description | |-------|----------|-------------| | `key` | Yes | Unique identifier | | `label` | Yes | Entry title | | `permalink` | Yes | Where it links to | | `description` | No | Short text under the title (card layout only) | | `icon` | No | An inline SVG string (card layout only) | ```php $menuItems[] = [ 'key' => 'my_plugin', 'label' => __('My Plugin', 'your-plugin'), 'permalink' => $urlBase . 'my-plugin', 'layout_class' => 'fc_1_col_menu', 'sub_items' => [ [ 'key' => 'my_plugin_orders', 'label' => __('Orders', 'your-plugin'), 'permalink' => $urlBase . 'my-plugin/orders', 'description' => __('Browse orders linked to your contacts', 'your-plugin'), 'icon' => '', ], ], ]; ``` ::: warning Icons are not escaped `icon` is printed as raw HTML. Only pass SVG markup that you wrote yourself, never a value that comes from user input. `label` and `permalink` are escaped for you. ::: ### Choosing the filter There are two filters, and the difference decides where your item appears: | Filter | When it runs | Use it to | |--------|--------------|-----------| | `fluent_crm/core_menu_items` | After Dashboard, Contacts, Campaigns, Emails, Forms and Automations are built, **before** Reports and Settings | Add an item that sits before Reports. This is the usual choice. | | `fluent_crm/menu_items` | Last, after everything is built | Reorder or remove any item, including Reports and Settings | Settings is not shown in the center bar. It always renders as the gear icon on the right. ### Also add it to the WordPress sidebar FluentCRM also lists its main screens under **FluentCRM** in the WordPress admin sidebar. That list is separate from the top bar, so to add yours there, use the `fluent_crm/after_core_menu_items` action: ```php add_action('fluent_crm/after_core_menu_items', function ($permissions, $isAdmin) { if (!in_array('fcrm_read_contacts', $permissions, true)) { return; } add_submenu_page( 'fluentcrm-admin', __('My Plugin', 'your-plugin'), __('My Plugin', 'your-plugin'), $isAdmin ? 'manage_options' : 'fcrm_read_contacts', 'fluentcrm-admin#/my-plugin', '__return_null' ); }, 10, 2); ``` The page slug `fluentcrm-admin#/my-plugin` sends the click to the `/my-plugin` route inside the FluentCRM app, which is what [Option 2](#option-2-vue-screen) registers. ## Option 2: Vue screen Here your plugin ships a Vue component and registers it as a route in the FluentCRM app. The menu item from Option 1 links to that route, so the page opens inside FluentCRM with the top bar still visible. It takes three pieces: 1. **A menu item** (PHP), exactly as in Option 1, with `permalink` set to `$urlBase . 'my-plugin'`. 2. **A route** (JavaScript), added with the `fluentcrm_global_routes` filter. 3. **A script** (PHP), enqueued so it loads before the admin app starts. ### 1. Add the menu item (PHP) Use the Option 1 code and point `permalink` at your route: ```php 'permalink' => $urlBase . 'my-plugin', // opens #/my-plugin ``` `$urlBase` is the FluentCRM app address, for example `…/wp-admin/admin.php?page=fluentcrm-admin#/`. ### 2. Register the route (JavaScript) ```js // admin/my-plugin.js const { addFilter } = window.FLUENTCRM || {}; if (addFilter) { addFilter( 'fluentcrm_global_routes', 'my-plugin/screen', (routes) => routes.concat([ { name: 'my_plugin', path: '/my-plugin', component: () => import('./MyPlugin.vue'), props: true, meta: { active_menu: 'my_plugin', // the menu item `key` permission: 'fcrm_read_contacts', side_path: '/my-plugin' } } ]) ); } ``` | `meta` field | What it does | |--------------|--------------| | `active_menu` | Highlights the menu item whose `key` matches. Without it, your item is not marked active on your screen. | | `permission` | Capability required to open the route. Use the same one you checked in PHP. | | `side_path` | The path the app treats as the current section | Guard on `window.FLUENTCRM`: core's boot script creates it, and if that did not run there is nothing useful your script can do. Use a lazy `import()` so the screen becomes its own chunk. ### 3. Build the component ```vue ``` Use the Options API, matching core. Your own REST routes need a permission check like any other, see [Extending the REST API](/rest-api/extending/). ### 4. Enqueue the script (PHP) ```php add_filter('fluent_crm_asset_listed_slugs', function ($slugs) { $slugs[] = 'your-plugin'; // your plugin's folder name return $slugs; }); add_action('fluentcrm_loading_app', function () { if (!defined('FLUENTCRM_MODULE_API') || FLUENTCRM_MODULE_API < 1) { return; // older core without the shared modules } wp_enqueue_script( 'my-plugin-screen', plugins_url('assets/admin/my-plugin.js', YOUR_PLUGIN_FILE), ['fluentcrm_admin_app_boot'], YOUR_PLUGIN_VERSION, true ); }); // The bundle imports `@fluentcrm/*`, so it must load as an ES module. add_filter('script_loader_tag', function ($tag, $handle) { if ($handle === 'my-plugin-screen') { $tag = str_replace('