Integrating Admin Global Search

Updated at 1772979858000
EzyPlatform provides an admin global search feature that allows administrators to quickly search across multiple types of data in the admin panel, such as:
  • products
  • users
  • orders
  • articles
  • plugin-provided data
The key idea behind this feature is that it is designed to be extensible through plugins. Any module can contribute search results simply by implementing the AdminSearchService interface.
This guide explains how to integrate your module or plugin into EzyPlatform admin global search.

How Global Search Works

The global search follows a simple workflow:
  • The admin enters a keyword
  • The endpoint /api/v1/search is called
  • The controller retrieves all AdminSearchService implementations
  • Each service performs its own search logic
  • The results are merged and returned to the frontend
flowchart TD
    A[Admin enters keyword] --> B[GET /api/v1/search]
    B --> C[AdminApiSearchController]
    C --> D[Load all AdminSearchService implementations]
    D --> E[Execute search keyword]
    E --> F[Merge results]
    F --> G[Return response to frontend]
The controller does not need to know where the data comes from. Each plugin is responsible for providing its own search results.

Global Search Controller

The search endpoint is provided by the following controller:
@Controller("/api/v1")
public class AdminApiSearchController {

    private final EzyLazyInitializer<List<AdminSearchService>> searchServices;

    public AdminApiSearchController(
        EzySingletonFactory singletonFactory
    ) {
        searchServices = new EzyLazyInitializer<>(() -> {
            List<AdminSearchService> searchServices =
                singletonFactory.getSingletonsOf(AdminSearchService.class);
            searchServices.sort(
                Comparator.comparingInt(AdminSearchService::priority)
            );
            return searchServices;
        });
    }

    @DoGet("/search")
    public List<SearchResultModel> searchGet(
        @AdminId long adminId,
        @AdminRoles AdminRolesProxy adminRoles,
        @RequestParam("keyword") String keyword
    ) {
        String trimmedKeyword = trimOrNull(keyword);
        if (trimmedKeyword == null) {
            return Collections.emptyList();
        }

        List<SearchResultModel> result = new ArrayList<>();

        for (AdminSearchService searchService : searchServices.get()) {
            try {
                result.addAll(
                    searchService.search(
                        adminId,
                        adminRoles,
                        trimmedKeyword
                    )
                );
            } catch (Exception e) {
                // ignore
            }
        }
        return result;
    }
}
This controller is responsible for:
  • retrieving all AdminSearchService implementations
  • sorting them by priority
  • executing search() on each service
  • merging all results into a single list

Interface for Plugin Integration

To integrate with global search, plugins must implement the following interface:
public interface AdminSearchService {

    default List<SearchResultModel> search(
        long adminId,
        AdminRolesProxy rolesProxy,
        String keyword
    ) {
        return this.search(adminId, keyword);
    }

    default List<SearchResultModel> search(long adminId, String keyword) {
        return this.search(keyword);
    }

    default List<SearchResultModel> search(String keyword) {
        return Collections.emptyList();
    }

    default int priority() {
        return 0;
    }
}
Explanation:
MethodPurpose
`search(keyword)`simple keyword search
`search(adminId, keyword)`search with admin context
`search(adminId, roles, keyword)`search with role-based filtering
`priority()`defines execution order
Plugins can override whichever method fits their needs.

Example: Product Search Integration

Below is an example of integrating product search from the ecommerce module.
@Service
@AllArgsConstructor
public class AdminEcommerceAdminSearchControllerService
    implements AdminSearchService {

    private final AdminProductService productService;

    @Override
    public List<SearchResultModel> search(String keyword) {
        if (isBlank(keyword)) {
            return Collections.emptyList();
        }

        List<ProductModel> products =
            productService.getProductsByKeywordPrefix(
                keyword,
                LIMIT_30_RECORDS
            );

        return newArrayList(products, it ->
            SearchResultModel.builder()
                .title(it.getProductCode())
                .resultType("product")
                .content(it.getProductName())
                .url("/ecommerce/products/" + it.getId())
                .build()
        );
    }
}
This service:
  • searches products by keyword
  • converts ProductModel to SearchResultModel
  • returns the results to the controller

SearchResultModel Structure

Search results must be standardized using SearchResultModel.
Example fields:
FieldDescription
`title`result title
`content`result description
`resultType`data type identifier
`url`link to the admin page
Because all results share the same model, the frontend can display mixed results from different plugins in a single search dropdown.

Example: Adding Search for a New Plugin

For example, a plugin may add coupon search like this:
@Service
public class AdminCouponSearchService
    implements AdminSearchService {

    @Override
    public List<SearchResultModel> search(String keyword) {

        List<CouponModel> coupons = couponService.search(keyword);

        return coupons.stream()
            .map(it -> SearchResultModel.builder()
                .title(it.getCode())
                .content(it.getName())
                .resultType("coupon")
                .url("/coupons/" + it.getId())
                .build())
            .toList();
    }
}
No modification to the global search controller is required.

Best Practices

When implementing AdminSearchService, consider the following:
  • Limit the number of results (for example 20–30 records)
  • Avoid heavy or slow queries
  • Use prefix or indexed searches when possible
  • Return direct URLs to admin pages

Conclusion

EzyPlatform admin global search is built around a plugin-based search service architecture.
To integrate a plugin into the global search:
  • implement AdminSearchService
  • perform the search logic
  • return results as SearchResultModel
The global search controller will automatically discover your service and merge its results with others.
This architecture provides:
  • easy extensibility
  • modular plugin development
  • no need to modify core code when adding new search sources

Table Of Contents