Skip to content

Instantly share code, notes, and snippets.

@pbsull
Created May 2, 2025 14:18
Show Gist options
  • Select an option

  • Save pbsull/17b032ecf397050c3774b84f7744befd to your computer and use it in GitHub Desktop.

Select an option

Save pbsull/17b032ecf397050c3774b84f7744befd to your computer and use it in GitHub Desktop.

πŸ“Š Salesforce Report Folder Audit (Go)

This Go script queries your Salesforce org and outputs a JSON file that maps:

  • Report Folders
    • Name
    • Access Type (Public, Private, Shared)
    • Shared Users
    • Contained Reports
      • Report Name
      • Report Owner

It uses the Salesforce REST and Tooling APIs to pull folder structure, access control, and report metadata into a single, human-readable file.


βœ… Features

  • Fetches all Report folders via SOQL
  • Retrieves FolderShare records using the Tooling API
  • Resolves UserId references to actual names
  • Lists all reports per folder, including the report owner
  • Outputs a structured JSON file for easy auditing or visualization

πŸ“¦ Output Format

report_folders.json:

[
  {
    "name": "Sales_Team",
    "access_type": "Public",
    "shared_with": ["Alice Johnson", "Bob Lee"],
    "reports": [
      {
        "name": "Q1 Pipeline",
        "owner": "Alice Johnson"
      },
      ...
    ]
  },
  ...
]

πŸ›  Requirements

  • Go 1.18+

  • Salesforce API access with:

    • OAuth bearer token (with View Setup and Configuration permission)
    • REST and Tooling API enabled

πŸ”§ Setup

  1. Clone or copy this Gist locally.
  2. Open main.go.
  3. Set the following constants:
const (
  instanceURL = "https://yourInstance.salesforce.com" // e.g. https://na85.salesforce.com
  token       = "YOUR_ACCESS_TOKEN"                   // from OAuth flow or Workbench
)
  1. Run the script:
go run main.go

πŸ“ Output

The script will create a file:

report_folders.json

Use this to review sharing access, audit report ownership, or feed into other workflows.


βœ… TODO / Extensions

  • Support .env or CLI flags for auth
  • Resolve Groups and Roles in FolderShare
  • Add Dashboard support
  • Export as CSV or HTML

πŸ” Security Note

Do not commit or share your OAuth token. Use environment variables or secret managers for secure usage in production environments.


🧠 Author

Built by Pat Sullivan (TechTended), based on needs for full auditability of Salesforce reporting access patterns across GTM teams.

package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
)
const (
instanceURL = "https://yourInstance.salesforce.com" // e.g. https://na85.salesforce.com
token = "YOUR_ACCESS_TOKEN"
)
type Folder struct {
Id string `json:"Id"`
Name string `json:"Name"`
AccessType string `json:"AccessType"`
DeveloperName string `json:"DeveloperName"`
}
type FolderShare struct {
ParentId string `json:"ParentId"`
UserOrGroupId string `json:"UserOrGroupId"`
AccessLevel string `json:"AccessLevel"`
}
type Report struct {
Id string `json:"Id"`
Name string `json:"Name"`
FolderName string `json:"FolderName"`
OwnerId string `json:"OwnerId"`
}
type User struct {
Id string `json:"Id"`
Name string `json:"Name"`
}
type OutputReport struct {
Name string `json:"name"`
Owner string `json:"owner"`
}
type OutputFolder struct {
Name string `json:"name"`
AccessType string `json:"access_type"`
SharedWith []string `json:"shared_with"`
Reports []OutputReport `json:"reports"`
}
type QueryResponse struct {
Records json.RawMessage `json:"records"`
}
func querySOQL(soql string) []byte {
url := fmt.Sprintf("%s/services/data/v58.0/query?q=%s", instanceURL, strings.ReplaceAll(soql, " ", "+"))
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Fatalf("Query failed: %v", err)
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
return body
}
func main() {
folders := []Folder{}
reports := []Report{}
shares := []FolderShare{}
users := map[string]string{}
// Step 1: Get folders
folderQuery := querySOQL("SELECT Id, Name, DeveloperName, AccessType FROM Folder WHERE Type = 'Report'")
json.Unmarshal(folderQuery, &QueryResponse{Records: &folders})
// Step 2: Get reports
reportQuery := querySOQL("SELECT Id, Name, FolderName, OwnerId FROM Report")
json.Unmarshal(reportQuery, &QueryResponse{Records: &reports})
// Step 3: Get folder shares
shareQuery := querySOQL("SELECT ParentId, UserOrGroupId, AccessLevel FROM FolderShare")
json.Unmarshal(shareQuery, &QueryResponse{Records: &shares})
// Step 4: Get all unique user IDs
userIdSet := map[string]bool{}
for _, r := range reports {
userIdSet[r.OwnerId] = true
}
for _, s := range shares {
userIdSet[s.UserOrGroupId] = true
}
// Build IN clause
ids := []string{}
for id := range userIdSet {
ids = append(ids, fmt.Sprintf("'%s'", id))
}
userQuery := querySOQL("SELECT Id, Name FROM User WHERE Id IN (" + strings.Join(ids, ",") + ")")
usersRaw := []User{}
json.Unmarshal(userQuery, &QueryResponse{Records: &usersRaw})
for _, u := range usersRaw {
users[u.Id] = u.Name
}
// Step 5: Build output
folderMap := map[string]*OutputFolder{}
for _, f := range folders {
folderMap[f.Name] = &OutputFolder{
Name: f.Name,
AccessType: f.AccessType,
Reports: []OutputReport{},
SharedWith: []string{},
}
}
for _, s := range shares {
for _, f := range folders {
if f.Id == s.ParentId {
name := users[s.UserOrGroupId]
folderMap[f.Name].SharedWith = append(folderMap[f.Name].SharedWith, name)
}
}
}
for _, r := range reports {
f := folderMap[r.FolderName]
if f != nil {
f.Reports = append(f.Reports, OutputReport{
Name: r.Name,
Owner: users[r.OwnerId],
})
}
}
output := []OutputFolder{}
for _, f := range folderMap {
output = append(output, *f)
}
data, _ := json.MarshalIndent(output, "", " ")
ioutil.WriteFile("report_folders.json", data, 0644)
fmt.Println("βœ… Saved to report_folders.json")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment