# Sinks and materialization

Source: https://docs.graphops.xyz/substreams/concepts/sinks-and-materialization

Decide how a block stream becomes data your application can query. A sink writes Substreams output
to your database, files, or queue and tracks its cursor so it can recover after a disconnect. You
choose between a standard sink and your own consumer.

## Consumption modes

Run a consumer or sink in your own infrastructure, or contact GraphOps about enterprise sink hosting.

| Mode               | GraphOps support | Use it for                                                         |
| ------------------ | ---------------- | ------------------------------------------------------------------ |
| Direct gRPC stream | Self-service     | Application consumers and custom materializers.                    |
| Customer-run sink  | Self-service     | Writing to SQL, files, queues, or custom stores.                   |
| Hosted sink        | Enterprise       | GraphOps-managed sink hosting. Contact support in the platform UI. |

## Choose a sink

Each destination has a standard Substreams tool. The CLI sinks take your GraphOps endpoint with
`-e` and read your API key from `SUBSTREAMS_API_KEY`.

| Destination            | Tool                                                                              | Guide                                                                                                                                  |
| ---------------------- | --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| Your application       | Substreams libraries for JavaScript, Go, Rust, or Python                          | [Substreams:Stream](https://docs.substreams.dev/how-to-guides/sinks/stream)                                                            |
| Postgres or ClickHouse | `substreams sink postgres` or `substreams sink clickhouse`, built into the CLI    | [Run a sink](/substreams/guides/run-a-sink), [Substreams:SQL](https://docs.substreams.dev/how-to-guides/sinks/sql)                     |
| Files                  | `substreams sink protojson` for JSONL; `substreams-sink-files` for CSV or Parquet | [ProtoJSON](https://docs.substreams.dev/how-to-guides/sinks/protojson), [Files](https://docs.substreams.dev/how-to-guides/sinks/files) |
| Google Cloud Pub/Sub   | `substreams sink pubsub`                                                          | [Substreams:PubSub](https://docs.substreams.dev/how-to-guides/sinks/pubsub)                                                            |

The SQL sink needs an output module that emits `DatabaseChanges`, usually `db_out`, or Protobuf
output annotated for relational mappings.

## Sink responsibilities

A standard sink takes care of most of these. If you write your own consumer, it must do all of them.

_A sink decodes the output module, applies each block's data to durable state, and persists the cursor only after that commit. On an undo signal it rolls local state back to the last valid block._

A production sink should:

* Decode the output module type.
* Apply block-scoped data to durable state.
* Persist cursors after durable commits.
* Handle undo signals and rollback local state.
* Expose metrics for processed blocks, bytes out, bytes read, bytes written, lag, retries, and
failures.
* Bound worker concurrency according to the organization's limits.

## Next steps

* [Run a sink](/substreams/guides/run-a-sink): Write a package's output to Postgres and verify the rows.
* [Resume with cursors](/substreams/guides/resume-with-cursors): Apply the commit order in a consumer you write yourself.
* [Usage and limits](/substreams/usage-and-limits): Size sink concurrency to your organization's limits.