A flat-file PHP signup grid for amateur radio operating events.
This tool is intended to be a "quick and dirty" signup method for integration into a larger online ecosystem for a radio club or similar organization. As such, it does not have any form of real security, accounts, abuse-protection, etc. Please be thoughtful when deploying this.
opergrid/
├── public ← root/DocumentRoot for webserver
├── index.php ← Main UI (the grid)
├── api.php ← REST API (read/write signups)
├── data
├── config.json ← Event config: stations + schedule
├── signups.json ← Signup data (auto-created / editable)
└── config.php ← Core configuration
- PHP 8.x-enabled webserver write permission to the directory
data/
- Copy all files to a reasonable location (e.g.
/var/www/opergrid/) - Make sure the
datadirectory is writable by the same userver as the webserver. For example on Debian the user iswww-data:chmod 755 /var/www/opergrid/data chmod 664 /var/www/opergrid/data/*.json chown -R www-data:www-data /var/www/opergrid/data/ - Edit config.json to set your event details, stations, and hours.
- Edit your webserver configuration to serve the contents of /var/www/opergrid/public either as the root of the site or using an appropriate sub-director rewrite/Location. Restart when complete.
- Open the location in your browser.
The event label is simply the title.
The stations[] is an array of values:
id- The unique ID for the station, limited to[a-zA-Z0-9], max 10 chars, must be uniquename- The title for this itemband- The band labelmode- The mode for the operationschedule- (Optional) Specify one or more times this station is avalable, bounded by the overall schedule. If absent, the station's schedule is the full range of the event schdule.
Here's an example showing different combinations:
{
"event": {
"name": "Field Day 2026"
},
"stations": [
{ "id": "sta80", "name": "80m Station", "band": "80m", "mode": "Any" },
{ "id": "sta40", "name": "40m Station", "band": "40m", "mode": "Any",
"schedule": { "start": "2026-06-27 18:00", "end": "2026-06-28 12:00" } },
{ "id": "sta20", "name": "20m Station", "band": "20m", "mode": "Any",
"schedule": { "start": "2026-06-27 14:00", "end": "2026-06-28 02:00" } },
{ "id": "sta15", "name": "15m Station", "band": "15m", "mode": "Any",
"schedule": [
{ "start": "2026-06-27 14:00", "end": "2026-06-27 22:00" },
{ "start": "2026-06-28 08:00", "end": "2026-06-28 12:00" }
] },
{ "id": "sta10", "name": "10m Station", "band": "10m", "mode": "Any",
"schedule": [
{ "start": "2026-06-27 14:00", "end": "2026-06-27 20:00" },
{ "start": "2026-06-28 10:00", "end": "2026-06-28 12:00" }
] }
],
"schedule": {
"start": "2026-06-27 14:00",
"end": "2026-06-28 12:00",
"tz": "EDT (UTC-4)"
}
}Day boundaries are automatically highlighted in the grid with an amber divider line and a date label (e.g. "Sat 6/28") in the time column.
The ID must be no more than 10 characters.
Keys are stationId|HH:MM. Written automatically by api.php.
{
"sta1|08:00": {
"name": "Jane Smith",
"callsign": "W1XYZ",
"email": "jane@example.com",
"claimed_at": "2026-04-01T10:23:00+00:00"
}
}These fields are limited to 30 characters each except callsign which is limited
to 10 characters. Fields will be auto-truncated as necessary.
| Method | URL | Description |
|---|---|---|
| GET | api.php?action=config[&event=ID] |
Returns config.json |
| GET | api.php?action=signups[&event=ID] |
Returns all signups |
| POST | api.php?action=claim[&event=ID] |
Claim a slot |
{
"station": "sta1",
"timeslot": "08:00",
"name": "Jane Smith",
"callsign": "W1XYZ",
"email": "jane@example.com"
}Simply replace signups.json with {} to clear all signups.
OperGrid can handle multiple events at the same time. By default,
the system uses an event name of "default" internally which uses
the config.js and signups.js files listed above.
However, the URIs can have an event= parameter which will cause
the system to ouse a different set of files, thus effectively
creating a separate event. For example event=foo will cause
the use of config_foo.js and signups_foo.js (Note that
the underscore _ is added automatically). One can create as many
arbitrary config_$event.js files as needed. There is no main menu
to find/select events however as, again, this is intended to be
used within a large web ecosystem.
