
Product
Rust Support Now in Beta
Socket's Rust support is moving to Beta: all users can scan Cargo projects and generate SBOMs, including Cargo.toml-only crates, with Rust-aware supply chain checks.
@cslegany/multi-select-filter-strapi5
Advanced tools
A customizable Multi-Select Filter to replace the default one.
This package adds a customizable Multi-Select Filter to replace the default one.
NPM:
npm install @cslegany/multi-select-filter-strapi5
Yarn:
yarn add @cslegany/multi-select-filter-strapi5
You can write your custom api endpoint for your main Strapi project to suit your business needs. The following is an example that supports a scheduled_at field for articles. Logic is that you need only those articles which were scheduled before the current date and we suppose that scheduled_at always has a value. Meta pagination info is needed for infinite scrolling. Return value of the service is fixed, it has to contain an object
{
result: {"id", "documentId" }[],
mainField: string,
meta: {
total: number;
pageSize: number;
pageCount: number;
currentPage: number;
} | undefined,
}
The api endpoint POST request also has a fixed format and have to supply the following values: filter: string, publishedOnly?: boolean, queryStart?: number, queryLimit?: number
articlefilter.ts controller is as follows
import type * as strapi from '@strapi/strapi';
export default ({ strapi }: { strapi: strapi.Core.Strapi }) => ({
async getFeaturedNews(ctx) {
return await strapi.service("api::article-filter.articlefilter").getFeaturedNews();
},
async getFilteredArticles(ctx) {
const body = ctx.request.body;
return await strapi.service('api::article-filter.articlefilter').getFilteredArticles(
body.filter, body.publishedOnly, body.queryStart, body.queryLimit);
},
})
articlefilter.ts route is as follows
export default {
routes: [
{
method: 'POST',
path: '/article-filter',
handler: 'articlefilter.getFilteredArticles',
config: {
auth: false,
policies: [],
middlewares: [],
},
},
{
method: 'GET',
path: '/featured-news',
handler: 'articlefilter.getFeaturedNews',
config: {
auth: false,
policies: [],
middlewares: [],
},
}
]
};
Now you can develop your own api endpoint to get results from the plugin. Here we suppose that the Single Type page contains three instances of the custom field named as left_articles, main_articles and right_articles. articlefilter.ts service is as follows
import type * as strapi from '@strapi/strapi';
type Settings = {
mainField: string;
defaultSortBy: string;
defaultSortOrder: string;
};
interface DocumentResponse {
id: number;
documentId: string;
}
interface OrderedDocumentResponse extends DocumentResponse {
order: number;
tag: string;
}
interface GetDocumentsByTagResult {
result: {
uid: string;
items: OrderedDocumentResponse[];
}[];
errorMessage: string;
}
interface FeaturedNewsResult {
left_articles: DocumentResponse[];
main_articles: DocumentResponse[];
right_articles: DocumentResponse[];
}
export default ({ strapi }: { strapi: strapi.Core.Strapi }) => ({
async getFeaturedNews() {
const multiSelectFilter = strapi.plugin("multi-select-filter").service("multiSelectFilter");
const documentResult: GetDocumentsByTagResult = await multiSelectFilter.getDocumentsGroupedByTag();
if (documentResult.errorMessage)
return;
const articleResult = documentResult.result.find(x => x.uid === "api::article.article");
if (!articleResult)
return;
let leftArticles = articleResult.items.filter(x => x.tag === "left_side_multi_select_filter");
leftArticles = leftArticles.sort(x => x.order);
let mainArticles = articleResult.items.filter(x => x.tag === "main_multi_select_filter");
mainArticles = mainArticles.sort(x => x.order);
let rightArticles = articleResult.items.filter(x => x.tag === "right_side_multi_select_filter");
rightArticles = rightArticles.sort(x => x.order);
return <FeaturedNewsResult>{
left_articles: leftArticles.map(x => <DocumentResponse>{ id: x.id, documentId: x.documentId }),
main_articles: mainArticles.map(x => <DocumentResponse>{ id: x.id, documentId: x.documentId }),
right_articles: rightArticles.map(x => <DocumentResponse>{ id: x.id, documentId: x.documentId }),
}
},
async getFilteredArticles(filter: string, publishedOnly?: boolean, queryStart?: number, queryLimit?: number) {
let res = {
result: [],
errorMessage: "",
mainField: "",
};
try {
const { findConfiguration } = strapi.plugin('content-manager').service('content-types');
const { settings }: Record<string, Settings> = await findConfiguration(strapi.contentType("api::article.article"));
const { mainField, defaultSortBy, defaultSortOrder } = settings; //defaultSortBy is 'title' in case of an article
const scheduledAtFilter = {
scheduled_at: { $lte: (new Date).toISOString() }
};
const mainFieldFilter = {
[mainField]: {
$contains: filter
}
};
const publishedFilter = {
$and: [
mainFieldFilter,
{ publishedAt: { $notNull: true } },
scheduledAtFilter,
],
};
const notPublishedFilter = {
$and: [
mainFieldFilter,
scheduledAtFilter,
],
};
let filters = (!filter)
? scheduledAtFilter
: publishedOnly
? publishedFilter
: notPublishedFilter;
const start = queryStart ?? 0;
const limit = queryLimit ?? undefined;
const sort = publishedOnly ? `scheduled_at:desc` : `${defaultSortBy}:${defaultSortOrder}`;
const total = await strapi.documents("api::article.article").count({
filters,
status: publishedOnly ? 'published' : undefined,
});
const documents = await strapi.documents("api::article.article").findMany({
fields: ["id", "publishedAt", "scheduled_at", mainField] as any,
filters,
status: publishedOnly ? 'published' : undefined,
start,
limit,
sort: sort as any,
});
const hasMeta = limit !== undefined;
const meta = !hasMeta ? undefined : {
total, // gets the total number of records
pageSize: limit, // gets the limit we set earlier
pageCount: Math.ceil(total / limit), // gives us the number of total pages
currentPage: start / limit + 1, // returns the current page
};
return {
result: documents,
mainField,
meta,
}
}
catch (error) {
console.error(error);
res.result = [];
res.errorMessage = error;
}
return res;
}
})
Time to develop your Single Type page which will be called Highlight Settings. Generate a Single Type in src/api/highlight-setting. Adjust schema.json in content-types as follows:
{
"kind": "singleType",
"collectionName": "highlight_settings",
"info": {
"singularName": "highlight-setting",
"pluralName": "highlight-settings",
"displayName": "Highlight Settings",
"description": ""
},
"options": {
"draftAndPublish": true
},
"pluginOptions": {},
"attributes": {
"name": {
"type": "string"
},
"featured_news_info": {
"type": "string"
},
"featured_news": {
"type": "component",
"repeatable": false,
"component": "highlighted.highlighted-articles"
}
}
}
Make a lifecycles.ts file here and add the following code to ensure that multiselect items will get published when you publish your Single Type.
import * as Attribute from '@strapi/types/dist/modules/documents/params/attributes';
import { Event } from '@strapi/database/dist/lifecycles';
type LifecycleEvent<T> = Event & {
result?: T; // since result is only available in `afterXXX` events
}
type MyEvent = LifecycleEvent<Attribute.GetValues<'api::highlight-setting.highlight-setting'>>;
interface MultiSelectItemId {
id: number;
documentId: string;
}
const publishByTag = async (tag: string) => {
const pluginId = "plugin::multi-select-filter.multiselect";
//delete existing published items
const multiSelectsToDelete = (await strapi.documents(pluginId).findMany({
fields: ['id', 'documentId'] as any,
filters: {
tag: { $eq: tag },
},
status: 'published',
})) as MultiSelectItemId[];
await Promise.all(multiSelectsToDelete.map(async (x) => {
await strapi.documents(pluginId).delete({
documentId: x.documentId,
filters: {
publishedAt: { $notNull: true },
}
})
}))
//publish not published items
const multiSelectsToPublish = (await strapi.documents(pluginId).findMany({
fields: ['id', 'documentId'] as any,
filters: {
$and: [
{ tag: { $eq: tag } },
{ publishedAt: { $null: true } },
]
},
})) as MultiSelectItemId[];
await Promise.all(multiSelectsToPublish.map(async (x) => {
await strapi.documents(pluginId).publish({
documentId: x.documentId,
})
}))
}
export default {
async beforeDelete(event: MyEvent) {
const where = event.params.where;
//we receive the unpublished item's id in where?.id
//get the documentId belonging to that item
const unpublishedItem = await strapi.db.query("api::highlight-setting.highlight-setting").findOne({
where,
select: ['id', 'documentId']
});
//get the id of the published version of this item
const publishedItem = await strapi.documents("api::highlight-setting.highlight-setting").findOne({
documentId: unpublishedItem.documentId,
fields: ['publishedAt'],
status: 'published',
})
//save it to strapi as a temp storage
strapi["highlight-updating-id"] = publishedItem.id;
},
async afterCreate(event: MyEvent) {
// a publish means a delete and create
// if there was a delete beforehand, try to get back our saved id which indicates id of the previous published version of the item
const where = event.params.where;
const item2UpdatingId = strapi["highlight-updating-id"];
const unpublishedItem = await strapi.db.query("api::highlight-setting.highlight-setting").findOne({
where,
select: ['id', 'documentId']
});
const publishedItem = await strapi.documents("api::highlight-setting.highlight-setting").findOne({
documentId: unpublishedItem.documentId,
fields: ['publishedAt'],
populate: ["featured_news"],
status: 'published',
})
//if the current published item isn't the saved one, we published an item
if (publishedItem.id !== item2UpdatingId || item2UpdatingId === undefined) {
const multiFilterTags = Object.keys(publishedItem.featured_news).filter(x => x !== "id");
await Promise.all(multiFilterTags.map(async (tag) => {
await publishByTag(tag)
}))
}
strapi["highlight-updating-id"] = undefined;
}
}
Finally add the following code to components / highlighted / highlighted-articles.json to add the component required by the Single Type page.
{
"collectionName": "components_highlighted_highlighted_articles",
"info": {
"displayName": "Highlighted Articles",
"icon": "dashboard",
"description": ""
},
"options": {},
"attributes": {
"left_side_multi_select_filter": {
"type": "customField",
"options": {
"publishedOnly": true,
"queryLimit": 20
},
"customField": "plugin::multi-select-filter.multiSelectFilter"
},
"main_multi_select_filter": {
"type": "customField",
"options": {
"publishedOnly": true,
"queryLimit": 20
},
"customField": "plugin::multi-select-filter.multiSelectFilter"
},
"right_side_multi_select_filter": {
"type": "customField",
"options": {
"publishedOnly": true,
"queryLimit": 20
},
"customField": "plugin::multi-select-filter.multiSelectFilter"
}
}
}
Here you go, now you can use your api endpoint to get highlighted articles in a Next JS application with the following code:
const featuredNews = await fetchAPI(`/featured-news`, []);
if (!featuredNews)
return null;
const mainArticles = featuredNews.main_articles ?? [];
const leftSideArticles = featuredNews?.left_side_articles ?? [];
const rightSideArticles = featuredNews?.right_side_articles ?? [];
FAQs
A customizable Multi-Select Filter to replace the default one.
The npm package @cslegany/multi-select-filter-strapi5 receives a total of 0 weekly downloads. As such, @cslegany/multi-select-filter-strapi5 popularity was classified as not popular.
We found that @cslegany/multi-select-filter-strapi5 demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Product
Socket's Rust support is moving to Beta: all users can scan Cargo projects and generate SBOMs, including Cargo.toml-only crates, with Rust-aware supply chain checks.
Product
Socket Fix 2.0 brings targeted CVE remediation, smarter upgrade planning, and broader ecosystem support to help developers get to zero alerts.
Security News
Socket CEO Feross Aboukhadijeh joins Risky Business Weekly to unpack recent npm phishing attacks, their limited impact, and the risks if attackers get smarter.