Skip to main content
strapi2front generates complete service modules with typed CRUD methods for every collection and single type in your Strapi schema.

What Gets Generated

For each content type, you get a service module with methods for:
1

Read Operations

Find single items, query lists, fetch all with auto-pagination
2

Write Operations

Create, update, and delete content
3

Advanced Queries

Filtering, sorting, population, pagination, localization

Collection Type Services

Generated Service Example

For an Article collection type, strapi2front generates:
article.service.ts
/**
 * Article Service
 * Blog article with author and categories
 * Generated by strapi2front
 * Strapi version: v5
 */

import { collection } from '../client';
import type { Article, ArticleFilters } from '@/types/collections/article';
import type { StrapiPagination } from '@/types/utils';
import type { Locale } from '../locales';

export interface FindManyOptions {
  filters?: ArticleFilters;
  pagination?: {
    /** Page number (1-indexed) - use with pageSize */
    page?: number;
    /** Number of items per page (default: 25) - use with page */
    pageSize?: number;
    /** Offset to start from (0-indexed) - use with limit */
    start?: number;
    /** Maximum number of items to return - use with start */
    limit?: number;
  };
  sort?: string | string[];
  populate?: string | string[] | Record<string, unknown>;
  locale?: Locale;  // Only if localized
  status?: 'draft' | 'published';  // Only if draft & publish enabled
}

export interface FindOneOptions {
  populate?: string | string[] | Record<string, unknown>;
  locale?: Locale;
  status?: 'draft' | 'published';
}

// Create typed collection helper
const articleCollection = collection<Article>('articles');

export const articleService = {
  /**
   * Find multiple articles
   */
  async findMany(options: FindManyOptions = {}): Promise<{ data: Article[]; pagination: StrapiPagination }> {
    const response = await articleCollection.find({
      filters: options.filters,
      pagination: options.pagination,
      sort: options.sort,
      populate: options.populate,
      locale: options.locale,
      status: options.status,
    });

    return {
      data: response.data,
      pagination: response.meta.pagination,
    };
  },

  /**
   * Find all articles (handles pagination automatically)
   */
  async findAll(options: Omit<FindManyOptions, 'pagination'> = {}): Promise<Article[]> {
    const allItems: Article[] = [];
    let page = 1;
    let hasMore = true;

    while (hasMore) {
      const { data, pagination } = await this.findMany({
        ...options,
        pagination: { page, pageSize: 100 },
      });

      allItems.push(...data);
      hasMore = page < pagination.pageCount;
      page++;
    }

    return allItems;
  },

  /**
   * Find one article by documentId
   */
  async findOne(documentId: string, options: FindOneOptions = {}): Promise<Article | null> {
    try {
      const response = await articleCollection.findOne(documentId, {
        populate: options.populate,
        locale: options.locale,
        status: options.status,
      });

      return response.data;
    } catch (error) {
      // Return null if not found
      if (error instanceof Error && error.message.includes('404')) {
        return null;
      }
      throw error;
    }
  },

  /**
   * Find one article by slug
   */
  async findBySlug(slug: string, options: FindOneOptions = {}): Promise<Article | null> {
    const { data } = await this.findMany({
      filters: { slug: { $eq: slug } } as ArticleFilters,
      pagination: { pageSize: 1 },
      populate: options.populate,
      locale: options.locale,
      status: options.status,
    });

    return data[0] || null;
  },

  /**
   * Create a new article
   */
  async create(data: Partial<Omit<Article, 'id' | 'documentId' | 'createdAt' | 'updatedAt' | 'publishedAt'>>): Promise<Article> {
    const response = await articleCollection.create({ data });
    return response.data;
  },

  /**
   * Update an article
   */
  async update(documentId: string, data: Partial<Omit<Article, 'id' | 'documentId' | 'createdAt' | 'updatedAt' | 'publishedAt'>>): Promise<Article> {
    const response = await articleCollection.update(documentId, { data });
    return response.data;
  },

  /**
   * Delete an article
   */
  async delete(documentId: string): Promise<void> {
    await articleCollection.delete(documentId);
  },

  /**
   * Count articles
   */
  async count(filters?: ArticleFilters): Promise<number> {
    const { pagination } = await this.findMany({
      filters,
      pagination: { pageSize: 1 },
    });

    return pagination.total;
  },
};

Service Methods

Query multiple items with filtering, sorting, and pagination.
const { data, pagination } = await articleService.findMany({
  filters: {
    status: 'published',
    publishedAt: { $gte: '2024-01-01' },
  },
  sort: ['-publishedAt', 'title'],
  pagination: { page: 1, pageSize: 10 },
  populate: ['author', 'categories', 'coverImage'],
});

console.log(`Found ${data.length} articles`);
console.log(`Total: ${pagination.total}, Pages: ${pagination.pageCount}`);

Single Type Services

For single types (like Homepage, Settings), the service is simpler:
homepage.service.ts
/**
 * Homepage Service (Single Type)
 * Main homepage configuration
 * Generated by strapi2front
 * Strapi version: v5
 */

import { single } from '../client';
import type { Homepage } from '@/types/collections/homepage';
import type { Locale } from '../locales';

export interface FindOptions {
  populate?: string | string[] | Record<string, unknown>;
  locale?: Locale;
  status?: 'draft' | 'published';
}

const homepageSingle = single<Homepage>('homepage');

export const homepageService = {
  /**
   * Get Homepage
   */
  async find(options: FindOptions = {}): Promise<Homepage | null> {
    try {
      const response = await homepageSingle.find({
        populate: options.populate,
        locale: options.locale,
        status: options.status,
      });

      return response.data;
    } catch (error) {
      if (error instanceof Error && error.message.includes('404')) {
        return null;
      }
      throw error;
    }
  },

  /**
   * Update Homepage
   */
  async update(data: Partial<Omit<Homepage, 'id' | 'documentId' | 'createdAt' | 'updatedAt'>>): Promise<Homepage> {
    const response = await homepageSingle.update({ data });
    return response.data;
  },

  /**
   * Delete Homepage
   */
  async delete(): Promise<void> {
    await homepageSingle.delete();
  },
};

Query Options

Filtering

Use typed filters with Strapi’s query operators:
const { data } = await articleService.findMany({
  filters: {
    status: 'published',
    featured: true,
  },
});

Sorting

Sort by one or multiple fields:
// Single field
await articleService.findMany({ sort: 'title' });

// Multiple fields
await articleService.findMany({ sort: ['-publishedAt', 'title'] });

// Descending order (prefix with -)
await articleService.findMany({ sort: '-views' });

Pagination

Two pagination styles are supported:
const { data, pagination } = await articleService.findMany({
  pagination: {
    page: 2,        // Page number (1-indexed)
    pageSize: 20,   // Items per page
  },
});

console.log(`Page ${pagination.page} of ${pagination.pageCount}`);

Population

Populate related fields:
// Populate everything (use sparingly)
const article = await articleService.findOne('abc123', {
  populate: '*',
});

Localization

For internationalized content types:
import type { Locale } from '@/strapi/locales';

// Fetch French version
const article = await articleService.findOne('abc123', {
  locale: 'fr',
});

// Fetch all French articles
const { data } = await articleService.findMany({
  locale: 'fr',
  filters: { status: 'published' },
});

Draft & Publish

For content types with draft & publish enabled:
// Get published version (default)
const article = await articleService.findOne('abc123', {
  status: 'published',
});

// Get draft version
const draft = await articleService.findOne('abc123', {
  status: 'draft',
});

Real-World Examples

Blog Post Page

pages/blog/[slug].astro
---
import { articleService } from '@/strapi/collections/article/service';
import type { Article } from '@/strapi/collections/article/types';

const { slug } = Astro.params;

const article = await articleService.findBySlug(slug, {
  populate: {
    author: { populate: ['avatar'] },
    categories: true,
    coverImage: true,
    seo: true,
  },
  status: 'published',
});

if (!article) {
  return Astro.redirect('/404');
}
---

<article>
  <h1>{article.title}</h1>
  {article.coverImage && (
    <img src={article.coverImage.url} alt={article.coverImage.alternativeText} />
  )}
  <div class="meta">
    <span>By {article.author?.name}</span>
    <time>{new Date(article.publishedAt).toLocaleDateString()}</time>
  </div>
  <div class="content">
    <BlocksRenderer content={article.content} />
  </div>
</article>

Paginated Blog List

pages/blog/page/[page].astro
---
import { articleService } from '@/strapi/collections/article/service';

const currentPage = Number(Astro.params.page) || 1;
const pageSize = 12;

const { data: articles, pagination } = await articleService.findMany({
  filters: { status: 'published' },
  sort: ['-publishedAt'],
  pagination: { page: currentPage, pageSize },
  populate: ['author', 'coverImage', 'categories'],
});
---

<div class="articles">
  {articles.map(article => (
    <ArticleCard article={article} />
  ))}
</div>

<Pagination 
  currentPage={pagination.page}
  totalPages={pagination.pageCount}
  basePath="/blog/page"
/>

Search Results

api/search.ts
import { articleService } from '@/strapi/collections/article/service';

export async function GET({ url }: { url: URL }) {
  const query = url.searchParams.get('q');
  
  if (!query) {
    return new Response(JSON.stringify({ results: [] }), {
      headers: { 'Content-Type': 'application/json' },
    });
  }
  
  const { data: results } = await articleService.findMany({
    filters: {
      $or: [
        { title: { $contains: query } },
        { excerpt: { $contains: query } },
      ],
      status: 'published',
    },
    pagination: { pageSize: 20 },
    populate: ['coverImage'],
  });
  
  return new Response(JSON.stringify({ results }), {
    headers: { 'Content-Type': 'application/json' },
  });
}

Strapi v4 Differences

For Strapi v4, the main differences are:
  • Uses id: number instead of documentId: string
  • No documentId field in responses
  • Services accept id: number parameter instead of documentId: string
// Strapi v4
const article = await articleService.findOne(42);  // number ID

// Strapi v5  
const article = await articleService.findOne('abc123def456');  // documentId string
strapi2front automatically generates the correct service based on your Strapi version.

Next Steps