Flow Scheduled Transactions Documentation
Introduction
Scheduled transactions are part of the Forte network upgrade and are currently available on Flow Emulator (CLI v2.7.0+) and [Flow Testnet]. See the announcement for context: [Forte: Introducing Actions & Agents].
Scheduled transactions on the Flow blockchain enable users and smart contracts to autonomously execute predefined logic at specific future times without external triggers. This powerful feature allows developers to create "wake up" patterns where contracts can schedule themselves to run at predetermined block timestamps, enabling novel blockchain automation patterns.
Key benefits include:
- Autonomous execution: No need for external services or manual intervention
- Time-based automation: Execute transactions based on blockchain time
- Predictable scheduling: Guaranteed execution within specified time windows
Common use cases include recurring payments, automated arbitrage, time-based contract logic, delayed executions, and periodic maintenance tasks.
Flow provides a scheduled transaction manager to make managing your scheduled transactions more streamlined. Check out the scheduled transactions intro for a tutorial on how to schedule some basic transactions with the manager.
Concepts
Creating a Scheduled Transaction
In order to create a scheduled transaction, the logic that will be executed in the transaction must already be defined in a function that the scheduler will call when it is time for the transaction to be executed.
Therefore, all scheduled transactions must include a capability to a resource that conforms to this Transaction Handler interface defined in the Scheduler contract and includes getters that conform to the Flow metadata views standard:
_12access(all) resource interface TransactionHandler {_12    // Called by the protocol to executed the scheduled transaction_12    // **Transaction ID**: Unique identifier for tracking, returned during scheduling_12    // **Data**: The optional data provided during scheduling that may relate_12    // to the specific scheduled transaction_12    access(Execute) fun executeTransaction(id: UInt64, data: AnyStruct?)_12_12    // Allows querying this handler to get metadata about it_12    // See the flow metadata views standard for more info_12    access(all) view fun getViews(): [Type]_12    access(all) fun resolveView(_ view: Type): AnyStruct?_12}
To schedule a transaction, you store an instance of this resource in your account storage and pass a capability to the scheduler contract as part of the schedule request.
Here is a simple example implementation for a Handler's executeTransaction() function that transfers FLOW
at the scheduled time:
_43access(all) contract TransferFLOWHandler {_43_43    access(all) let HandlerStoragePath: StoragePath_43    access(all) let HandlerPublicPath: PublicPath_43    _43    access(all) resource Handler: FlowTransactionScheduler.TransactionHandler {_43_43        access(all) var from: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>_43_43        access(all) var amount: UFix64_43_43        // other functions left out for simplicity_43_43        // The actual logic that is executed when the scheduled transaction_43        // is executed_43        access(FlowTransactionScheduler.Execute) _43        fun executeTransaction(id: UInt64, data: AnyStruct?) {_43            if let to = data as Address {_43                let providerRef = self.from.borrow()_43                    ?? panic("Could not borrow a reference to the provider FlowToken Vault")_43_43                // Get a reference to the recipient's Receiver_43                let receiverRef =  getAccount(to)_43                    .capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)_43                    ?? panic("Could not borrow a Receiver reference to the FlowToken Vault in account \(to.toString())")_43_43                // Deposit the withdrawn tokens in the recipient's receiver_43                receiverRef.deposit(from: <-providerRef.withdraw(amount: self.amount))_43            _43            } else {_43                panic("Unable to transfer FLOW because the data provided when scheduling the transaction is not a Flow address!")_43            }_43        }_43    }_43_43    // A user would call this to get an instance of this handler_43    // for their own scheduling use_43    access(all) fun createHandler(amount: UFix64, from: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>): @Handler {_43        return <- create Handler(name: "Transfer FLOW Handler Resource", amount: amount, from: from)_43    }_43_43    // other functions left out for simplicity_43}
Scheduling
Scheduling involves creating the specific transaction that will execute at a specified future timestamp. The system uses three priority levels:
- High Priority: Guarantees execution in the first block with the scheduled time or fails scheduling, requires the highest fees
- Medium Priority: Best-effort execution as close as possible to the scheduled time known during scheduling
- Low Priority: Opportunistic execution when network capacity allows, lowest fees but no guarantee about timing.
Each transaction requires:
- Handler Capability: A capability to a resource implementing TransactionHandlerinterface, like the FLOW transfer one above.
- Timestamp: Future Unix timestamp when execution should occur (fractional seconds ignored)
- Execution Effort: Computational resources allocated (gas limit for the transaction)
- Fees: Flow tokens to cover execution costs and storage costs for the transaction data.
- Optional Data: Arbitrary data forwarded to the handler during execution that may be relevant to the transaction.
These arguments are required by the FlowTransactionScheduler.schedule() function.
This function returns a ScheduledTransaction resource object.
The Scheduled Transaction Manager standard (mentioned in the intro) provides an easy way for developers
and users to manage their scheduled transactions from a central place in their account. Users are strongly encouraged to use this.
More information about the Scheduled Transaction manager is in the section at the end of this document.
When a transaction is scheduled, the FlowTransactionScheduler.Scheduled event
is emitted with information about the scheduled transaction and handler.
Fees
Fee calculation includes:
- Base execution fee: Based on computational effort using standard Flow fee structure
- Priority multiplier: Higher priorities pay more (High: 10x, Medium: 5x, Low: 2x base rate)
- Storage fee: Cost for storing transaction data on-chain
Fees are paid upfront and are used in full, no refunds if the cost of execution was lower.
Please keep in mind the priority multiplier can change in the future. The fee configuration can be obtained from the contract, and estimate function can be used to check the fees upfront.
Execution of Transaction Handlers
When the scheduled time arrives, the Flow blockchain calls the executeTransaction method on your handler resource.
If the transaction succeeds, the FlowTransactionScheduler.Executed event
is emitted with information about the executed transaction.
If the scheduled transaction fails at any point during execution, the Executed event
is not emitted.
Canceling
Scheduled transactions can be canceled before execution. Canceling returns a portion of the fees (configurable refund percentage, 50% as of now). Please keep in mind the refund percentage can change in the future.
To cancel, you need the ScheduledTransaction resource that was returned during scheduling. The scheduled transaction manager also makes cancelling scheduled transaction easier.
Transaction Lifecycle
Scheduled transactions follow a specific lifecycle with corresponding events:
- 
Scheduled: Transaction is created and queued for future execution - Event: FlowTransactionScheduler.Scheduled
- Status: Scheduled
 
- Event: 
- 
Pending Execution: Transaction timestamp has arrived and it's ready for execution - Event: FlowTransactionScheduler.PendingExecution
- Status: Executed(Executed does not necessarily mean it succeeded, just that execution was attempted)
 
- Event: 
- 
Executed: Transaction has been processed by the blockchain - Event: FlowTransactionScheduler.Executed
- Status: Executed
 
- Event: 
- 
Canceled: Transaction was canceled before execution (optional path) - Event: FlowTransactionScheduler.Canceled
- Status: Canceled
 
- Event: 
Contracts
The FlowTransactionScheduler contract is deployed to the service account and manages all scheduled transactions across the network.
The FlowTransactionSchedulerUtils contract provides utilities for scheduled transactions, such as the transaction Manager resource, common handlers, and metadata views related to scheduled transactions.
Below are listed the addresses of both transaction scheduler contracts on each network they are deployed:
- Emulator: 0xf8d6e0586b0a20c7
- **Cadence Testing Framework: 0x0000000000000001
- Testnet: 0x8c5303eaa26202d6
Examples
1. Example Test Handler Contract
This contract implements the TransactionHandler interface and will be used in the following examples. It emits events when scheduled transactions are executed.
_55// TestFlowCallbackHandler.cdc - Simple test handler_55import "FlowTransactionScheduler"_55_55access(all) contract TestFlowScheduledTransactionHandler {_55    access(all) let HandlerStoragePath: StoragePath_55    access(all) let HandlerPublicPath: PublicPath_55    _55    access(all) event TransactionExecuted(data: String)_55_55    access(all) resource Handler: FlowTransactionScheduler.TransactionHandler {_55        _55        access(FlowTransactionScheduler.Execute) _55        fun executeTransaction(id: UInt64, data: AnyStruct?) {_55            if let string: String = data as? String {_55                emit TransactionExecuted(data: string)_55            } else {_55                emit TransactionExecuted(data: "bloop")_55            }_55        }_55_55        // public functions that anyone can call to get information about _55        // this handler_55        access(all) view fun getViews(): [Type] {_55            return [Type<StoragePath>(), Type<PublicPath>(), Type<MetadataViews.Display>()]_55        }_55_55        access(all) fun resolveView(_ view: Type): AnyStruct? {_55            switch view {_55                case Type<StoragePath>():_55                    return TestFlowScheduledTransactionHandler.HandlerStoragePath_55                case Type<PublicPath>():_55                    return TestFlowScheduledTransactionHandler.HandlerPublicPath_55                case Type<MetadataViews.Display>():_55                    return MetadataViews.Display(_55                        name: "Basic Scheduled Transaction Handler",_55                        description: "Emits a TransactionExecuted event when the scheduled transaction is executed",_55                        thumbnail: MetadataViews.HTTPFile(_55                            url: ""_55                        )_55                    )_55                default:_55                    return nil_55            }_55        }_55    }_55_55    access(all) fun createHandler(): @Handler {_55        return <- create Handler()_55    }_55_55    init() {_55        self.HandlerStoragePath = /storage/testCallbackHandler_55        self.HandlerPublicPath = /public/testCallbackHandler_55    }_55}
2. Scheduling a Transaction with the Manager
This example shows how to create and schedule a transaction that will execute at a future timestamp using the TestFlowCallbackHandler from Example 1.
_70// schedule.cdc_70import "FlowTransactionScheduler"_70import "FlowTransactionSchedulerUtils"_70import "TestFlowScheduledTransactionHandler"_70import "FlowToken"_70import "FungibleToken"_70_70transaction(timestamp: UFix64, feeAmount: UFix64, effort: UInt64, priority: UInt8, testData: AnyStruct?) {_70_70    prepare(account: auth(BorrowValue, SaveValue, IssueStorageCapabilityController, PublishCapability, GetStorageCapabilityController) &Account) {_70_70        // if a transaction scheduler manager has not been created for this account yet, create one_70        if !account.storage.check<@{FlowTransactionSchedulerUtils.Manager}>(from: FlowTransactionSchedulerUtils.managerStoragePath) {_70            let manager <- FlowTransactionSchedulerUtils.createManager()_70            account.storage.save(<-manager, to: FlowTransactionSchedulerUtils.managerStoragePath)_70_70            // create a public capability to the callback manager_70            let managerRef = account.capabilities.storage.issue<&{FlowTransactionSchedulerUtils.Manager}>(FlowTransactionSchedulerUtils.managerStoragePath)_70            account.capabilities.publish(managerRef, at: FlowTransactionSchedulerUtils.managerPublicPath)_70        }_70        _70        // If a transaction handler has not been created for this account yet, create one,_70        // store it, and issue a capability that will be used to create the transaction_70        if !account.storage.check<@TestFlowScheduledTransactionHandler.Handler>(from: TestFlowScheduledTransactionHandler.HandlerStoragePath) {_70            let handler <- TestFlowScheduledTransactionHandler.createHandler()_70        _70            account.storage.save(<-handler, to: TestFlowScheduledTransactionHandler.HandlerStoragePath)_70            account.capabilities.storage.issue<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>(TestFlowScheduledTransactionHandler.HandlerStoragePath)_70            _70            let publicHandlerCap = account.capabilities.storage.issue<&{FlowTransactionScheduler.TransactionHandler}>(TestFlowScheduledTransactionHandler.HandlerStoragePath)_70            account.capabilities.publish(publicHandlerCap, at: TestFlowScheduledTransactionHandler.HandlerPublicPath)_70        }_70_70        // Get the entitled capability that will be used to create the transaction_70        // Need to check both controllers because the order of controllers is not guaranteed_70        var handlerCap: Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>? = nil_70        _70        if let cap = account.capabilities.storage_70                            .getControllers(forPath: TestFlowScheduledTransactionHandler.HandlerStoragePath)[0]_70                            .capability as? Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}> {_70            handlerCap = cap_70        } else {_70            handlerCap = account.capabilities.storage_70                            .getControllers(forPath: TestFlowScheduledTransactionHandler.HandlerStoragePath)[1]_70                            .capability as! Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>_70        }_70        _70        // borrow a reference to the vault that will be used for fees_70        let vault = account.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault)_70            ?? panic("Could not borrow FlowToken vault")_70        _70        let fees <- vault.withdraw(amount: feeAmount) as! @FlowToken.Vault_70        let priorityEnum = FlowTransactionScheduler.Priority(rawValue: priority)_70            ?? FlowTransactionScheduler.Priority.High_70_70        // borrow a reference to the callback manager_70        let manager = account.storage.borrow<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}>(from: FlowTransactionSchedulerUtils.managerStoragePath)_70            ?? panic("Could not borrow a Manager reference from \(FlowTransactionSchedulerUtils.managerStoragePath)")_70_70        // Schedule the regular transaction with the main contract_70        manager.schedule(_70            handlerCap: handlerCap!,_70            data: testData,_70            timestamp: timestamp,_70            priority: priorityEnum,_70            executionEffort: effort,_70            fees: <-fees_70        )_70    }_70}
3. Querying Transaction Information
Get Status: This script demonstrates how to check the current status of a scheduled transaction using the global status function.
Get all Tx Info: This script gets all the internal information about a scheduled transaction.
Manager Scripts
The manager provides many different ways to get information about all of your scheduled transactions. Check out all the scripts you can use with your manager here.
4. Canceling a Scheduled Transaction
This transaction shows how to cancel a scheduled transaction and receive a partial refund of the fees paid.
_19// cancel_transaction.cdc_19import "FlowTransactionScheduler"_19import "FlowToken"_19_19transaction(transactionId: UInt64) {_19    prepare(account: auth(BorrowValue, SaveValue, LoadValue) &Account) {_19        _19        // borrow a reference to the manager_19        let manager = account.storage.borrow<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}>(from: FlowTransactionSchedulerUtils.managerStoragePath)_19            ?? panic("Could not borrow a Manager reference from \(FlowTransactionSchedulerUtils.managerStoragePath)")_19_19        // Get the vault where the refund should be deposited_19        let vault = account.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault)_19            ?? panic("Could not borrow FlowToken vault")_19_19        // cancel the transaction_19        vault.deposit(from: <-manager.cancel(id: id))_19    }_19}
5. Fee Estimation
This script helps estimate the cost of scheduling a transaction before actually submitting it, useful for budgeting and validation.
_20// estimate_fees.cdc - Script to estimate scheduling costs  _20import "FlowTransactionScheduler"_20_20access(all) fun main(_20    dataSize: AnyStruct?,_20    timestamp: UFix64,_20    priority: UInt8,_20    executionEffort: UInt64_20): FlowTransactionScheduler.EstimatedScheduledTransaction {_20    _20    let priorityEnum = FlowTransactionScheduler.Priority(rawValue: priority)_20        ?? FlowTransactionScheduler.Priority.Medium_20    _20    return FlowTransactionScheduler.estimate(_20        data: dataSize,_20        timestamp: timestamp, _20        priority: priorityEnum,_20        executionEffort: executionEffort_20    )_20}
6. Monitoring Execution Events
Use the Flow CLI to monitor all scheduled transaction events in real-time (example for testnet - account addresses may differ):
_10flow events get \_10  A.8c5303eaa26202d6.FlowTransactionScheduler.Scheduled \_10  A.8c5303eaa26202d6.FlowTransactionScheduler.PendingExecution \_10  A.8c5303eaa26202d6.FlowTransactionScheduler.Executed \_10  A.8c5303eaa26202d6.FlowTransactionScheduler.Canceled \_10  A.373ce83aef691d2d.TestFlowCallbackHandler.TransactionExecuted \_10  --last 200 \_10  -n testnet
This command fetches the last 200 blocks of events for:
- Scheduled: When a transaction is scheduled
- PendingExecution: When a transaction is ready for execution
- Executed: When a transaction has been executed
- Canceled: When a transaction is canceled
- TransactionExecuted: Custom event from the test handler
These examples demonstrate the complete lifecycle of scheduled transactions: creating handlers, scheduling execution, monitoring events, and managing cancellations. The system provides flexibility for various automation scenarios while maintaining network stability through resource limits and priority management.
Tools
Support for scheduled transactions in different tools is still work in progress and is coming soon. The Flow CLI and Access Node API will support specific commands and APIs to query scheduled transactions by ID, making it easier to manage and monitor your scheduled transactions programmatically.
The flow-go-sdk will also add support for these new commands, providing native integration for Go applications working with scheduled transactions.
Block explorer support for scheduled transactions is also coming, which will provide a visual interface to view and track scheduled transaction execution on the Flow blockchain.
For feature requests and suggestions for scheduled transaction tooling, please visit github.com/onflow/flow and create an issue with the tag scheduled_transactions.
Read FLIP for more details: https://github.com/onflow/flips/blob/main/protocol/20250609-scheduled-callbacks.md