Skip to main content

Rich Menu (Default / Custom / Switch)

Overview

A rich menu is the image-based menu pinned below the chat area in a LINE OA. In the CMS, this module is where those menus are designed, where you decide who sees which menu, and where you review click statistics.

The module is divided into three types, which drive the page structure, the permission subjects and the business rules alike:

TypePurposeConstraints
DefaultThe OA's standard menu, split into a guest version and a member versionOne of each; once both exist the create button is hidden
CustomA menu that applies during a defined date range, tied to one audience — or flagged so an automation drives it insteadRequires a date range, and an audience unless it is automation-driven
SwitchA sub-menu attached to a parent menu, used as the destination when a tap switches menu pagesRequires an existing parent; its status depends on the parent's

All three share the same container and form, swapping fields, columns and validation rules according to the type in play.

Business Flow

1. List screen

  1. Each type has its own page with its own permission check.
  2. Filter state is cached per page in sessionStorage, so filters survive navigating away and back.
  3. The list is fetched alongside the parent-menu options used both in filters and in the form, grouped into default and custom menus with thumbnails.
  4. All three types share columns for row number, thumbnail, menu name, status and actions, with type-specific additions:
    • Default adds type and period columns.
    • Custom adds audience and period columns.
    • Switch adds parent menu and last-updated columns, and omits the status column.
  5. Menus flagged for automation carry a tag next to their name.
  6. Only custom (search, status, date range) and switch (search, parent menu, updated range) have filters; the default type has none.
  7. Row actions cover view, edit and delete — delete only for custom and switch types. Edit and delete are disabled by business rules, with a tooltip explaining why:
    • Custom — editable while active or inactive, but not once completed or expired, and not once the start or end date has passed. Cannot be deleted once completed.
    • Switch — editable only while its parent menu is in draft or inactive state. Cannot be deleted once it or its parent has completed.
  8. Deletion always requires confirmation.

2. Designing a menu

  1. The form is shared across all types and performs its own permission check based on the type in the URL, rather than using the standard page wrapper.
  2. Choose a layout — fifteen templates are offered across three groups: large, compact, and custom-area. Switching groups selects that group's first template automatically, and choosing a new layout clears the image and tap areas before creating empty slots to match the new layout.
  3. Upload an image — three checks run before the file is accepted: it must be JPEG or PNG, under 1 MB, and its resolution must match one of the accepted sizes for that layout group.
  4. Configure an action per area — each area is labelled A, B, C and so on, and offers five action types:
    • Link — a URL plus a label capped at 20 characters per LINE's own limit, with an option to enable click tracking.
    • Text — sends a message back into the chat.
    • Phone — a phone number with a label.
    • Switch rich menu — pick a destination from a list grouped into parent and sub-menus, with a refresh control and a shortcut to create a new sub-menu.
    • No action.
  5. Required-field rules are relaxed when saving as a draft, so incomplete work can be parked.
  6. Custom-area layouts — users draw tappable regions directly on the image, moving and resizing them with corner and edge handles. Overlaps are prevented and a maximum of 20 areas applies. If existing data is found with incomplete coordinates, the app reports it and renumbers the areas.
  7. Status — the rules vary by type and mode. Creating a switch menu hides the control and forces it active. Custom menus are validated against their dates: setting the status to active while the start or end date lies in the past fails validation.
  8. Saving — a background image is mandatory. The app assembles the full payload including the image file, then opens a confirmation dialog. On confirmation it creates or updates the menu. Field-specific server errors are mapped back onto the form, and a few specific error codes are translated into readable messages — the rich menu is not ready, the sub-menu is not active, or another menu still references it.

3. Reviewing click statistics

  1. In view mode the menu is previewed read-only alongside a click statistics table.
  2. The table lists the area position (rendered as letters A through Z), that area's data, the action type, total clicks and unique clicks; the last two are sortable.
  3. Areas configured as links show their data as a clickable link to the real destination.
  4. A re-sync button asks the backend to recalculate the statistics from the tracking log, after which the page reloads the data following a short delay. This is a background job, not a live calculation in the browser.

Key Screens & Components

List screen

The list container (src/components/rich-menu/list/rich-menu.container.tsx) owns the table, filters, deletion and all the rules governing when action buttons are enabled, with separate filter forms for the custom and switch types.

Form screen

The entire form lives in one large container (src/components/rich-menu/form/default/rich-menu-default-form.container.tsx), covering data loading, layout selection, image upload, date-versus-status validation, saving, and triggering a statistics re-sync.

Layout picker and tap areas

src/components/rich-menu/form/template/ brings three pieces together: the tabbed template picker, the definition file for all fifteen layouts and their area counts, and the drawing tool for custom-area layouts.

Action configuration

Action configuration splits into a collapsible panel component per area, and a component that renders the fields for each action type along with its specific validation rules.

API endpoints

OperationEndpoint
List rich menusGET /rich-menu
Dropdown optionsGET /rich-menu/find-all-object
Read one rich menu, including statisticsGET /rich-menu/{id}
Create a rich menuPOST /rich-menu
Update a rich menu or change its statusPUT /rich-menu/{id}
Delete a rich menuDELETE /rich-menu/{id}
Trigger a statistics recalculationPOST /rich-menu/{id}/recalculate-stat
Audience listGET /audiences

Dependencies

  • Audiences — custom menus must be tied to an audience unless they are flagged for automation.
  • Sub-menu ordering — a switch action needs its destination to already exist, so parent menus must be created before sub-menus. The rules for editing and deleting a sub-menu also depend on the parent's status.
  • Workflows and automation — custom menus flagged for automation are activated by an automation rather than by their own date range.
  • Tracking system — click statistics come from a background job that processes the tracking log, not from live calculation in the browser. If the numbers look stale, trigger a re-sync and wait a moment.
  • Access control — the server-side rich-menu module unlocks all three types at once, and the sidebar shows each child entry according to its own permission.
  • Rich Message Management — shares the data model used for tappable areas on images.

Backend Details (CMS API)

The rich menu module is one of the most complex in cms-api, because a single save does not merely write to the database — it changes real state on LINE.

What the backend does on one save

  1. Checks for a duplicate name first, rejecting with error code RMN_001 if found.
  2. Uploads the image and creates the rich menu on LINE for real, calling LINE in sequence: create the rich menu, upload and attach the image, and — for menus that need an alias (multi-level or tabbed menus) — create or update the alias.
  3. Rewrites link actions into token-based tracking URLs before sending them to LINE. This is the machinery behind the "enable click tracking" option on the form, and it is a different mechanism from campaign link tracking, which uses the redirect service.
  4. Binds the menu to its recipients. For an OA-wide menu it asks LINE to set it as the default. For an audience-bound menu it reads the audience's CSV of LINE users from object storage and links the menu to those users in batches, recording who has been linked.
  5. Archives the previous version before replacing it, preserving a history of earlier menu versions.

A practical note: because these steps call external systems repeatedly, saving a menu bound to a large audience takes considerably longer than an ordinary save, and a mid-way failure can leave LINE's state and the database temporarily out of sync. That is the origin of the specific error messages the frontend translates, such as "rich menu is not ready yet".

Click statistics really are a background job

POST /api/rich-menu/:id/recalculate-stat does not compute anything within the request. It publishes a job to the calculate_rich_menu_stat_item queue for a worker to process, so the request returns immediately with the figures unchanged. That is the technical reason the frontend waits a moment before reloading, and if the numbers still do not move, the worker is where to look — not cms-api.

Event-driven menu switching for individual users also goes through a queue, line_change_richmenu, which is how workflows and automations request a menu swap.

Permissions and deletion

  • Every route is wrapped in ModuleGate for the rich-menu module, enforced across all three types at once — there is no per-type gate for default / custom / switch at the API level. Splitting permissions by type is purely a frontend concern.
  • Per-action permission metadata exists but is not enforced.
  • GET /api/rich-menu/find-all-object, used for dropdowns, carries no permission metadata.
  • DELETE /api/rich-menu/:id is a soft delete that also deletes the menu on LINE, not merely hides the row. Hard deletion requires super admin plus an explicit confirmation parameter.

A technical note worth knowing

This module reads LINE OA data by querying the table directly instead of going through the usual LINE OA service. The reason is that it needs the channel access token, which that service strips from its results for security. It is a deliberate exception, not untidy code.