Need to test against production data without exposing sensitive information? Anonymized branches let you create development copies with masked personally identifiable information (PII) - such as emails, phone numbers, and other sensitive data.
Neon uses PostgreSQL Anonymizer for static data masking, and applies masking rules when you create or update the branch. This approach gives you realistic test data while protecting user privacy and supporting compliance requirements like GDPR.
Key characteristics:
- Static masking: Data is masked once during branch creation or when you rerun anonymization
- PostgreSQL Anonymizer integration: Uses the PostgreSQL Anonymizer extension's masking functions
- Branch-specific rules: You can define different masking rules for each anonymized Neon branch
Static versus dynamic masking
This feature uses static masking, which permanently transforms data in the branch when anonymization runs. Unlike dynamic masking (which masks data during queries), static masking creates an actual masked copy of the data. To get fresh data from the parent, create a new anonymized branch.
Create a branch with anonymized data
Select Anonymized data as the data option when creating a new branch.
- Navigate to your project in the Neon Console
- Select Projects -> Branches from the sidebar
- Click New Branch
- In the Create new branch dialog:
- Select your Parent branch (typically
productionormain) - (Optional) Enter a Branch name
- (Optional) Automatically delete branch after is checked by default with 1 day selected. You can change it, uncheck it, or leave it as is to automatically delete the branch after the specified time.
- Under data options, select Anonymized data
- Select your Parent branch (typically
- Click Create

After creation, the Console loads the Data Masking page where you define and execute anonymization rules for your branch.
Manage masking rules
From the Data Masking page:
- Select the schema, table, and column you want to mask.
- Choose a masking function from the dropdown list (e.g., "Dummy Free Email" to execute
anon.dummy_free_email()). The Console provides a curated list of common functions. For the full set of PostgreSQL Anonymizer functions, you must use the API. - Repeat for all sensitive columns.
- When you are ready, click
Apply masking rulesto start the anonymization job. You can monitor its progress on this page or via the API.

Important: Rerunning the anonymization process on the anonymized branch applies rules to previously anonymized data, not fresh data from the parent branch. To start from the parent's original data, create a new anonymized branch.
Common workflow
- Create an anonymized branch from your production branch.
- Define masking rules for sensitive columns (emails, names, addresses, etc.).
- Apply the masking rules.
- Connect your development environment to the anonymized branch.
- When you need fresh data, create a new anonymized branch.
How anonymization works
When you create a branch with anonymized data:
- Neon creates a new branch with the schema and data from the parent branch.
- You define masking rules for tables and columns containing sensitive data:
- Console: The Data Masking page opens automatically after branch creation.
- API: Include masking rules in the creation request or add them later via the masking rules endpoint.
- You apply the masking rules (in Console, click Apply masking rules), and the PostgreSQL Anonymizer extension masks the branch data.
- You can update rules and rerun anonymization on the branch as needed.
The parent branch data remains unchanged. Rerunning anonymization applies rules to the branch's current (already masked) data, not fresh data from the parent.
note
The branch is unavailable for connections while anonymization is in progress.
Limitations
- Currently cannot reset to parent, restore, or delete the read-write endpoint for anonymized branches.
- Rerunning anonymization works on already-masked data. Create a new branch for fresh parent data.
- Branch is unavailable during anonymization.
- Masking does not enforce database constraints (e.g., primary keys can be masked as NULL).
- The Console provides a curated subset of masking functions - use the API for all PostgreSQL Anonymizer masking functions.
API reference
The Neon API provides comprehensive control over anonymized branches, including access to all PostgreSQL Anonymizer masking functions and the ability to export/import masking rules for management outside of Neon.
Create anonymized branch
POST /projects/{project_id}/branch_anonymizedCreates a new branch with anonymized data using PostgreSQL Anonymizer for static masking.
Request body parameters:
masking_rules(optional): Array of masking rules to apply to the branchstart_anonymization(optional): Set totrueto automatically start anonymization after creation
Example request:
curl -X POST \
'https://console.neon.tech/api/v2/projects/{project_id}/branch_anonymized' \
-H 'Authorization: Bearer $NEON_API_KEY' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"masking_rules": [
{
"database_name": "neondb",
"schema_name": "public",
"table_name": "users",
"column_name": "email",
"masking_function": "anon.dummy_free_email()"
},
{
"database_name": "neondb",
"schema_name": "public",
"table_name": "users",
"column_name": "age",
"masking_function": "anon.random_int_between(25,65)"
}
],
"start_anonymization": true
}'Response body
Returns the created branch object with restricted_actions indicating operations not allowed on anonymized branches (restore and delete read-write endpoint).
{
"branch": {
"id": "br-divine-feather-a1b2c3d4",
"project_id": "purple-moon-12345678",
"parent_id": "br-plain-hill-e5f6g7h8",
"parent_lsn": "0/1C3C998",
"name": "br-divine-feather-a1b2c3d4",
"current_state": "init",
"pending_state": "ready",
"state_changed_at": "2025-10-16T02:58:58Z",
"creation_source": "console",
"primary": false,
"default": false,
"protected": false,
"cpu_used_sec": 0,
"compute_time_seconds": 0,
"active_time_seconds": 0,
"written_data_bytes": 0,
"data_transfer_bytes": 0,
"created_at": "2025-10-16T02:58:58Z",
"updated_at": "2025-10-16T02:58:58Z",
"init_source": "parent-data",
"restricted_actions": [
{
"name": "restore",
"reason": "cannot restore anonymized branches"
},
{
"name": "delete-rw-endpoint",
"reason": "cannot delete read-write endpoints for anonymized branches"
}
]
},
"endpoints": [
{
"host": "ep-fragrant-breeze-a1b2c3d4.us-east-1.aws.neon.tech",
"id": "ep-fragrant-breeze-a1b2c3d4",
"project_id": "purple-moon-12345678",
"branch_id": "br-divine-feather-a1b2c3d4",
"autoscaling_limit_min_cu": 1,
"autoscaling_limit_max_cu": 4,
"region_id": "aws-us-east-1",
"type": "read_write",
"current_state": "init",
"pending_state": "active",
"settings": {
"preload_libraries": {
"use_defaults": false,
"enabled_libraries": ["anon"]
}
},
"pooler_enabled": false,
"pooler_mode": "transaction",
"disabled": false,
"passwordless_access": true,
"creation_source": "console",
"created_at": "2025-10-16T02:58:58Z",
"updated_at": "2025-10-16T02:58:58Z",
"proxy_host": "us-east-1.aws.neon.tech",
"suspend_timeout_seconds": 0,
"provisioner": "k8s-neonvm"
}
],
"operations": [
{
"id": "262dc2ba-4d78-4b7b-bb9a-e29532385f3a",
"project_id": "purple-moon-12345678",
"branch_id": "br-divine-feather-a1b2c3d4",
"action": "create_branch",
"status": "running",
"failures_count": 0,
"created_at": "2025-10-16T02:58:58Z",
"updated_at": "2025-10-16T02:58:58Z",
"total_duration_ms": 0
},
{
"id": "f9f52b52-9828-47e4-9842-c08c2a9c14d3",
"project_id": "purple-moon-12345678",
"branch_id": "br-divine-feather-a1b2c3d4",
"endpoint_id": "ep-fragrant-breeze-a1b2c3d4",
"action": "start_compute",
"status": "scheduling",
"failures_count": 0,
"created_at": "2025-10-16T02:58:58Z",
"updated_at": "2025-10-16T02:58:58Z",
"total_duration_ms": 0
}
],
"roles": [
{
"branch_id": "br-divine-feather-a1b2c3d4",
"name": "neondb_owner",
"protected": false,
"created_at": "2025-09-12T13:47:59Z",
"updated_at": "2025-09-12T13:47:59Z"
}
],
"databases": [
{
"id": 21560101,
"branch_id": "br-divine-feather-a1b2c3d4",
"name": "neondb",
"owner_name": "neondb_owner",
"created_at": "2025-09-12T13:47:59Z",
"updated_at": "2025-09-12T13:47:59Z"
}
],
"connection_uris": [
{
"connection_uri": "postgresql://neondb_owner:[REDACTED]@ep-fragrant-breeze-a1b2c3d4.us-east-1.aws.neon.tech/neondb?sslmode=require",
"connection_parameters": {
"database": "neondb",
"password": "[REDACTED]",
"role": "neondb_owner",
"host": "ep-fragrant-breeze-a1b2c3d4.us-east-1.aws.neon.tech",
"pooler_host": "ep-fragrant-breeze-a1b2c3d4-pooler.us-east-1.aws.neon.tech"
}
}
]
}Get anonymization status
GET /projects/{project_id}/branches/{branch_id}/anonymized_statusRetrieves the current status of an anonymized branch, including state and progress information.
Example request:
curl -X GET \
'https://console.neon.tech/api/v2/projects/{project_id}/branches/{branch_id}/anonymized_status' \
-H 'Authorization: Bearer $NEON_API_KEY' \
-H 'Accept: application/json'Response body
State values: created, initialized, initialization_error, anonymizing, anonymized, error. Response may include failed_at timestamp if operation failed.
{
"branch_id": "br-aged-salad-637688",
"project_id": "simple-truth-637688",
"state": "anonymizing",
"status_message": "Anonymizing table mydb.public.users (3/5)",
"created_at": "2022-11-30T18:25:15Z",
"updated_at": "2022-11-30T18:30:22Z"
}Start anonymization
POST /projects/{project_id}/branches/{branch_id}/anonymizeStarts or restarts the anonymization process for branches in initialized, error, or anonymized state. Applies all defined masking rules.
Example request:
curl -X POST \
'https://console.neon.tech/api/v2/projects/{project_id}/branches/{branch_id}/anonymize' \
-H 'Authorization: Bearer $NEON_API_KEY' \
-H 'Accept: application/json'Response body
{
"branch_id": "br-shiny-butterfly-w4393738",
"project_id": "wild-sky-00366102",
"state": "anonymized",
"status_message": "Anonymization completed successfully (2 tables, 3 masking rules applied)",
"created_at": "2025-11-01T14:01:39Z",
"updated_at": "2025-11-01T14:01:41Z"
}Get masking rules
GET /projects/{project_id}/branches/{branch_id}/masking_rulesRetrieves all masking rules defined for the specified anonymized branch.
Example request:
curl -X GET \
'https://console.neon.tech/api/v2/projects/{project_id}/branches/{branch_id}/masking_rules' \
-H 'Authorization: Bearer $NEON_API_KEY' \
-H 'Accept: application/json'Response body
{
"masking_rules": [
{
"database_name": "neondb",
"schema_name": "public",
"table_name": "users",
"column_name": "age",
"masking_function": "anon.random_int_between(25,65)"
},
{
"database_name": "neondb",
"schema_name": "public",
"table_name": "users",
"column_name": "email",
"masking_function": "anon.dummy_free_email()"
}
]
}You can also query masking rules directly from the database:
SELECT * FROM anon.pg_masking_rules;Update masking rules
PATCH /projects/{project_id}/branches/{branch_id}/masking_rulesUpdates masking rules for the specified anonymized branch. After updating, use the start anonymization endpoint to apply changes.
Example request:
curl -X PATCH \
'https://console.neon.tech/api/v2/projects/{project_id}/branches/{branch_id}/masking_rules' \
-H 'Authorization: Bearer $NEON_API_KEY' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"masking_rules": [
{
"database_name": "neondb",
"schema_name": "public",
"table_name": "users",
"column_name": "email",
"masking_function": "anon.dummy_free_email()"
}
]
}'Response body
Returns the updated list of masking rules for the branch.
{
"masking_rules": [
{
"database_name": "neondb",
"schema_name": "public",
"table_name": "users",
"column_name": "email",
"masking_function": "anon.dummy_free_email()"
}
]
}Related resources
Automate data anonymization with GitHub Actions
important
The GitHub Actions workflow below uses manual SQL commands with PostgreSQL Anonymizer. For automation using the new Console/API approach documented above, wait for upcoming post-beta improvements to better support automated anonymization.
As an interim solution, you can automate anonymized branch creation using direct SQL commands as outlined below.
Creating anonymized database copies for development, testing, or preview environments can be automated with GitHub Actions. The following workflow creates anonymized Neon branches automatically whenever a pull request is opened or updated.
What you'll achieve for each pull request:
- Automatic creation of a new Neon branch
- Installation and initialization of the PostgreSQL Anonymizer extension
- Application of predefined masking rules to sensitive fields
- A ready-to-use anonymized dataset for use in CI, preview environments, or manual testing
Requirements
Before setting up the GitHub Action:
- A Neon project with a populated parent branch
- The following GitHub repository secrets:
NEON_PROJECT_IDNEON_API_KEY
tip
The Neon GitHub integration can configure these secrets automatically. See Neon GitHub integration.
Set up the GitHub Actions workflow
Create a file at
.github/workflows/create-anon-branch.yml(or similar) with the following content. It implements the same masking rules we used in the manual approach:note
This simple workflow example covers the basics. For production use, consider enhancing it with error handling, retry logic, and additional security controls.
name: PR Open - Create Branch, Run Static Anonymization on: pull_request: types: opened jobs: on-pr-open: runs-on: ubuntu-latest steps: - name: Create branch uses: neondatabase/create-branch-action@v6 id: create-branch with: project_id: ${{ secrets.NEON_PROJECT_ID }} branch_name: anon-pr-${{ github.event.number }} role: neondb_owner api_key: ${{ secrets.NEON_API_KEY }} - name: Confirm branch created run: echo branch_id ${{ steps.create-branch.outputs.branch_id }} - name: Confirm connection possible run: | echo "Checking connection to the database..." psql "${{ steps.create-branch.outputs.db_url }}" -c "SELECT NOW();" - name: Enable anon extension run: | echo "Initializing the extension..." psql "${{ steps.create-branch.outputs.db_url }}" <<EOSQL SET neon.allow_unstable_extensions='true'; CREATE EXTENSION IF NOT EXISTS anon CASCADE; EOSQL echo "Anon extension initialized." - name: Apply security labels run: | echo "Applying security labels..." psql "${{ steps.create-branch.outputs.db_url }}" <<EOSQL SECURITY LABEL FOR anon ON COLUMN users.first_name IS 'MASKED WITH FUNCTION anon.fake_first_name()'; SECURITY LABEL FOR anon ON COLUMN users.last_name IS 'MASKED WITH FUNCTION anon.fake_last_name()'; SECURITY LABEL FOR anon ON COLUMN users.iban IS 'MASKED WITH FUNCTION anon.fake_iban()'; SECURITY LABEL FOR anon ON COLUMN users.email IS 'MASKED WITH FUNCTION anon.fake_email()'; EOSQL echo "Security labels applied." - name: Run anonymization run: | echo "Running anonymization..." psql "${{ steps.create-branch.outputs.db_url }}" <<EOSQL SELECT anon.init(); SELECT anon.anonymize_database(); EOSQL echo "Database anonymization completed successfully."Testing the workflow
To test this automation workflow:
- Customize the workflow for your environment by adjusting the branch naming convention and security labels
- Push the changes to your repository
- Open a new pull request
- Check the Actions tab in your GitHub repository to monitor the workflow execution
- Verify the anonymized branch creation and data anonymization by:
- Viewing the GitHub Actions logs
- Connecting to the new branch and confirm that the original values were replaced
- Checking the data in the Neon Console's Tables view
Cleaning up
Remember to clean up anonymized branches when they're no longer needed. You can delete them manually or automate cleanup with the delete-branch-action GitHub Action when PRs are closed.
Conclusion
The PostgreSQL Anonymizer extension with Neon's branching functionality provides a solution for protecting sensitive data in development workflows. By using static masking with Neon branches, you can:
- Create realistic test environments without exposing sensitive information
- Obfuscate sensitive information such as names, addresses, emails, and other personally identifiable details (PII)
- Automate anonymization processes as part of your CI/CD pipeline
While only static masking is currently supported in Neon, this approach offers a robust solution for most development and testing use cases.