Digital Signature for Filament GitHub

#Configuration

After publishing, the config file lives at config/signature.php.

Your copy is merged with the package defaults one level deep. A top-level block you delete falls back to the default. A block you keep replaces the default whole, along with the env variables inside it. Keep blocks complete, or remove them and use .env. See Keep your published config current.


#Certificate driver

PHP
'cert_driver' => env('SIGNATURE_CERT_DRIVER', 'openssl'),
ValueDescription
opensslSelf-signed via PHP's openssl_* extension (default)
cfsslIssues certificates from a running CFSSL server

#OpenSSL options

PHP
'openssl' => [
    'digest_alg'       => 'sha256',
    'private_key_bits' => 2048,
    'private_key_type' => OPENSSL_KEYTYPE_RSA,
    'cert_lifetime'    => 3650,   // days before expiry
    'ca_cert_path'     => storage_path('app/certs/ca.crt'),  // optional
    'ca_key_path'      => storage_path('app/certs/ca.key'),  // optional
],

When ca_cert_path and ca_key_path point to valid files, certificates are CA-signed. Leave absent for development (self-signed).

#CFSSL options

PHP
'cfssl' => [
    'host'    => env('CFSSL_HOST', 'http://localhost:8888'),
    'profile' => env('CFSSL_PROFILE', 'client'),
],

#PDF driver

PHP
'pdf_driver' => env('SIGNATURE_PDF_DRIVER', 'fpdi'),
ValueDescription
fpdiFPDI + TCPDF — imports existing PDF pages, embeds PKCS#7 signature (default)
tcpdfPure TCPDF

#Storage

PHP
'storage_disk'     => env('SIGNATURE_DISK', 'local'),
'certs_path'       => 'certs',         // PFX certificate files
'signatures_path'  => 'signatures',    // raw signature images
'signed_docs_path' => 'signed-docs',   // completed signed PDFs
'preview_url_ttl'  => 5,               // minutes a signature preview link stays valid

All paths are relative to the disk root. Any Laravel disk driver works (local, s3, etc.).


#Image constraints

Applied both client-side (JS) and server-side (PHP) during upload validation.

PHP
'image' => [
    'max_kb'        => 512,
    'allowed_mimes' => ['image/png', 'image/jpeg'],
    'canvas_width'  => 600,
    'canvas_height' => 200,
],

#Admin Resource

Controls whether and how the built-in Signatures resource appears in the panel.

PHP
'resource' => [
    'enabled'          => env('SIGNATURE_RESOURCE_ENABLED', true),
    'navigation_icon'  => env('SIGNATURE_RESOURCE_ICON', 'heroicon-o-pencil-square'),
    'navigation_group' => env('SIGNATURE_RESOURCE_GROUP', null),
    'navigation_sort'  => env('SIGNATURE_RESOURCE_SORT', null),
    'navigation_label' => env('SIGNATURE_RESOURCE_LABEL', 'Signatures'),
],

These are the defaults. Values set on SignaturePlugin::make() take precedence:

PHP
SignaturePlugin::make()
    ->navigationGroup('Documents')
    ->navigationIcon('heroicon-o-pencil-square')
    ->navigationSort(10)
    ->navigationLabel('Document Signatures')

If you manually register or discover SignatureResource without SignaturePlugin::make(), the resource falls back to these config values. The recommended setup is still to register the plugin on the panel.


#Metadata & machine binding

Every stored signature PNG receives HMAC-signed tEXt chunks and XMP metadata. Machine lock requires the same browser/device on re-upload.

PHP
'metadata' => [
    'enforce_machine_lock' => env('SIGNATURE_MACHINE_LOCK', true),
],
SettingEffect
falseOnly verifies HMAC + user ID on re-upload
true (default)Also verifies the device fingerprint and DB machine_fingerprint

#Registered signing devices

Each browser holds a non-extractable P-256 key in IndexedDB and proves to the server that it holds the key. Every signature then records the device it was created on or used on. See security.md §10 and Device registration.

PHP
'devices' => [
    'enabled'               => env('SIGNATURE_DEVICES_ENABLED', true),
    'require'               => env('SIGNATURE_DEVICES_REQUIRE', 'off'),   // off | warn | enforce
    'usage_policy'          => env('SIGNATURE_DEVICES_USAGE_POLICY', 'any_registered'), // or creation_device_only
    'notify_on_new_device'  => env('SIGNATURE_DEVICES_NOTIFY', true),
    'notification_channels' => ['mail'],
    'max_per_user'          => 10,
    'challenge_ttl'         => 120,  // seconds a challenge can be answered
    'attestation_ttl'       => 900,  // seconds a browser's proof stays valid
],
requireEffect
off (default)Record the device when there is one, never block
warnThe same, and log each signature act that had no verified device
enforceCreating or using a signature fails with UnregisteredDeviceException without a verified device

A revoked device is refused in every mode.

#Desktop agent (Kukux Sign Agent)

Pairs a computer whose signing key lives in its Secure Enclave or TPM. See Desktop agent.

PHP
'devices' => [
    // …
    'agent' => [
        'enabled'          => env('SIGNATURE_AGENT_ENABLED', false),
        'approval'         => env('SIGNATURE_AGENT_APPROVAL', 'prefer'),   // off | prefer | enforce
        'scheme'           => 'kukuxsign',   // the custom URL scheme the agent registers for pairing links
        'download_url'     => env('SIGNATURE_AGENT_DOWNLOAD_URL', 'https://github.com/cortejojicoy/digital-signature-agent/releases/latest'),
        'min_version'      => env('SIGNATURE_AGENT_MIN_VERSION', '0.1.0'),
        'require_presence' => env('SIGNATURE_AGENT_REQUIRE_PRESENCE', true),
        'server_id'        => env('SIGNATURE_AGENT_SERVER_ID'),   // null = derived from APP_KEY
        'salt'             => env('SIGNATURE_AGENT_SALT'),        // null = derived from APP_KEY
        'pairing_ttl'      => 600,
        'job_ttl'          => 300,
        'skip_ttl'         => 120,

        // Detected device types refused at pairing. Empty = allow VMs.
        'blocked_device_types' => array_values(array_filter(array_map(
            'trim',
            explode(',', (string) env('SIGNATURE_AGENT_BLOCKED_DEVICE_TYPES', 'virtual_machine')),
        ))),
    ],
],

A computer holds one account's signature per app. Re-pairing from the same computer updates the existing device instead of adding one. Free a computer with php artisan signature:agent-release. See Desktop agent: one signature per computer.

approvalWhen a user with a paired computer signs a document
offThe agent is never asked
prefer (default)Approve on the computer, with Sign in the browser instead
enforceApproval on a paired computer is required, so a user with no paired computer can't sign documents

server_id and salt must stay stable for an installation. If you rotate APP_KEY, pin them first, or every paired agent will have to pair again. The agent only connects to HTTPS origins.

#Certificate keys (PKCS#12)

An agent can also sign with a key imported from a certificate file, such as a PNPKI .p12. Off by default. See Desktop agent: certificate keys.

PHP
'devices' => [
    // …
    'pkcs12' => [
        'enabled'         => env('SIGNATURE_AGENT_PKCS12', false),
        // DER or PEM files; default: the bundled Philippine Root CA - G2.
        'trusted_roots'   => [\Kukux\DigitalSignature\Security\AgentCertificateValidator::PNPKI_ROOT],
        'revocation'      => env('SIGNATURE_AGENT_PKCS12_REVOCATION', 'ocsp_then_crl'),   // ocsp_then_crl | crl | off
        'unreachable'     => env('SIGNATURE_AGENT_PKCS12_UNREACHABLE', 'allow'),          // allow | deny
        'recheck_ttl'     => env('SIGNATURE_AGENT_PKCS12_RECHECK_TTL', 86400),            // seconds
        'subject_binding' => env('SIGNATURE_AGENT_PKCS12_SUBJECT_BINDING', 'none'),       // none | name_match
        'one_device_per_certificate' => env('SIGNATURE_AGENT_PKCS12_ONE_DEVICE', true),
    ],
],

#Queue

PHP
'queue'            => env('SIGNATURE_QUEUE', 'default'),
'queue_connection' => env('SIGNATURE_QUEUE_CONNECTION', null),

null uses the application's default connection. Only relevant when SignDocumentAction::make()->queued() is used — the default is synchronous.


#Timestamp Authority (TSA)

Embeds an RFC 3161 trusted timestamp inside the PKCS#7 block. Disabled when url is null.

PHP
'tsa' => [
    'url' => env('SIGNATURE_TSA_URL', null),
],

Free public endpoints:

EndpointProvider
https://freetsa.org/tsrFreeTSA
http://timestamp.digicert.comDigiCert
http://tsa.starfieldtech.comStarfield

#CRL validation

Checks certificate revocation lists before signing. Disabled by default.

PHP
'crl' => [
    'enabled'         => env('SIGNATURE_CRL_ENABLED', false),
    'cache_ttl_hours' => 24,
],

Requires the openssl CLI binary in PATH. Self-signed certificates (no CDP extension) are silently skipped.


#Templates and documents

PHP
'templates' => [
    \App\Pdf\DtrTemplate::class,                                   // a class
    'payslip' => ['view' => 'pdf.payslip', 'slots' => ['employee']],  // or a Blade template by key
],

'documents' => [
    'dtr' => \App\Signatures\DtrDocument::class,                     // 2.0: documents this app routes
],

Class-strings, not closures, so php artisan config:cache keeps working. Both are built through the container on first use. See PDF Templates and Integrating documents.


#Placement designer

KeyEnvDefaultPurpose
designer.dpiSIGNATURE_DESIGNER_DPI144Resolution of the page previews. Higher is sharper, slower and larger.

#Stamp: caption and the public page

What is drawn inside each signature's box. It follows COA Circular No. 2021-006, IV.C.13: the handwritten signature with the signatory's full name and the date beside it. Everything is carved out of the placement rectangle; the stamp never grows past the box a signatory or the form gave it.

Text
[signature]  Digitally signed
             by Juan DelaCruz
             Date: 2020.05.21
             19:37:33 +08'00'

#caption

The text is drawn at a fixed size whatever size the box is, so resizing a placement scales only the signature image.

KeyEnvDefaultPurpose
caption.enabledSIGNATURE_CAPTION_ENABLEDtrueDraw the text at all.
caption.labelSIGNATURE_CAPTION_LABELDigitally signedFirst line.
caption.font_ptSIGNATURE_CAPTION_FONT7Fixed size of the text.
caption.min_font_ptSIGNATURE_CAPTION_MIN_FONT4Only used when a box is too small for font_pt. The name is never truncated.
caption.line_heightSIGNATURE_CAPTION_LINE_HEIGHT1.15Leading.
caption.gapSIGNATURE_CAPTION_GAP3Points between the signature and the text.
caption.date_formatSIGNATURE_CAPTION_DATE_FORMATY.m.dPHP format for the Date: line.
caption.time_formatSIGNATURE_CAPTION_TIME_FORMATH:i:sPHP format for the time line. The UTC offset is appended as +08'00'.
caption.timezoneSIGNATURE_CAPTION_TIMEZONEnullZone the time is shown in. null uses the app timezone; Asia/Manila prints Philippine time.
caption.color—[0, 0, 0]RGB.

#verify

KeyEnvDefaultPurpose
verify.enabledSIGNATURE_VERIFY_ENABLEDtrueThe public GET /signature/verify/{uuid} page. It discloses only what the printed page already shows.

#Hashing

KeyDefaultPurpose
hash_algosha256Algorithm for image, document and signed-document hashes. Changing it on a live install makes earlier hashes incomparable; every stored version would report a mismatch.

#Full environment variable reference

Terminal
# Drivers
SIGNATURE_CERT_DRIVER=openssl       # openssl | cfssl
SIGNATURE_PDF_DRIVER=fpdi           # fpdi | tcpdf
 
# Storage
SIGNATURE_DISK=local                # any Laravel disk
 
# Admin resource
SIGNATURE_RESOURCE_ENABLED=true
SIGNATURE_RESOURCE_ICON=heroicon-o-pencil-square
SIGNATURE_RESOURCE_GROUP=           # blank = ungrouped
SIGNATURE_RESOURCE_SORT=            # blank = default order
SIGNATURE_RESOURCE_LABEL=Signatures
 
# Security
SIGNATURE_MACHINE_LOCK=true         # reject re-upload from different device
SIGNATURE_DEVICES_ENABLED=true      # browser device keys
SIGNATURE_DEVICES_REQUIRE=off       # off | warn | enforce
SIGNATURE_DEVICES_USAGE_POLICY=any_registered  # or creation_device_only
SIGNATURE_DEVICES_NOTIFY=true       # mail the owner when a new device registers
SIGNATURE_AGENT_ENABLED=false       # desktop agent pairing + approval
SIGNATURE_AGENT_APPROVAL=prefer     # off | prefer | enforce
SIGNATURE_AGENT_MIN_VERSION=0.1.0   # older agents get HTTP 426
SIGNATURE_AGENT_SERVER_ID=          # blank = derived from APP_KEY; `signature:install --agent` pins it
SIGNATURE_AGENT_SALT=               # same
SIGNATURE_AGENT_BLOCKED_DEVICE_TYPES=virtual_machine  # comma-separated; empty = allow VMs
SIGNATURE_AGENT_PKCS12=false        # pair agents whose key is an imported certificate (.p12)
SIGNATURE_AGENT_PKCS12_ROOTS=       # comma-separated root files; blank = bundled PNPKI root
SIGNATURE_AGENT_PKCS12_REVOCATION=ocsp_then_crl   # ocsp_then_crl | crl | off
SIGNATURE_AGENT_PKCS12_UNREACHABLE=allow          # allow | deny, when OCSP and CRL are both down
SIGNATURE_AGENT_PKCS12_RECHECK_TTL=86400          # seconds before a signing re-checks revocation
SIGNATURE_AGENT_PKCS12_SUBJECT_BINDING=none       # none | name_match
SIGNATURE_AGENT_PKCS12_ONE_DEVICE=true            # one active computer per certificate
 
# Routing, sessions and consent
SIGNATURE_SEQUENCE_MODE=sequential  # sequential | parallel
SIGNATURE_MULTI_MODE=progressive    # progressive | incremental
SIGNATURE_SESSION_EXPIRY_DAYS=      # blank = sessions never expire
SIGNATURE_AUTO_AFFIX_MODE=approval  # approval | delegated | implicit
SIGNATURE_ALLOW_IMPLICIT_AFFIX=false
SIGNATURE_AUTO_AFFIX_NOTIFY=true
 
# Placement designer
SIGNATURE_DESIGNER_DPI=144          # page preview sharpness
 
# Queue
SIGNATURE_QUEUE=default
SIGNATURE_QUEUE_CONNECTION=         # blank = app default
 
# Floating launcher
SIGNATURE_LAUNCHER_ENABLED=true
SIGNATURE_LAUNCHER_REPLACES_NAV=true
SIGNATURE_LAUNCHER_POSITION=bottom-right
SIGNATURE_LAUNCHER_LABEL=Signatures
SIGNATURE_LAUNCHER_COLOR=
SIGNATURE_LAUNCHER_POLL=60
SIGNATURE_LAUNCHER_WIDTH=56rem
SIGNATURE_LAUNCHER_MANAGE_WIDTH=72rem
SIGNATURE_LAUNCHER_CUSTOMIZABLE=true  # placement controls in the super-admin Settings tab
SIGNATURE_LAUNCHER_AVOID_OVERLAP=true
SIGNATURE_LAUNCHER_OFFSET_X=1.5rem
SIGNATURE_LAUNCHER_OFFSET_Y=1.5rem
SIGNATURE_LAUNCHER_GAP=12
SIGNATURE_LAUNCHER_Z_INDEX=40
 
# Stamp: COA caption, verification page
SIGNATURE_VERIFY_ENABLED=true
SIGNATURE_CAPTION_ENABLED=true
SIGNATURE_CAPTION_LABEL="Digitally signed"
SIGNATURE_CAPTION_FONT=7            # fixed; only the signature scales
SIGNATURE_CAPTION_MIN_FONT=4
SIGNATURE_CAPTION_LINE_HEIGHT=1.15
SIGNATURE_CAPTION_GAP=3
SIGNATURE_CAPTION_DATE_FORMAT="Y.m.d"
SIGNATURE_CAPTION_TIME_FORMAT="H:i:s"
SIGNATURE_CAPTION_TIMEZONE=Asia/Manila
 
# Optional features
SIGNATURE_TSA_URL=                  # blank = disabled
SIGNATURE_CRL_ENABLED=false
 
# CFSSL (only if cert_driver=cfssl)
CFSSL_HOST=http://localhost:8888
CFSSL_PROFILE=client

Added by Signatory Routing. Full explanations of each mode live there; this is the key reference.

#sessions

KeyEnvDefaultPurpose
sessions.sequence_modeSIGNATURE_SEQUENCE_MODEsequentialsequential honours SlotDefinition::$order (Prepared → Attested → Noted); parallel lets any assigned signatory act at any time
sessions.expires_after_daysSIGNATURE_SESSION_EXPIRY_DAYSnullSessions stop accepting signatures after this many days; null disables expiry
sessions.notification_channels—['mail']Channels for SignatureRequestedNotification, sent to each signatory when it's their turn. [] turns it off.

#multi_signature

KeyEnvDefault
multi_signature.modeSIGNATURE_MULTI_MODEprogressive
  • progressive — each signature is stamped onto the previous signatory's output and re-signed with that signatory's own certificate. Every visible signature is present and the database holds a verifiable hash chain, but only the most recent PKCS#7 block survives inside the PDF (FPDI rewrites the file on every pass).
  • incremental — true PAdES; requires a driver implementing SupportsIncrementalSigning. Neither bundled driver does, so selecting this mode without one throws IncrementalSigningUnsupportedException at sign time rather than silently producing a document whose earlier signatures are gone.

#auto_affix

KeyEnvDefaultPurpose
auto_affix.modeSIGNATURE_AUTO_AFFIX_MODEapprovalapproval, delegated or implicit
auto_affix.allow_implicitSIGNATURE_ALLOW_IMPLICIT_AFFIXfalseSecond acknowledgement required before implicit will run
auto_affix.notifySIGNATURE_AUTO_AFFIX_NOTIFYtrueNotify the signatory on every auto-affix. Leave this on.
auto_affix.default_grant_daysSIGNATURE_GRANT_DAYS365Default lifetime of a delegation grant
auto_affix.notification_channels—['mail']Channels for SignatureAutoAffixedNotification
  • approval (default) — never signs on anyone's behalf. The signature is always produced in the signatory's own authenticated request.
  • delegated — signs only where the signatory created a scoped, expiring, revocable SignatureDelegation. A standing grant means the server can produce that user's signature for the grant's lifetime; that is a deliberate trade.
  • implicit — treats being tagged on a record as consent. Unsafe: anyone who can edit the record can then cause that person's certificate to sign it.

Both auto_affix and sequence_mode can be overridden per template with the auto_affix and sequence_mode keys in the template's config array.

#inbox

KeyEnvDefault
inbox.enabledSIGNATURE_INBOX_ENABLEDtrue
inbox.navigationSIGNATURE_INBOX_NAVtrue
inbox.navigation_labelSIGNATURE_INBOX_LABELAwaiting my signature
inbox.navigation_iconSIGNATURE_INBOX_ICONheroicon-o-inbox-arrow-down
inbox.navigation_groupSIGNATURE_INBOX_GROUPnull
inbox.navigation_sortSIGNATURE_INBOX_SORTnull

Per-panel override: SignaturePlugin::make()->withoutInbox().

#launcher

The floating button, pinned to a corner of every panel page, that opens a drawer with the documents waiting on the signed-in user and their signature library. On by default.

The drawer is the signing surface, not a notification rail: opening a document renders the PDF inside it, and the signatory drags their signature onto the page. width is sized for that — 56rem by default, wide enough to read a page. It accepts any plain CSS length, never exceeds the viewport, and goes full-bleed below 640px regardless.

KeyEnvDefault
launcher.enabledSIGNATURE_LAUNCHER_ENABLEDtrue
launcher.replaces_navigationSIGNATURE_LAUNCHER_REPLACES_NAVtrue
launcher.positionSIGNATURE_LAUNCHER_POSITIONbottom-right
launcher.iconSIGNATURE_LAUNCHER_ICONheroicon-o-pencil-square
launcher.labelSIGNATURE_LAUNCHER_LABELSignatures
launcher.colorSIGNATURE_LAUNCHER_COLORnull (built-in neutral)
launcher.poll_secondsSIGNATURE_LAUNCHER_POLL60
launcher.hide_when_emptySIGNATURE_LAUNCHER_HIDE_WHEN_EMPTYfalse
launcher.widthSIGNATURE_LAUNCHER_WIDTH56rem
launcher.manage_widthSIGNATURE_LAUNCHER_MANAGE_WIDTH72rem (drawer width while Manage signatures or All documents I've signed is open)
launcher.customizableSIGNATURE_LAUNCHER_CUSTOMIZABLEtrue (a super admin can move their own button from the drawer's Settings tab)
launcher.avoid_overlapSIGNATURE_LAUNCHER_AVOID_OVERLAPtrue
launcher.offset.xSIGNATURE_LAUNCHER_OFFSET_X1.5rem
launcher.offset.ySIGNATURE_LAUNCHER_OFFSET_Y1.5rem
launcher.gapSIGNATURE_LAUNCHER_GAP12 (px)
launcher.z_indexSIGNATURE_LAUNCHER_Z_INDEX40
launcher.avoid—[]
launcher.ignore—[]

position accepts bottom-right, bottom-left, top-right, top-left. The slide-over enters from whichever side the button sits on.

position and offset are the default placement. The drawer's Settings tab is for super admins only. With customizable on, it lets them pick their own corner and offsets: the button moves as they choose, and the choice is saved per user (in digital_signature_user_preferences) and follows them to other devices. Reset to default goes back to the config. Set customizable to false to drop the placement controls and apply the config placement to everyone; saved choices are then ignored, not deleted. The tab stays for feature access.

#Not landing on the host app's own floating button

A plugin does not own the corner it is dropped into. Host apps put chat widgets, cookie bars, "back to top" buttons and their own FABs in exactly the same place, so with avoid_overlap on (the default) the launcher measures what is already pinned in its corner and stacks itself clear of it:

  • It probes the corner on load, on resize, on livewire:navigated, twice more shortly after load, and whenever something is appended to <body> — chat widgets and cookie bars routinely mount seconds late.
  • Fixed or sticky, short, and clickable counts as an obstacle: another FAB, a cookie bar, a topbar. It stacks above it, leaving gap pixels.
  • Tall elements (over 60% of the viewport height) are treated as layout — sidebars, full-height drawers, backdrops. Floating in front of those is the job; stacking above one would push the button off-screen.
  • pointer-events: none decoration, such as a full-width toast rail, is ignored.
  • A corner crowded past 60% of the viewport height gives up and stays put: drifting into the middle of the page would be worse than the overlap.

Two escape hatches for widgets the detector gets wrong:

PHP
'launcher' => [
    // Always stack clear of these — for widgets that render into an iframe,
    // or mount far too late to be probed.
    'avoid'  => ['#intercom-launcher', '.crisp-client'],

    // Never treat these as obstacles.
    'ignore' => ['.my-app-toast-rail'],
],

If you already know where the button should go, placing it by hand is better than probing:

PHP
'launcher' => [
    'position'      => 'bottom-left',
    'offset'        => ['x' => '1.5rem', 'y' => '6rem'],  // above the host's FAB
    'avoid_overlap' => false,
],

offset accepts a plain CSS length (px, rem, em, vh, vw, %); anything else falls back to the default, since the value lands in a style attribute. z_index sets the button's layer — the slide-over sits one above, its backdrop one below — so raise it if a host overlay covers the button, and lower it if the button covers something that matters more.

replaces_navigation is the key worth understanding. While the launcher is on, the inbox page and the Signatures resource stop registering sidebar/topbar items — the launcher is the entry point, and two doors to one room is clutter. Both pages stay fully routable and the slide-over links to them. Set it to false to have the launcher and the navigation items.

Turning the launcher off restores both navigation items automatically: suppression is conditional on there being a launcher to replace them with, so no combination of these flags can leave a panel with no way to reach signatures.

color is a plain hex rather than a Filament color token, because the CSS custom-property format for those changed between Filament majors and a button that renders invisible on one of the three supported versions would be worse than one that isn't brand-coloured by default.

Per-panel override: SignaturePlugin::make()->withoutFloatingLauncher().

#feature_access

A super admin can limit a feature to selected people from the drawer's Settings tab while it's being tested. See Feature access.

KeyDefault
feature_access.search_columns['name', 'email']Users-table columns searched when adding people. A column your table doesn't have gives no results.

#filament_version

KeyEnvDefault
filament_versionSIGNATURE_FILAMENT_VERSIONauto-detected

Normally read from Composer's installed-versions manifest — a class probe cannot tell Filament 4 from 5, since both ship Filament\Schemas\Schema. Set this only to force a branch (3, 4 or 5), e.g. to exercise the v3 component classes on a v5 install.