> ## Documentation Index
> Fetch the complete documentation index at: https://docs.strapi2front.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Generated Services

> Type-safe data fetching functions for all your Strapi content types

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:

<Steps>
  <Step title="Read Operations">
    Find single items, query lists, fetch all with auto-pagination
  </Step>

  <Step title="Write Operations">
    Create, update, and delete content
  </Step>

  <Step title="Advanced Queries">
    Filtering, sorting, population, pagination, localization
  </Step>
</Steps>

## Collection Type Services

### Generated Service Example

For an **Article** collection type, strapi2front generates:

```typescript article.service.ts theme={null}
/**
 * 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

<Tabs>
  <Tab title="findMany">
    Query multiple items with filtering, sorting, and pagination.

    ```typescript theme={null}
    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}`);
    ```
  </Tab>

  <Tab title="findAll">
    Fetch all items automatically handling pagination.

    <Warning>
      Use with caution on large datasets. This method fetches ALL items.
    </Warning>

    ```typescript theme={null}
    // Fetches all published articles, paginating automatically
    const allArticles = await articleService.findAll({
      filters: { status: 'published' },
      sort: '-publishedAt',
      populate: ['author'],
    });

    console.log(`Retrieved ${allArticles.length} total articles`);
    ```
  </Tab>

  <Tab title="findOne">
    Find a single item by its documentId (v5) or id (v4).

    ```typescript theme={null}
    // Strapi v5
    const article = await articleService.findOne('abc123def456', {
      populate: ['author', 'categories', 'coverImage'],
    });

    if (article) {
      console.log(article.title);
    }
    ```

    ```typescript theme={null}
    // Strapi v4
    const article = await articleService.findOne(42, {
      populate: ['author', 'categories'],
    });
    ```
  </Tab>

  <Tab title="findBySlug">
    Find by slug field (only generated if your content type has a `slug` attribute).

    ```typescript theme={null}
    const article = await articleService.findBySlug('my-first-post', {
      populate: '*',
    });
    ```
  </Tab>

  <Tab title="create">
    Create a new entry.

    ```typescript theme={null}
    const newArticle = await articleService.create({
      title: 'New Article',
      slug: 'new-article',
      content: [
        {
          type: 'paragraph',
          children: [{ type: 'text', text: 'Hello world' }],
        },
      ],
      status: 'draft',
    });

    console.log(`Created article with ID: ${newArticle.documentId}`);
    ```
  </Tab>

  <Tab title="update">
    Update an existing entry.

    ```typescript theme={null}
    const updated = await articleService.update('abc123', {
      status: 'published',
      publishedAt: new Date().toISOString(),
    });
    ```
  </Tab>

  <Tab title="delete">
    Delete an entry.

    ```typescript theme={null}
    await articleService.delete('abc123');
    ```
  </Tab>

  <Tab title="count">
    Count entries matching filters.

    ```typescript theme={null}
    const publishedCount = await articleService.count({
      status: 'published',
    });

    console.log(`${publishedCount} published articles`);
    ```
  </Tab>
</Tabs>

## Single Type Services

For single types (like Homepage, Settings), the service is simpler:

```typescript homepage.service.ts theme={null}
/**
 * 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:

<CodeGroup>
  ```typescript Basic Filters theme={null}
  const { data } = await articleService.findMany({
    filters: {
      status: 'published',
      featured: true,
    },
  });
  ```

  ```typescript Comparison Operators theme={null}
  const { data } = await articleService.findMany({
    filters: {
      views: { $gte: 100 },  // Greater than or equal
      publishedAt: { $lt: '2024-01-01' },  // Less than
      title: { $contains: 'TypeScript' },
    },
  });
  ```

  ```typescript Logical Operators theme={null}
  const { data } = await articleService.findMany({
    filters: {
      $or: [
        { status: 'published' },
        { status: 'archived' },
      ],
      $and: [
        { views: { $gte: 100 } },
        { featured: true },
      ],
    },
  });
  ```
</CodeGroup>

### Sorting

Sort by one or multiple fields:

```typescript theme={null}
// 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:

<Tabs>
  <Tab title="Page-based">
    ```typescript theme={null}
    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}`);
    ```
  </Tab>

  <Tab title="Offset-based">
    ```typescript theme={null}
    const { data, pagination } = await articleService.findMany({
      pagination: {
        start: 10,  // Skip first 10 items
        limit: 5,   // Get 5 items
      },
    });
    ```
  </Tab>
</Tabs>

### Population

Populate related fields:

<CodeGroup>
  ```typescript Populate All theme={null}
  // Populate everything (use sparingly)
  const article = await articleService.findOne('abc123', {
    populate: '*',
  });
  ```

  ```typescript Selective Population theme={null}
  // Populate specific relations
  const article = await articleService.findOne('abc123', {
    populate: ['author', 'categories', 'coverImage'],
  });
  ```

  ```typescript Deep Population theme={null}
  // Populate nested relations
  const article = await articleService.findOne('abc123', {
    populate: {
      author: {
        populate: ['avatar'],
      },
      categories: true,
      coverImage: true,
    },
  });
  ```
</CodeGroup>

### Localization

For internationalized content types:

```typescript theme={null}
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:

```typescript theme={null}
// 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

```astro pages/blog/[slug].astro theme={null}
---
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

```astro pages/blog/page/[page].astro theme={null}
---
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

```typescript api/search.ts theme={null}
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`

```typescript theme={null}
// Strapi v4
const article = await articleService.findOne(42);  // number ID

// Strapi v5  
const article = await articleService.findOne('abc123def456');  // documentId string
```

<Note>
  strapi2front automatically generates the correct service based on your Strapi version.
</Note>

## Next Steps

<CardGroup cols={2}>
  <Card title="Schemas" icon="shield-check" href="/guides/schemas">
    Validate data with generated Zod schemas
  </Card>

  <Card title="Relations" icon="link" href="/guides/relations">
    Work with related content types
  </Card>

  <Card title="Media" icon="image" href="/guides/media">
    Upload and manage files
  </Card>

  <Card title="Types" icon="code" href="/guides/types">
    Understand generated TypeScript types
  </Card>
</CardGroup>
