What an iOS keyboard can't do without Full Access

By Ruslan Zhafiarov, the developer of Light Clipboard ยท October 2026

Every third-party keyboard on iOS runs in one of two sandboxes. Which one is decided by a single key in the extension's Info.plist, RequestsOpenAccess, and by a switch the user may or may not turn on in Settings. I built a snippet keyboard, Light Clipboard, entirely inside the stricter of the two. This is what that sandbox allows, what it refuses, and what it did to the design.

Two sandboxes

With RequestsOpenAccess set to false, Settings never shows an "Allow Full Access" switch for the keyboard. Apple's documentation lists what such a keyboard gets:

The same page states the consequence in one line: without open access, "keystrokes can't be sent back to your server or anywhere else."

Set the key to true and the user gets the switch, along with the system warning about everything they type. If they turn it on, the keyboard gains the network and a writable shared container.

Most keyboards that ask do so because they need something from that second list: sync, cloud suggestions, GIF search. A snippet keyboard needs none of it. It has to read a list and insert a string.

The app writes, the keyboard reads

A read-only shared container settles the architecture. The app is the only writer:

// App
let defaults = UserDefaults(suiteName: "group.com.example.snippets")
defaults?.set(try? JSONEncoder().encode(items), forKey: "items")

The keyboard reads the same key every time it appears, because iOS keeps the extension process alive between appearances and the list may have changed in the meantime. To avoid rebuilding the grid each time a text field gets focus, it compares the raw bytes first:

// Keyboard extension
private func loadItems() {
    let data = userDefaults?.data(forKey: storageKey)
    guard data != lastLoadedData || lastLoadedData == nil else { return }
    lastLoadedData = data

    if let data, let decoded = try? JSONDecoder().decode([Snippet].self, from: data) {
        items = decoded
    } else {
        items = []
    }
    collectionView.reloadData()
}

Nothing flows the other way. There is no path by which the keyboard could report anything, to the app or to anyone else.

Insert, don't paste

Tapping a tile calls one method:

textDocumentProxy.insertText(snippet.text)

The text goes into the document through the proxy, not through the pasteboard, so whatever the user copied earlier is still on their clipboard. The extension never reads from the proxy either. It has no letter keys, so it has no use for the text around the cursor, and documentContextBeforeInput is never called.

The globe key

App Review guideline 4.4.1 requires a keyboard to offer a way to the next one. The catch is that the system sometimes draws that key for you. On an iPhone with Face ID, iOS puts its own globe below your view and needsInputModeSwitchKey returns false; a second globe would be a duplicate. On an iPad it returns true, and without your own key the user is stuck.

nextKeyboardButton.addTarget(
    self,
    action: #selector(handleInputModeList(from:with:)),
    for: .allTouchEvents
)

// in viewWillAppear, because the answer can change between appearances
nextKeyboardKey.isHidden = !needsInputModeSwitchKey

.allTouchEvents rather than .touchUpInside is what lets the system turn a long press into the keyboard picker and a tap into a plain switch.

Height is yours to get wrong

A keyboard extension sets its own height with a constraint, and nothing stops you from choosing badly. Mine was a hardcoded 260 points while the parts inside added up to 208; the extra 52 pushed the top edge over the host app's text field. Now the height is derived from the tiles, follows Dynamic Type, and is capped at 45% of the screen, after which the tiles scroll.

On iPad the keyboard can be floated, which changes its width without creating a new view. A flow layout keeps the cell sizes it measured first, so the layout has to say when to measure again:

override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
    newBounds.width != collectionView?.bounds.width
}

What it costs

Was it worth it

For this kind of keyboard, yes. "We don't collect what you type" is a promise. "This keyboard has no network access, and you can check that in Settings" is a property of the system. The second is easier to say, and easier to believe.

Light Clipboard is on the App Store, free for three snippets, for iPhone and iPad.

Get Light Clipboard

Source: Apple, Configuring open access for a custom keyboard.