1. Go to this page and download the library: Download gimucco/atproto-php library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
gimucco / atproto-php example snippets
use Gimucco\Atproto\ClientConfig;
use Gimucco\Atproto\OAuthClient;
use Gimucco\Atproto\Storage\FileSessionStore;
use Gimucco\Atproto\Storage\FileStateStore;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
// 1. Configure
$config = new ClientConfig(
clientId: 'https://your-app.com/client-metadata.json',
redirectUri: 'https://your-app.com/callback',
scope: 'atproto transition:generic',
clientName: 'My App',
jwksUri: 'https://your-app.com/jwks.json',
privateKey: file_get_contents('/path/to/private.pem'),
);
// 2. Create the OAuth client
$factory = new HttpFactory();
$oauth = new OAuthClient(
config: $config,
sessionStore: new FileSessionStore('/var/app/sessions', 'encryption-passphrase'),
stateStore: new FileStateStore('/var/app/states', 'encryption-passphrase'),
httpClient: new Client(),
requestFactory: $factory,
streamFactory: $factory,
);
// 3. Start login — redirect the user
$authUrl = $oauth->beginAuthorization('alice.bsky.social');
header('Location: '.$authUrl);
// 4. Handle callback (in your callback endpoint)
$session = $oauth->completeAuthorization($_GET['code'], $_GET['state'], $_GET['iss']);
// 5. Make authenticated requests
$response = $session->authenticatedRequest(
'GET',
$session->pdsUrl.'/xrpc/com.atproto.server.getSession',
);
echo $response->getBody(); // {"did":"did:plc:...","handle":"alice.bsky.social",...}
$authUrl = $oauth->beginAuthorization(); // no handle
header('Location: '.$authUrl);
exit;
// Per-call override
$authUrl = $oauth->beginAuthorization(
handleOrDid: null,
authorizationServer: 'https://auth.example.com',
);
// Or set the project-wide default once in ClientConfig:
$config = new ClientConfig(
// ...
defaultAuthorizationServer: 'https://auth.example.com',
);
// The authorization server redirects back with code, state, and iss
$session = $oauth->completeAuthorization(
code: $_GET['code'],
state: $_GET['state'],
iss: $_GET['iss'],
);
// $session->did — "did:plc:..."
// $session->handle — "alice.bsky.social"
// $session->pdsUrl — "https://bsky.social" (or wherever their PDS is)
// GET request
$response = $session->authenticatedRequest(
'GET',
$session->pdsUrl.'/xrpc/com.atproto.server.getSession',
);
// POST request with JSON body
$response = $session->authenticatedRequest(
'POST',
$session->pdsUrl.'/xrpc/com.atproto.repo.createRecord',
[
'repo' => $session->did,
'collection' => 'app.bsky.feed.post',
'record' => [
'text' => 'Hello from atproto-php!',
'createdAt' => date('c'),
],
],
);
// Upload an image as a blob (returns a blob ref you can embed in a post)
$bytes = file_get_contents('/path/to/photo.jpg');
$response = $session->authenticatedRawRequest(
method: 'POST',
url: $session->pdsUrl.'/xrpc/com.atproto.repo.uploadBlob',
body: $bytes,
contentType: 'image/jpeg',
);
$blob = json_decode((string) $response->getBody(), true)['blob'];
// $blob is now { "$type": "blob", "ref": {...}, "mimeType": "image/jpeg", "size": ... }
// — embed it in an app.bsky.feed.post record's `embed.images[].image` field
use Gimucco\Atproto\Storage\InMemorySessionStore;
use Gimucco\Atproto\Storage\InMemoryStateStore;
$sessionStore = new InMemorySessionStore();
$stateStore = new InMemoryStateStore();
use Gimucco\Atproto\Storage\FileSessionStore;
use Gimucco\Atproto\Storage\FileStateStore;
$sessionStore = new FileSessionStore(
directory: '/var/app/sessions',
passphrase: 'your-strong-passphrase', // encrypts tokens at rest
);
$stateStore = new FileStateStore(
directory: '/var/app/states',
passphrase: 'your-strong-passphrase',
);
use Gimucco\Atproto\Storage\PdoSessionStore;
use Gimucco\Atproto\Storage\PdoStateStore;
use Gimucco\Atproto\Storage\Pdo\Schema;
$pdo = new PDO('mysql:host=localhost;dbname=myapp', 'user', 'pass');
// Create tables (run once)
$sql = Schema::createTablesSql('mysql');
$pdo->exec($sql['sessions']);
$pdo->exec($sql['states']);
$sessionStore = new PdoSessionStore($pdo, passphrase: 'your-strong-passphrase');
$stateStore = new PdoStateStore($pdo, passphrase: 'your-strong-passphrase');
$sql = Schema::createTablesSql('mysql'); // or 'pgsql' or 'sqlite'
$session->refresh();
if ($session->isExpired()) {
// Token has expired
}
$expiresAt = $session->expiresAt(); // DateTimeImmutable
use Gimucco\Atproto\Exception\ResolutionException;
use Gimucco\Atproto\Exception\AuthorizationException;
use Gimucco\Atproto\Exception\TokenException;
use Gimucco\Atproto\Exception\DpopException;
use Gimucco\Atproto\Exception\SessionException;
use Gimucco\Atproto\Exception\ConfigurationException;
use Gimucco\Atproto\Exception\NetworkException;
try {
$authUrl = $oauth->beginAuthorization($handle);
} catch (ResolutionException $e) {
// Handle/DID/PDS could not be resolved
} catch (AuthorizationException $e) {
// PAR request failed
} catch (NetworkException $e) {
// HTTP transport failure
}
try {
$session = $oauth->completeAuthorization($code, $state, $iss);
} catch (TokenException $e) {
// Token endpoint returned an error
echo $e->error; // e.g., "invalid_grant"
echo $e->errorDescription; // Human-readable message
} catch (AuthorizationException $e) {
// State/issuer/sub mismatch
}
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$logger = new Logger('atproto');
$logger->pushHandler(new StreamHandler('/var/log/atproto.log'));
$oauth = new OAuthClient(
config: $config,
sessionStore: $sessionStore,
stateStore: $stateStore,
httpClient: $httpClient,
requestFactory: $factory,
streamFactory: $factory,
logger: $logger,
);
$httpClient = new \GuzzleHttp\Client(['verify' => false]);