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

# File Naming Conventions

> Recommended patterns for organizing spec, impl, and related files in your confect directory.

Confect requires a small number of fixed entry-point files in your `confect/` directory (see [Project Structure](/concepts/project-structure)). Beyond those, organize your API as colocated `*.spec.ts`/`*.impl.ts` pairs, one pair per group. The group's name is the file's path within `confect/` (its stem for top-level groups, the dot-joined directory path for nested groups).

## Spec and impl files

Name each group's spec and impl with `.spec.ts` and `.impl.ts` suffixes. Each file must default-export its `GroupSpec` or `GroupImpl`; additional named exports on `.spec.ts` (for example error classes) are fine.

A complete pair looks like this. The impl default-imports its sibling spec, passes it to `FunctionImpl.make` and `GroupImpl.make`, and finalizes the resulting layer with `GroupImpl.finalize`:

```ts confect/notes.spec.ts theme={null}
import { GroupSpec, FunctionSpec } from "@confect/core";
import * as Schema from "effect/Schema";

import notes from "./_generated/tables/notes";

const list = FunctionSpec.publicQuery({
  name: "list",
  args: () => Schema.Struct({}),
  returns: () => Schema.Array(notes.Doc),
});

export default GroupSpec.make().addFunction(list);
```

```ts confect/notes.impl.ts theme={null}
import { FunctionImpl, GroupImpl } from "@confect/server";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";

import databaseSchema from "./_generated/schema";
import { DatabaseReader } from "./_generated/services";
import notes from "./notes.spec";

const list = FunctionImpl.make(databaseSchema, notes, "list", () =>
  Effect.gen(function* () {
    const reader = yield* DatabaseReader;

    return yield* reader
      .table("notes")
      .index("by_creation_time", "desc")
      .collect();
  }).pipe(Effect.orDie),
);

export default GroupImpl.make(databaseSchema, notes).pipe(
  Layer.provide(list),
  GroupImpl.finalize,
);
```

Run `confect codegen` after adding or changing specs and impls.

<Note>
  A file's path within `confect/` determines the public Convex API path it
  produces—`confect/notes_and_random/notes.spec.ts` becomes
  `internal.notes_and_random.notes.list`. Renaming a file or directory renames
  the API path, so treat these paths as part of your API's contract.
</Note>

<Warning>
  Some test runners (such as Vitest and Jest) treat `.spec.ts` files as test
  files by default. If your test runner is picking up Confect spec files,
  configure it to exclude them. For example, in Vitest you can add
  `"**/*.spec.ts"` to the `test.exclude` array in your config.
</Warning>

## Native Convex functions

When a group wraps [native Convex functions](/server/plain-convex-functions) (for use with components or other libraries), place the plain function definitions in a file named after the group—without a suffix. This puts all three files for a group side by side:

```
confect/
  workpool.ts          ← native Convex function definitions
  workpool.spec.ts     ← spec (type-only imports from workpool.ts)
  workpool.impl.ts     ← impl (runtime imports from workpool.ts)
```

## Node actions

[Node action](/server/node-actions) groups follow the same `.spec.ts`/`.impl.ts` naming and nesting conventions as any other group. A group is Node-runtime when its spec is built with `GroupSpec.makeNode()` (rather than `GroupSpec.make()`); its impl is otherwise identical to a non-node impl, passing the database schema from `_generated/schema`. Confect emits Convex's `"use node"` directive into the generated module based on the spec.

```
confect/
  email.spec.ts
  email.impl.ts
```

## Nested groups

When a group contains subgroups, place each subgroup's `.spec.ts`/`.impl.ts` pair inside a subdirectory named after the parent group. The parent group itself does not need a spec or impl file—it is composed from its subgroups by their paths.

```
confect/
  billing/
    invoices.spec.ts        ← group path billing.invoices
    invoices.impl.ts
    subscriptions.spec.ts   ← group path billing.subscriptions
    subscriptions.impl.ts
```

## Full example

Putting it all together, a project using all of these conventions might look like this:

<Tree>
  <Tree.Folder name="confect" defaultOpen>
    <Tree.Folder name="_generated" defaultOpen>
      <Tree.File name="convexSchema.ts" />

      <Tree.File name="refs.ts" />

      <Tree.File name="schema.ts" />

      <Tree.File name="services.ts" />

      <Tree.File name="spec.ts" />

      <Tree.Folder name="registeredFunctions" defaultOpen>
        <Tree.File name="env.ts" />

        <Tree.Folder name="notes_and_random" defaultOpen>
          <Tree.File name="notes.ts" />

          <Tree.File name="random.ts" />
        </Tree.Folder>

        <Tree.File name="workpool.ts" />

        <Tree.File name="email.ts" />
      </Tree.Folder>

      <Tree.Folder name="tables" defaultOpen>
        <Tree.File name="notes.ts" />

        <Tree.File name="users.ts" />
      </Tree.Folder>

      <Tree.File name="id.ts" />
    </Tree.Folder>

    <Tree.Folder name="notes_and_random" defaultOpen>
      <Tree.File name="notes.spec.ts" />

      <Tree.File name="notes.impl.ts" />

      <Tree.File name="random.spec.ts" />

      <Tree.File name="random.impl.ts" />
    </Tree.Folder>

    <Tree.Folder name="tables" defaultOpen>
      <Tree.File name="notes.ts" />

      <Tree.File name="users.ts" />
    </Tree.Folder>

    <Tree.File name="email.spec.ts" />

    <Tree.File name="email.impl.ts" />

    <Tree.File name="env.spec.ts" />

    <Tree.File name="env.impl.ts" />

    <Tree.File name="http.ts" />

    <Tree.File name="workpool.ts" />

    <Tree.File name="workpool.spec.ts" />

    <Tree.File name="workpool.impl.ts" />
  </Tree.Folder>
</Tree>
