# Publishing (/docs/code/modules/publishing)

---
title: Publishing
description: Ship a module on npm the way QUESTPIE ships its own. You describe the package once with packageConfig, and codegen writes the static module object from the same file convention an app uses.
kind: guide
package: questpie
---

You have a feature that works, and you want it in the next project too. This
page is the directory layout, the one config file it needs, and what you export
at the end.

## Lay out the package

One directory per module, under a directory you choose.

```
packages/blog/
  questpie.config.ts        # the package config, below
  src/server/plugin.ts      # your CodegenPlugin factory, optional
  src/server/modules/
    blog/
      collections/posts.ts  # same convention as an app
      jobs/digest.ts
      config/blog.ts        # needs a discover entry, see below
      fields.ts             # your field factories
      modules.ts            # this module's own dependencies
      plugin.ts             # re-exports the factory above
```

Inside a module directory the app conventions apply. `collections/` holds
collections, `routes/` holds routes, `jobs/` holds jobs.

Three files mean something extra here. `modules.ts` default-exports this
module's dependencies. `fields.ts` default-exports your field factories.
`plugin.ts` default-exports a `CodegenPlugin`, and codegen puts it on the
module's `plugin` key.

Two things need that plugin. `config/blog.ts` becomes `config.blog` only when
the plugin declares it in `discover`. Out of the box codegen finds
`config/app.ts` and `config/auth.ts` and nothing else. Your field factories
reach a consumer's `f` only through the plugin. It has to list them under
`factoryImports` on the `fieldTypes` category.

## Describe the package

Add a `questpie.config.ts` at the package root. It exports `packageConfig()`
instead of `runtimeConfig()`, and that is what puts codegen in package mode.

```ts title="packages/blog/questpie.config.ts"
import { packageConfig } from "questpie/cli";

import { blogPlugin } from "./src/server/plugin.js";

export default packageConfig({
	modulesDir: "src/server/modules", // relative to this file
	modulePrefix: "acme", // dir "blog" becomes module "acme-blog"
	plugins: [blogPlugin()], // shared by every module in the package
});
```

| Option         | What it does                                                        |
| -------------- | ------------------------------------------------------------------- |
| `modulesDir`   | The directory to scan. Every subdirectory becomes one module.       |
| `modulePrefix` | Joined to the directory name with a hyphen. Defaults to `questpie`. |
| `plugins`      | Codegen plugins applied while generating every module here.         |

Subdirectories starting with a dot are skipped. The rest are generated in name
order.

## Generate

```bash
questpie generate
```

For each subdirectory codegen writes `.generated/module.ts`, a plain object
holding everything it found. It writes `.generated/registries.ts` beside it when
the module augments a global registry. A `fields.ts` does that.

```ts title="src/server/modules/blog/.generated/module.ts (abridged)"
import _modules from "../modules";
import _coll_posts from "../collections/posts";
import _blogConfig from "../config/blog";

const _module = {
	name: "acme-blog" as const,
	modules: _modules,
	collections: { posts: _coll_posts },
	config: { blog: _blogConfig },
	// every other category, empty
};
export default _module;
```

Commit that file. It is the module, and it is what your users import.

## Export it

Give the generated object a named export from your package.

```ts title="packages/blog/src/exports/modules/blog.ts"
export { default as blogModule } from "../../server/modules/blog/.generated/module.js";
```

Someone installs `@acme/blog` and adds `blogModule` to their `modules.ts`. They
run `questpie generate`. Every collection, job and config key you wrote is now
in their app, typed.

<Callout type="info" title="`packageConfig` is for building, not consuming">
	It is dev tooling for your repo. It never ships, and nobody who installs your
	package writes one. Consuming a module is an import and one array entry.
</Callout>

## Two generated modules from one directory

A codegen plugin can declare a target with its own `moduleRoot`. Codegen then
generates a second module from that subdirectory of each module directory.

That is how `@questpie/admin` ships two modules. The second is the client module
you list in the admin app's own `modules.ts`. You need this only when your
package ships UI. See [Building a plugin](/docs/guides/build-a-plugin).
