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

# Published Tables

> Declare tables whose rows only a publication can write

A published table is a table in your schema whose rows are written by
publication instead of by your mutations. It is stored, indexed and queried
exactly like an ordinary table. The difference is ownership: its rows change
only when Bijection switches it to a new publication.

Declare one with `definePublished` in your `schema.ts`:

```ts bijection/schema.ts {1,11-21} theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { defineSchema, defineTable, definePublished } from "bijection/server";
import { v } from "bijection/values";

export const order = v.object({
  order_number: v.string(),
  customer: v.string(),
  status: v.string(),
  amount: v.int64(),
});

export const openOrders = definePublished({
  producer: "orderSummary",
  member: "openOrders",
  row: { order_number: v.string(), customer: v.string(), amount: v.int64() },
}).index("by_customer", ["customer"]);

export const customerTotals = definePublished({
  producer: "orderSummary",
  member: "customerTotals",
  row: { customer: v.string(), open_count: v.int64(), open_amount: v.int64() },
}).index("by_customer", ["customer"]);

export default defineSchema({
  orders: defineTable(order).index("by_customer", ["customer"]),
  open_orders: openOrders,
  customer_totals: customerTotals,
});
```

The published tables are exported so that the [pipeline](/publications/pipelines)
that fills them can refer to the same declarations.

## Producer and member

`definePublished` takes three fields:

* `producer` names the pipeline that publishes this table. Every table with the
  same producer belongs to one group, and the whole group is published together.
* `member` names this table's place in the producer's output. The pipeline's
  output with the same name writes this table.
* `row` is the document type, either an object of validators or a `v.object`
  validator.

Producer and member names must start with a letter, `_` or `$`, contain only
letters, digits, `_` and `$`, and be at most 128 characters long. Two tables in
one schema cannot claim the same producer and member; deploying such a schema
is refused.

Bijection adds `_id` and `_creationTime` to every published row, as it does for
any table. See [system fields](/database/types#system-fields).

## Indexes and access rules

Declare indexes with `.index(...)` exactly as on an ordinary table, and read
through them with `withIndex`. See [indexes](/database/reading-data/indexes/indexes).

You can also attach a read rule with `.access(...)`. See
[access rules](/access/overview).

Everything that would give the rows a second writer or a second derivation is
refused when the schema is evaluated:

| Declaration | On a published table |
| - | - |
| `.index(name, fields)` | Allowed |
| `.access(...)` | Allowed |
| Staged index (`staged: true`) | Refused |
| `.searchIndex(...)`, `.vectorIndex(...)` | Refused |
| `.staged(...)` | Refused |
| `.source(...)`, `.govern(...)`, `.link(...)` | Refused |

A refused declaration throws an error such as:

```
A published relation cannot declare a search index: its rows change only through its admitted publication.
```

## Published tables are read-only

The generated data model marks a published table as not writable, so a write
is a TypeScript error:

```ts bijection/orders.ts {8} theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { mutation } from "./_generated/server";
import { v } from "bijection/values";

export const closeOrder = mutation({
  args: { id: v.id("open_orders") },
  handler: async (ctx, args) => {
    // Type error: open_orders is not a writable table.
    await ctx.db.delete("open_orders", args.id);
  },
});
```

The same rule holds at runtime. An insert, patch, replace or delete of a
published table fails with `ReadOnlyPublished`, and a
[data import](/database/import-export/import) cannot write one either. To
change what a published table holds, change its inputs or its pipeline and let
it publish again.

## Adding and removing published tables

Deploying a schema that turns a table into a published table requires that
table to be empty; a table that already holds rows is refused. Once a table is
published, a later deployment cannot silently drop `definePublished` from it or
move it to another producer. Changing a group's producer, its members or a
member's row type requires an explicit
[ownership transition](/publications/pipelines#changing-a-published-group).

## Before the first publication

A published table is unavailable until its first publication succeeds. A query
that reads it before then fails with `PublishedUnavailable`. The failed read
stays a dependency of the query, so a subscribed client receives the result as
soon as the table becomes available. See
[Reading publications](/publications/reading).
