strapi2front uses a by-feature (“screaming architecture”) output structure. Each content type lives in its own folder with all related code co-located.
Structure Overview
output : {
path : "src/strapi" , // Base output directory
}
Generated structure:
src/strapi/
├── collections/ # Collection types (many entries)
│ ├── article/
│ │ ├── types.ts # TypeScript interfaces
│ │ ├── schemas.ts # Zod validation schemas
│ │ ├── service.ts # API service functions
│ │ └── actions.ts # Astro Actions
│ ├── author/
│ │ ├── types.ts
│ │ ├── schemas.ts
│ │ └── service.ts
│ └── category/
│ └── ...
├── singles/ # Single types (one entry)
│ ├── homepage/
│ │ ├── types.ts
│ │ ├── schemas.ts
│ │ └── service.ts
│ └── settings/
│ └── ...
├── components/ # Reusable components
│ ├── hero.ts # Component type + schema
│ ├── seo.ts
│ └── cta-button.ts
└── shared/ # Shared utilities
├── utils.ts # System types (media, pagination, etc.)
├── client.ts # Strapi client wrapper
├── locales.ts # i18n locale definitions
├── upload-client.ts # Browser upload helper (if enabled)
└── upload-action.ts # Server upload action (if enabled)
Collections vs Singles
Content types that have multiple entries (articles, products, users, etc.) Location: collections/{singular-name}/Files:
types.ts - Type definitions + filters
schemas.ts - Create/update validation
service.ts - Full CRUD operations
actions.ts - Astro action wrappers
Example: collections/
article/
types.ts → Article, ArticleFilters
schemas.ts → articleCreateSchema, articleUpdateSchema
service.ts → articleService (findMany, findOne, create, etc.)
actions.ts → article actions
Content types that have one entry (homepage, settings, about page, etc.) Location: singles/{singular-name}/Files:
types.ts - Type definition only (no filters)
schemas.ts - Update validation only
service.ts - find/update/delete operations
Example: singles/
homepage/
types.ts → Homepage
schemas.ts → homepageUpdateSchema
service.ts → homepageService (find, update, delete)
Single types don’t have create() or findMany() - there’s only one instance.
File Naming
All folder and file names use kebab-case derived from your Strapi content type names:
Strapi Content Type Folder Name Import Path Articlearticle/collections/article/BlogPostblog-post/collections/blog-post/ProductCategoryproduct-category/collections/product-category/SEO (component)seo.tscomponents/seoCTAButton (component)cta-button.tscomponents/cta-button
The converter uses singularName from Strapi (e.g., “article”, “blogPost”) and converts to kebab-case.
Import Patterns
strapi2front does NOT generate barrel exports (index.ts). Always import from specific files:
✅ Correct - Import from specific file
❌ Incorrect - No barrel exports
import { articleService } from '@/strapi/collections/article/service' ;
import type { Article } from '@/strapi/collections/article/types' ;
import { articleCreateSchema } from '@/strapi/collections/article/schemas' ;
Multiple Imports from Same Feature
// Import multiple items from same file
import type {
Article ,
ArticleFilters ,
ArticleCreateInput ,
ArticleUpdateInput
} from '@/strapi/collections/article/types' ;
// Import from different files
import { articleService } from '@/strapi/collections/article/service' ;
import { articleCreateSchema } from '@/strapi/collections/article/schemas' ;
import type { Article } from '@/strapi/collections/article/types' ;
Generated File Contents
Types File
collections/{name}/types.ts
/**
* Article
* News and blog articles
* Generated by strapi2front
*/
import type { StrapiBaseEntity , StrapiMedia , BlocksContent } from '../../shared/utils' ;
import type { Author } from '../../collections/author/types' ;
import type { Tag } from '../tag/types' ;
import type { Seo } from '../../components/seo' ;
export interface Article extends StrapiBaseEntity {
title : string ;
slug : string ;
content : BlocksContent ;
excerpt ?: string ;
cover : StrapiMedia | null ;
author : Author | null ;
tags : Tag [];
seo : Seo | null ;
publishedDate : string ;
}
export interface ArticleFilters {
id ?: number | { $eq ?: number ; $ne ?: number ; $in ?: number []; $notIn ?: number [] };
documentId ?: string | { $eq ?: string ; $ne ?: string };
title ?: string | { $contains ?: string ; $startsWith ?: string ; $endsWith ?: string };
slug ?: string | { $eq ?: string };
createdAt ?: string | { $gt ?: string ; $gte ?: string ; $lt ?: string ; $lte ?: string };
updatedAt ?: string | { $gt ?: string ; $gte ?: string ; $lt ?: string ; $lte ?: string };
publishedAt ?: string | null | { $eq ?: string ; $ne ?: string ; $null ?: boolean };
$and ?: ArticleFilters [];
$or ?: ArticleFilters [];
$not ?: ArticleFilters ;
}
Includes:
Interface extending StrapiBaseEntity
Relations imported from other types
Filter interface for querying (collections only)
collections/{name}/schemas.ts
/**
* Article Schemas
* Generated by strapi2front
*/
import { z } from 'zod' ;
import { seoSchema } from '../../components/seo' ;
/**
* Zod schema for creating articles
*/
export const articleCreateSchema = z . object ({
title: z . string (),
slug: z . string (),
content: z . array ( z . any ()), // BlocksContent
excerpt: z . string (). optional (),
cover: z . number (). optional (),
author: z . string (). optional (), // documentId
tags: z . array ( z . string ()). optional (),
seo: seoSchema . optional (),
publishedDate: z . string (). optional (),
});
/**
* Zod schema for updating articles (all fields optional)
*/
export const articleUpdateSchema = z . object ({
title: z . string (). optional (),
slug: z . string (). optional (),
content: z . array ( z . any ()). optional (),
excerpt: z . string (). optional (),
cover: z . number (). optional (),
author: z . string (). optional (),
tags: z . array ( z . string ()). optional (),
seo: seoSchema . optional (),
publishedDate: z . string (). optional (),
});
export type ArticleCreateInput = z . infer < typeof articleCreateSchema >;
export type ArticleUpdateInput = z . infer < typeof articleUpdateSchema >;
Includes:
Create schema (required fields enforced)
Update schema (all fields optional)
TypeScript types inferred from schemas
collections/{name}/service.ts
See Features > Services for complete service API. Exports:
{name}Service object with methods:
findMany() - Paginated list
findAll() - All entries (auto-paginated)
findOne() - By documentId/id
findBySlug() - By slug (if exists)
create() - Create new
update() - Update existing
delete() - Delete
count() - Count with filters
collections/{name}/actions.ts
/**
* Article Actions (Astro)
* Generated by strapi2front
*/
import { defineAction } from 'astro:actions' ;
import { z } from 'zod' ;
import { articleService } from './service' ;
import { articleCreateSchema , articleUpdateSchema } from './schemas' ;
const findManySchema = z . object ({
filters: z . any (). optional (),
pagination: z . object ({
page: z . number (). optional (),
pageSize: z . number (). optional (),
}). optional (),
populate: z . any (). optional (),
});
export const article = {
findMany: defineAction ({
input: findManySchema ,
handler : async ( input ) => {
return articleService . findMany ( input );
},
}),
findOne: defineAction ({
input: z . object ({ documentId: z . string () }),
handler : async ({ documentId }) => {
return articleService . findOne ( documentId );
},
}),
create: defineAction ({
input: articleCreateSchema ,
handler : async ( input ) => {
return articleService . create ( input );
},
}),
// ... update, delete actions
};
Components
Components generate a single file with type + schema:
/**
* SEO component
* Category: shared
* Generated by strapi2front
*/
import { z } from 'zod' ;
import type { StrapiMedia } from '../shared/utils' ;
export interface Seo {
id : number ;
metaTitle : string ;
metaDescription : string ;
metaImage : StrapiMedia | null ;
keywords ?: string ;
}
/**
* Zod schema for SEO component
*/
export const seoSchema = z . object ({
metaTitle: z . string (),
metaDescription: z . string (),
metaImage: z . number (). optional (),
keywords: z . string (). optional (),
});
export type SeoInput = z . infer < typeof seoSchema >;
Shared Files
System types used across all generated code: /**
* Strapi utility types
* Generated by strapi2front
* Strapi version: v5
*/
export interface StrapiBaseEntity {
id : number ;
documentId : string ; // v5 only
createdAt : string ;
updatedAt : string ;
publishedAt : string | null ;
}
export interface StrapiMedia {
id : number ;
documentId : string ;
name : string ;
alternativeText : string | null ;
caption : string | null ;
width : number ;
height : number ;
url : string ;
formats : { /* ... */ } | null ;
// ...
}
export interface StrapiPagination {
page : number ;
pageSize : number ;
pageCount : number ;
total : number ;
}
export interface StrapiResponse < T > { /* ... */ }
export interface StrapiListResponse < T > { /* ... */ }
export interface StrapiFileInfo { /* ... */ }
// Rich text content
export type BlocksContent = unknown []; // or from @strapi/blocks-react-renderer
Wrapper around @strapi/client with type safety: import { strapi } from '@strapi/client' ;
export interface ClientOptions {
authToken ?: string ;
baseURL ?: string ;
}
export function createStrapiClient ( options ?: ClientOptions ) { /* ... */ }
export const strapiClient = createStrapiClient ();
export function collection < T >( pluralName : string , clientOptions ?: ClientOptions ) { /* ... */ }
export function single < T >( singularName : string , clientOptions ?: ClientOptions ) { /* ... */ }
export const files = {
upload : async ( file : File , options ? ) => { /* ... */ },
find : async ( params ? ) => { /* ... */ },
// ...
};
i18n locale definitions from your Strapi instance: /**
* Strapi locales
* Generated by strapi2front
*/
export const locales = [ 'en' , 'es' , 'fr' ] as const ;
export type Locale = typeof locales [ number ];
export const defaultLocale : Locale = 'en' ;
export const localeNames : Record < Locale , string > = {
'en' : 'English' ,
'es' : 'Español' ,
'fr' : 'Français' ,
};
export function isValidLocale ( code : string ) : code is Locale {
return locales . includes ( code as Locale );
}
export function getLocaleName ( code : Locale ) : string {
return localeNames [ code ] || code ;
}
Path Alias Setup
For cleaner imports, configure a path alias in your project:
TypeScript
Astro
Next.js
Vite
{
"compilerOptions" : {
"baseUrl" : "." ,
"paths" : {
"@/strapi/*" : [ "./src/strapi/*" ]
}
}
}
Usage: import { articleService } from '@/strapi/collections/article/service' ;
export default defineConfig ({
vite: {
resolve: {
alias: {
'@/strapi' : '/src/strapi' ,
},
},
} ,
}) ;
{
"compilerOptions" : {
"paths" : {
"@/strapi/*" : [ "./src/strapi/*" ]
}
}
}
Next.js automatically supports tsconfig.json paths. import { defineConfig } from 'vite' ;
import path from 'path' ;
export default defineConfig ({
resolve: {
alias: {
'@/strapi' : path . resolve ( __dirname , './src/strapi' ),
},
} ,
}) ;
Why By-Feature?
Projects can grow to hundreds of content types without becoming unwieldy. Each content type is isolated in its own folder. collections/
article/ ← 4 files
author/ ← 4 files
category/ ← 4 files
... ← 100+ more, easy to navigate
Everything related to a content type lives together. Want to see all article-related code? Look in collections/article/. No hunting across types/, services/, schemas/ folders.
Import paths clearly show relationships: // Article depends on Author and Tag
import type { Author } from '../../collections/author/types' ;
import type { Tag } from '../tag/types' ;
Renaming or removing a content type? Just delete/rename its folder. All related code is co-located.
Alternative Structures
strapi2front currently only supports by-feature structure. Future versions may add:
By-type structure - Group by file type (types/, services/, etc.)
Flat structure - All files in one directory
Custom templates - Define your own structure
Output Example
With this Strapi schema:
Collections: Article, Author, Category
Singles: Homepage
Components: SEO, Hero
You get:
src/strapi/
├── collections/
│ ├── article/
│ │ ├── types.ts (Article, ArticleFilters)
│ │ ├── schemas.ts (articleCreateSchema, articleUpdateSchema)
│ │ ├── service.ts (articleService)
│ │ └── actions.ts (article actions)
│ ├── author/
│ │ ├── types.ts
│ │ ├── schemas.ts
│ │ ├── service.ts
│ │ └── actions.ts
│ └── category/
│ └── ...
├── singles/
│ └── homepage/
│ ├── types.ts (Homepage)
│ ├── schemas.ts (homepageUpdateSchema)
│ └── service.ts (homepageService)
├── components/
│ ├── seo.ts (Seo, seoSchema)
│ └── hero.ts (Hero, heroSchema)
└── shared/
├── utils.ts
├── client.ts
├── locales.ts
└── upload-client.ts (if enabled)
Next Steps