---
title: Change history
description: What every committed change to a flag or segment records, why the trail outlives the target, and how to read it back.
type: explanation
product: flags
summary: The record of what changed, when, and by whom, kept after the flag is gone
updated: 2026-09-28
order: 1
---

A flag is a decision that changes over time, so the current state of a flag is
never the whole story. A ConfigChange is one committed change to a flag or a
segment of an environment: the audit trail that answers what the value was
before, who changed it, and when. It is written by the platform, not by you —
there is no call that creates one.

## What one record holds

<PropertyTable
	properties={[
		{
			name: 'target',
			type: 'string',
			description: 'The flag or segment changed.',
		},
		{
			name: 'action',
			type: 'ConfigChangeAction',
			description: 'What happened to it. One of create, update, delete.',
		},
		{
			name: 'target_generation',
			type: 'int64',
			description: 'The target’s generation after the change.',
		},
		{
			name: 'spec',
			type: 'struct',
			description: 'The target’s spec after the change, as JSON; empty for a delete.',
		},
		{
			name: 'principal',
			type: 'string',
			description: 'Who made it: key:<key id>, or user:<id> from the console.',
		},
		{
			name: 'origin_request_id',
			type: 'string',
			description: 'The request that made it (Sylphx-Request-Id).',
		},
	]}
/>

`meta.create_time` is when the change committed, and `spec` is the state the
change left behind — so a record is a complete statement of the target at that
moment, not a diff to be replayed. Reconstructing history is therefore reading
the records in order and taking each one as the state from there on.

The two fields that make it an audit trail rather than a log are `principal`
and `origin_request_id`. `principal` separates a change made with an API key
from one made by a person in the console, and `origin_request_id` is the
request id the caller already saw in the response, so a change found in the
trail can be traced back to the exact call that made it.

## The trail outlives the target

Records are kept after the flag or segment is deleted. A delete is itself a
record — `action` is `delete` and `spec` is empty — and every record before it
stays, so the history of a flag that no longer exists is still answerable. That
is the reason the trail is its own collection rather than a field on the flag:
the flag's own storage goes away when the flag does, and the record of what it
was must not.

## Reading it back

```bash
sylphx config config-changes list orgs/acme/projects/shop/envs/production
```

A change's id sorts in commit order, so the listing reads as a history without
sorting it yourself. To follow one flag or segment rather than the whole
environment, filter on its name:

```bash
sylphx config config-changes list orgs/acme/projects/shop/envs/production --filter 'target = "orgs/acme/projects/shop/envs/production/config_flags/config-flag"'
```

The filter supports `target = "<flag or segment name>"`, and a page holds 50
records by default, up to 1000. One record on its own is a `get`:

```bash
sylphx config config-changes get orgs/acme/projects/shop/envs/production/config_changes/config-change
```

<RelatedDocs
	links={[
		{
			href: '/docs/flags/targeting',
			label: 'Targeting rules',
			description: 'What a change to a rule means for live evaluations.',
		},
		{
			href: '/docs/flags/segments',
			label: 'Segments',
			description: 'The audience a change reaches through every flag that names it.',
		},
		{
			href: '/docs/api/config_changes',
			label: 'The config_changes collection',
			description: 'Every method, its scope and its examples.',
		},
	]}
/>
