Contents

1 Who this is for

You have a Shiny app and some genomic data — intervals, alignments, variants, association statistics — and you want to look at it in a genome browser without leaving R. This vignette takes you from installation to a working app with a track loaded from your own data.frame.

It assumes you have written a Shiny app before. It does not assume you know igv.js, the JavaScript browser igvShiny wraps.

For the complete API — every track type and the options each one accepts — see the Track options reference vignette. For a live app you can click through before installing anything, see the demo on Posit Connect Cloud.

2 Install

if (!requireNamespace("BiocManager", quietly = TRUE))
    install.packages("BiocManager")

BiocManager::install("igvShiny")
library(igvShiny)

3 The three moving parts

An igvShiny browser is an htmlwidget, so it follows the usual Shiny pattern of an output function in the UI and a render function on the server. What is specific to this package is the third part: the browser has to be told which genome to display before it is created.

Part Function Where
Genome specification parseAndValidateGenomeSpec() top of the app
Placeholder in the layout igvShinyOutput() UI
The widget itself renderIgvShiny() + igvShiny() server

parseAndValidateGenomeSpec() returns a validated list. Building it separately means a bad genome name fails immediately, with a readable message, rather than producing an empty browser in the page:

genomeOptions <- parseAndValidateGenomeSpec(
    genomeName = "hg38",
    initialLocus = "NDUFS2"
    )
str(genomeOptions)
#> List of 8
#>  $ stockGenome : logi TRUE
#>  $ dataMode    : logi NA
#>  $ validated   : logi TRUE
#>  $ genomeName  : chr "hg38"
#>  $ initialLocus: chr "NDUFS2"
#>  $ fasta       : logi NA
#>  $ fastaIndex  : logi NA
#>  $ annotation  : logi NA

initialLocus accepts a gene symbol, a chrom:start-end string, or "all" for the whole-genome view.

4 A complete app

Everything above assembled — this is the smallest useful igvShiny app:

library(shiny)
library(igvShiny)

genomeOptions <- parseAndValidateGenomeSpec(
    genomeName = "hg38",
    initialLocus = "NDUFS2"
    )

ui <- fluidPage(
    titlePanel("igvShiny"),
    igvShinyOutput("igv")
    )

server <- function(input, output, session) {
    output$igv <- renderIgvShiny({
        igvShiny(genomeOptions)
        })
    }

shinyApp(ui, server)

Two notes on sizing. igvShinyOutput() takes height as an explicit pixel measure ("800px"); percentages do not work, because the browser needs a concrete height to lay its panels out. And the widget only draws once its container is visible, so if you put it behind a tabsetPanel() tab, expect it to appear when that tab is first opened.

5 Loading a track from your own data

Tracks are added by sending a message to a browser that already exists, so the loaders are called from the server, take session and the widget id, and are usually wired to an event:

tbl <- data.frame(
    chrom = c("chr1", "chr1", "chr1"),
    start = c(7432000, 7437000, 7443000),
    end   = c(7436000, 7442000, 7447000),
    value = c(0.2, 0.9, 0.4),
    stringsAsFactors = FALSE
    )

server <- function(input, output, session) {
    output$igv <- renderIgvShiny({
        igvShiny(genomeOptions)
        })

    observeEvent(input$addTrack, {
        loadBedTrack(
            session,
            id = "igv",
            trackName = "my regions",
            tbl = tbl,
            color = "darkblue"
            )
        })
    }

loadBedTrack() needs chrom, start and end columns; anything else is ignored. The other loaders follow the same shape — loadBedGraphTrack(), loadSegTrack(), loadVcfTrack(), loadGwasTrack(), loadBamTrackFromLocalData() — and each has a *FromURL() counterpart for data that already lives on a web server:

gff3 <- paste0(
    "https://s3.amazonaws.com/igv.org.genomes/hg38/",
    "Homo_sapiens.GRCh38.94.chr.gff3.gz"
    )

loadGFF3TrackFromURL(
    session,
    id = "igv",
    trackName = "genes",
    gff3URL = gff3,
    indexURL = paste0(gff3, ".tbi"),
    color = "darkgreen",
    trackHeight = 100
    )

The From URL variants ask the user’s browser to fetch the file, not R. A URL that works in download.file() can still fail here if the server does not send permissive CORS headers.

Tracks the user added during a session can be cleared with removeUserAddedTracks(session, id = "igv"), or individually by name with removeTracksByName().

5.1 Loading tracks at startup

Tracks that should always be present do not need an event. Pass them to igvShiny() through tracks, as a list of igv.js track configurations:

output$igv <- renderIgvShiny({
    igvShiny(
        genomeOptions,
        tracks = list(
            list(
                name = "genes",
                type = "annotation",
                format = "gff3",
                url = paste0(
                    "https://s3.amazonaws.com/igv.org.genomes/hg38/",
                    "Homo_sapiens.GRCh38.94.chr.gff3.gz"
                    ),
                indexed = FALSE
                )
            )
        )
    })

Keys that igv.js does not recognise are dropped with a warning rather than passed through, so a typo produces a message instead of a silently empty track.

6 Moving around, and knowing where you are

To drive the browser from R, use showGenomicRegion():

observeEvent(input$goto, {
    showGenomicRegion(
        session,
        id = "igv",
        region = "chr5:88,700,000-88,800,000"
        )
    })

Reading the position back is asynchronous, which is the one part of the API that surprises people. getGenomicRegion() does not return the region; it asks the browser for it, and the answer arrives later as a Shiny input named currentGenomicRegion.<id>:

observeEvent(input$whereAmI, {
    getGenomicRegion(session, id = "igv")
    })

observeEvent(input[["currentGenomicRegion.igv"]], {
    message("now showing: ", input[["currentGenomicRegion.igv"]])
    })

The same input fires whenever the user pans or zooms, so an observer on it is enough if you only want to follow the view rather than poll it.

Two other inputs are available: igvReady, which fires once the browser has finished initialising — the right place to load tracks automatically — and trackClick, which carries the feature the user clicked on.

7 Inside a Shiny module

igvShiny works in modules with no extra arguments: the widget reads the namespace from the session it is created in. The one thing to remember is that the loaders address the browser by its HTML element id, not by the input name — so they need the namespaced id, ns("igv"), and not "igv":

igvModuleUI <- function(id) {
    ns <- NS(id)
    tagList(
        actionButton(ns("addTrack"), "Add track"),
        igvShinyOutput(ns("igv"))
        )
    }

igvModuleServer <- function(id, genomeOptions, tbl) {
    moduleServer(id, function(input, output, session) {
        ns <- session$ns

        output$igv <- renderIgvShiny({
            igvShiny(genomeOptions)
            })

        observeEvent(input$addTrack, {
            loadBedTrack(
                session,
                id = ns("igv"),
                trackName = "regions",
                tbl = tbl
                )
            })

        # the region input keeps its unnamespaced name inside the module
        observeEvent(input[["currentGenomicRegion.igv"]], {
            message("now showing: ", input[["currentGenomicRegion.igv"]])
            })
        })
    }

That observer follows the user panning and zooming. Asking for the region explicitly with getGenomicRegion() is currently unreliable inside a module unless the module id happens to be "igv" — see issue #134.

A runnable version is in inst/demos/igvShinyDemo-withModules.R.

8 Custom genomes

Any genome igv.js knows can be requested by name — get_css_genomes() lists them. For anything else, supply the sequence yourself: a FASTA file, its index, and optionally an annotation, either as local paths (dataMode = "localFiles") or URLs (dataMode = "http"). The package ships a small example genome:

data_directory <- system.file(package = "igvShiny", "extdata")

customOptions <- parseAndValidateGenomeSpec(
    genomeName = "ribosomal RNA gene",
    initialLocus = "U13369.1:7,276-8,225",
    stockGenome = FALSE,
    dataMode = "localFiles",
    fasta = file.path(data_directory, "ribosomal-RNA-gene.fasta"),
    fastaIndex = file.path(data_directory, "ribosomal-RNA-gene.fasta.fai"),
    genomeAnnotation = file.path(data_directory, "ribosomal-RNA-gene.gff3")
    )
customOptions[c("genomeName", "dataMode", "stockGenome")]
#> $genomeName
#> [1] "ribosomal RNA gene"
#> 
#> $dataMode
#> [1] "localFiles"
#> 
#> $stockGenome
#> [1] FALSE

The result goes to igvShiny() exactly like a stock genome specification. See the overview vignette for the URL-based variant.

9 When something does not show up

The browser is blank. Almost always the genome, not the track. Check the JavaScript console: a 404 on the FASTA or its index leaves an empty browser. Some upstream genome URLs have broken in the past without any change on our side.

A track loads but is empty. Chromosome naming. If your data says 1 and the genome says chr1, igv.js finds nothing and reports nothing. Compare one interval against the locus shown in the search box.

Nothing at all happens after a loader call. The loaders are one-way messages to the browser: no error comes back to R when they fail. Call the loader with quiet = FALSE to log what was sent, and read the JavaScript console for what happened to it.

Local data files 404. Tracks built from R data are written to a temporary directory that Shiny serves. If you have set TRACKS_DIR, make sure the process can write there; get_tracks_dir() reports the directory in use.

10 Where to go next

11 Session Info

sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.4 LTS
#> 
#> Matrix products: default
#> BLAS:   /home/biocbuild/bbs-3.24-bioc/R/lib/libRblas.so 
#> LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0  LAPACK version 3.12.0
#> 
#> locale:
#>  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
#>  [3] LC_TIME=en_GB              LC_COLLATE=C              
#>  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
#>  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
#>  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
#> 
#> time zone: America/New_York
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats4    stats     graphics  grDevices utils     datasets  methods  
#> [8] base     
#> 
#> other attached packages:
#> [1] igvShiny_1.9.26      shiny_1.14.0         GenomicRanges_1.65.1
#> [4] Seqinfo_1.3.0        IRanges_2.47.2       S4Vectors_0.51.5    
#> [7] BiocGenerics_0.59.10 generics_0.1.4       BiocStyle_2.41.0    
#> 
#> loaded via a namespace (and not attached):
#>  [1] sass_0.4.10             futile.options_1.0.1    stringi_1.8.7          
#>  [4] digest_0.6.39           magrittr_2.0.5          RColorBrewer_1.1-3     
#>  [7] evaluate_1.0.5          bookdown_0.47           fastmap_1.2.0          
#> [10] jsonlite_2.0.0          backports_1.5.1         formatR_1.14           
#> [13] promises_1.5.0          BiocManager_1.30.27     httr_1.4.8             
#> [16] scales_1.4.0            randomcoloR_1.1.0.1     jquerylib_0.1.4        
#> [19] cli_3.6.6               rlang_1.3.0             futile.logger_1.4.9    
#> [22] cachem_1.1.0            yaml_2.3.12             otel_0.2.0             
#> [25] Rtsne_0.17              tools_4.6.1             checkmate_2.3.4        
#> [28] colorspace_2.1-3        httpuv_1.6.17           GenomeInfoDbData_1.2.15
#> [31] lambda.r_1.2.4          curl_7.1.0              R6_2.6.1               
#> [34] mime_0.13               lifecycle_1.0.5         stringr_1.6.0          
#> [37] V8_8.2.0                htmlwidgets_1.6.4       cluster_2.1.8.2        
#> [40] bslib_0.11.0            later_1.4.8             glue_1.8.1             
#> [43] Rcpp_1.1.2              xfun_0.60               dichromat_2.0-1        
#> [46] knitr_1.51              farver_2.1.2            xtable_1.8-8           
#> [49] htmltools_0.5.9         rmarkdown_2.31          compiler_4.6.1