react-native-view-shot: one option with four failure modes, and a webm alias that is not a video
Snapshot a React Native view and save it to an image
At a glance
- What is it?
- A React Native screenshot library with no runtime dependencies of its own, three peer dependencies of which two are optional, and an exports map that hands React Native consumers raw TypeScript source for the bundler to compile. Its best-documented option expands a scroll view to capture its full content, and then lists four different ways that can fail depending on platform and on which scrolling component you used.
- Who is it for?
- Reach for this when you need a still image of a specific view inside a React Native app and want the API to be explicit about what it will and will not capture, because the documentation on the tricky option is better than most libraries manage. Three things to check before you rely on it.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 5, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One option, four documented ways it can fail
The most valuable entry on the page is a single boolean, and the reason it is worth reading is the list attached to it.
The option captures a scroll view's entire scrollable content rather than just the visible viewport. It works on both mobile platforms, and the mechanism is stated: the library temporarily expands the view to its full content size during the draw and then restores it.
Then four caveats, each with a reason.
On Android only the vertical scroll view is expanded. Horizontal ones are not, and fall back to capturing the visible bounds. On iOS both axes are handled by the platform's own scroll view.
On Windows it is not supported at all. The platform's bitmap renderer respects the live clip of the scroll viewer, there is no reliable way to capture the full scrollable area, the option is ignored, only the visible viewport is captured, and a warning fires in development.
And for a virtualised list, off-screen items are not mounted at the React layer at all, so they cannot appear in the capture. The page tells you to disable view clipping and use a large window size, or to use a plain scroll view, for content of up to a few hundred items, and links to a working example screen.
So one boolean behaves four different ways depending on platform and on which scrolling component you used. The documentation is unusually good; the API shape is what forces the caveats.
On web, a missing dependency fails at two different moments
Web capture is delegated to a canvas library that is an optional peer dependency, so nothing is installed for you unless you are building for the web.
The failure behaviour is where the care is. The library is loaded lazily, on the first capture rather than at import, so a native-only project never notices it is missing. Then the outcome forks on how your toolchain resolves modules.
If it is missing and your bundler resolves at build time, the build fails with an error naming the package that could not be resolved. If instead the environment resolves at runtime, the capture call rejects with an explicit hint telling you what to install. Same cause, two different failure points, two different symptoms, both documented with their exact messages.
That is a direct consequence of loading lazily, and it is the kind of detail most libraries leave to discovery.
The second optional peer is the React Native package itself, which lets a web-only project install the web variant without pulling the native one, by aliasing one name to the other in the bundler.
Then there is the development global. The library reads the same flag React Native code does, which Metro and Expo define for you. With a custom setup you must define it yourself:
// webpack.config.js
new webpack.DefinePlugin({ __DEV__: JSON.stringify(process.env.NODE_ENV !== "production") })
// esbuild
--define:__DEV__=falseThe webpack line derives the value from the environment. The esbuild line hardcodes false, which is a production value, so applying it to a development build silently disables development behaviour.
Also note the fence is tagged as JavaScript while its second half is a command-line flag.
React Native consumers get TypeScript source, not compiled output
The package declares itself as an ES module and splits its entry points three ways. There is a react-native condition resolving to a TypeScript source file, a types condition resolving to the generated declarations, and both an import and a default condition resolving to the compiled JavaScript. A legacy top-level field points at the same TypeScript source.
Which means a React Native app does not receive compiled JavaScript. Metro reads the source and compiles it as part of the app's own build, while a web or Node bundler receives the compiled output.
That is a deliberate and common design for a library in this ecosystem, and it has a cost worth naming. Consumers inherit a requirement that their toolchain can transform TypeScript from inside a dependency, and a project with a strict transform rule for third-party code can fail on this package specifically while every other dependency works.
Two related details from the same map. The package manifest is exposed as its own subpath, which is a convention rather than a requirement but is deliberate. And there is no require condition at all. Combined with the module declaration, that means a CommonJS consumer has nothing valid to resolve to, so this library cannot be loaded with require.
The build itself is a single TypeScript compiler invocation, and it also runs automatically on install through a prepare hook, so the compiled output a web consumer loads is generated rather than committed. That is why the compiled directory is absent from the repository listing while being referenced by both entry fields.
Two linter configs, two type configs, two package managers
The root of the repository carries a legacy dot-prefixed linter configuration and a flat linter configuration at the same time. The flat config takes precedence in current tooling, so the legacy file is very likely inert. The example project carries its own legacy one as well, which is where the duplication comes from.
Two type-checker configurations sit beside them, one for TypeScript and one for JavaScript, in a package whose source is TypeScript. Having a JavaScript config as well suggests editor support for the config files and example JavaScript rather than a second source tree, but it is one more file to keep in step.
Two package managers are configured too: an npm lockfile at the root and a Yarn Berry configuration file, next to an npm configuration file. And the install section on the page offers npm, Yarn and Expo as three live routes. So a project that tries to support all three ends up with two lockfiles and two sets of tool configuration.
The packaging is the item with a practical consequence. There is no allowlist of files to publish. What ends up in the tarball is decided by an npm ignore file and the git ignore file, so the contents of a published version depend on ignore rules rather than on an explicit declaration of intent. A file added to the repository is published by default and excluded only if someone remembers.
The lint scripts come in two strengths as well: one for ordinary use, and one for continuous integration that fails on any warning. The gate contributors meet in continuous integration is stricter than the one they get locally.
Half the capture modes are labelled experimental
The component takes a capture mode with four settings.
Unset is the default, and it means nothing happens until you call the capture method through the ref yourself.
Mount captures once when the view mounts, and it carries a caveat that image loading is not awaited. So a capture taken at mount can produce an image without the picture in it. The page tells you the fix: omit the mode and call the capture method from the image's load callback when you need the content present.
The remaining two are marked experimental in the documentation itself. One captures continuously, and the page warns in plain terms that it will capture a great many images. The other captures each time the view redraws, which for anything animating means per frame.
So half the mode list is a per-redraw capture path the maintainers have flagged as unfinished, and it is exactly the mode that pairs with the cleanup behaviour described further down.
That detail completes the picture. The component calls the release method on every capture after the first, specifically so that continuous mode does not leak temporary files. The disk side of that mode is handled properly. What the experimental label is warning about is the rendering cost of capturing on every redraw, which the library cannot avoid on your behalf.
It is worth noting that the release method is a no-op for every result type except the temporary-file one, so the cleanup only has anything to clean in the default mode.
webm is a deprecated alias that produces a still image
The format option takes PNG or JPEG everywhere, with two additional values on Android: a modern image format and a raw pixel format. The default is PNG.
One deprecated alias survives, and it is the kind of entry that is worth more than its length. A container name that most developers read as video is retained as an alias for the modern image format, and the documentation says so directly: it produces image data, not a video.
That is a small line and it prevents a real failure. A caller who asks for that container by name, for any reason, receives a still image with a misleading extension and no error. The only way to discover it is to read this paragraph.
Three result modes then decide where the bytes end up. The default writes a temporary file that exists only while the app is running. A base64 mode returns the raw string, with a warning to use it only for small images because the string is sent across the bridge, and an explicit note that it is not a data URI, so callers who want the scheme header should choose the third mode. That third mode returns base64 with the scheme header already included.
So the default is the one that loses your image when the app closes, and the convenient one is the one with a size limit. Neither is wrong; both are the kind of default that is worth reading before shipping.
One more platform leak in the same list: a file name option is documented as Android only and constrained to at least three characters, which is a file-system rule from one platform surfacing in a cross-platform API.
The homepage points at the example repository
Two URLs answer the question of where this project lives, and they disagree.
The repository metadata names the example repository as its homepage. The package manifest names the library repository. So anything that reads repository metadata, which includes a package index and most repository listings, sends a reader to the sample application rather than to the code.
It is a small thing, and the example project is genuinely worth sending people to. There are four separate example applications in the repository: one for the bare framework, one for Expo, one for web and one for Windows.
The bare-framework example is the busy one. It carries an end-to-end testing configuration and its own launcher script, a Ruby gemfile for the CocoaPods side, an Expo application manifest alongside a plain entry point, and its own bundler, test, type-checker and lint configurations plus its own lockfile. Having both that Expo manifest and a separate Expo example means the overlap between the two is not obvious from the directory listing.
Each example having its own lint configuration is where the root ends up with two lint files.
The library itself keeps native code in three platform directories, with a podspec and a framework configuration file for autolinking at the root. That autolinking file is what the page's linking note depends on, though the note itself is historical: it says linking has been automatic since a much older framework version, while the actual stated requirement is a recent one.
Editorial conclusion
Reach for this when you need a still image of a specific view inside a React Native app and want the API to be explicit about what it will and will not capture, because the documentation on the tricky option is better than most libraries manage. Three things to check before you rely on it. Full-content capture of a scroll view works vertically on Android, both ways on iOS, and not at all on Windows, so if your list is virtualized the off-screen rows will be missing from the image. If you target web, the canvas dependency is optional and missing, it fails at build time or at runtime depending on your bundler. And the default result mode writes a temporary file that disappears when the app closes, so anything you need to keep has to be moved or encoded.
Frequently asked questions
How do I install react-native-view-shot?
With `npm install react-native-view-shot`, or the Yarn equivalent, or `npx expo install react-native-view-shot` for Expo. Linking is handled automatically by the framework's autolinking, and on iOS you additionally run `npx pod-install` for the CocoaPods dependencies.
What does react-native-view-shot do?
It captures a React Native view to an image. It can capture a single view through a component with automatic modes, capture a ref imperatively, capture the whole screen on Android, iOS and Windows, and release a previously captured file. On web it delegates to a canvas library.
Does react-native-view-shot capture the whole content of a ScrollView?
With the snapshotContentContainer option it can, but not everywhere. Android expands only a vertical ScrollView and falls back to the visible bounds for horizontal ones. iOS handles both axes. Windows is not supported and captures only the visible viewport, warning in development. A virtualised list will be missing off-screen items.
What image formats does react-native-view-shot produce?
PNG or JPEG everywhere, plus WebP and a raw format on Android, with PNG as the default. A deprecated webm alias is retained for WebP and produces still image data rather than video, which the documentation states explicitly.
Where does react-native-view-shot save the captured image?
By default to a temporary file that exists only while the app is running. You can instead return a base64 string, which the page warns is for small images only because it crosses the bridge, or a data URI which includes the scheme header.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/gre-react-native-view-shot)