A draft of the documentation for creating if-this-then-at blueprints.
blueprint_creation_guide.md edited
1546 lines 46 kB view raw view code

Blueprint Creation Guide#

This guide explains how to create blueprints for the ifthisthenat automation system. Blueprints are automated workflows that respond to events on the AT Protocol network (including Bluesky) and can perform various actions.

Overview#

A blueprint consists of:

  1. Entry Node: Defines what triggers the blueprint (usually jetstream_entry for AT Protocol events)
  2. Processing Nodes: Transform, filter, or validate the data (e.g., condition, transform)
  3. Action Node: Performs the final action (e.g., publish_record, publish_webhook)

Blueprints are evaluated sequentially, with each node's output becoming the next node's input.

Jetstream Event Examples#

The system receives real-time events from the AT Protocol network via Jetstream. Here are examples of the event structures you'll work with:

Bluesky Feed Post Event#

{
    "commit": {
        "cid": "bafyreiga4g6vrd3lj557afdyec4xwld4gs2t3u47y4ybggvnc7i24udcnq",
        "collection": "app.bsky.feed.post",
        "operation": "create",
        "record": {
            "$type": "app.bsky.feed.post",
            "createdAt": "2025-09-04T03:19:50.142Z",
            "langs": [
                "en"
            ],
            "text": "Who's interested in automation?"
        },
        "rev": "3lxy6sv2j5k2b",
        "rkey": "3lxy6suve4c2y"
    },
    "did": "did:plc:tgudj2fjm77pzkuawquqhsxm",
    "kind": "commit",
    "time_us": 1756955990660025
}

Bluesky Feed Like Event#

{
    "commit": {
        "cid": "bafyreihus6whshnyleuzec4okl7gbweyxfsiacc5qhfnn5wqumpstklvey",
        "collection": "app.bsky.feed.like",
        "operation": "create",
        "record": {
            "$type": "app.bsky.feed.like",
            "createdAt": "2025-09-04T03:20:36.439Z",
            "subject": {
                "cid": "bafyreiga4g6vrd3lj557afdyec4xwld4gs2t3u47y4ybggvnc7i24udcnq",
                "uri": "at://did:plc:tgudj2fjm77pzkuawquqhsxm/app.bsky.feed.post/3lxy6suve4c2y"
            }
        },
        "rev": "3lxy6ub5bok2b",
        "rkey": "3lxy6ub42mk2b"
    },
    "did": "did:plc:cbkjy5n7bk3ax2wplmtjofq2",
    "kind": "commit",
    "time_us": 1756956036754875
}

Bluesky Feed Post Reply Event#

{
    "commit": {
        "cid": "bafyreif53mmglhsbqeuk6zftxh2kauz5l2jbnlavrvluobqdmm33f6uga4",
        "collection": "app.bsky.feed.post",
        "operation": "create",
        "record": {
            "$type": "app.bsky.feed.post",
            "createdAt": "2025-09-04T03:26:18.985Z",
            "langs": [
                "en"
            ],
            "reply": {
                "parent": {
                    "cid": "bafyreiga4g6vrd3lj557afdyec4xwld4gs2t3u47y4ybggvnc7i24udcnq",
                    "uri": "at://did:plc:tgudj2fjm77pzkuawquqhsxm/app.bsky.feed.post/3lxy6suve4c2y"
                },
                "root": {
                    "cid": "bafyreiga4g6vrd3lj557afdyec4xwld4gs2t3u47y4ybggvnc7i24udcnq",
                    "uri": "at://did:plc:tgudj2fjm77pzkuawquqhsxm/app.bsky.feed.post/3lxy6suve4c2y"
                }
            },
            "text": "I know I sure am."
        },
        "rev": "3lxy76hutsk2b",
        "rkey": "3lxy76hpwlc2y"
    },
    "did": "did:plc:cbkjy5n7bk3ax2wplmtjofq2",
    "kind": "commit",
    "time_us": 1756956379343219
}

Lexicon Community Calendar Event#

{
    "commit": {
        "cid": "bafyreihenqybbcny2al7t3pi5h5m25jmpqxwfwia5jvntcxh76rhh6wxpe",
        "collection": "community.lexicon.calendar.event",
        "operation": "create",
        "record": {
            "$type": "community.lexicon.calendar.event",
            "createdAt": "2025-09-04T03:32:33.621Z",
            "description": "DAYTON MUSIC FEST 2025 IS SEPTEMBER 5TH AT THE BRIGHTSIDE!\n\nSeptember 5, 2025\nDoors open at 7pm\nIllwin (hip-hop) at 8pm\nSocks (alternative) at 9:15pm\ncrabswithoutlegs (jazz-fusion) at 10:30pm",
            "locations": [
                {
                    "$type": "community.lexicon.location.address",
                    "country": "US",
                    "locality": "Dayton",
                    "name": "The Brightside",
                    "postalCode": "45402",
                    "region": "Ohio",
                    "street": "905 E 3rd St"
                }
            ],
            "mode": "community.lexicon.calendar.event#inperson",
            "name": "Dayton Music Fest",
            "startsAt": "2025-09-05T21:30:00.000Z",
            "status": "community.lexicon.calendar.event#scheduled",
            "uris": [
                {
                    "$type": "community.lexicon.calendar.event#uri",
                    "name": "thebrightsidedayton.com",
                    "uri": "https://www.thebrightsidedayton.com/event-details/dayton-music-fest-2"
                }
            ]
        },
        "rev": "3lxy7jnc6wc2b",
        "rkey": "3lxy7jnbfjs2b"
    },
    "did": "did:plc:cbkjy5n7bk3ax2wplmtjofq2",
    "kind": "commit",
    "time_us": 1756956754949182
}

Lexicon Community Calendar RSVP Event#

{
    "commit": {
        "cid": "bafyreicpfvezre4cuyzvc4xsiri4wodhxworsvfpmkka6o3j3bzkmgdani",
        "collection": "community.lexicon.calendar.rsvp",
        "operation": "create",
        "record": {
            "$type": "community.lexicon.calendar.rsvp",
            "createdAt": "2025-09-04T03:32:45.166Z",
            "status": "community.lexicon.calendar.rsvp#going",
            "subject": {
                "cid": "bafyreihenqybbcny2al7t3pi5h5m25jmpqxwfwia5jvntcxh76rhh6wxpe",
                "uri": "at://did:plc:cbkjy5n7bk3ax2wplmtjofq2/community.lexicon.calendar.event/3lxy7jnbfjs2b"
            }
        },
        "rev": "3lxy7jy3rjc2b",
        "rkey": "BF1P0SPNSZQPS"
    },
    "did": "did:plc:cbkjy5n7bk3ax2wplmtjofq2",
    "kind": "commit",
    "time_us": 1756956766029684
}

Node Configuration Examples#

Jetstream Entry Nodes#

Match by Collection#

Triggers when any record in the specified collection is created:

{
    "node_type": "jetstream_entry",
    "configuration": {
        "collection": ["app.bsky.feed.post"]
    },
    "payload": true
}

Match by Identity (DID)#

Triggers when a specific user performs any action:

{
    "node_type": "jetstream_entry",
    "configuration": {
        "did": ["did:plc:tgudj2fjm77pzkuawquqhsxm"]
    },
    "payload": true
}

Match Both Collection and Identity#

Triggers when specific users perform actions in specific collections:

{
    "node_type": "jetstream_entry",
    "configuration": {
        "did": ["did:plc:tgudj2fjm77pzkuawquqhsxm", "did:plc:cbkjy5n7bk3ax2wplmtjofq2"],
        "collection": ["app.bsky.feed.post", "app.bsky.feed.like"]
    },
    "payload": true
}

Match Posts with Specific Text Content#

Triggers when posts contain specific keywords:

{
    "node_type": "jetstream_entry",
    "configuration": {
        "collection": ["app.bsky.feed.post"]
    },
    "payload": {
        "and": [
            {"==": [{"val": ["commit", "operation"]}, "create"]},
            {"exists": ["commit", "record", "text"]},
            {"or": [
                {"starts_with": [{"val": ["commit", "record", "text"]}, "#automation"]},
                {"ends_with": [{"val": ["commit", "record", "text"]}, "#ifthisthenat"]}
            ]}
        ]
    }
}

Match Replies to Specific Post#

Triggers when someone replies to a specific post:

{
    "node_type": "jetstream_entry",
    "configuration": {
        "collection": ["app.bsky.feed.post"]
    },
    "payload": {
        "and": [
            {"exists": ["commit", "record", "reply"]},
            {"==": [
                {"val": ["commit", "record", "reply", "parent", "uri"]}, 
                "at://did:plc:tgudj2fjm77pzkuawquqhsxm/app.bsky.feed.post/3lxy6suve4c2y"
            ]}
        ]
    }
}

Match Posts with Minimum Length#

Triggers on posts that meet minimum text length requirements:

{
    "node_type": "jetstream_entry",
    "configuration": {
        "collection": ["app.bsky.feed.post"]
    },
    "payload": {
        "and": [
            {"exists": ["commit", "record", "text"]},
            {">=": [{"length": {"val": ["commit", "record", "text"]}}, 50]},
            {"<": [{"length": {"val": ["commit", "record", "text"]}}, 300]}
        ]
    }
}

Condition Nodes#

Check if Like Applies to Specific DIDs#

Ensures a like event is for content from one of several specific users:

{
    "node_type": "condition",
    "configuration": {},
    "payload": {
        "and": [
            {"==": [{"val": ["commit", "collection"]}, "app.bsky.feed.like"]},
            {"exists": ["commit", "record", "subject", "uri"]},
            {"in": [
                {"slice": [
                    {"split": [{"val": ["commit", "record", "subject", "uri"]}, "/"]},
                    1,
                    2
                ]},
                ["did:plc:user1", "did:plc:user2", "did:plc:user3"]
            ]}
        ]
    }
}

Check Post Language and Length#

Ensures posts are in English and meet length requirements:

{
    "node_type": "condition",
    "configuration": {},
    "payload": {
        "and": [
            {"exists": ["commit", "record", "text"]},
            {">=": [{"length": {"val": ["commit", "record", "text"]}}, 10]},
            {"<=": [{"length": {"val": ["commit", "record", "text"]}}, 280]},
            {"or": [
                {"in": ["en", {"val": ["commit", "record", "langs"]}]},
                {"!": {"exists": ["commit", "record", "langs"]}}
            ]}
        ]
    }
}

Check if Event is Recent#

Ensures events are from the last 24 hours:

{
    "node_type": "condition", 
    "configuration": {},
    "payload": {
        ">": [
            {"val": ["time_us"]},
            {"/": [{"-": [{"*": [{"now": []}, 1000]}, 86400000]}, 1000]}
        ]
    }
}

Validate Calendar Event Location#

Ensures calendar events are in specific cities:

{
    "node_type": "condition",
    "configuration": {},
    "payload": {
        "and": [
            {"==": [{"val": ["commit", "collection"]}, "community.lexicon.calendar.event"]},
            {"exists": ["commit", "record", "locations"]},
            {">": [{"length": {"val": ["commit", "record", "locations"]}}, 0]},
            {"some": [
                {"val": ["commit", "record", "locations"]},
                {"in": [
                    {"val": ["locality"]},
                    ["Dayton", "Columbus", "Cincinnati", "Cleveland"]
                ]}
            ]}
        ]
    }
}

Webhook Entry Nodes#

Webhook entry nodes process HTTP webhook requests, allowing external systems to trigger blueprints via HTTP POST requests to /webhooks/{blueprint_record_key}.

Basic Webhook Handler#

Processes any valid JSON webhook request:

{
    "node_type": "webhook_entry",
    "configuration": {},
    "payload": true
}

Filter by Content-Type#

Only processes webhooks with specific content type:

{
    "node_type": "webhook_entry", 
    "configuration": {},
    "payload": {
        "==": [{"val": ["headers", "content-type"]}, "application/json"]
    }
}

Validate Webhook Structure#

Ensures webhook contains required fields:

{
    "node_type": "webhook_entry",
    "configuration": {},
    "payload": {
        "and": [
            {"exists": ["body", "event_type"]},
            {"exists": ["body", "data"]},
            {"in": [{"val": ["body", "event_type"]}, ["user_signup", "payment_success", "order_complete"]]}
        ]
    }
}

Webhook Request Structure:

{
    "headers": {
        "content-type": "application/json",
        "authorization": "Bearer token"
    },
    "body": {
        "event_type": "user_signup",
        "data": { /* event data */ }
    },
    "query": { /* URL parameters */ },
    "method": "POST",
    "path": "/webhooks/blueprint-record-key"
}

Periodic Entry Nodes#

Periodic entry nodes generate events on a schedule rather than reacting to external events. They use cron expressions to define when they should trigger.

Hourly Status Report#

Generates a status report every hour:

{
    "node_type": "periodic_entry",
    "configuration": {
        "cron": "0 0 * * * *"  // Every hour at minute 0
    },
    "payload": {
        "event_type": "hourly_status",
        "timestamp": {"datetime": [{"now": []}]},
        "metadata": {
            "source": "scheduler",
            "hour": {"format_date": [{"now": []}, "HH"]},
            "day_of_week": {"format_date": [{"now": []}, "dddd"]}
        }
    }
}

Daily Summary at 9 AM#

Creates a daily summary post every day at 9 AM:

{
    "node_type": "periodic_entry",
    "configuration": {
        "cron": "0 0 9 * * *"  // Daily at 9:00 AM
    },
    "payload": {
        "event_type": "daily_summary",
        "timestamp": {"datetime": [{"now": []}]},
        "summary": {
            "date": {"format_date": [{"now": []}, "YYYY-MM-DD"]},
            "is_weekend": {"in": [
                {"format_date": [{"now": []}, "dddd"]},
                ["Saturday", "Sunday"]
            ]}
        }
    }
}

Weekly Monday Morning Report#

Runs every Monday at 8 AM:

{
    "node_type": "periodic_entry",
    "configuration": {
        "cron": "0 0 8 * * MON"  // Every Monday at 8:00 AM
    },
    "payload": {
        "event_type": "weekly_report",
        "week_number": {"format_date": [{"now": []}, "W"]},
        "year": {"format_date": [{"now": []}, "YYYY"]},
        "timestamp": {"datetime": [{"now": []}]}
    }
}

Every 30 Minutes Check#

Performs a check every 30 minutes:

{
    "node_type": "periodic_entry",
    "configuration": {
        "cron": "0 */30 * * * *"  // Every 30 minutes
    },
    "payload": {
        "event_type": "periodic_check",
        "timestamp": {"datetime": [{"now": []}]},
        "check_id": {"uuid": []}
    }
}

Business Hours Monitoring#

Active only during business hours (9 AM - 5 PM on weekdays):

{
    "node_type": "periodic_entry",
    "configuration": {
        "cron": "0 */15 9-17 * * MON-FRI"  // Every 15 minutes, 9 AM - 5 PM, Monday-Friday
    },
    "payload": {
        "event_type": "business_hours_check",
        "timestamp": {"datetime": [{"now": []}]},
        "is_business_hours": true
    }
}

Important Notes for Periodic Entry:

  • Cron expressions must have intervals between 30 seconds and 90 days
  • Use 6-field cron format for second-level precision: SEC MIN HOUR DAY MONTH WEEKDAY
  • Use 5-field format for minute-level precision: MIN HOUR DAY MONTH WEEKDAY
  • Special strings are supported: @yearly, @monthly, @weekly, @daily, @hourly
  • The payload is evaluated each time the schedule triggers, allowing dynamic content

Transform Nodes#

Create Calendar RSVP from Calendar Event#

Automatically RSVP "going" to calendar events:

{
    "node_type": "transform",
    "configuration": {},
    "payload": {
        "record": {
            "$type": "community.lexicon.calendar.rsvp",
            "createdAt": {"datetime": [{"now": []}]},
            "status": "community.lexicon.calendar.rsvp#going",
            "subject": {
                "cid": {"val": ["commit", "cid"]},
                "uri": {"cat": [
                    "at://",
                    {"val": ["did"]},
                    "/",
                    {"val": ["commit", "collection"]},
                    "/",
                    {"val": ["commit", "rkey"]}
                ]}
            }
        }
    }
}

Extract Post Text and Author#

Extract specific fields from a Bluesky post for further processing:

{
    "node_type": "transform", 
    "configuration": {},
    "payload": {
        "post_text": {"val": ["commit", "record", "text"]},
        "author_did": {"val": ["did"]},
        "post_uri": {"cat": [
            "at://",
            {"val": ["did"]},
            "/app.bsky.feed.post/", 
            {"val": ["commit", "rkey"]}
        ]},
        "created_time": {"val": ["commit", "record", "createdAt"]},
        "has_images": {"exists": ["commit", "record", "embed", "images"]},
        "lang": {"first": {"val": ["commit", "record", "langs"]}}
    }
}

Create Post with Conditional Content#

Creates different post content based on the type of event:

{
    "node_type": "transform",
    "configuration": {},
    "payload": {
        "record": {
            "$type": "app.bsky.feed.post",
            "createdAt": {"datetime": [{"now": []}]},
            "text": {"if": [
                {"==": [{"val": ["commit", "collection"]}, "app.bsky.feed.like"]},
                {"cat": [
                    "Thanks for the like on my post! ❤️"
                ]},
                {"if": [
                    {"==": [{"val": ["commit", "collection"]}, "app.bsky.feed.repost"]},
                    "Thanks for the repost! 🔄",
                    {"cat": [
                        "New activity: ",
                        {"val": ["commit", "collection"]}
                    ]}
                ]}
            ]},
            "langs": ["en"]
        }
    }
}

Process Array Data#

Transform array data and calculate aggregates:

{
    "node_type": "transform",
    "configuration": {},
    "payload": {
        "event_locations": {"map": [
            {"val": ["commit", "record", "locations"]},
            {
                "name": {"val": ["name"]},
                "city": {"val": ["locality"]},
                "full_address": {"cat": [
                    {"val": ["name"]}, ", ",
                    {"val": ["locality"]}, ", ",
                    {"val": ["region"]}
                ]}
            }
        ]},
        "location_count": {"length": {"val": ["commit", "record", "locations"]}},
        "has_remote_option": {"some": [
            {"val": ["commit", "record", "locations"]},
            {"==": [{"val": ["type"]}, "virtual"]}
        ]}
    }
}

Publish Record Nodes#

Publish Auto-Generated Like#

Automatically like posts from specific accounts:

{
    "node_type": "publish_record",
    "configuration": {
        "collection": "app.bsky.feed.like",
        "did": "did:plc:your-did-here"
    },
    "payload": {"val": []}
}

Publish Reply with Dynamic Content#

Create a reply with content based on the original post:

{
    "node_type": "publish_record",
    "configuration": {
        "collection": "app.bsky.feed.post",
        "did": "did:plc:your-did-here"
    },
    "payload": {"val": []}
}

Note: The actual record content should come from a preceding transform node.

Publish Calendar RSVP#

Publish an RSVP record to a calendar event:

{
    "node_type": "publish_record",
    "configuration": {
        "collection": "community.lexicon.calendar.rsvp",
        "did": "did:plc:your-did-here",
        "rkey": {"cat": [
            {"val": ["commit", "rkey"]},
            "-rsvp"
        ]}
    },
    "payload": {"val": []}
}

Publish Webhook Nodes#

Send to Analytics Service#

Send event data to an external analytics service:

{
    "node_type": "publish_webhook",
    "configuration": {
        "url": "https://analytics.example.com/events",
        "timeout_ms": 5000,
        "headers": {
            "Authorization": "Bearer your-api-key",
            "Content-Type": "application/json"
        }
    },
    "payload": {"val": []}
}

Send Alert for High-Value Posts#

Send alerts when posts exceed engagement thresholds:

{
    "node_type": "publish_webhook",
    "configuration": {
        "url": "https://alerts.example.com/high-engagement",
        "timeout_ms": 10000,
        "headers": {
            "X-Alert-Type": "engagement",
            "Authorization": "Bearer alert-api-key"
        }
    },
    "payload": {
        "alert_type": "high_engagement",
        "post_uri": {"cat": [
            "at://",
            {"val": ["did"]},
            "/app.bsky.feed.post/",
            {"val": ["commit", "rkey"]}
        ]},
        "metrics": {
            "likes": {"val": ["metrics", "likeCount"]},
            "reposts": {"val": ["metrics", "repostCount"]},
            "replies": {"val": ["metrics", "replyCount"]}
        },
        "timestamp": {"datetime": [{"now": []}]}
    }
}

Cross-Post to External Platform#

Transform and send data to external social media platforms:

{
    "node_type": "publish_webhook", 
    "configuration": {
        "url": "https://api.external-platform.com/posts",
        "timeout_ms": 15000,
        "headers": {
            "Authorization": "Bearer external-platform-token",
            "User-Agent": "ATProto-Bridge/1.0"
        }
    },
    "payload": {
        "content": {"val": ["commit", "record", "text"]},
        "author": {"val": ["did"]},
        "source": "bluesky",
        "original_uri": {"cat": [
            "at://",
            {"val": ["did"]},
            "/app.bsky.feed.post/",
            {"val": ["commit", "rkey"]}
        ]}
    }
}

Facet Text Nodes#

Facet text nodes process text to extract mentions (@handles) and URLs, creating AT Protocol facets that enable rich text features like clickable mentions and links. They're typically placed before publish_record nodes to enrich text content.

Basic Text Processing#

Process text to extract all mentions and URLs:

{
    "node_type": "facet_text",
    "configuration": {},
    "payload": {}
}

Custom Field Processing#

Process text from a specific field:

{
    "node_type": "facet_text",
    "configuration": {
        "field": "content"
    },
    "payload": {}
}

Complete Post Creation Pipeline#

Transform data, process facets, and publish a post:

{
    "node_type": "transform",
    "configuration": {},
    "payload": {
        "text": {"cat": [
            "Thanks for following @",
            {"resolve_handle": {"val": ["did"]}},
            "! Check out our website: https://example.com"
        ]}
    }
}

Followed by:

{
    "node_type": "facet_text",
    "configuration": {
        "field": "text"
    },
    "payload": {}
}

Then:

{
    "node_type": "transform", 
    "configuration": {},
    "payload": {
        "record": {
            "$type": "app.bsky.feed.post",
            "text": {"val": ["text"]},
            "facets": {"val": ["facets"]},
            "createdAt": {"datetime": [{"now": []}]},
            "langs": ["en"]
        }
    }
}

Process Rich Content#

Handle text with multiple mentions and links:

{
    "node_type": "facet_text",
    "configuration": {},
    "payload": {}
}

Input data example:

{
    "text": "Great discussion with @alice.bsky.social and @bob.test.com! More info at https://docs.example.com/guide and https://help.site.org"
}

Output structure:

{
    "text": "Great discussion with @alice.bsky.social and @bob.test.com! More info at https://docs.example.com/guide and https://help.site.org",
    "facets": [
        {
            "index": {"byteStart": 21, "byteEnd": 39},
            "features": [{"$type": "app.bsky.richtext.facet#mention", "did": "did:plc:alice123"}]
        },
        {
            "index": {"byteStart": 44, "byteEnd": 57},
            "features": [{"$type": "app.bsky.richtext.facet#mention", "did": "did:plc:bob456"}]
        },
        {
            "index": {"byteStart": 72, "byteEnd": 102},
            "features": [{"$type": "app.bsky.richtext.facet#link", "uri": "https://docs.example.com/guide"}]
        },
        {
            "index": {"byteStart": 107, "byteEnd": 127},
            "features": [{"$type": "app.bsky.richtext.facet#link", "uri": "https://help.site.org"}]
        }
    ]
}

Important Notes for Facet Text:

  • Handles are resolved to DIDs using the identity resolver
  • Unresolvable handles are skipped (rendered as plain text)
  • Both mentions and URLs are processed simultaneously
  • Byte positions are calculated accurately for UTF-8 text
  • Output includes both the original text and the facets array
  • Use before publish_record nodes that create posts with rich text

Debug Action Nodes#

Debug action nodes log data as it flows through the blueprint pipeline. They're essential for development, testing, and troubleshooting blueprint behavior. Debug nodes pass data through unchanged, allowing normal pipeline execution to continue.

Basic Debug Logging#

Log data at any point in the pipeline:

{
    "node_type": "debug_action",
    "configuration": {},
    "payload": {}
}

Debug After Filtering#

Inspect what data passes through conditions:

{
    "nodes": [
        {
            "node_type": "jetstream_entry",
            "configuration": {
                "collection": ["app.bsky.feed.post"]
            },
            "payload": true
        },
        {
            "node_type": "condition",
            "configuration": {},
            "payload": {
                "contains": [{"val": ["commit", "record", "text"]}, "debug"]
            }
        },
        {
            "node_type": "debug_action",
            "configuration": {},
            "payload": {}
        }
    ]
}

Debug Transformation Results#

Verify transform node outputs:

{
    "nodes": [
        {
            "node_type": "webhook_entry",
            "configuration": {},
            "payload": true
        },
        {
            "node_type": "transform",
            "configuration": {},
            "payload": {
                "processed": true,
                "original_path": {"val": ["path"]},
                "timestamp": {"datetime": [{"now": []}]}
            }
        },
        {
            "node_type": "debug_action",
            "configuration": {},
            "payload": {}
        },
        {
            "node_type": "publish_webhook",
            "configuration": {
                "url": "https://example.com/webhook"
            },
            "payload": {"val": []}
        }
    ]
}

Multiple Debug Points#

Trace data flow through complex pipelines:

{
    "nodes": [
        {
            "node_type": "jetstream_entry",
            "configuration": {
                "collection": ["app.bsky.feed.like"]
            },
            "payload": true
        },
        {
            "node_type": "debug_action",
            "configuration": {},
            "payload": {}
        },
        {
            "node_type": "transform",
            "configuration": {},
            "payload": {
                "simplified": {"val": ["commit", "record"]}
            }
        },
        {
            "node_type": "debug_action",
            "configuration": {},
            "payload": {}
        }
    ]
}

Debug Output Format: Debug logs include:

  • Node AT-URI
  • Blueprint AT-URI
  • Pretty-printed JSON of the input data

Use Cases for Debug Actions:

  1. Development: Inspect data structure at various pipeline stages
  2. Testing: Verify transformations are working correctly
  3. Troubleshooting: Identify where data filtering or transformation issues occur
  4. Validation: Ensure data matches expected format before actions
  5. Monitoring: Log specific events for analysis

Tips:

  • Place after transforms to verify output format
  • Place before conditions to see what data is being evaluated
  • Place before action nodes to verify final data structure
  • Use multiple debug nodes to trace data flow through complex pipelines
  • Remove or comment out debug nodes in production blueprints

Blueprint Structure#

Basic Blueprint Flow#

  1. Entry Node: jetstream_entry - Defines the trigger
  2. Condition Node: condition - Filters events (optional)
  3. Transform Node: transform - Modifies data (optional)
  4. Action Node: publish_record or publish_webhook - Performs action

Example: Auto-RSVP to Calendar Events#

{
    "nodes": [
        {
            "node_type": "jetstream_entry",
            "configuration": {
                "collection": ["community.lexicon.calendar.event"]
            },
            "payload": true
        },
        {
            "node_type": "condition", 
            "configuration": {},
            "payload": {"==": [
                {"val": ["commit", "record", "locations", 0, "locality"]},
                "Dayton"
            ]}
        },
        {
            "node_type": "transform",
            "configuration": {},
            "payload": {
                "record": {
                    "$type": "community.lexicon.calendar.rsvp",
                    "createdAt": {"datetime": [{"now": []}]},
                    "status": "community.lexicon.calendar.rsvp#going",
                    "subject": {
                        "cid": {"val": ["commit", "cid"]},
                        "uri": {"cat": [
                            "at://",
                            {"val": ["did"]},
                            "/",
                            {"val": ["commit", "collection"]},
                            "/",
                            {"val": ["commit", "rkey"]}
                        ]}
                    }
                }
            }
        },
        {
            "node_type": "publish_record",
            "configuration": {
                "collection": "community.lexicon.calendar.rsvp",
                "did": "did:plc:your-did-here"
            },
            "payload": {"val": []}
        }
    ]
}

Best Practices#

Entry Node Configuration#

  • Use specific collections when possible to reduce processing overhead
  • Use DID filters for user-specific automation
  • Combine multiple filters for precise targeting

Condition Expressions#

  • Use DataLogic expressions with {"val": [...]} to access nested data
  • Test conditions thoroughly with sample data
  • Keep expressions simple and readable
  • Use logical operators like {"and": [...]}, {"or": [...]} for complex conditions
  • Remember conditions must evaluate to boolean values

Transform Templates#

  • Use DataLogic expressions for dynamic content generation
  • Include proper $type and createdAt fields for AT Protocol records
  • Wrap record data in a "record" field for publish_record nodes
  • Use {"now": []} and {"datetime": [...]} for timestamps
  • Test templates with actual event data to verify correct field access

Security Considerations#

  • Never hardcode sensitive information in blueprints
  • Use environment variables for API keys and secrets
  • Validate input data in condition nodes
  • Be mindful of rate limits when publishing records

Available Node Types#

  • jetstream_entry: Entry point for AT Protocol events from Jetstream
  • webhook_entry: Entry point for HTTP webhook requests
  • periodic_entry: Entry point for scheduled/cron-based execution
  • condition: Filter events based on DataLogic expressions
  • transform: Transform data using DataLogic expressions
  • facet_text: Process text to extract mentions, hashtags, and links
  • publish_record: Publish records to AT Protocol repositories
  • publish_webhook: Send HTTP requests to external services
  • debug_action: Log information for debugging purposes

Entry Nodes (must be first node): jetstream_entry, webhook_entry, periodic_entry Action Nodes (blueprint must have at least one): publish_record, publish_webhook, debug_action

Testing Blueprints#

  1. Use the debug_action node to log intermediate data
  2. Test with known event structures
  3. Validate generated records against Lexicon schemas
  4. Monitor logs for processing errors
  5. Start with simple blueprints and gradually add complexity

Complex Blueprint Examples#

Multi-Stage Content Analysis Pipeline#

A sophisticated blueprint that analyzes posts for sentiment, extracts mentions, and triggers different actions based on analysis results:

{
    "nodes": [
        {
            "node_type": "jetstream_entry",
            "configuration": {
                "collection": ["app.bsky.feed.post"],
                "did": ["did:plc:specific-user"]
            },
            "payload": true
        },
        {
            "node_type": "condition",
            "configuration": {},
            "payload": {"and": [
                {">": [{"length": {"val": ["commit", "record", "text"]}}, 10]},
                {"<": [{"length": {"val": ["commit", "record", "text"]}}, 300]},
                {"not": {"contains": [{"val": ["commit", "record", "text"]}, "RT @"]}}
            ]}
        },
        {
            "node_type": "transform",
            "configuration": {},
            "payload": {
                "analysis": {
                    "post_text": {"val": ["commit", "record", "text"]},
                    "word_count": {"length": {"split": [{"val": ["commit", "record", "text"]}, " "]}},
                    "has_mentions": {"some": [
                        {"extract_mentions": {"val": ["commit", "record", "text"]}},
                        true
                    ]},
                    "mention_count": {"length": {"extract_mentions": {"val": ["commit", "record", "text"]}}},
                    "has_hashtags": {"contains": [{"val": ["commit", "record", "text"]}, "#"]},
                    "languages": {"val": ["commit", "record", "langs"]},
                    "engagement_score": {"+": [
                        {"*": [{"val": ["metrics", "likeCount"]}, 1]},
                        {"*": [{"val": ["metrics", "repostCount"]}, 2]},
                        {"*": [{"val": ["metrics", "replyCount"]}, 3]}
                    ]},
                    "priority_level": {"if": [
                        {">": [{"val": ["metrics", "likeCount"]}, 100]},
                        "high",
                        {"if": [
                            {">": [{"val": ["metrics", "likeCount"]}, 10]},
                            "medium", 
                            "low"
                        ]}
                    ]}
                },
                "metadata": {
                    "processed_at": {"datetime": [{"now": []}]},
                    "post_uri": {"cat": [
                        "at://",
                        {"val": ["did"]},
                        "/app.bsky.feed.post/",
                        {"val": ["commit", "rkey"]}
                    ]},
                    "author_handle": {"val": ["handle"]}
                }
            }
        },
        {
            "node_type": "condition",
            "configuration": {},
            "payload": {"==": [{"val": ["analysis", "priority_level"]}, "high"]}
        },
        {
            "node_type": "publish_webhook",
            "configuration": {
                "url": "https://api.analytics.example.com/high-priority-posts",
                "timeout_ms": 5000,
                "headers": {
                    "Authorization": "Bearer analytics-key",
                    "X-Priority": "high"
                }
            },
            "payload": {"val": []}
        }
    ]
}

Dynamic Event Aggregation with Conditional Responses#

A blueprint that aggregates events by type and responds differently based on patterns:

{
    "nodes": [
        {
            "node_type": "jetstream_entry",
            "configuration": {
                "collection": [
                    "app.bsky.feed.like",
                    "app.bsky.feed.repost", 
                    "app.bsky.graph.follow"
                ]
            },
            "payload": true
        },
        {
            "node_type": "transform",
            "configuration": {},
            "payload": {
                "event_summary": {
                    "type": {"val": ["commit", "collection"]},
                    "actor": {"val": ["did"]},
                    "target": {"if": [
                        {"==": [{"val": ["commit", "collection"]}, "app.bsky.feed.like"]},
                        {"val": ["commit", "record", "subject", "uri"]},
                        {"if": [
                            {"==": [{"val": ["commit", "collection"]}, "app.bsky.feed.repost"]},
                            {"val": ["commit", "record", "subject", "uri"]},
                            {"val": ["commit", "record", "subject"]}
                        ]}
                    ]},
                    "timestamp": {"datetime": [{"now": []}]}
                },
                "aggregation_key": {"cat": [
                    {"val": ["did"]},
                    "-",
                    {"date_trunc": ["hour", {"now": []}]}
                ]},
                "response_action": {"if": [
                    {"==": [{"val": ["commit", "collection"]}, "app.bsky.graph.follow"]},
                    "send_welcome",
                    {"if": [
                        {"and": [
                            {"==": [{"val": ["commit", "collection"]}, "app.bsky.feed.like"]},
                            {">=": [{"count": {"val": ["hourly_likes"]}}, 10]}
                        ]},
                        "send_thanks",
                        "log_only"
                    ]}
                ]}
            }
        },
        {
            "node_type": "condition",
            "configuration": {},
            "payload": {"!=": [{"val": ["response_action"]}, "log_only"]}
        },
        {
            "node_type": "transform",
            "configuration": {},
            "payload": {
                "record": {
                    "$type": "app.bsky.feed.post",
                    "createdAt": {"datetime": [{"now": []}]},
                    "text": {"if": [
                        {"==": [{"val": ["response_action"]}, "send_welcome"]},
                        {"cat": [
                            "Welcome to the community, ",
                            {"resolve_handle": {"val": ["event_summary", "actor"]}},
                            "! Thanks for the follow! 🎉"
                        ]},
                        {"cat": [
                            "Thanks for all the engagement today, ",
                            {"resolve_handle": {"val": ["event_summary", "actor"]}},
                            "! Your support means a lot! ❤️"
                        ]}
                    ]},
                    "langs": ["en"]
                }
            }
        },
        {
            "node_type": "publish_record",
            "configuration": {
                "collection": "app.bsky.feed.post",
                "did": "did:plc:your-did-here"
            },
            "payload": {"val": []}
        }
    ]
}

Advanced Data Processing with Error Handling#

A blueprint that processes complex nested data structures with fallback logic:

{
    "nodes": [
        {
            "node_type": "jetstream_entry", 
            "configuration": {
                "collection": ["community.lexicon.calendar.event"]
            },
            "payload": true
        },
        {
            "node_type": "transform",
            "configuration": {},
            "payload": {
                "processed_event": {
                    "id": {"val": ["commit", "rkey"]},
                    "name": {"or": [
                        {"val": ["commit", "record", "name"]},
                        "Untitled Event"
                    ]},
                    "description": {"or": [
                        {"val": ["commit", "record", "description"]},
                        ""
                    ]},
                    "start_time": {"or": [
                        {"val": ["commit", "record", "startTime"]},
                        {"datetime": [{"now": []}]}
                    ]},
                    "locations": {"map": [
                        {"or": [
                            {"val": ["commit", "record", "locations"]},
                            []
                        ]},
                        {
                            "name": {"or": [{"val": ["name"]}, "Unknown Location"]},
                            "address": {"if": [
                                {"exists": ["address"]},
                                {"cat": [
                                    {"or": [{"val": ["address", "streetAddress"]}, ""]},
                                    {"if": [
                                        {"and": [
                                            {"exists": ["address", "streetAddress"]},
                                            {"exists": ["address", "locality"]}
                                        ]},
                                        ", ",
                                        ""
                                    ]},
                                    {"or": [{"val": ["address", "locality"]}, ""]},
                                    {"if": [
                                        {"and": [
                                            {"exists": ["address", "locality"]},
                                            {"exists": ["address", "region"]}
                                        ]},
                                        ", ",
                                        ""
                                    ]},
                                    {"or": [{"val": ["address", "region"]}, ""]}
                                ]},
                                "Address not provided"
                            ]},
                            "coordinates": {"if": [
                                {"and": [
                                    {"exists": ["geo", "latitude"]},
                                    {"exists": ["geo", "longitude"]}
                                ]},
                                {
                                    "lat": {"val": ["geo", "latitude"]},
                                    "lng": {"val": ["geo", "longitude"]}
                                },
                                null
                            ]}
                        }
                    ]},
                    "attendee_capacity": {"or": [
                        {"val": ["commit", "record", "capacity"]},
                        -1
                    ]},
                    "is_virtual": {"some": [
                        {"or": [
                            {"val": ["commit", "record", "locations"]},
                            []
                        ]},
                        {"==": [{"val": ["type"]}, "virtual"]}
                    ]},
                    "tags": {"filter": [
                        {"extract_hashtags": {"or": [
                            {"val": ["commit", "record", "description"]},
                            ""
                        ]}},
                        {"!=": [{"val": []}, ""]}
                    ]}
                }
            }
        },
        {
            "node_type": "condition",
            "configuration": {},
            "payload": {"and": [
                {"!=": [{"val": ["processed_event", "name"]}, "Untitled Event"]},
                {">": [{"length": {"val": ["processed_event", "locations"]}}, 0]}
            ]}
        },
        {
            "node_type": "publish_webhook",
            "configuration": {
                "url": "https://api.eventprocessor.example.com/events",
                "timeout_ms": 10000,
                "headers": {
                    "Content-Type": "application/json",
                    "X-Processor-Version": "2.0"
                }
            },
            "payload": {"val": []}
        }
    ]
}

Common Patterns#

Auto-Reply to Mentions#

{
    "entry": "jetstream_entry with mention detection",
    "condition": "check if mentioned user matches yours",
    "action": "publish_record with reply"
}

Content Moderation#

{
    "entry": "jetstream_entry for posts",
    "condition": "check for inappropriate content",
    "action": "publish_webhook to moderation service"
}

Cross-Platform Posting#

{
    "entry": "jetstream_entry for your posts",
    "transform": "convert to external platform format",
    "action": "publish_webhook to external API"
}

Event Aggregation#

{
    "entry": "jetstream_entry for specific collection",
    "condition": "filter relevant events",
    "action": "publish_webhook to analytics service"
}

Scheduled Daily Post#

Create a daily motivational post at 9 AM:

{
    "nodes": [
        {
            "node_type": "periodic_entry",
            "configuration": {
                "cron": "0 0 9 * * *"  // Daily at 9 AM
            },
            "payload": {
                "event_type": "daily_motivation",
                "day": {"format_date": [{"now": []}, "dddd"]},
                "timestamp": {"datetime": [{"now": []}]}
            }
        },
        {
            "node_type": "transform",
            "configuration": {},
            "payload": {
                "record": {
                    "$type": "app.bsky.feed.post",
                    "createdAt": {"datetime": [{"now": []}]},
                    "text": {"if": [
                        {"==": [{"val": ["day"]}, "Monday"]},
                        "Happy Monday! 💪 Let's make this week amazing!",
                        {"if": [
                            {"==": [{"val": ["day"]}, "Friday"]},
                            "It's Friday! 🎉 Almost weekend time!",
                            {"cat": [
                                "Good morning! Today is ",
                                {"val": ["day"]},
                                " - make it count! ☀️"
                            ]}
                        ]}
                    ]},
                    "langs": ["en"]
                }
            }
        },
        {
            "node_type": "publish_record",
            "configuration": {
                "collection": "app.bsky.feed.post",
                "did": "did:plc:your-did-here"
            },
            "payload": {"val": []}
        }
    ]
}