Useful Patterns
Common patterns and best practices for Invoice Radar plugin development
Authentication Patterns
Common patterns for authentication checks (checkAuth)
Pattern 1: Go to Dashboard and Check URL
Many services automatically redirect to the login page if the user is not authenticated. We can use this behavior to check if the user is authenticated.
{
"action": "navigate",
"url": "https://example.com/login"
},
{
"action": "checkURL",
"url": "https://example.com/account"
}Depending on the service, they may redirect you from the dashboard to the login page if you are not authenticated. In this case, you can use the checkURL step to check if the URL still matches after visiting the dashboard.
{
"action": "navigate",
"url": "https://example.com/dashboard"
},
{
"action": "checkURL",
"url": "https://example.com/dashboard"
}Note that you can use glob patterns to match dynamic URLs: https://example.com/dashboard/**.
Pattern 2: Check for Logout Button
You can use a selector that is unique to the authenticated state to check if the user is authenticated, e.g. a logout button or profile link.
{
"action": "navigate",
"url": "https://example.com/home"
},
{
"action": "waitForElement",
"selector": "#logout-button"
}Pattern 3: Wait for Condition
You can use the waitForCondition step to wait for a custom JavaScript condition to become true.
{
"action": "waitForCondition",
"script": "document.cookie.includes('auth_token=')"
}Tip: Make sure the website is fully loaded
In some cases, the website has not fully loaded when the checkElementExists step is executed. To avoid this, you can use the waitForNetworkIdle attribute to wait for the page to be fully loaded.
{
"action": "navigate",
"url": "https://example.com/home",
"waitForNetworkIdle": true
},
{
"action": "checkElementExists",
"selector": "#logout-button"
}Common patterns for start authentication (startAuth)
Pattern 1: Go to Login Page and wait for logged in state
Most authentication processes start by navigating to the login page and waiting for a specific element to appear after a successful login.
Remember that the browser will be visible during the authentication process, allowing the user to interact with the login form. The authentication flow itself can be automated, but isn't required.
{
"action": "navigate",
"url": "https://example.com/login"
},
{
"action": "waitForElement",
"selector": "#logout-button"
}To give the user enough time to log in, it's recommended to provide a long timeout to the wait step, with a default of 120 seconds.
Advanced Patterns
Running a fetch request
Sometimes, you might need to run a fetch request inside a step to fetch data from an API. To do this, you can use the extractAll action.
{
"action": "extractAll",
"variable": "invoice",
"script": "fetch('https://example.com/api/invoices').then(res => res.json())",
"forEach": [
{
"action": "downloadPdf",
"url": "{{invoice.url}}",
"document": {
"id": "{{invoice.id}}",
"date": "{{invoice.date}}",
"total": "{{invoice.total}}"
}
}
]
}This will run the fetch request and return the result as a JavaScript object.
Paginating with a "Next" button
Use selector pagination when the UI has a next button or link.
{
"action": "extractAll",
"selector": ".invoice-row",
"fields": {
"id": ".invoice-id",
"date": ".invoice-date",
"total": ".invoice-total",
"url": ".invoice-link[href]"
},
"pagination": {
"selector": "button.next-page",
"waitForSelector": ".invoice-row"
},
"forEach": [
{
"action": "downloadPdf",
"url": "{{item.url}}",
"document": {
"id": "{{item.id}}",
"date": "{{item.date}}",
"total": "{{item.total}}"
}
}
]
}If waitForSelector is omitted, Invoice Radar waits for network idle after clicking the next selector.
Cursor-based pagination (script)
Use script pagination for APIs that return a cursor or nextPage token. It also works for selector-based extraction when you want custom logic to decide whether to paginate (and can optionally click "next" inside the script).
{
"action": "extractAll",
"script": "async (prev) => { const cursor = prev?.nextCursor; return fetch(`https://example.com/api/invoices?cursor=${cursor ?? ''}`).then(res => res.json()) }",
"transform": "(result) => result.items",
"pagination": {
"script": "(result) => Boolean(result.nextCursor)",
"behavior": "paginateThenProcess"
},
"forEach": [
{
"action": "downloadPdf",
"url": "{{item.pdfUrl}}",
"document": "{{item}}"
}
]
}In this pattern:
- The
extractAllscript receives the previous raw result asprev. - The
pagination.scriptreceives the current raw result and returnstrueto continue. transformmaps the raw response to an array of items.
For selector-based extractAll, pagination.script receives the extracted items array (after transform) and can also perform pagination actions like clicking a "next" button before returning true.
Run steps inside an <iframe/>
In some scenarios, you might need to run a step inside an <iframe/> element. To do this, you can use the iframe attribute on the step.
{
"action": "click",
"selector": "#button-inside-iframe",
"iframe": true
}By setting iframe to true, Invoice Radar will find the first <iframe/> element on the page and run the step inside it.
You can also use a string that is contained inside the iframe's src attribute to target a specific iframe.
{
"action": "click",
"selector": "#button-inside-iframe",
"iframe": "iframe.example.com"
}Use Network Idle Wisely
Only use waitForNetworkIdle when necessary, as it can slow down execution:
{
"action": "navigate",
"url": "https://example.com/dashboard",
"waitForNetworkIdle": true
}Use it for:
- Pages with heavy AJAX loading
- Single-page applications
- When authentication state depends on network requests