Was this page helpful?

Agents rules

Agents rules define how AI assistants, such as coding copilots or IDE-integrated agents, behave when they generate or modify code for Contentful apps. By setting clear rules, you improve reliability, reduce rework, and ensure that generated code follows Contentful best practices.

Why agents rules matter

AI tools can accelerate development, but without clear guidance they can introduce inconsistencies, generate unsupported code, or ignore existing patterns in your repository. Agents rules act as guardrails, helping agents understand your Contentful project context and align outputs with your team’s conventions.

How agents rules work

Define rules in one or more text files. Each agent can have its own rules implementation (for example, Cursor Rules). In this guide, use AGENTS.md, an open format supported by multiple coding agents. Most AI-enabled development tools automatically pick up these rules when they generate or refactor code.

A good rule set includes:

  • Repository context: Describe the repository structure.
  • Dos and don’ts: State what agents should and should not do.
  • Official tooling: Reference Contentful tools (CLI, SDKs, APIs) instead of custom scaffolding. For example, see the App Framework.
  • Verification: Require validation through tests, reviews, and automated checks.

You can also include rules you want to enforce, such as adding tests or following a specific architectural style.

App-specific rules

If your agent supports nested rule files, place an AGENTS.md file in the repository root, and optionally add another AGENTS.md file inside an app folder to apply rules specific to that app.

Best practices

Use these practices:

  • Keep instructions concise: Agents perform better with short, explicit rules.
  • Evolve rules: Update AGENTS.md as your repository grows or your conventions change.
  • Cross-reference docs: Link to official Contentful documentation to prevent unsupported implementations.

AGENTS.md example file

You can use the following AGENTS.md example as a starting point. Copy it into the root of your repository and adjust it to match your preferences. The example is based on a React application using TypeScript and Vite.

# Agents Rules for Contentful Apps

## 1) Context & Scope

- This repo contains multiple **Contentful Apps** built with the **App Framework** (React + TypeScript).
- Apps are bundled with **Vite** and tested with **Vitest**.
- Apps must follow Contentful design and UX conventions using **Forma 36** components.
- Agents must:
  - Load the **entire repository** into context (not just a single app).
  - Prefer **reuse** of existing components, hooks, utilities, and patterns.
  - Respect the repository’s **ESLint / Prettier / TypeScript** configuration.
  - Suggest solutions aligned with **Vite** build tooling (avoid Webpack/CRA assumptions).

---

## 2) Golden Rules (TL;DR)

1. **Use React + TypeScript + Vite + Vitest + Forma 36** in all solutions.
2. **Use official Contentful SDKs and APIs** and reference documentation when proposing solutions.
3. **Inspect `package.json` first** in the workspace and app:
   - Reuse installed libraries and versions.
   - Add new dependencies only if required and provide justification.
4. **Do not use deprecated APIs** (Contentful, Forma 36, React, Vite, or other libraries).
5. **TypeScript strictness**: avoid `any` unless absolutely unavoidable; prefer precise types.
6. **Clean Code + SOLID principles**: small units, single responsibility, dependency inversion.
7. Prefer **small, incremental changes** with clear scope and checklists. Do not add changes that were not requested.
8. When commiting changes to git, create atomic commmits and use **Conventional Commits** (https://www.conventionalcommits.org/en/v1.0.0/).

---

## 3) Official Tooling & Documentation

- App Framework & App SDK  
  https://www.contentful.com/developers/docs/extensibility/app-framework/  
  https://www.contentful.com/developers/docs/extensibility/app-framework/sdk/
- App Actions
  https://www.contentful.com/developers/docs/extensibility/app-framework/app-actions/
- App Functions
  https://www.contentful.com/developers/docs/extensibility/app-framework/functions/#use-case-app-actions
- Forma 36 (Design System & React UI Library)  
  https://f36.contentful.com/  
  https://www.contentful.com/developers/docs/extensibility/app-framework/forma-36/
- API references (index)  
  https://www.contentful.com/developers/docs/references/
  - Content Management API (CMA): https://www.contentful.com/developers/docs/references/content-management-api/
  - Content Delivery API (CDA): https://www.contentful.com/developers/docs/references/content-delivery-api/
  - GraphQL Content API: https://www.contentful.com/developers/docs/references/graphql/
  - Images API: https://www.contentful.com/developers/docs/references/images-api/
  - Preview API: https://www.contentful.com/developers/docs/references/content-preview-api/

---

## 4) Dependencies & Libraries Policy

- **Inspect `package.json`** in the target app.
- **Reuse** installed libraries and versions.
- **Do not introduce** new dependencies unless:
  - No adequate existing library is available, and
  - The ROI is clear (maintainability, reliability, performance).
- If adding a dependency, provide:
  - Short justification and rejected alternatives.
  - License, maintenance, and compatibility check.
  - Versioning consistent with repo conventions.
- **No deprecated APIs** (always check release notes).

---

## 5) TypeScript, Coding Standards & Architecture

- Follow standard TypeScript and React coding conventions.
- **TypeScript**
  - Avoid `any`. Use explicit, narrow types (interfaces, unions, generics).
  - Export types for public interfaces; keep internal types scoped.
- **Clean Code + SOLID**
  - Single Responsibility: each module does one thing.
  - Open/Closed: extend via composition.
  - Liskov, Interface Segregation, Dependency Inversion: stable boundaries, injected collaborators.
  - Clear names, small functions, shallow nesting.
- **Error handling & UX**
  - Fail gracefully with Forma 36 notifications and accessible messages.
  - Developer logs must be actionable, not user-facing stack traces.
- **Performance**
  - Optimize renders in React (memoization, hooks) only when profiling shows benefit.
- **Accessibility**
  - Use Forma 36 components (a11y-ready). Add labels/roles/alt text where required.

---

## 6) UI with Forma 36 (mandatory)

- Always use **Forma 36** components, tokens, spacing, and density rules.
- Avoid plain HTML and ad-hoc CSS; prefer F36 tokens for styling.
- Use Forma 36 icons and layout primitives for consistent spacing and typography.

---

## 7) Folder Organization

- All apps must reside in the `/apps` directory.
- Each app should be self-contained and avoid cross-app dependencies.

---

## 8) Testing Requirements

When adding tests:

- Use **Vitest** as the default testing framework.
- Use **React Testing Library** for component testing.
- What to test:
  - Cover critical logic paths and edge cases.
  - Prefer tests that resemble real user scenarios.
- Tests must run correctly under Vite + Vitest setup.

---

## 9) Code Suggestions scope

- Keep proposed code simple and to the point.
- UI matches **Forma 36** guidelines and accessibility requirements.
- Confirmed **no deprecated** methods by checking documentation.
- Propose to add tests after a code change.

After adding or changing code, post a short explanation. Explanation must include:

- **Goal** (what changes and why).
- **Approach** (high level) + links to the exact SDK/API/F36 docs.
- **Scope** (files/areas affected) and **dependencies** (reuse existing; propose new only if necessary).

Agent rules examples for testing

These are some rules you can add to your agent while testing

# Unit testing rules example

## Testing framework and libraries
- Use Vitest as the primary testing framework.
- Use TypeScript for all test files.
- Use descriptive test names that explain the behavior being tested.
- Use React Testing Library.

## Test structure and development patterns
- Group related tests using `describe` blocks.
- Use `beforeEach` and `afterEach` hooks for setup and cleanup.
- Create test data factories for complex objects.
- Mock external dependencies using `vi.fn()` and `vi.mock()`.
- Follow Arrange-Act-Assert pattern for all tests.
- Use the TDD approach: create the suite with all use cases first and then the implementation.

## Mocking guidelines
- Mock external API calls and services.
- Use `vi.spyOn()` for spying on methods.
- Create mock implementations for complex dependencies.

## Test utilities
- Create factory functions with sensible defaults.
- Allow parameter overrides for specific test cases.
- Use TypeScript interfaces for type safety.
- Include both valid and invalid test data.

# Integration Testing Rules Example

## Test Scope and Boundaries
- Test interactions between multiple components or services.
- Test data flow across system boundaries.
- Test external API integrations and third-party services.

## Integration Test Patterns
- Test complete user workflows from start to finish.
- Test error handling and rollback scenarios.
- Test performance under realistic data loads.
- Test concurrent operations and race conditions.
- Test system resilience and recovery.

You can also provide concrete testing examples to give the agent a clear, limited scope as a starting point, which helps avoid hallucinations.

  • This is an example of a unit test:

    it('selects and deselects content types, showing and removing pills', async () => {
        render(<TestWrapper />);
        const user = userEvent.setup();
    
        // Wait for content types to be loaded
        const blogPostOption = await screen.findByText('Blog Post');
    
        await user.click(blogPostOption);
    
        expect(await screen.findByLabelText('Close')).toBeInTheDocument();
    
        const closeButton = screen.getByLabelText('Close');
        await user.click(closeButton);
    
        await waitFor(() => {
          expect(screen.queryByLabelText('Close')).toBeNull();
        });
    });
  • This is an example of a simple integration test:

    it('removes entry from config and table when all fields are disconnected', async () => {
        mockGetEntryConnectedFields.mockResolvedValue([
        { fieldId: 'title', moduleName: 'mod1', updatedAt: '2024-05-01T10:00:00Z' },
        { fieldId: 'description', moduleName: 'mod2', updatedAt: '2024-05-01T10:00:00Z' },
        ]);
        mockRemoveEntryConnectedFields.mockResolvedValue({});
        mockGetConnectedFields.mockResolvedValueOnce({
        'entry-id': [
            { fieldId: 'title', moduleName: 'mod1', updatedAt: '2024-05-01T10:00:00Z' },
            { fieldId: 'description', moduleName: 'mod2', updatedAt: '2024-05-01T10:00:00Z' },
        ],
        });
        // After disconnect: entry is removed
        mockGetConnectedFields.mockResolvedValueOnce({});
        mockCma.entry.getMany = vi.fn().mockResolvedValue({
        items: [
            {
            sys: {
                id: 'entry-id',
                contentType: { sys: { id: 'Fruits' } },
                updatedAt: new Date().toISOString(),
                publishedAt: new Date().toISOString(),
            },
            fields: {
                title: { 'en-US': 'Banana' },
                description: { 'en-US': 'Description value' },
            },
            },
        ],
        });
        mockCma.contentType.get = vi.fn().mockResolvedValue({
        displayField: 'title',
        sys: { id: 'Fruits' },
        fields: [
            { id: 'title', name: 'Title', type: 'Text' },
            { id: 'description', name: 'Description', type: 'Text' },
        ],
        });
    
        render(<Page />);
        // Table should show the entry
        expect(await screen.findByText('Banana')).toBeInTheDocument();
        expect(screen.getByText('2')).toBeInTheDocument();
    
        const btn = await screen.findByRole('button', { name: /Manage fields/i });
        fireEvent.click(btn);
        await screen.findByRole('dialog');
    
        const selectAllCheckbox = screen.getByTestId('select-all-fields') as HTMLInputElement;
        fireEvent.click(selectAllCheckbox);
    
        const disconnectBtn = screen.getByRole('button', { name: /Disconnect/i });
        fireEvent.click(disconnectBtn);
    
        // After disconnect, entry should be removed from config and table, and notification shown
        await waitFor(() => {
        expect(mockRemoveEntryConnectedFields).toHaveBeenCalledWith('entry-id');
        expect(mockSdk.notifier.success).toHaveBeenCalledWith(
            '2 fields disconnected successfully.'
        );
        expect(screen.queryByText('Banana')).not.toBeInTheDocument();
        expect(
            screen.getByText(
            'No connected content. Sync entry fields from the entry page sidebar to get started.'
            )
        ).toBeInTheDocument();
        });
    });