Automatically archive spam records from Notion databases using GitHub Actions. This tool queries your Notion database for records matching specific criteria and moves them to trash on a scheduled basis.
This GitHub Action runs daily at midnight UTC (configurable). It:
- Queries your Notion database for records matching specific criteria
- Archives (moves to trash) all matching records
- Logs detailed information about the cleanup operation
- Respects Notion's API rate limits with built-in delays
- Dry-run mode for testing before actual deletions
- Moves to trash, not permanent deletion (recoverable for 30 days)
- Per-record error handling (one failure doesn't stop the entire cleanup)
- Rate limiting (waits 0.35s between deletions)
- Detailed logging for full audit trail
- Environment variable configuration (no secrets in code)
This repository uses environment variables for all sensitive configuration:
- API keys are stored in GitHub Secrets, never in code
- Database IDs are configured via environment variables
- Example configuration files contain only placeholders
- Go to Notion Integrations
- Click "+ New integration"
- Give it a name (e.g., "Database Cleanup")
- Select the workspace where your database lives
- Click "Submit"
- Copy the "Internal Integration Token" (starts with
secret_)
- Open your Notion database
- Click the "..." menu in the top right
- Scroll down and click "Add connections"
- Select your integration name
- Click "Confirm"
Important: The integration can only access databases you explicitly share with it.
Your database ID is in the URL when viewing the database:
https://www.notion.so/your-workspace/DATABASE_ID?v=...
The DATABASE_ID is the 32-character string before the ?v= parameter.
Example:
- URL:
https://www.notion.so/myworkspace/abc123def456...?v=xyz - Database ID:
abc123def456...(32 characters)
You can also get the database ID by:
- Right-click on the database
- Select "Copy link"
- Paste the link and extract the ID from the URL
- Click the "Fork" button in the top right of this page
- Select your GitHub account
- Wait for the fork to complete
- Go to your forked repository on GitHub
- Click "Settings" → "Secrets and variables" → "Actions"
- Click "New repository secret"
- Add the following secrets:
NOTION_API_KEY
- Name:
NOTION_API_KEY - Value: Your Notion integration token (from step 1)
NOTION_DATABASE_ID
- Name:
NOTION_DATABASE_ID - Value: Your database ID (from step 3)
Edit config/deletion_rules.json to match your specific filtering needs:
{
"databases": [
{
"database_id": "${NOTION_DATABASE_ID}",
"name": "My Database",
"filters": {
"property": "Status",
"select": {
"equals": "Spam"
}
},
"dry_run": false
}
]
}Filter Examples:
Delete records where a text property equals "Spam":
"filters": {
"property": "Organization",
"rich_text": {
"equals": "Spam"
}
}Delete records where a select property equals "Archived":
"filters": {
"property": "Status",
"select": {
"equals": "Archived"
}
}Delete records where a checkbox is checked:
"filters": {
"property": "Mark for Deletion",
"checkbox": {
"equals": true
}
}For more filter options, see Notion's Filter Documentation.
Edit .github/workflows/notion-cleanup.yml to change when the cleanup runs:
on:
schedule:
# Daily at midnight UTC
- cron: '0 0 * * *'Cron Examples:
0 0 * * *- Daily at midnight UTC0 */6 * * *- Every 6 hours0 9 * * 1- Every Monday at 9 AM UTC0 0 * * 0- Every Sunday at midnight UTC
Use crontab.guru to build custom schedules.
Before enabling automatic deletions, test with dry-run mode:
- Go to "Actions" tab in your repository
- Click "Notion Database Cleanup" workflow
- Click "Run workflow" button
- Ensure "Dry run mode" is checked
- Click "Run workflow"
- Check the logs to see what would be deleted
Once you've verified the dry run results:
- Edit
config/deletion_rules.json - Change
"dry_run": trueto"dry_run": false - Commit and push the changes
- The workflow will now delete matching records on schedule
You can also run the script locally for testing:
-
Clone the repository:
git clone https://github.com/YOUR_USERNAME/public-notion-spam-delete-automation.git cd public-notion-spam-delete-automation -
Install dependencies:
pip install -r requirements.txt
-
Create a
.envfile from the example:cp .env.example .env
-
Edit
.envand add your credentials:NOTION_API_KEY=secret_your_actual_key_here NOTION_DATABASE_ID=your_actual_database_id_here
Dry run (safe, shows what would be deleted):
source .env
export NOTION_API_KEY NOTION_DATABASE_ID
python scripts/notion_cleanup.py --config config/deletion_rules.json --dry-runActual deletion:
source .env
export NOTION_API_KEY NOTION_DATABASE_ID
python scripts/notion_cleanup.py --config config/deletion_rules.json- Ensure GitHub Secret is named exactly
NOTION_API_KEY - Check the secret is set in repository settings (not organization-level)
- Verify workflow YAML references the correct secret name
- Ensure GitHub Secret is named exactly
NOTION_DATABASE_ID - Verify you copied the full 32-character database ID
- Check that the database ID doesn't have extra spaces or characters
- Verify you've shared the database with your integration (step 2)
- Confirm the database ID is correct
- Check that the integration has the correct permissions
- Verify your filter criteria in
deletion_rules.json - Check that records actually exist matching your criteria
- Test filters directly in Notion first to verify they work
- The script already includes delays (0.35s per request)
- If you still hit limits, you may be running other integrations simultaneously
- Wait a few minutes and try again
{
"databases": [
{
"database_id": "${NOTION_DATABASE_ID}",
"name": "Human-readable name for logs",
"filters": { /* Notion filter object */ },
"dry_run": false
}
]
}Fields:
database_id: Database ID (use${NOTION_DATABASE_ID}to reference the environment variable)name: Optional friendly name for the database (used in logs)filters: Notion API filter object (documentation)dry_run: Set totrueto simulate deletions without actually deleting
You can clean up multiple databases in one run:
{
"databases": [
{
"database_id": "${NOTION_DATABASE_ID}",
"name": "Contacts Database",
"filters": {
"property": "Type",
"select": { "equals": "Spam" }
},
"dry_run": false
},
{
"database_id": "${NOTION_DATABASE_ID_2}",
"name": "Projects Database",
"filters": {
"property": "Status",
"select": { "equals": "Archived" }
},
"dry_run": false
}
]
}Add additional database IDs as GitHub Secrets (e.g., NOTION_DATABASE_ID_2) and reference them in the config.
- Scheduled Trigger: GitHub Actions runs the workflow on schedule or manual trigger
- Environment Setup: Loads API key and database ID from GitHub Secrets
- Query Database: Uses Notion API to query for matching records
- Archive Records: Archives each matching record (moves to trash)
- Rate Limiting: Waits 0.35s between requests to respect Notion's limits
- Error Handling: Continues processing even if individual deletions fail
- Logging: Uploads detailed logs as workflow artifacts
MIT License - feel free to fork and customize for your needs.
Contributions are welcome! Please feel free to submit a Pull Request.
If you encounter issues:
- Check the troubleshooting section above
- Review the workflow logs in the Actions tab
- Open an issue with details about your problem
- Built for automated Notion database maintenance
- Uses the official Notion API
- Designed with safety and recoverability in mind