
Sf Industry Commoncore Integration Procedure
- 937 installs
- 423 repo stars
- Updated April 27, 2026
- jaganpro/sf-skills
sf-industry-commoncore-integration-procedure is a code-generation skill that produces reusable Salesforce OmniStudio Integration Procedure templates with DataRaptor Extract Actions and Set Values steps for developers bui
About
sf-industry-commoncore-integration-procedure is a Salesforce OmniStudio skill that generates Integration Procedure element JSON templates for Industry Cloud Common Core workflows. It outputs OmniProcess element definitions including DataRaptor Extract Action blocks with PropertySetConfig fields such as bundle, chainOnStep, failOnStepError, and ignoreCache, plus Set Values steps with correct sequence numbers and parent process bindings. Developers reach for this skill when standing up repeatable Integration Procedures instead of hand-authoring each OmniStudio element in Setup. The templates use placeholders like {{ParentProcessId}}, {{ElementName}}, and {{DataRaptorName}} for copy-paste or scripted insertion into Salesforce metadata.
- Generates complete OmniProcess JSON definitions for Integration Procedures
- Includes DataRaptor Extract Action and Set Values step templates
- Handles PropertySetConfig with bundle, error handling, and response options
- Produces ready-to-import Integration Procedure metadata with WebComponentKey
- Built on collective Salesforce OmniStudio community patterns
Sf Industry Commoncore Integration Procedure by the numbers
- 937 all-time installs (skills.sh)
- +4 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #414 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/jaganpro/sf-skills --skill sf-industry-commoncore-integration-procedureAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 937 |
|---|---|
| repo stars | ★ 423 |
| Security audit | 3 / 3 scanners passed |
| Last updated | April 27, 2026 |
| Repository | jaganpro/sf-skills ↗ |
How do you scaffold Salesforce OmniStudio Integration Procedures?
Generate reusable Salesforce OmniStudio Integration Procedure templates that combine DataRaptor Extract Actions, Set Values steps, and properly configured property sets
Who is it for?
Salesforce developers and OmniStudio architects building Industry Cloud Common Core integrations who need consistent Integration Procedure scaffolding.
Skip if: Teams outside Salesforce OmniStudio or projects that only need Apex REST endpoints without Integration Procedures.
When should I use this skill?
The user asks to create or template a Salesforce Integration Procedure, DataRaptor Extract Action, or OmniStudio Industry Cloud integration element.
What you get
Reusable Integration Procedure element JSON with DataRaptor Extract Actions, Set Values steps, and configured PropertySetConfig blocks.
- Integration Procedure element JSON
- DataRaptor Extract Action templates
- Set Values step templates
Files
sf-industry-commoncore-integration-procedure: OmniStudio Integration Procedure Creation and Validation
Expert OmniStudio Integration Procedure (IP) builder with deep knowledge of server-side process orchestration. Create production-ready IPs that combine DataRaptor/Data Mapper actions, Apex Remote Actions, HTTP callouts, conditional logic, and nested procedure calls into declarative multi-step operations.
Quick Reference
Scoring: 110 points across 6 categories. Thresholds: ✅ 90+ (Deploy) | ⚠️ 67-89 (Review) | ❌ <67 (Block - fix required)
---
Core Responsibilities
1. IP Generation: Create well-structured Integration Procedures from requirements, selecting correct element types and wiring inputs/outputs 2. Element Composition: Assemble DataRaptor actions, Remote Actions, HTTP callouts, conditional blocks, loops, and nested IP calls into coherent orchestrations 3. Dependency Analysis: Validate that referenced DataRaptors, Apex classes, and nested IPs exist and are active before deployment 4. Error Handling: Enforce try/catch patterns, conditional rollback, and response validation across all data-modifying steps
---
CRITICAL: Orchestration Order
sf-industry-commoncore-omnistudio-analyze -> sf-industry-commoncore-datamapper -> sf-industry-commoncore-integration-procedure -> sf-industry-commoncore-omniscript -> sf-industry-commoncore-flexcard (you are here: sf-industry-commoncore-integration-procedure)
Data Mappers referenced by the IP must exist FIRST. Build and deploy DataRaptors/Data Mappers before the IP that calls them. The IP must be active before any OmniScript or FlexCard can invoke it.
---
Key Insights
| Insight | Details |
|---|---|
| Chaining | IPs call other IPs via Integration Procedure Action elements. Output of one step feeds input of the next via response mapping. Design data flow linearly where possible. |
| Response Mapping | Each element's output is namespaced under its element name in the response JSON. Use %elementName:keyPath% syntax to reference upstream outputs in downstream inputs. |
| Caching | IPs support platform cache for read-heavy orchestrations. Set cacheType and cacheTTL in the procedure's PropertySet. Avoid caching procedures that perform DML. |
| Versioning | Type/SubType pairs uniquely identify an IP. Use SubType for versioning (e.g., Type=AccountOnboarding, SubType=v2). Only one version can be active at a time per Type/SubType. |
Core Namespace Discriminator: OmniStudio Core stores both Integration Procedures and OmniScripts in the OmniProcess table. Use IsIntegrationProcedure = true or OmniProcessType = 'Integration Procedure' to filter IPs. Without a filter, queries return mixed results.
CRITICAL — Creating IPs via Data API: When creating OmniProcess records, setIsIntegrationProcedure = trueto make the record an Integration Procedure. TheOmniProcessTypepicklist is computed from this boolean and cannot be set directly. Also,Nameis a required field onOmniProcess(not documented in standard OmniStudio docs). Usesf api request rest --method POST --body @file.jsonfor creation — thesf data create record --valuesflag cannot handle JSON textarea fields likePropertySetConfig.
---
Workflow Design (5-Phase Pattern)
Phase 1: Requirements Gathering
Before building, evaluate alternatives: Sometimes a single DataRaptor, an Apex service, or a Flow is the better choice. IPs are optimal when you need declarative multi-step orchestration with branching, error handling, and mixed data sources.
Ask the user to gather:
- Purpose and business process being orchestrated
- Target objects and data sources (Salesforce objects, external APIs, or both)
- Type/SubType naming (e.g.,
Type=OrderProcessing,SubType=Standard) - Target org alias for deployment
Then: Check existing IPs via CLI query (see CLI Commands below), identify reusable DataRaptors/Data Mappers, and review dependent components with sf-industry-commoncore-omnistudio-analyze.
Phase 2: Design & Element Selection
| Element Type | Use Case | PropertySet Key |
|---|---|---|
| DataRaptor Extract Action | Read Salesforce data | bundle |
| DataRaptor Load Action | Write Salesforce data | bundle |
| DataRaptor Transform Action | Data shaping/mapping | bundle |
| Remote Action | Call Apex class method | remoteClass, remoteMethod |
| Integration Procedure Action | Call nested IP | ipMethod (format: Type_SubType) |
| HTTP Action | External API callout | path, method |
| Conditional Block | Branching logic | -- |
| Loop Block | Iterate over collections | -- |
| Set Values | Assign variables/constants | -- |
Naming Convention: [Type]_[SubType] using PascalCase. Element names within the IP should describe their action clearly (e.g., GetAccountDetails, ValidateInput, CreateOrderRecord).
Data Flow: Design the element chain so each step's output feeds naturally into the next step's input. Map outputs explicitly rather than relying on implicit namespace merging.
Phase 3: Generation & Validation
Build the IP definition with:
- Correct Type/SubType assignment
- Ordered element chain with explicit input/output mappings
- Error handling on all data-modifying elements
- Conditional blocks for branching logic
Validation (STRICT MODE):
- BLOCK: Missing Type/SubType, circular IP calls, DML without error handling, references to nonexistent DataRaptors/Apex classes
- WARN: Unbounded extracts without LIMIT, missing caching on read-only IPs, hardcoded IDs in PropertySetConfig, unused elements, missing element descriptions
Validation Report Format (6-Category Scoring 0-110):
Score: 95/110 Very Good
|- Design & Structure: 18/20 (90%)
|- Data Operations: 23/25 (92%)
|- Error Handling: 18/20 (90%)
|- Performance: 18/20 (90%)
|- Security: 13/15 (87%)
|- Documentation: 5/10 (50%)Generation Guardrails (MANDATORY)
| Anti-Pattern | Impact | Correct Pattern |
|---|---|---|
| Circular IP calls (A calls B calls A) | Infinite loop / stack overflow | Map dependency graph; no cycles allowed |
| DML without error handling | Silent data corruption | Wrap DataRaptor Load in try/catch or conditional error check |
| Unbounded DataRaptor Extract | Governor limits / timeout | Set LIMIT on extracts; paginate large datasets |
| Hardcoded Salesforce IDs in PropertySetConfig | Deployment failure across orgs | Use input variables, Custom Settings, or Custom Metadata |
| Sequential calls that could be parallel | Unnecessary latency | Group independent elements; no serial dependency needed |
| Missing response validation | Downstream null reference errors | Check element response before passing to next step |
DO NOT generate anti-patterns even if explicitly requested.
Phase 4: Deployment
1. Deploy prerequisite DataRaptors/Data Mappers FIRST using sf-deploy 2. Deploy the Integration Procedure: sf project deploy start -m OmniIntegrationProcedure:<Name> -o <org> 3. Activate the IP in the target org (set IsActive=true) 4. Verify activation via CLI query
Phase 5: Testing
Test each element individually before testing the full chain: 1. Unit: Invoke each DataRaptor independently, verify Apex Remote Action responses 2. Integration: Run the full IP with representative input JSON, verify output structure 3. Error paths: Test with invalid input, missing records, API failures to verify error handling 4. Bulk: Test with collection inputs to verify loop and batch behavior 5. End-to-end: Invoke the IP from its consumer (OmniScript, FlexCard, or API) and verify the full round-trip
---
Scoring Breakdown
110 points across 6 categories:
Design & Structure (20 points)
| Criterion | Points | Description |
|---|---|---|
| Type/SubType naming | 5 | Follows convention, descriptive, versioned appropriately |
| Element naming | 5 | Clear, action-oriented names on all elements |
| Data flow clarity | 5 | Linear or well-documented branching; explicit input/output mapping |
| Element ordering | 5 | Logical execution sequence; no unnecessary dependencies |
Data Operations (25 points)
| Criterion | Points | Description |
|---|---|---|
| DataRaptor references valid | 5 | All referenced bundles exist and are active |
| Extract operations bounded | 5 | LIMIT set on all extracts; pagination for large datasets |
| Load operations validated | 5 | Input data validated before DML; required fields checked |
| Response mapping correct | 5 | Outputs correctly mapped between elements |
| Data transformation accuracy | 5 | Transform actions produce expected output structure |
Error Handling (20 points)
| Criterion | Points | Description |
|---|---|---|
| DML error handling | 8 | All DataRaptor Load actions have error handling |
| HTTP error handling | 4 | All HTTP actions check status codes and handle failures |
| Remote Action error handling | 4 | Apex exceptions caught and surfaced |
| Rollback strategy | 4 | Multi-step DML has conditional rollback or compensating actions |
Performance (20 points)
| Criterion | Points | Description |
|---|---|---|
| No unbounded queries | 5 | All extracts have reasonable LIMIT values |
| Caching applied | 5 | Read-only procedures use platform cache where appropriate |
| Parallel execution | 5 | Independent elements not serialized unnecessarily |
| No redundant calls | 5 | Same data not fetched multiple times across elements |
Security (15 points)
| Criterion | Points | Description |
|---|---|---|
| No hardcoded IDs | 5 | IDs passed as input variables or from metadata |
| No hardcoded credentials | 5 | API keys/tokens use Named Credentials or Custom Settings |
| Input validation | 5 | User-supplied input sanitized before use in queries or DML |
Documentation (10 points)
| Criterion | Points | Description |
|---|---|---|
| Procedure description | 3 | Clear description of purpose and business context |
| Element descriptions | 4 | Each element has a description explaining its role |
| Input/output documentation | 3 | Expected input JSON and output JSON structure documented |
---
CLI Commands
# Query active Integration Procedures
sf data query -q "SELECT Id,Name,Type,SubType,IsActive FROM OmniProcess WHERE IsActive=true AND IsIntegrationProcedure=true" -o <org>
# Query all Integration Procedures (including inactive)
sf data query -q "SELECT Id,Name,Type,SubType,IsActive,LastModifiedDate FROM OmniProcess WHERE IsIntegrationProcedure=true ORDER BY LastModifiedDate DESC" -o <org>
# Retrieve an Integration Procedure
sf project retrieve start -m OmniIntegrationProcedure:<Name> -o <org>
# Deploy an Integration Procedure
sf project deploy start -m OmniIntegrationProcedure:<Name> -o <org>
# Deploy with dry-run validation first
sf project deploy start -m OmniIntegrationProcedure:<Name> -o <org> --dry-runCore Namespace Note: The IsIntegrationProcedure=true filter is REQUIRED (or equivalently OmniProcessType='Integration Procedure'). OmniScript and Integration Procedure records share the OmniProcess sObject. Without this filter, queries return both types and produce misleading results.
---
Cross-Skill Integration
| From Skill | To sf-industry-commoncore-integration-procedure | When |
|---|---|---|
| sf-industry-commoncore-omnistudio-analyze | -> sf-industry-commoncore-integration-procedure | "Analyze dependencies before building IP" |
| sf-industry-commoncore-datamapper | -> sf-industry-commoncore-integration-procedure | "DataRaptor/Data Mapper is ready, wire it into IP" |
| sf-apex | -> sf-industry-commoncore-integration-procedure | "Apex Remote Action class deployed, configure in IP" |
| From sf-industry-commoncore-integration-procedure | To Skill | When |
|---|---|---|
| sf-industry-commoncore-integration-procedure | -> sf-deploy | "Deploy IP to target org" |
| sf-industry-commoncore-integration-procedure | -> sf-industry-commoncore-omniscript | "IP is active, build OmniScript that calls it" |
| sf-industry-commoncore-integration-procedure | -> sf-industry-commoncore-flexcard | "IP is active, build FlexCard data source" |
| sf-industry-commoncore-integration-procedure | -> sf-industry-commoncore-omnistudio-analyze | "Verify IP dependency graph before deployment" |
---
Edge Cases
| Scenario | Solution |
|---|---|
| IP calls itself (direct recursion) | Block at design time; circular dependency check is mandatory |
| IP calls IP that calls original (indirect recursion) | Map full call graph; sf-industry-commoncore-omnistudio-analyze detects cycles |
| DataRaptor not yet deployed | Deploy DataRaptors first; IP deployment will fail on missing references |
| External API timeout | Set timeout values on HTTP Action elements; implement retry logic or graceful degradation |
| Large collection input to Loop Block | Set batch size; test with realistic data volumes to avoid CPU timeout |
| Type/SubType collision with existing IP | Query existing IPs before creating; SubType versioning avoids collisions |
| Mixed namespace (Vlocity vs Core) | Confirm org namespace; element property names differ between packages |
Debug: IP not executing -> check IsActive flag + Type/SubType match | Elements skipped -> verify conditional block logic + input data shape | Timeout -> check DataRaptor query scope + HTTP timeout settings | Deployment failure -> verify all referenced components deployed and active
---
Notes
Dependencies (optional): sf-deploy, sf-industry-commoncore-datamapper, sf-industry-commoncore-omnistudio-analyze | API: 66.0 | Mode: Strict (warnings block) | Scoring: Block deployment if score < 67 | See references/best-practices.md and references/element-types.md for detailed guidance.
Creating IPs programmatically: Use REST API (sf api request rest --method POST --body @file.json). Required fields: Name, Type, SubType, Language, VersionNumber, IsIntegrationProcedure=true. Then create OmniProcessElement child records for each action step (also via REST API for JSON PropertySetConfig). Activate by setting IsActive=true after all elements are created.
---
License
MIT License. Copyright (c) 2026 David Ryan (weytani)
{
"OmniProcessId": "{{ParentProcessId}}",
"Name": "{{ElementName}}",
"Type": "DataRaptor Extract Action",
"Description": "{{Description}}",
"IsActive": true,
"Level": 0,
"SequenceNumber": {{SequenceNumber}},
"PropertySetConfig": "{\"bundle\":\"{{DataRaptorName}}\",\"chainOnStep\":false,\"disableUniqueSFDCObjectCheck\":false,\"failOnStepError\":true,\"ignoreCache\":false,\"postTransformBundle\":\"\",\"preTransformBundle\":\"\",\"remoteOptions\":{},\"responseJSONNode\":\"\",\"responseJSONPath\":\"\",\"sendOnlyFailedResponse\":false,\"vlcSI\":{\"bundleName\":\"\",\"interfaceName\":\"\",\"remoteMethodName\":\"\"}}"
}
{
"OmniProcessId": "{{ParentProcessId}}",
"Name": "{{ElementName}}",
"Type": "Set Values",
"Description": "{{Description}}",
"IsActive": true,
"Level": 0,
"SequenceNumber": {{SequenceNumber}},
"PropertySetConfig": "{\"ElementValueMap\":{\"{{OutputKey}}\":\"{{OutputValue}}\"},\"chainOnStep\":false,\"failOnStepError\":true}"
}
{
"Name": "{{Type}}_{{SubType}}",
"Type": "{{Type}}",
"SubType": "{{SubType}}",
"Language": "English",
"VersionNumber": 1,
"IsActive": false,
"IsIntegrationProcedure": true,
"Description": "{{Description}}",
"PropertySetConfig": "{\"persistentComponent\":true,\"trackingCustomData\":{},\"enableLWCRuntime\":true}",
"WebComponentKey": "{{Type}}/{{SubType}}/English/1"
}
Credits & Acknowledgments
This skill was built upon the collective wisdom of the Salesforce OmniStudio community. We gratefully acknowledge the following authors and resources whose ideas, patterns, and best practices have shaped this skill.
---
Authors & Contributors
Jag Valaiyapathy
sf-skills Project Creator
Key contributions:
- sf-skills framework architecture and skill patterns
- Scoring rubric design (110-point system)
- Cross-skill orchestration model
- OmniStudio skill suite design
David Ryan (weytani)
Primary Contributor — sf-industry-commoncore-integration-procedure — GitHub
Key contributions:
- Integration Procedure element type reference
- Error handling and rollback patterns
- Caching strategy documentation
- Best practices consolidation
---
Official Salesforce Resources
Salesforce OmniStudio Documentation
[help.salesforce.com](https://help.salesforce.com/s/articleView?id=sf.os_integration_procedures.htm)
Key contributions:
- Integration Procedure configuration reference
- Element type definitions and PropertySetConfig properties
- OmniProcess data model documentation
- Core namespace migration guidance
Salesforce Trailhead — OmniStudio Modules
[trailhead.salesforce.com](https://trailhead.salesforce.com/en/content/learn/modules/omnistudio-integration-procedures)
Key contributions:
- Integration Procedure fundamentals
- Hands-on exercises for element configuration
- DataRaptor and IP chaining patterns
- OmniStudio Developer certification prep material
Salesforce Architects
[architect.salesforce.com](https://architect.salesforce.com/)
Key contributions:
- OmniStudio architecture patterns
- Performance and scalability guidance
- Enterprise integration strategies
---
Community Resources
UnofficialSF Team
[UnofficialSF.com](https://unofficialsf.com/)
Key contributions:
- OmniStudio community resources and tutorials
- Integration Procedure pattern documentation
- DataRaptor best practices that inform IP design
Salesforce Ben
[salesforceben.com](https://www.salesforceben.com/)
Key contributions:
- OmniStudio overview articles
- Integration Procedure vs Flow comparison guides
- Practical implementation tutorials
Salesforce Stack Exchange
[salesforce.stackexchange.com](https://salesforce.stackexchange.com/)
Key contributions:
- Community Q&A on Integration Procedure issues
- PropertySetConfig troubleshooting solutions
- DataRaptor-IP integration patterns
- Core namespace migration solutions
Vlocity / Industries CPQ Community
Various community blogs and forums
Key contributions:
- Integration Procedure patterns from Vlocity managed package era
- VlocityOpenInterface implementation patterns
- Enterprise-scale IP orchestration examples
- Migration guidance from Vlocity to OmniStudio Core
---
Key Concepts Credited
Element Composition Patterns
The element ordering and composition patterns draw from both Salesforce official documentation and community-established practices for multi-step server-side orchestrations.
Error Handling and Rollback
The compensating action pattern for IP rollback was established by enterprise implementations where strict data integrity requirements demanded explicit undo logic across independent DML operations.
Caching Strategies
Platform cache integration patterns for Integration Procedures were documented through community experience with high-volume OmniScript/FlexCard deployments that required performant data retrieval.
Type/SubType Versioning
The versioning strategy using SubType for IP lifecycle management emerged from enterprise deployment practices managing multiple active versions across sandbox and production environments.
---
Special Thanks
To the Salesforce OmniStudio community — developers, architects, and consultants — for continuously sharing knowledge about Integration Procedure design, performance optimization, and real-world implementation patterns.
---
If we've missed anyone whose work influenced this skill, please let us know so we can add proper attribution.
MIT License
Copyright (c) 2026 David Ryan and Jag Valaiyapathy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
<!-- Parent: sf-industry-commoncore-integration-procedure/SKILL.md -->
Integration Procedure Best Practices
Version: 1.0.0
Applies to: OmniStudio Integration Procedures (Core namespace and Vlocity managed package)
This guide consolidates best practices for building maintainable, performant, and reliable Integration Procedures.
---
Table of Contents
Strategy & Planning 1. When to Use an Integration Procedure 2. IP vs Flow vs Apex: Decision Framework
Design & Structure 3. Element Ordering and Execution Flow 4. Naming Conventions 5. Input/Output Contract Design
Error Handling 6. Error Handling Patterns 7. Conditional Rollback
Data Operations 8. Response Mapping from Data Mappers 9. Bounded Extracts and Pagination
Performance 10. Caching Strategies 11. Batch vs Sequential Execution 12. Reducing Redundant Calls
Maintenance 13. Versioning with Type/SubType 14. Documentation Standards
---
1. When to Use an Integration Procedure
Integration Procedures are optimal when:
- You need declarative multi-step orchestration combining reads, writes, transforms, and callouts
- The process involves multiple data sources (Salesforce objects + external APIs)
- You need server-side execution without user interaction (unlike OmniScripts)
- The orchestration requires conditional branching or loop iteration over collections
- You want reusability — the same IP can be called by OmniScripts, FlexCards, Apex, or other IPs
Integration Procedures are NOT optimal when:
- A single DataRaptor can accomplish the task (no orchestration needed)
- The logic is purely computational with no data access (use Apex or a Formula)
- You need real-time user interaction during execution (use OmniScript with embedded IP calls)
- The process is a simple CRUD operation on one object (use a DataRaptor Load directly)
---
2. IP vs Flow vs Apex: Decision Framework
| Criterion | Integration Procedure | Flow | Apex |
|---|---|---|---|
| Multi-step orchestration | Strong — designed for chaining | Possible but verbose | Manual coding required |
| External API callouts | HTTP Action element | HTTP Callout action (GA) | Full control via HttpRequest |
| OmniStudio integration | Native — called by OmniScript/FlexCard | Requires adapter | Requires @AuraEnabled or REST |
| Complex business logic | Limited — use Remote Action for complex logic | Decision elements | Full language support |
| Bulk data processing | Loop Block with batch sizing | Collection operations | SOQL/DML with governor awareness |
| Error handling granularity | Per-element try/catch | Fault paths per DML | Full try/catch/finally |
| Admin maintainability | Declarative, visual | Declarative, visual | Requires developer |
| Debugging | Preview panel, debug logs | Flow debug | Apex debug logs |
| Transaction control | Limited — each element may be its own transaction | Single transaction (before-save) or per-screen | Full transaction control |
Rule of thumb: If the process serves OmniStudio components, use an IP. If it serves standard Salesforce UI automation, use a Flow. If it requires complex computation or fine-grained transaction control, use Apex.
---
3. Element Ordering and Execution Flow
Elements execute top-to-bottom in the order they appear in the procedure definition. Follow these ordering principles:
Recommended Order
1. Set Values — Initialize variables, set defaults, normalize input 2. Validation — Conditional Block to validate required inputs before proceeding 3. Data Retrieval — DataRaptor Extract or HTTP Action to fetch required data 4. Data Transformation — DataRaptor Transform to shape data for downstream use 5. Business Logic — Conditional Blocks, Remote Actions for decisions 6. Data Modification — DataRaptor Load to write records 7. Response Assembly — Set Values to build the output response
Ordering Rules
- Fetch before transform: Extract data before attempting to reshape it
- Validate before mutate: Check preconditions before performing DML
- Independent reads first: Group all DataRaptor Extracts at the top where possible
- Error-prone steps last: Place steps that might fail (HTTP, DML) after all preparatory work
- Response assembly at the end: Build the final output after all processing completes
---
4. Naming Conventions
Type/SubType
- Type: Business process name in PascalCase (e.g.,
AccountOnboarding,OrderProcessing) - SubType: Version or variant identifier (e.g.,
Standard,v2,Retail) - Combined format:
Type_SubType(e.g.,AccountOnboarding_Standard)
Element Names
- Use descriptive, action-oriented names:
GetAccountDetails,ValidateAddress,CreateContactRecord - Prefix with the action type for scannability:
Extract_,Load_,Transform_,Check_,Call_ - Avoid generic names like
Step1,Action2,Process
Variables
- Input variables:
in_variableName - Output variables:
out_variableName - Internal variables:
var_variableName
---
5. Input/Output Contract Design
Define clear contracts for what the IP expects and returns:
Input Contract
{
"accountId": "001XXXXXXXXXXXX",
"includeContacts": true,
"contactLimit": 10
}- Document every expected input field with type and whether it is required
- Set default values in a Set Values element for optional inputs
- Validate required inputs with a Conditional Block before processing
Output Contract
{
"success": true,
"account": { "Name": "Acme Corp", "Industry": "Technology" },
"contacts": [ { "Name": "John Doe", "Email": "john@acme.com" } ],
"errors": []
}- Return a consistent structure regardless of success or failure
- Include a
successboolean and anerrorsarray in every response - Namespace outputs clearly so consumers know which element produced which data
---
6. Error Handling Patterns
Per-Element Error Handling
Every element that performs DML or makes an external call should have error handling:
1. DataRaptor Load Actions: Check the response for error indicators. Use a Conditional Block after the Load to inspect the result and branch to error handling if needed.
2. HTTP Actions: Check the HTTP status code in the response. Branch on non-2xx responses.
3. Remote Actions: Apex exceptions surface in the element response. Check for error keys in the output.
Try/Catch Pattern
Structure error handling as a three-step pattern:
1. Try: Execute the data-modifying element 2. Check: Conditional Block inspects the element's response for errors 3. Handle: Set Values element builds an error response or a Conditional Block routes to compensating actions
Error Response Structure
{
"success": false,
"errors": [
{
"element": "CreateOrderRecord",
"code": "FIELD_CUSTOM_VALIDATION_EXCEPTION",
"message": "Order amount exceeds credit limit"
}
]
}---
7. Conditional Rollback
When an IP performs multiple sequential DML operations and a later step fails:
Compensating Action Pattern
1. After each successful DML, store the created record IDs in variables 2. If a subsequent step fails, use stored IDs to execute compensating DataRaptor Load actions (delete/update) to undo prior changes 3. Return a clear error response indicating which steps succeeded and which failed
Design Considerations
- IPs do not have native transaction rollback across elements
- Each DataRaptor Load action may commit independently
- Plan for partial failure in multi-step write operations
- Consider whether the business process can tolerate partial completion
- For strict atomicity requirements, move the multi-DML logic to an Apex Remote Action where you control the transaction boundary
---
8. Response Mapping from Data Mappers
How Response Namespacing Works
Each element's output is stored under a key matching the element name. For example, if a DataRaptor Extract element named GetAccount returns { "Name": "Acme" }, the IP's response contains:
{
"GetAccount": {
"Name": "Acme"
}
}Referencing Upstream Outputs
In downstream elements, reference upstream data using the element namespace:
- In PropertySetConfig:
%GetAccount:Name% - In Set Values: Map from
GetAccount.Nameto the target variable
Common Mapping Issues
| Problem | Cause | Fix |
|---|---|---|
| Null value in downstream element | Element name mismatch in reference | Verify exact element name spelling and case |
| Array vs object confusion | DataRaptor returns collection, consumer expects single | Use [0] accessor or Transform to extract first item |
| Nested path not resolving | Deep path syntax incorrect | Use dot notation: GetAccount.BillingAddress.City |
---
9. Bounded Extracts and Pagination
Setting Limits
Every DataRaptor Extract Action should have a reasonable LIMIT:
- Set the LIMIT in the DataRaptor definition itself, not in the IP
- For user-facing queries, limit to 50-200 records
- For batch processing, limit to 2000 records per iteration
- For count/existence checks, limit to 1
Pagination Pattern
For large datasets that exceed a single extract's limit:
1. First extract: fetch records with LIMIT and OFFSET 0 2. Check if result count equals LIMIT (more records may exist) 3. Loop: increment OFFSET by LIMIT, re-extract, append results 4. Exit when result count is less than LIMIT
Avoid this pattern for datasets exceeding 10,000 records — use Apex Batch or a scheduled process instead.
---
10. Caching Strategies
When to Cache
- Read-only IPs that serve reference data (picklist values, configuration)
- IPs called frequently by multiple OmniScripts or FlexCards
- IPs whose source data changes infrequently
When NOT to Cache
- IPs that perform DML (creates, updates, deletes)
- IPs whose output depends on the current user's permissions or context
- IPs that return time-sensitive data (real-time pricing, inventory)
Cache Configuration
Set caching properties in the IP's PropertySet:
cacheType:Platformfor org-level cache,Sessionfor user-session cachecacheTTL: Time-to-live in seconds (e.g., 300 for 5 minutes, 3600 for 1 hour)
Cache Key Considerations
The cache key includes the IP's Type/SubType and the input JSON. Different inputs produce different cache entries. Normalize input data to maximize cache hit rates.
---
11. Batch vs Sequential Execution
Sequential (Default)
Elements execute one after another. Use when:
- Each step depends on the output of the previous step
- Strict ordering is required for business logic
- Error handling needs to stop execution on first failure
Batch / Parallel Opportunities
While IPs execute elements sequentially by default, you can optimize by:
- Grouping independent DataRaptor Extracts at the beginning of the procedure
- Using a single DataRaptor Extract with multiple output fields instead of multiple extracts
- Combining related DataRaptor Transforms into a single transform with multiple mappings
- Moving parallelizable logic into an Apex Remote Action that handles concurrent operations internally
Loop Block Performance
- Set appropriate batch sizes for Loop Blocks processing large collections
- Avoid nested loops — flatten data with a Transform first
- Minimize the number of elements inside a loop body
- Move invariant computations outside the loop
---
12. Reducing Redundant Calls
Common Redundancy Patterns
| Pattern | Problem | Fix |
|---|---|---|
| Same DataRaptor called twice | Duplicate SOQL queries | Extract once, reference the result in multiple downstream elements |
| Nested IP re-fetches parent data | Wasted queries | Pass data as input to the nested IP instead of re-fetching |
| Loop body fetches lookup data | N+1 query pattern | Fetch lookup data before the loop, reference it inside |
Data Passing vs Re-Fetching
Prefer passing data between elements over re-fetching:
- Use Set Values to copy data from one element's output to another element's input
- Pass context data to nested IP calls as input parameters
- Store intermediate results in variables for reuse
---
13. Versioning with Type/SubType
Versioning Strategy
- Use SubType to version IPs:
Type=AccountOnboarding,SubType=v1andSubType=v2 - Only one version of a Type/SubType pair can be active at a time
- Deploy and test the new version as inactive before deactivating the old version
- Update consumers (OmniScripts, FlexCards) to reference the new SubType after activation
Migration Checklist
1. Create the new IP with updated SubType 2. Deploy and test in sandbox with the new SubType 3. Update all consumers to reference the new SubType 4. Activate the new IP 5. Deactivate the old IP (do not delete until consumers are verified)
---
14. Documentation Standards
Procedure-Level Documentation
Every IP should have:
- A description field explaining the business purpose
- Input/output JSON contract documented in element descriptions or external documentation
- Dependency list (which DataRaptors, Apex classes, and nested IPs it requires)
Element-Level Documentation
Every element should have:
- A description explaining what it does and why
- Notes on expected input shape and output shape
- Error handling behavior documented
Maintenance Documentation
- Record the Type/SubType and the org(s) where the IP is deployed
- List all consumers (OmniScripts, FlexCards, Apex classes, other IPs) that call this IP
- Note any external API dependencies with endpoint URLs and authentication method
<!-- Parent: sf-industry-commoncore-integration-procedure/SKILL.md -->
Integration Procedure Element Types Reference
Version: 1.0.0
Applies to: OmniStudio Integration Procedures (Core namespace and Vlocity managed package)
This reference documents every element type available in Integration Procedures, including PropertySetConfig JSON structures, input/output node mapping, and variable scoping rules.
---
Table of Contents
1. DataRaptor Extract Action 2. DataRaptor Load Action 3. DataRaptor Transform Action 4. Remote Action 5. Integration Procedure Action 6. HTTP Action 7. Conditional Block 8. Loop Block 9. Set Values 10. Variable Scoping and Data Passing 11. PropertySetConfig Common Properties
---
1. DataRaptor Extract Action
Reads data from Salesforce objects using a DataRaptor (Data Mapper) Extract definition.
PropertySetConfig
{
"bundle": "DRExtract_AccountDetails",
"disableFlushCacheForGet": false,
"useQueueableApexRemoting": false,
"additionalInput": {},
"additionalOutput": {},
"sendOnlyAdditionalInput": false,
"responseJSONPath": "",
"responseJSONNode": ""
}Key Properties
| Property | Type | Required | Description |
|---|---|---|---|
bundle | String | Yes | Name of the DataRaptor Extract definition |
additionalInput | Object | No | Extra key-value pairs merged into the DataRaptor input |
sendOnlyAdditionalInput | Boolean | No | If true, only sends additionalInput, ignoring upstream data |
responseJSONPath | String | No | JSONPath expression to extract a subset of the response |
responseJSONNode | String | No | Key name to wrap the response under |
disableFlushCacheForGet | Boolean | No | If true, uses cached DataRaptor results |
Input Mapping
The element receives the IP's current data context. To pass specific values to the DataRaptor:
- Use
additionalInputto map values from upstream elements:
{
"additionalInput": {
"AccountId": "%PreviousElement:accountId%"
}
}Output
The DataRaptor's output is stored under the element name in the IP response:
{
"GetAccountDetails": {
"Name": "Acme Corp",
"Industry": "Technology",
"BillingCity": "San Francisco"
}
}For multi-record results, the output is an array.
---
2. DataRaptor Load Action
Writes data to Salesforce objects using a DataRaptor (Data Mapper) Load definition.
PropertySetConfig
{
"bundle": "DRLoad_CreateContact",
"disableFlushCacheForGet": false,
"useQueueableApexRemoting": false,
"additionalInput": {},
"additionalOutput": {},
"sendOnlyAdditionalInput": false,
"responseJSONPath": "",
"responseJSONNode": ""
}Key Properties
| Property | Type | Required | Description |
|---|---|---|---|
bundle | String | Yes | Name of the DataRaptor Load definition |
additionalInput | Object | No | Extra key-value pairs merged into the DataRaptor input |
sendOnlyAdditionalInput | Boolean | No | If true, only sends additionalInput as input |
Input Mapping
Map upstream data into the fields the DataRaptor Load expects:
{
"additionalInput": {
"FirstName": "%SetInputValues:firstName%",
"LastName": "%SetInputValues:lastName%",
"AccountId": "%GetAccountDetails:Id%"
}
}Output
Returns the result of the DML operation:
{
"CreateContact": {
"Id": "003XXXXXXXXXXXX",
"errors": [],
"success": true
}
}Error Handling
Always follow a DataRaptor Load with error inspection. Check for:
successfield (boolean)errorsarray (contains error objects withstatusCodeandmessage)
---
3. DataRaptor Transform Action
Reshapes data without making any Salesforce queries or DML. Uses a DataRaptor Transform definition to map fields between JSON structures.
PropertySetConfig
{
"bundle": "DRTransform_FlattenAddress",
"additionalInput": {},
"additionalOutput": {},
"sendOnlyAdditionalInput": false,
"responseJSONPath": "",
"responseJSONNode": ""
}Key Properties
| Property | Type | Required | Description |
|---|---|---|---|
bundle | String | Yes | Name of the DataRaptor Transform definition |
additionalInput | Object | No | Extra key-value pairs to include in the transform input |
Use Cases
- Flatten nested JSON structures for downstream processing
- Rename fields to match external API contracts
- Combine data from multiple upstream elements into a single structure
- Extract specific fields from a large response
Input/Output
Input: The full IP data context or specific values via additionalInput Output: The transformed JSON structure, stored under the element name
---
4. Remote Action
Calls an Apex class method. The Apex class must implement the vlocity_cmt.VlocityOpenInterface (managed package) or omnistudio.VlocityOpenInterface2 (Core namespace) interface.
PropertySetConfig
{
"remoteClass": "AccountValidationService",
"remoteMethod": "validateAccount",
"additionalInput": {},
"additionalOutput": {},
"sendOnlyAdditionalInput": false,
"responseJSONPath": "",
"responseJSONNode": "",
"useQueueableApexRemoting": false,
"useFuture": false
}Key Properties
| Property | Type | Required | Description |
|---|---|---|---|
remoteClass | String | Yes | Apex class name |
remoteMethod | String | Yes | Method name to invoke |
useQueueableApexRemoting | Boolean | No | If true, executes as a Queueable job (async) |
useFuture | Boolean | No | If true, executes as a future method (async, no return value) |
Apex Interface
The Apex class must follow this pattern:
// Core namespace
global class AccountValidationService implements omnistudio.VlocityOpenInterface2 {
global Object invokeMethod(
String methodName,
Map<String, Object> inputMap,
Map<String, Object> outputMap,
Map<String, Object> options
) {
if (methodName == 'validateAccount') {
// Business logic here
String accountId = (String) inputMap.get('AccountId');
// ... validation logic ...
outputMap.put('isValid', true);
outputMap.put('validationMessages', new List<String>());
}
return null;
}
}Input Mapping
The IP data context is passed as inputMap. Use additionalInput to add or override values:
{
"additionalInput": {
"AccountId": "%GetAccountDetails:Id%",
"validationType": "full"
}
}Output
The outputMap contents are stored under the element name:
{
"ValidateAccount": {
"isValid": true,
"validationMessages": []
}
}---
5. Integration Procedure Action
Calls another Integration Procedure. Enables composition and reuse of IP logic.
PropertySetConfig
{
"ipMethod": "SharedLookup_Standard",
"additionalInput": {},
"additionalOutput": {},
"sendOnlyAdditionalInput": false,
"responseJSONPath": "",
"responseJSONNode": "",
"chainable": false
}Key Properties
| Property | Type | Required | Description |
|---|---|---|---|
ipMethod | String | Yes | The nested IP's Type_SubType identifier |
chainable | Boolean | No | If true, allows chaining outputs from the nested IP |
sendOnlyAdditionalInput | Boolean | No | If true, only sends additionalInput to the nested IP |
Input Mapping
By default, the entire IP data context is passed to the nested IP. Use sendOnlyAdditionalInput: true with additionalInput to send only specific data:
{
"ipMethod": "ContactLookup_Standard",
"sendOnlyAdditionalInput": true,
"additionalInput": {
"accountId": "%GetAccountDetails:Id%"
}
}Output
The nested IP's full response is stored under the calling element's name:
{
"LookupContacts": {
"contacts": [...],
"totalCount": 5
}
}Circular Dependency Prevention
Before configuring an IP Action, verify that the target IP does not call back to the current IP (directly or through intermediaries). Circular calls cause stack overflow errors at runtime.
---
6. HTTP Action
Makes an HTTP callout to an external API endpoint.
PropertySetConfig
{
"path": "https://api.example.com/v1/accounts",
"method": "POST",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer %GetToken:accessToken%"
},
"body": {},
"additionalInput": {},
"additionalOutput": {},
"sendOnlyAdditionalInput": false,
"responseJSONPath": "",
"responseJSONNode": "",
"namedCredential": "",
"timeout": 30000
}Key Properties
| Property | Type | Required | Description |
|---|---|---|---|
path | String | Yes | Full URL or relative path (if using Named Credential) |
method | String | Yes | HTTP method: GET, POST, PUT, PATCH, DELETE |
headers | Object | No | Request headers as key-value pairs |
body | Object | No | Request body (for POST/PUT/PATCH) |
namedCredential | String | No | Salesforce Named Credential for authentication |
timeout | Number | No | Request timeout in milliseconds |
Security Best Practices
- Use Named Credentials for API authentication instead of hardcoded tokens
- Reference tokens from upstream elements (e.g., OAuth token retrieval) using
%elementName:key%syntax - Never store API keys in PropertySetConfig — use Custom Settings or Custom Metadata
Input Mapping
Use additionalInput to build dynamic request bodies:
{
"body": {
"accountName": "%GetAccountDetails:Name%",
"externalId": "%InputData:externalId%"
}
}Output
The HTTP response is stored under the element name:
{
"CallExternalAPI": {
"statusCode": 200,
"body": {
"result": "success",
"externalRecordId": "EXT-12345"
}
}
}Error Handling
Check statusCode in a downstream Conditional Block:
- 2xx: Success, proceed
- 4xx: Client error, log and return error response
- 5xx: Server error, consider retry or graceful degradation
---
7. Conditional Block
Evaluates conditions and branches execution based on the result. Contains child elements that execute only when the condition evaluates to true.
Configuration
Conditional Blocks do not use PropertySetConfig in the same way as action elements. Instead, conditions are defined in the element's condition configuration:
Condition Types
| Condition Type | Description | Example |
|---|---|---|
| Value Comparison | Compare two values | %GetAccount:Industry% EQUALS 'Technology' |
| Null Check | Check if a value is null/empty | %GetAccount:Id% IS NOT NULL |
| Boolean Check | Evaluate a boolean value | %ValidateInput:isValid% EQUALS true |
| Group (AND/OR) | Combine multiple conditions | condition1 AND condition2 |
Nested Elements
Elements inside a Conditional Block execute only when the condition is true. They follow the same ordering principles as top-level elements.
If/Else Pattern
Use two Conditional Blocks with complementary conditions:
1. Conditional Block A: condition = %check:value% EQUALS true -> contains success path elements 2. Conditional Block B: condition = %check:value% NOT EQUALS true -> contains error/alternate path elements
---
8. Loop Block
Iterates over a collection (array) and executes child elements for each item.
Configuration
| Property | Description |
|---|---|
| Loop collection | JSONPath to the array to iterate over (e.g., %GetContacts:records%) |
| Loop element variable | Variable name for the current item in each iteration |
Child Elements
Elements inside a Loop Block execute once per iteration. The current item is accessible via the loop element variable.
Performance Considerations
- Minimize the number of elements inside the loop body
- Avoid DataRaptor Extract/Load inside loops (N+1 pattern)
- Prefer bulk operations: collect data in the loop, perform a single DML after the loop
- Set appropriate batch sizes for large collections
- If loop body requires DML, consider using a single DataRaptor Load with a collection input instead
Example Pattern: Collect and Bulk Process
Instead of:
Loop -> DataRaptor Load (per item) // BAD: N DML operationsUse:
Loop -> Set Values (collect items into array)
DataRaptor Load (entire array) // GOOD: 1 DML operation---
9. Set Values
Assigns values to variables within the IP context. Used for initialization, response assembly, and data transformation.
Configuration
Set Values elements define key-value mappings:
| Mapping Type | Description | Example |
|---|---|---|
| Static value | Hardcoded constant | "status": "Processed" |
| Element reference | Value from upstream element | "accountName": "%GetAccount:Name%" |
| Formula | Computed value | Concatenation, conditional expressions |
| Input reference | Value from IP input | "requestId": "%input:requestId%" |
Common Uses
1. Initialize defaults: Set default values at the beginning of the procedure 2. Build output response: Assemble the final response JSON at the end 3. Intermediate variables: Store computed values for use in downstream conditions 4. Error response: Build standardized error response objects
Example: Response Assembly
Set Values: "BuildResponse"
out_success = true
out_accountId = %GetAccount:Id%
out_contactCount = %GetContacts:totalSize%
out_status = "Complete"---
10. Variable Scoping and Data Passing
Scope Rules
| Scope Level | Visibility | Lifetime |
|---|---|---|
| IP Input | All elements | Entire execution |
| Element Output | All downstream elements | Entire execution |
| Set Values Variable | All downstream elements | Entire execution |
| Loop Variable | Elements inside the loop body | Current iteration |
| Conditional Block Variable | Elements inside the block | Block execution |
| Nested IP Output | Calling element and downstream | Entire execution (under element name) |
Data Context
The IP maintains a cumulative data context. Each element's output is added to this context under the element's name. Downstream elements can reference any upstream element's output.
Reference Syntax
| Syntax | Description | Example |
|---|---|---|
%elementName:key% | Reference a specific key from an element's output | %GetAccount:Name% |
%elementName:nested.key% | Reference a nested key using dot notation | %GetAccount:BillingAddress.City% |
%elementName:array[0]% | Reference an array element by index | %GetContacts:records[0].Name% |
%input:key% | Reference an IP input parameter | %input:accountId% |
Data Passing to Nested IPs
When calling a nested IP via Integration Procedure Action:
- Default: The entire data context is passed as input
- Selective: Use
sendOnlyAdditionalInput: true+additionalInputto pass specific values - Output: The nested IP's response is namespaced under the calling element's name
Data Passing Best Practices
- Use
sendOnlyAdditionalInput: truefor nested IP calls to avoid leaking unnecessary data - Keep variable names consistent across elements for readability
- Document the expected data shape at each stage of the procedure
- Use Set Values to explicitly name and scope intermediate results rather than relying on implicit context merging
---
11. PropertySetConfig Common Properties
These properties are available on most action elements:
| Property | Type | Default | Description |
|---|---|---|---|
additionalInput | Object | {} | Extra key-value pairs added to the element's input |
additionalOutput | Object | {} | Extra key-value pairs added to the element's output |
sendOnlyAdditionalInput | Boolean | false | If true, only additionalInput is sent (upstream context excluded) |
responseJSONPath | String | "" | JSONPath to extract a subset of the element's response |
responseJSONNode | String | "" | Key name to wrap the response under |
useQueueableApexRemoting | Boolean | false | Execute asynchronously via Queueable Apex |
disableFlushCacheForGet | Boolean | false | Use cached results if available |
responseJSONPath vs responseJSONNode
responseJSONPath: Extracts a subset of the response. For example,$.recordsextracts only therecordsarray from the response.responseJSONNode: Wraps the response under a named key. For example, settingresponseJSONNodetoaccountDatawraps the entire response under{"accountData": {...}}.
These can be combined: first extract via path, then wrap under a node name.
additionalInput Merge Behavior
When sendOnlyAdditionalInput is false (default): 1. The IP's current data context is used as the base input 2. additionalInput key-value pairs are merged into this context 3. If a key exists in both, additionalInput takes precedence (override)
When sendOnlyAdditionalInput is true: 1. The data context is ignored 2. Only additionalInput key-value pairs are sent as input 3. Use this for nested IP calls where you want a clean input boundary
Related skills
How it compares
Use sf-industry-commoncore-integration-procedure for OmniStudio Integration Procedure JSON scaffolding; use Apex or Flow skills when integrations do not run through OmniStudio.
FAQ
What OmniStudio artifacts does this skill generate?
sf-industry-commoncore-integration-procedure outputs Integration Procedure element JSON for DataRaptor Extract Actions and Set Values steps. Each template includes OmniProcessId, Name, Type, SequenceNumber, IsActive, and a serialized PropertySetConfig string with bundle and error
Does the skill replace manual OmniStudio Setup configuration?
No. sf-industry-commoncore-integration-procedure accelerates scaffolding with placeholder-driven JSON templates. Developers still import or paste elements into OmniStudio and bind real DataRaptor bundle names and parent process IDs.
Is Sf Industry Commoncore Integration Procedure safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.