Hello, this is Leslie from the Femiwiki development team. If you run a MediaWiki site, you have probably heard that the Graph extension (Extension:Graph) was taken down over security problems and the Chart extension (Extension:Chart) took its place.[1] To use the Chart extension outside the Wikimedia Foundation, however, you have to provide your own renderer to draw the charts. Femiwiki has run this renderer on AWS Lambda since 28 October 2026 so that pages can contain charts, and we are releasing the same setup as an OpenTofu module called terraform-aws-mediawiki-chart-renderer so that other wikis can use it too. In this post I will walk you through attaching this module to a MediaWiki site on AWS, step by step.
Why a renderer is needed
The Chart extension cannot draw charts with PHP alone; a renderer written in Node.js has to draw them as SVG. The extension can call the renderer in two ways: by running node directly on the wiki server, or by sending the chart definition to the HTTP service set in $wgChartServiceUrl and getting SVG back.[2] The Wikimedia Foundation uses the latter, but its service is only reachable from inside the Foundation's network, so other wikis cannot borrow it.[3]
Running node on the wiki server takes more work than you might expect. The renderer requires Node.js 20 or later, so on a distribution that ships Node.js 18, such as Debian bookworm, you have to install it separately. Under the 300 MB virtual memory limit that MediaWiki puts on shell commands by default ($wgMaxShellMemory), Node.js 22 cannot even start, so you have to raise the limit to around 1 GB. That limit, however, applies to every other shell command as well, and every chart render uses that much of the wiki server's memory. If you run your wiki on a single EC2 instance, as Femiwiki does, you are better off splitting the renderer out into an HTTP service, and since charts only need to be drawn when a page is saved or parsed, Lambda, which runs only when a request comes in, is a good fit.
How it works
The module creates one Lambda function holding the renderer, plus its function URL. The function runs on the arm64 Node.js 22 runtime with 256 MB of memory and takes 11–98 ms to draw a chart.[4]
Anyone on the internet can call a function URL, so it has to accept only requests sent by the wiki. But the Chart extension cannot add any header other than Content-Type to its requests to the renderer. That rules out the function URL's IAM authentication, which needs signature headers, so the credentials have to go inside the $wgChartServiceUrl address itself. A fixed secret in the address would stay in configuration files and logs as is, and once leaked it could be used until it was changed, so instead the module has the wiki append a JWT signed by an OpenID Connect issuer to the address. The function verifies the signature with the issuer's public keys and draws the chart only if the issuer, audience (aud), subject (sub) and expiry all match.
On AWS, STS's GetWebIdentityToken issues such tokens for an IAM role.[5] If the wiki server gets its tokens with its own IAM role, the wiki has no secret to store at all, and each token expires within an hour at most. The rest of this post is based on this approach.
Deploying the module
First, STS only issues these tokens once IAM outbound web identity federation is turned on for the account. The aws_iam_outbound_web_identity_federation resource below turns it on for the whole account (AWS provider 6.26 or later), and deleting the resource turns it off again. Then tell the module the issuer and the wiki server's IAM role. aws_iam_role.wiki is the IAM role used by the wiki server (EC2 instance). The module works at the same address in both OpenTofu and Terraform.[6]
resource "aws_iam_outbound_web_identity_federation" "this" {}
module "chart_renderer" {
# OpenTofu fetches it from registry.opentofu.org, Terraform from registry.terraform.io.
source = "femiwiki/mediawiki-chart-renderer/aws"
version = "~> 0.1"
issuer = aws_iam_outbound_web_identity_federation.this.issuer_identifier
subjects = [aws_iam_role.wiki.arn]
algorithms = ["ES384"]
}
Concurrency is capped at 2 by default, so even if someone floods the address with requests, the cost cannot grow beyond a certain point. Requests from your own wiki will queue up behind such a flood too, though, so if your wiki is large, raise it with reserved_concurrent_executions. Also, if the account's concurrency limit is low (new accounts sometimes get 10), reserved concurrency cannot be set and the deployment fails; in that case, raise the limit in Service Quotas first. Once deployed, the url output is the renderer's address.
Permission to get tokens
Next, attach a policy so that the wiki server's IAM role can only get tokens for the renderer. By tying down the audience, signing algorithm and lifetime as conditions, this permission cannot be used to get tokens for other services.
data "aws_iam_policy_document" "chart_token" {
statement {
actions = ["sts:GetWebIdentityToken"]
resources = ["*"]
condition {
test = "ForAllValues:StringEquals"
variable = "sts:IdentityTokenAudience"
values = ["chart-renderer"]
}
condition {
test = "StringEquals"
variable = "sts:SigningAlgorithm"
values = ["ES384"]
}
condition {
test = "NumericLessThanEquals"
variable = "sts:DurationSeconds"
values = ["3600"]
}
}
}
resource "aws_iam_role_policy" "chart_token" {
name = "ChartRendererToken"
role = aws_iam_role.wiki.name
policy = data.aws_iam_policy_document.chart_token.json
}
Refreshing the token
A token expires in an hour, so the wiki server fetches a new one more often than that, for example every 15 minutes from cron, and writes it to a file only PHP can read. So that PHP never reads a half-written token, it writes to a temporary file and then replaces the file whole, running as root for the chown. With AWS CLI v2 it looks like this:
umask 077
mkdir -p /run/secrets
aws sts get-web-identity-token --audience chart-renderer \
--signing-algorithm ES384 --duration-seconds 3600 \
--query WebIdentityToken --output text > /run/secrets/.chart-renderer-token &&
chown www-data /run/secrets/.chart-renderer-token &&
mv /run/secrets/.chart-renderer-token /run/secrets/chart-renderer-token
GetWebIdentityToken does not work on the global STS endpoint, so you need a regional endpoint. AWS CLI v2 uses regional endpoints by default, but AWS CLI v1 and the AWS SDK for PHP may use the global endpoint depending on the version, so it is safer to set sts_regional_endpoints to regional.[6] Also, this command only exists in AWS CLI v2 releases from late 2025 onward, and if you fetch the token from PHP you need aws/aws-sdk-php 3.361.0 or later. /run is emptied on reboot, so run the script once at boot as well as from cron. Femiwiki fetches its token from PHP; you can see that code in the docker-mediawiki repository.[7]
LocalSettings.php
Finally, have LocalSettings.php read the token file on every request and append it to the renderer's address. Set $chartRendererUrl to the module's url output. If $wgChartServiceUrl is empty, the Chart extension tries to run node on the wiki server, so the address must be set even when there is no token file.
wfLoadExtension( 'Chart' );
// The module's url output
$chartRendererUrl = 'https://….lambda-url.ap-northeast-2.on.aws/';
$chartRendererToken = '/run/secrets/chart-renderer-token';
$wgChartServiceUrl = $chartRendererUrl . ( is_readable( $chartRendererToken )
? trim( file_get_contents( $chartRendererToken ) )
: '' );
The Chart extension reads tabular data (.tab) and chart definitions (.chart) in the Data: namespace through the JsonConfig extension, so after installing JsonConfig you need to register the two content models yourself.[8]
wfLoadExtension( 'JsonConfig' );
$wgJsonConfigModels['Tabular.JsonConfig'] = 'JsonConfig\JCTabularContent';
$wgJsonConfigs['Tabular.JsonConfig'] = [
'namespace' => 486,
'nsName' => 'Data',
'pattern' => '/.\.tab$/',
'license' => 'CC-BY-SA-4.0',
'isLocal' => true,
];
$wgJsonConfigModels['Chart.JsonConfig'] = 'MediaWiki\Extension\Chart\JCChartContent';
$wgJsonConfigs['Chart.JsonConfig'] = [
'namespace' => 486,
'nsName' => 'Data',
'pattern' => '/.\.chart$/',
'license' => 'CC-BY-SA-4.0',
'isLocal' => true,
];
Once everything is set up, create a tabular data page (.tab) and a chart definition page (.chart) in the Data: namespace, put {{#chart:Example.chart}} on any page, and check that the chart shows up.
In production
On Femiwiki, the function was called 180 times in the three days after installation, with no errors or throttled requests. At that rate a month comes to about 1,700 requests and about 670 GB-seconds of run time, which is roughly $0.01 a month even without the free tier. The first chart after a quiet spell, though, takes about 1–4 seconds while a new function instance starts and fetches the public keys.
Closing
The module is released under the MIT licence. It has not reached 1.0 yet and its inputs may change in future versions, so we recommend pinning the version with version as in the example. Because the issuer is not tied to STS, you can use it even if your wiki server is outside AWS, as long as it can get OpenID Connect tokens. If you run into a problem, please open an issue on the repository.
References
- ↑ “T334940 Graph extension disabled”. 《Wikimedia Phabricator》 (영어). 2026년 11월 7일에 확인함.
- ↑ “Extension:Chart”. 《MediaWiki》 (영어). 2026년 11월 7일에 확인함.
- ↑ “wmf-config/CommonSettings.php - operations/mediawiki-config”. 《Wikimedia Gerrit》 (영어). 2026년 11월 7일에 확인함.
- ↑ “feat(chart-renderer): render charts for MediaWiki's Chart extension · Pull Request #230 · femiwiki/lambda”. 《GitHub》 (영어). 2026년 11월 8일에 확인함.
- ↑ “GetWebIdentityToken”. 《AWS Security Token Service API Reference》 (영어). 2026년 11월 7일에 확인함.
- ↑ 6.0 6.1 “femiwiki/terraform-aws-mediawiki-chart-renderer”. 《GitHub》 (영어). 2026년 11월 7일에 확인함.
- ↑ “feat(femiwiki): enable charts drawn by the chart-renderer Lambda · Pull Request #1733 · femiwiki/docker-mediawiki”. 《GitHub》 (영어). 2026년 11월 7일에 확인함.
- ↑ “README.md - mediawiki/extensions/Chart”. 《Wikimedia Gerrit》 (영어). 2026년 11월 8일에 확인함.